-
Notifications
You must be signed in to change notification settings - Fork 83
Adds a StructuredLogger based on cats.mtl.Ask #900
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; | ||
}; | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This (and line 40) wire nothing in yet. We'd need a parallel set of Ask or Local based constructors here. |
||
|
||
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)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By getting the default context into the logger, as an effect, we are now able to avoid the asking and concatenation of the context map when the log is disabled. But this is a bit less conducive to the middleware approach: we don't want |
||
)(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)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dependency will be part of cats-effect-3.6, so I'm not feeling guilty for introducing it to core. Otherwise, this could be a log4cats-mtl module.