diff --git a/build.sbt b/build.sbt index 1f730cc8..b6a7062c 100644 --- a/build.sbt +++ b/build.sbt @@ -28,6 +28,7 @@ ThisBuild / tlVersionIntroduced := Map("3" -> "2.1.1") val catsV = "2.11.0" val catsEffectV = "3.5.7" +val catsMtlV = "1.4.0" val slf4jV = "1.7.36" val munitCatsEffectV = "2.0.0" val logbackClassicV = "1.2.13" @@ -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 diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..5afd32d0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,109 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "nixpkgs": [ + "typelevel-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741473158, + "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", + "owner": "numtide", + "repo": "devshell", + "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1741402956, + "narHash": "sha256-y2hByvBM03s9T2fpeLjW6iprbxnhV9mJMmSwCHc41ZQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ed0b1881565c1ffef490c10d663d4f542031dad3", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": [ + "typelevel-nix", + "flake-utils" + ], + "nixpkgs": [ + "typelevel-nix", + "nixpkgs" + ], + "typelevel-nix": "typelevel-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "typelevel-nix": { + "inputs": { + "devshell": "devshell", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1741614612, + "narHash": "sha256-HbUszqqxC7YhlO6B3azKSPz8jOR3k0WRlOAKRxgV7IU=", + "owner": "typelevel", + "repo": "typelevel-nix", + "rev": "52367e3d4b26033c80c7caf2a0038b26b2b3a1d0", + "type": "github" + }, + "original": { + "owner": "typelevel", + "repo": "typelevel-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..5ae23a8c --- /dev/null +++ b/flake.nix @@ -0,0 +1,26 @@ +{ + inputs = { + typelevel-nix.url = "github:typelevel/typelevel-nix"; + nixpkgs.follows = "typelevel-nix/nixpkgs"; + flake-utils.follows = "typelevel-nix/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, typelevel-nix }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ typelevel-nix.overlays.default ]; + }; + in { + devShell = pkgs.devshell.mkShell { + imports = [ typelevel-nix.typelevelShell ]; + name = "log4cats"; + typelevelShell = { + jdk.package = pkgs.jdk8; + native.enable = true; + nodejs.enable = true; + }; + }; + }); +} diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala index 099dcd45..acebd48f 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala @@ -17,6 +17,7 @@ package org.typelevel.log4cats package slf4j +import cats.Applicative import cats.effect.Sync import org.typelevel.log4cats.slf4j.internal.Slf4jLoggerInternal import org.slf4j.Logger as JLogger @@ -33,10 +34,10 @@ object Slf4jLogger extends Slf4jLoggerCompat { getLoggerFromSlf4j[F](org.slf4j.LoggerFactory.getLogger(clazz)) def getLoggerFromSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] = - new Slf4jLoggerInternal.Slf4jLogger(logger, Sync.Type.Delay) + new Slf4jLoggerInternal.Slf4jLogger(logger, Sync.Type.Delay, Applicative[F].pure(Map.empty)) def getLoggerFromBlockingSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] = - new Slf4jLoggerInternal.Slf4jLogger(logger, Sync.Type.Blocking) + new Slf4jLoggerInternal.Slf4jLogger(logger, Sync.Type.Blocking, Applicative[F].pure(Map.empty)) def create[F[_]: Sync](implicit name: LoggerName): F[SelfAwareStructuredLogger[F]] = Sync[F].delay(getLoggerFromName(name.value)) diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala index 4f652a08..98efb8ca 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala @@ -36,39 +36,35 @@ private[slf4j] object Slf4jLoggerInternal { def apply(t: Throwable)(msg: => String): F[Unit] } - // Need this to make sure MDC is correctly cleared before logging - private[this] def noContextLog[F[_]](isEnabled: F[Boolean], logging: () => Unit)(implicit - F: Sync[F] - ): F[Unit] = - contextLog[F](isEnabled, Map.empty, logging) - private[this] def contextLog[F[_]]( isEnabled: F[Boolean], - ctx: Map[String, String], + ctxF: F[Map[String, String]], logging: () => Unit )(implicit F: Sync[F]): F[Unit] = { - val ifEnabled = F.delay { - val backup = - try MDC.getCopyOfContextMap() - catch { - case e: IllegalStateException => - // MDCAdapter is missing, no point in doing anything with - // the MDC, so just hope the logging backend can salvage - // something. - logging() - throw e - } - - try { - // Once 2.12 is no longer supported, change this to MDC.setContextMap(ctx.asJava) - MDC.clear() - ctx.foreach { case (k, v) => MDC.put(k, v) } - logging() - } finally - if (backup eq null) MDC.clear() - else MDC.setContextMap(backup) - } + val ifEnabled = ctxF.flatMap(ctx => + F.delay { + val backup = + try MDC.getCopyOfContextMap() + catch { + case e: IllegalStateException => + // MDCAdapter is missing, no point in doing anything with + // the MDC, so just hope the logging backend can salvage + // something. + logging() + throw e + } + + try { + // Once 2.12 is no longer supported, change this to MDC.setContextMap(ctx.asJava) + MDC.clear() + ctx.foreach { case (k, v) => MDC.put(k, v) } + logging() + } finally + if (backup eq null) MDC.clear() + else MDC.setContextMap(backup) + } + ) isEnabled.ifM( ifEnabled, @@ -77,14 +73,19 @@ private[slf4j] object Slf4jLoggerInternal { } @nowarn("msg=used") - final class Slf4jLogger[F[_]](val logger: JLogger, sync: Sync.Type = Sync.Type.Delay)(implicit - F: Sync[F] - ) extends SelfAwareStructuredLogger[F] { + final class Slf4jLogger[F[_]]( + val logger: JLogger, + sync: Sync.Type = Sync.Type.Delay, + defaultCtx: F[Map[String, String]] + )(implicit F: Sync[F]) + extends SelfAwareStructuredLogger[F] { + + @deprecated("Use constructor with sync", "2.6.0") + def this(logger: JLogger, sync: Sync.Type)(F: Sync[F]) = + this(logger, Sync.Type.Delay, F.pure(Map.empty))(F) @deprecated("Use constructor with sync", "2.6.0") - def this(logger: JLogger)( - F: Sync[F] - ) = + def this(logger: JLogger)(F: Sync[F]) = this(logger, Sync.Type.Delay)(F) override def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) @@ -94,48 +95,48 @@ private[slf4j] object Slf4jLoggerInternal { override def isErrorEnabled: F[Boolean] = F.delay(logger.isErrorEnabled) override def trace(t: Throwable)(msg: => String): F[Unit] = - noContextLog(isTraceEnabled, () => logger.trace(msg, t)) + contextLog(isTraceEnabled, defaultCtx, () => logger.trace(msg, t)) override def trace(msg: => String): F[Unit] = - noContextLog(isTraceEnabled, () => logger.trace(msg)) + contextLog(isTraceEnabled, defaultCtx, () => logger.trace(msg)) override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - contextLog(isTraceEnabled, ctx, () => logger.trace(msg)) + contextLog(isTraceEnabled, defaultCtx.map(_ ++ ctx), () => logger.trace(msg)) override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - contextLog(isTraceEnabled, ctx, () => logger.trace(msg, t)) + contextLog(isTraceEnabled, defaultCtx.map(_ ++ ctx), () => logger.trace(msg, t)) override def debug(t: Throwable)(msg: => String): F[Unit] = - noContextLog(isDebugEnabled, () => logger.debug(msg, t)) + contextLog(isDebugEnabled, defaultCtx, () => logger.debug(msg, t)) override def debug(msg: => String): F[Unit] = - noContextLog(isDebugEnabled, () => logger.debug(msg)) + contextLog(isDebugEnabled, defaultCtx, () => logger.debug(msg)) override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - contextLog(isDebugEnabled, ctx, () => logger.debug(msg)) + contextLog(isDebugEnabled, defaultCtx.map(_ ++ ctx), () => logger.debug(msg)) override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - contextLog(isDebugEnabled, ctx, () => logger.debug(msg, t)) + contextLog(isDebugEnabled, defaultCtx.map(_ ++ ctx), () => logger.debug(msg, t)) override def info(t: Throwable)(msg: => String): F[Unit] = - noContextLog(isInfoEnabled, () => logger.info(msg, t)) + contextLog(isInfoEnabled, defaultCtx, () => logger.info(msg, t)) override def info(msg: => String): F[Unit] = - noContextLog(isInfoEnabled, () => logger.info(msg)) + contextLog(isInfoEnabled, defaultCtx, () => logger.info(msg)) override def info(ctx: Map[String, String])(msg: => String): F[Unit] = - contextLog(isInfoEnabled, ctx, () => logger.info(msg)) + contextLog(isInfoEnabled, defaultCtx.map(_ ++ ctx), () => logger.info(msg)) override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - contextLog(isInfoEnabled, ctx, () => logger.info(msg, t)) + contextLog(isInfoEnabled, defaultCtx.map(_ ++ ctx), () => logger.info(msg, t)) override def warn(t: Throwable)(msg: => String): F[Unit] = - noContextLog(isWarnEnabled, () => logger.warn(msg, t)) + contextLog(isWarnEnabled, defaultCtx, () => logger.warn(msg, t)) override def warn(msg: => String): F[Unit] = - noContextLog(isWarnEnabled, () => logger.warn(msg)) + contextLog(isWarnEnabled, defaultCtx, () => logger.warn(msg)) override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - contextLog(isWarnEnabled, ctx, () => logger.warn(msg)) + contextLog(isWarnEnabled, defaultCtx.map(_ ++ ctx), () => logger.warn(msg)) override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - contextLog(isWarnEnabled, ctx, () => logger.warn(msg, t)) + contextLog(isWarnEnabled, defaultCtx.map(_ ++ ctx), () => logger.warn(msg, t)) override def error(t: Throwable)(msg: => String): F[Unit] = - noContextLog(isErrorEnabled, () => logger.error(msg, t)) + contextLog(isErrorEnabled, defaultCtx, () => logger.error(msg, t)) override def error(msg: => String): F[Unit] = - noContextLog(isErrorEnabled, () => logger.error(msg)) + contextLog(isErrorEnabled, defaultCtx, () => logger.error(msg)) override def error(ctx: Map[String, String])(msg: => String): F[Unit] = - contextLog(isErrorEnabled, ctx, () => logger.error(msg)) + contextLog(isErrorEnabled, defaultCtx.map(_ ++ ctx), () => logger.error(msg)) override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - contextLog(isErrorEnabled, ctx, () => logger.error(msg, t)) + contextLog(isErrorEnabled, defaultCtx.map(_ ++ ctx), () => logger.error(msg, t)) } }