Skip to content

Reopen #905: Adopt cats.mtl.Local for Logging Context and IOLocal (was #905, continues #676) #919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8709392
Add helpers for logging with some propagated ctx and kleisli implemen…
wunderk1nd-e Aug 17, 2022
d8f1164
Add ce3 module and iolocal instances
wunderk1nd-e Aug 17, 2022
d2d340d
Merge branch 'main' into ctxtual
rossabaker Feb 20, 2023
f0629f3
Regenerate workflow after merge
rossabaker Feb 20, 2023
a0b1449
added Cats Effect 3.6.0
Jay-Lokhande Mar 27, 2025
55b7fb2
added name "log4cats-cats-effect"
Jay-Lokhande Mar 27, 2025
036981b
Update IOLocalHelpers.scala
Jay-Lokhande Mar 27, 2025
447bdf5
Update IOLocalHelpers.scala
Jay-Lokhande Mar 27, 2025
6d653eb
Resolve conflicts and add changes for ctxtual branch
Jay-Lokhande Mar 27, 2025
9e42efd
added cats.mtl.Local usage
Jay-Lokhande Mar 28, 2025
5a42ec4
Update ci.yml
Jay-Lokhande Mar 28, 2025
c2a5a94
Removed unused import
Jay-Lokhande Mar 28, 2025
6198718
applied sbt scalafmt
Jay-Lokhande Mar 28, 2025
d66f965
reconfigured sbt scalafmt extra removal
Jay-Lokhande Mar 28, 2025
63fabf8
sbt scalafmt on LogMessage.scala
Jay-Lokhande Mar 28, 2025
72e1dc9
Update IOLocalHelpers with format
Jay-Lokhande Mar 28, 2025
1ce07a5
Update IOLocalHelpers with naming
Jay-Lokhande Mar 28, 2025
5a2e24f
Add cats-mtl dependency to core for LocalHelpers
Jay-Lokhande Apr 7, 2025
1981df7
Added header to the file
Jay-Lokhande Apr 7, 2025
2aeeb37
Add FlatMap[F] constraint to LocalHelpers for withContextF compatibility
Jay-Lokhande Apr 7, 2025
9054d87
removed same package installation multiple times
Jay-Lokhande Apr 7, 2025
489d29e
handled imports
Jay-Lokhande Apr 7, 2025
d752b40
updated build.sbt
Jay-Lokhande Apr 8, 2025
fc05a9f
sbt githubWorkflowGenerate
Jay-Lokhande Apr 8, 2025
2d9865d
Merge branch 'typelevel:main' into ctxtual
Jay-Lokhande Jun 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ val catsEffectV = "3.6.1"
val slf4jV = "1.7.36"
val munitCatsEffectV = "2.1.0"
val logbackClassicV = "1.2.13"
val catsMtlV = "1.4.0"

Global / onChangedBuildSource := ReloadOnSourceChanges

Expand All @@ -47,7 +48,8 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
name := "log4cats-core",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % catsV,
"org.typelevel" %%% "cats-effect-std" % catsEffectV
"org.typelevel" %%% "cats-effect-std" % catsEffectV,
"org.typelevel" %%% "cats-mtl" % catsMtlV
),
libraryDependencies ++= {
if (tlIsScala3.value) Seq.empty
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2018 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.log4cats

import cats.FlatMap
import cats.mtl.Local

object LocalHelpers {
implicit class LoggerOps[F[_]](sl: SelfAwareStructuredLogger[F]) {
def withLocalContext(implicit
local: Local[F, Map[String, String]],
F: FlatMap[F]
): SelfAwareStructuredLogger[F] =
SelfAwareStructuredLogger.withContextF(sl)(local.ask)
}

implicit class FactoryOps[F[_]](lf: LoggerFactory[F]) {
def withLocalContext(implicit
local: Local[F, Map[String, String]],
F: FlatMap[F]
): LoggerFactory[F] =
LoggerFactory.withContextF(lf)(local.ask)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

package org.typelevel.log4cats

import cats.FlatMap
import cats.Functor
import cats.Show.Shown
import cats.Monad
import cats.data.EitherT
import cats.data.Kleisli
import cats.data.OptionT
import cats.Show.Shown
import cats.syntax.functor.*
import cats.~>
import cats.data.OptionT
import cats.data.EitherT

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -71,6 +73,22 @@ object LoggerFactory extends LoggerFactoryGenCompanion {
}
}

def withContextF[F[_]: FlatMap](
lf: LoggerFactory[F]
)(ctx: F[Map[String, String]]): LoggerFactory[F] =
new LoggerFactory[F] {
override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] =
SelfAwareStructuredLogger.withContextF(lf.getLoggerFromName(name))(ctx)

override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = lf
.fromName(name)
.map(SelfAwareStructuredLogger.withContextF(_)(ctx))
}

def withContextFromKleisli[F[_]: Monad](
lf: LoggerFactory[Kleisli[F, Map[String, String], *]]
): LoggerFactory[Kleisli[F, Map[String, String], *]] = withContextF(lf)(Kleisli.ask)

private def addContext[F[_]: Functor](
lf: LoggerFactory[F],
ctx: Map[String, String]
Expand All @@ -94,5 +112,4 @@ object LoggerFactory extends LoggerFactoryGenCompanion {
override def fromName(name: String): F[SelfAwareStructuredLogger[F]] =
lf.fromName(name).map(_.withModifiedString(f))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package org.typelevel.log4cats

import cats.*
import cats.Show.Shown
import cats.data.Kleisli
import cats.implicits.{toFlatMapOps, toFunctorOps}

trait SelfAwareStructuredLogger[F[_]] extends SelfAwareLogger[F] with StructuredLogger[F] {
override def mapK[G[_]](fk: F ~> G): SelfAwareStructuredLogger[G] =
Expand Down Expand Up @@ -94,6 +96,90 @@ object SelfAwareStructuredLogger {
sl.trace(modify(ctx), t)(message)
}

def withContextFromKleisli[F[_]: Monad](
sl: SelfAwareStructuredLogger[Kleisli[F, Map[String, String], *]]
): SelfAwareStructuredLogger[Kleisli[F, Map[String, String], *]] =
withContextF(sl)(
Kleisli.ask[F, Map[String, String]]
)
def withContextF[F[_]: FlatMap](
sl: SelfAwareStructuredLogger[F]
)(ctx: F[Map[String, String]]): SelfAwareStructuredLogger[F] =
new ModifiedContextFSelfAwareStructuredLogger[F](sl)(existingCtx => ctx.map(_ ++ existingCtx))

private class ModifiedContextFSelfAwareStructuredLogger[F[_]: FlatMap](
sl: SelfAwareStructuredLogger[F]
)(
modify: Map[String, String] => F[Map[String, String]]
) extends SelfAwareStructuredLogger[F] {
private lazy val defaultCtx: F[Map[String, String]] = modify(Map.empty)

def error(message: => String): F[Unit] = defaultCtx.flatMap(sl.error(_)(message))

def warn(message: => String): F[Unit] = defaultCtx.flatMap(sl.warn(_)(message))

def info(message: => String): F[Unit] = defaultCtx.flatMap(sl.info(_)(message))

def debug(message: => String): F[Unit] = defaultCtx.flatMap(sl.debug(_)(message))

def trace(message: => String): F[Unit] = defaultCtx.flatMap(sl.trace(_)(message))

def trace(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.trace(_)(msg))

def debug(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.debug(_)(msg))

def info(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.info(_)(msg))

def warn(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.warn(_)(msg))

def error(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.error(_)(msg))

def isTraceEnabled: F[Boolean] = sl.isTraceEnabled

def isDebugEnabled: F[Boolean] = sl.isDebugEnabled

def isInfoEnabled: F[Boolean] = sl.isInfoEnabled

def isWarnEnabled: F[Boolean] = sl.isWarnEnabled

def isErrorEnabled: F[Boolean] = sl.isErrorEnabled

def error(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.error(_, t)(message))

def warn(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.warn(_, t)(message))

def info(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.info(_, t)(message))

def debug(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.debug(_, t)(message))

def trace(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.trace(_, t)(message))

def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.error(_, t)(message))

def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.warn(_, t)(message))

def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.info(_, t)(message))

def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.debug(_, t)(message))

def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.trace(_, t)(message))
}

private def withModifiedString[F[_]](
l: SelfAwareStructuredLogger[F],
f: String => String
Expand Down
Loading