diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08fe9a3f..e9611a67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,11 +119,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p testing/jvm/target noop/jvm/target target .js/target core/native/target site/target testing/native/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target .jvm/target .native/target slf4j/target project/target + run: mkdir -p testing/jvm/target noop/jvm/target target .js/target core/native/target site/target testing/native/target ce3/js/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target .jvm/target .native/target slf4j/target ce3/jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar testing/jvm/target noop/jvm/target target .js/target core/native/target site/target testing/native/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target .jvm/target .native/target slf4j/target project/target + run: tar cf targets.tar testing/jvm/target noop/jvm/target target .js/target core/native/target site/target testing/native/target ce3/js/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target .jvm/target .native/target slf4j/target ce3/jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/build.sbt b/build.sbt index b81309d4..ad51566c 100644 --- a/build.sbt +++ b/build.sbt @@ -76,6 +76,16 @@ lazy val noop = crossProject(JSPlatform, JVMPlatform, NativePlatform) ) .nativeSettings(commonNativeSettings) +lazy val ce3 = crossProject(JSPlatform, JVMPlatform) + .settings(commonSettings) + .dependsOn(core) + .settings( + name := "log4cats-ce3", + libraryDependencies ++= Seq( + "org.typelevel" %%% "cats-effect" % catsEffectV + ) + ) + lazy val slf4j = project .settings(commonSettings) .dependsOn(core.jvm) diff --git a/ce3/shared/src/main/scala/org/typelevel/log4cats/ce3/IOLocalHelpers.scala b/ce3/shared/src/main/scala/org/typelevel/log4cats/ce3/IOLocalHelpers.scala new file mode 100644 index 00000000..c7a7b642 --- /dev/null +++ b/ce3/shared/src/main/scala/org/typelevel/log4cats/ce3/IOLocalHelpers.scala @@ -0,0 +1,16 @@ +package org.typelevel.log4cats.ce3 + +import cats.effect.{IO, IOLocal} +import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} + +object IOLocalHelpers { + def loggerWithContextFromIOLocal( + sl: SelfAwareStructuredLogger[IO], + local: IOLocal[Map[String, String]] + ): SelfAwareStructuredLogger[IO] = SelfAwareStructuredLogger.withContextF(sl)(local.get) + + def factoryWithContextFromIOLocal( + lf: LoggerFactory[IO], + local: IOLocal[Map[String, String]] + ): LoggerFactory[IO] = LoggerFactory.withContextF(lf)(local.get) +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala index 9672373d..3e658849 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala @@ -16,12 +16,14 @@ package org.typelevel.log4cats +import cats.FlatMap import cats.Functor +import cats.Monad +import cats.data.EitherT import cats.data.Kleisli +import cats.data.OptionT import cats.syntax.functor._ import cats.~> -import cats.data.OptionT -import cats.data.EitherT import scala.annotation.implicitNotFound @@ -60,4 +62,20 @@ object LoggerFactory extends LoggerFactoryGenCompanion { fk(logger) } } + + 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) } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala index f143960a..e8966864 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala @@ -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] = @@ -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