diff --git a/README.md b/README.md index 41dfb5f..ca21ceb 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,49 @@ class MyClass extends LazyLogging { } ``` +`LoggerTakingImplicit` provides the same methods as `Logger` class, but with additional implicit parameter `A`. +During creation of the `LoggerTakingImplicit` evidence `CanLog[A]` is required. +It may be useful when contextual parameter (e.g. _Correlation ID_) is being passed around and you would like to include it in the log messages: + +```scala +case class CorrelationId(value: String) +implicit case object CanLogCorrelationId extends CanLog[CorrelationId] { + override def logMessage(originalMsg: String, a: CorrelationId): String = s"${a.value} $originalMsg" +} + +implicit val correlationId = CorrelationId("ID") + +val logger = Logger.takingImplicit[CorrelationId]("test") +logger.info("Test") // takes implicit correlationId and logs "ID Test" +``` + +It's possible to use `MDC` through `CanLog` without any troubles with execution context. + +```scala +case class CorrelationId(value: String) +implicit case object CanLogCorrelationId extends CanLog[CorrelationId] { + override def logMessage(originalMsg: String, a: CorrelationId): String = { + MDC.put("correlationId", a.value) + originalMsg + } + + override def afterLog(a: A): Unit = { + MDC.remove("correlationId") + } +} + +implicit val correlationId = CorrelationId("ID") + +val logger = Logger.takingImplicit[CorrelationId]("test") + +def serviceMethod(implicit correlationId: CorrelationId): Future[Result] = { + dbCall.map { value => + logger.trace(s"Received value $value from db") // takes implicit correlationId + toResult(value) + } +} +``` + ### What's new? #### 3.7.2 diff --git a/src/main/scala/com/typesafe/scalalogging/Logger.scala b/src/main/scala/com/typesafe/scalalogging/Logger.scala index 9b9041a..0ce5b91 100644 --- a/src/main/scala/com/typesafe/scalalogging/Logger.scala +++ b/src/main/scala/com/typesafe/scalalogging/Logger.scala @@ -31,6 +31,12 @@ object Logger { def apply(underlying: Underlying): Logger = new Logger(underlying) + /** + * Create a [[LoggerTakingImplicit]] wrapping the given underlying `org.slf4j.Logger`. + */ + def takingImplicit[A](underlying: Underlying)(implicit ev: CanLog[A]): LoggerTakingImplicit[A] = + new LoggerTakingImplicit[A](underlying) + /** * Create a [[Logger]] for the given name. * Example: @@ -41,12 +47,28 @@ object Logger { def apply(name: String): Logger = new Logger(LoggerFactory.getLogger(name)) + /** + * Create a [[LoggerTakingImplicit]] for the given name. + * Example: + * {{{ + * val logger = Logger.takingImplicit[CorrelationId]("application") + * }}} + */ + def takingImplicit[A](name: String)(implicit ev: CanLog[A]): LoggerTakingImplicit[A] = + new LoggerTakingImplicit[A](LoggerFactory.getLogger(name)) + /** * Create a [[Logger]] wrapping the created underlying `org.slf4j.Logger`. */ def apply(clazz: Class[_]): Logger = new Logger(LoggerFactory.getLogger(clazz.getName)) + /** + * Create a [[LoggerTakingImplicit]] wrapping the created underlying `org.slf4j.Logger`. + */ + def takingImplicit[A](clazz: Class[_])(implicit ev: CanLog[A]): LoggerTakingImplicit[A] = + new LoggerTakingImplicit[A](LoggerFactory.getLogger(clazz.getName)) + /** * Create a [[Logger]] for the runtime class wrapped by the implicit class * tag parameter. @@ -57,6 +79,17 @@ object Logger { */ def apply[T](implicit ct: ClassTag[T]): Logger = new Logger(LoggerFactory.getLogger(ct.runtimeClass.getName.stripSuffix("$"))) + + /** + * Create a [[LoggerTakingImplicit]] for the runtime class wrapped by the implicit class + * tag parameter. + * Example: + * {{{ + * val logger = Logger.takingImplicit[MyClass, CorrelationId] + * }}} + */ + def takingImplicit[T, A](implicit ct: ClassTag[T], ev: CanLog[A]): LoggerTakingImplicit[A] = + new LoggerTakingImplicit[A](LoggerFactory.getLogger(ct.runtimeClass.getName.stripSuffix("$"))) } /** diff --git a/src/main/scala/com/typesafe/scalalogging/LoggerTakingImplicit.scala b/src/main/scala/com/typesafe/scalalogging/LoggerTakingImplicit.scala new file mode 100644 index 0000000..9ebc295 --- /dev/null +++ b/src/main/scala/com/typesafe/scalalogging/LoggerTakingImplicit.scala @@ -0,0 +1,83 @@ +package com.typesafe.scalalogging + +import org.slf4j.{ Marker, Logger => Underlying } + +trait CanLog[A] { + def logMessage(originalMsg: String, a: A): String + def afterLog(a: A): Unit = () +} + +@SerialVersionUID(957385465L) +final class LoggerTakingImplicit[A] private[scalalogging] (val underlying: Underlying)(implicit val canLogEv: CanLog[A]) extends Serializable { + + // Error + + def error(message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.errorMessage[A] + + def error(message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.errorMessageCause[A] + + def error(message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.errorMessageArgs[A] + + def error(marker: Marker, message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.errorMessageMarker[A] + + def error(marker: Marker, message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.errorMessageCauseMarker[A] + + def error(marker: Marker, message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.errorMessageArgsMarker[A] + + // Warn + + def warn(message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.warnMessage[A] + + def warn(message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.warnMessageCause[A] + + def warn(message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.warnMessageArgs[A] + + def warn(marker: Marker, message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.warnMessageMarker[A] + + def warn(marker: Marker, message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.warnMessageCauseMarker[A] + + def warn(marker: Marker, message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.warnMessageArgsMarker[A] + + // Info + + def info(message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.infoMessage[A] + + def info(message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.infoMessageCause[A] + + def info(message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.infoMessageArgs[A] + + def info(marker: Marker, message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.infoMessageMarker[A] + + def info(marker: Marker, message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.infoMessageCauseMarker[A] + + def info(marker: Marker, message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.infoMessageArgsMarker[A] + + // Debug + + def debug(message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.debugMessage[A] + + def debug(message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.debugMessageCause[A] + + def debug(message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.debugMessageArgs[A] + + def debug(marker: Marker, message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.debugMessageMarker[A] + + def debug(marker: Marker, message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.debugMessageCauseMarker[A] + + def debug(marker: Marker, message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.debugMessageArgsMarker[A] + + // Trace + + def trace(message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.traceMessage[A] + + def trace(message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.traceMessageCause[A] + + def trace(message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.traceMessageArgs[A] + + def trace(marker: Marker, message: String)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.traceMessageMarker[A] + + def trace(marker: Marker, message: String, cause: Throwable)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.traceMessageCauseMarker[A] + + def trace(marker: Marker, message: String, args: Any*)(implicit a: A): Unit = macro LoggerTakingImplicitMacro.traceMessageArgsMarker[A] + +} diff --git a/src/main/scala/com/typesafe/scalalogging/LoggerTakingImplicitMacro.scala b/src/main/scala/com/typesafe/scalalogging/LoggerTakingImplicitMacro.scala new file mode 100644 index 0000000..a3a6472 --- /dev/null +++ b/src/main/scala/com/typesafe/scalalogging/LoggerTakingImplicitMacro.scala @@ -0,0 +1,389 @@ +package com.typesafe.scalalogging + +import org.slf4j.Marker + +import scala.reflect.macros.blackbox.Context + +private object LoggerTakingImplicitMacro { + type LoggerContext[A] = Context { type PrefixType = LoggerTakingImplicit[A] } + + def errorMessage[A](c: LoggerContext[A])(message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isErrorEnabled) { + $underlying.error($canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def errorMessageCause[A](c: LoggerContext[A])(message: c.Expr[String], cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isErrorEnabled) { + $underlying.error($canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def errorMessageArgs[A](c: LoggerContext[A])(message: c.Expr[String], args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isErrorEnabled) { + $underlying.error($canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isErrorEnabled) { + $underlying.error($canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def errorMessageMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isErrorEnabled) { + $underlying.error($marker, $canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def errorMessageCauseMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isErrorEnabled) { + $underlying.error($marker, $canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def errorMessageArgsMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isErrorEnabled) { + $underlying.error($marker, $canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isErrorEnabled) { + $underlying.error($marker, $canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def warnMessage[A](c: LoggerContext[A])(message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def warnMessageCause[A](c: LoggerContext[A])(message: c.Expr[String], cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def warnMessageArgs[A](c: LoggerContext[A])(message: c.Expr[String], args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def warnMessageMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($marker, $canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def warnMessageCauseMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($marker, $canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def warnMessageArgsMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($marker, $canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isWarnEnabled) { + $underlying.warn($marker, $canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def infoMessage[A](c: LoggerContext[A])(message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isInfoEnabled) { + $underlying.info($canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def infoMessageCause[A](c: LoggerContext[A])(message: c.Expr[String], cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isInfoEnabled) { + $underlying.info($canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def infoMessageArgs[A](c: LoggerContext[A])(message: c.Expr[String], args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isInfoEnabled) { + $underlying.info($canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isInfoEnabled) { + $underlying.info($canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def infoMessageMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isInfoEnabled) { + $underlying.info($marker, $canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def infoMessageCauseMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isInfoEnabled) { + $underlying.info($marker, $canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def infoMessageArgsMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isInfoEnabled) { + $underlying.info($marker, $canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isInfoEnabled) { + $underlying.info($marker, $canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def debugMessage[A](c: LoggerContext[A])(message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def debugMessageCause[A](c: LoggerContext[A])(message: c.Expr[String], cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def debugMessageArgs[A](c: LoggerContext[A])(message: c.Expr[String], args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def debugMessageMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($marker, $canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def debugMessageCauseMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($marker, $canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def debugMessageArgsMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($marker, $canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isDebugEnabled) { + $underlying.debug($marker, $canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def traceMessage[A](c: LoggerContext[A])(message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def traceMessageCause[A](c: LoggerContext[A])(message: c.Expr[String], cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def traceMessageArgs[A](c: LoggerContext[A])(message: c.Expr[String], args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } + + def traceMessageMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($marker, $canLogEv.logMessage($message, $a)) + $canLogEv.afterLog($a) + }""" + } + + def traceMessageCauseMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + cause: c.Expr[Throwable])(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($marker, $canLogEv.logMessage($message, $a), $cause) + $canLogEv.afterLog($a) + }""" + } + + def traceMessageArgsMarker[A](c: LoggerContext[A])(marker: c.Expr[Marker], message: c.Expr[String], + args: c.Expr[Any]*)(a: c.Expr[A]) = { + import c.universe._ + val underlying = q"${c.prefix}.underlying" + val canLogEv = q"${c.prefix}.canLogEv" + if (args.length == 2) { + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($marker, $canLogEv.logMessage($message, $a), List(${args(0)}, ${args(1)}): _*) + $canLogEv.afterLog($a) + }""" + } else { + q"""if ($underlying.isTraceEnabled) { + $underlying.trace($marker, $canLogEv.logMessage($message, $a), ..$args) + $canLogEv.afterLog($a) + }""" + } + } +} diff --git a/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala b/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala index 06a908f..4345c92 100644 --- a/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala +++ b/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala @@ -524,6 +524,66 @@ class LoggerSpec extends WordSpec with Matchers with MockitoSugar { } } + "Serializing LoggerTakingImplicit" should { + case class CorrelationId(value: String) + implicit val correlationId = CorrelationId(value = "correlationId") + + implicit case object canLogCorrelationId extends CanLog[CorrelationId] { + def logMessage(originalMsg: String, a: CorrelationId): String = s"${a.value} $originalMsg" + } + + def serialize[A](logger: LoggerTakingImplicit[A]): Array[Byte] = { + val byteArrayStream = new ByteArrayOutputStream + val out = new ObjectOutputStream(byteArrayStream) + + out.writeObject(logger) + out.close() + byteArrayStream.close() + + byteArrayStream.toByteArray + } + + def deserialize[A](array: Array[Byte]): LoggerTakingImplicit[A] = { + val byteArrayStream = new ByteArrayInputStream(array) + val in = new ObjectInputStream(byteArrayStream) + + val logger = in.readObject.asInstanceOf[LoggerTakingImplicit[A]] + in.close() + byteArrayStream.close() + + logger + } + + "be usable after deserialization" in { + val logger = + deserialize[CorrelationId]( + serialize[CorrelationId]( + Logger.takingImplicit[CorrelationId]( + org.slf4j.LoggerFactory.getLogger("test")))) + + logger.trace("Back from deserialization") + } + + "constructed by explicit class and be usable after deserialization" in { + val logger = + deserialize[CorrelationId]( + serialize[CorrelationId]( + Logger.takingImplicit[CorrelationId]( + classOf[LoggerSpec]))) + + logger.trace("Back from deserialization") + } + + "constructed by implicit class tag and be usable after deserialization" in { + val logger = + deserialize[CorrelationId]( + serialize[CorrelationId]( + Logger.takingImplicit[LoggerSpec, CorrelationId])) + + logger.trace("Back from deserialization") + } + } + def fixture(p: Underlying => Boolean, isEnabled: Boolean) = new { val msg = "msg" diff --git a/src/test/scala/com/typesafe/scalalogging/LoggerTakingImplicitSpec.scala b/src/test/scala/com/typesafe/scalalogging/LoggerTakingImplicitSpec.scala new file mode 100644 index 0000000..dbb7783 --- /dev/null +++ b/src/test/scala/com/typesafe/scalalogging/LoggerTakingImplicitSpec.scala @@ -0,0 +1,393 @@ +package com.typesafe.scalalogging + +import org.mockito.Matchers._ +import org.mockito.Mockito._ +import org.scalatest.mockito.MockitoSugar +import org.scalatest.{ Matchers, WordSpec } +import org.slf4j.{ Logger => Underlying } + +class LoggerTakingImplicitSpec extends WordSpec with Matchers with MockitoSugar { + + case class CorrelationId(value: String) + + // Error + + "Calling error with a message" should { + + "call the underlying logger's error method if the error level is enabled" in { + val f = fixture(_.isErrorEnabled, isEnabled = true) + import f._ + logger.error(msg) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).error(logMsg) + } + + "not call the underlying logger's error method if the error level is not enabled" in { + val f = fixture(_.isErrorEnabled, isEnabled = false) + import f._ + logger.error(msg) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).error(anyString) + } + } + + "Calling error with a message and cause" should { + + "call the underlying logger's error method if the error level is enabled" in { + val f = fixture(_.isErrorEnabled, isEnabled = true) + import f._ + logger.error(msg, cause) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).error(logMsg, cause) + } + + "not call the underlying logger's error method if the error level is not enabled" in { + val f = fixture(_.isErrorEnabled, isEnabled = false) + import f._ + logger.error(msg, cause) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).error(anyString, anyObject) + } + } + + "Calling error with a message and parameters" should { + + "call the underlying logger's error method if the error level is enabled" in { + val f = fixture(_.isErrorEnabled, isEnabled = true) + import f._ + logger.error(msg, arg1) + verify(underlying).error(logMsg, List(arg1): _*) + logger.error(msg, arg1, arg2) + verify(underlying).error(logMsg, List(arg1, arg2): _*) + logger.error(msg, arg1, arg2, arg3) + verify(underlying).error(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, times(3)).logMessage(msg, correlationId) + verify(canLogCorrelationId, times(3)).afterLog(correlationId) + } + + "not call the underlying logger's error method if the error level is not enabled" in { + val f = fixture(_.isErrorEnabled, isEnabled = false) + import f._ + logger.error(msg, arg1) + verify(underlying, never).error(logMsg, List(arg1): _*) + logger.error(msg, arg1, arg2) + verify(underlying, never).error(logMsg, List(arg1, arg2): _*) + logger.error(msg, arg1, arg2, arg3) + verify(underlying, never).error(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + } + } + + // Warn + + "Calling warn with a message" should { + + "call the underlying logger's warn method if the warn level is enabled" in { + val f = fixture(_.isWarnEnabled, isEnabled = true) + import f._ + logger.warn(msg) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).warn(logMsg) + } + + "not call the underlying logger's warn method if the warn level is not enabled" in { + val f = fixture(_.isWarnEnabled, isEnabled = false) + import f._ + logger.warn(msg) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).warn(anyString) + } + } + + "Calling warn with a message and cause" should { + + "call the underlying logger's warn method if the warn level is enabled" in { + val f = fixture(_.isWarnEnabled, isEnabled = true) + import f._ + logger.warn(msg, cause) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).warn(logMsg, cause) + } + + "not call the underlying logger's warn method if the warn level is not enabled" in { + val f = fixture(_.isWarnEnabled, isEnabled = false) + import f._ + logger.warn(msg, cause) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).warn(anyString, anyObject) + } + } + + "Calling warn with a message and parameters" should { + + "call the underlying logger's warn method if the warn level is enabled" in { + val f = fixture(_.isWarnEnabled, isEnabled = true) + import f._ + logger.warn(msg, arg1) + verify(underlying).warn(logMsg, List(arg1): _*) + logger.warn(msg, arg1, arg2) + verify(underlying).warn(logMsg, List(arg1, arg2): _*) + logger.warn(msg, arg1, arg2, arg3) + verify(underlying).warn(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, times(3)).logMessage(msg, correlationId) + verify(canLogCorrelationId, times(3)).afterLog(correlationId) + } + + "not call the underlying logger's warn method if the warn level is not enabled" in { + val f = fixture(_.isWarnEnabled, isEnabled = false) + import f._ + logger.warn(msg, arg1) + verify(underlying, never).warn(logMsg, List(arg1): _*) + logger.warn(msg, arg1, arg2) + verify(underlying, never).warn(logMsg, List(arg1, arg2): _*) + logger.warn(msg, arg1, arg2, arg3) + verify(underlying, never).warn(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + } + } + + // Info + + "Calling info with a message" should { + + "call the underlying logger's info method if the info level is enabled" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + logger.info(msg) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).info(logMsg) + } + + "not call the underlying logger's info method if the info level is not enabled" in { + val f = fixture(_.isInfoEnabled, isEnabled = false) + import f._ + logger.info(msg) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).info(anyString) + } + } + + "Calling info with a message and cause" should { + + "call the underlying logger's info method if the info level is enabled" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + logger.info(msg, cause) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).info(logMsg, cause) + } + + "not call the underlying logger's info method if the info level is not enabled" in { + val f = fixture(_.isInfoEnabled, isEnabled = false) + import f._ + logger.info(msg, cause) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).info(anyString, anyObject) + } + } + + "Calling info with a message and parameters" should { + + "call the underlying logger's info method if the info level is enabled" in { + val f = fixture(_.isInfoEnabled, isEnabled = true) + import f._ + logger.info(msg, arg1) + verify(underlying).info(logMsg, List(arg1): _*) + logger.info(msg, arg1, arg2) + verify(underlying).info(logMsg, List(arg1, arg2): _*) + logger.info(msg, arg1, arg2, arg3) + verify(underlying).info(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, times(3)).logMessage(msg, correlationId) + verify(canLogCorrelationId, times(3)).afterLog(correlationId) + } + + "not call the underlying logger's info method if the info level is not enabled" in { + val f = fixture(_.isInfoEnabled, isEnabled = false) + import f._ + logger.info(msg, arg1) + verify(underlying, never).info(logMsg, List(arg1): _*) + logger.info(msg, arg1, arg2) + verify(underlying, never).info(logMsg, List(arg1, arg2): _*) + logger.info(msg, arg1, arg2, arg3) + verify(underlying, never).info(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + } + } + + // Debug + + "Calling debug with a message" should { + + "call the underlying logger's debug method if the debug level is enabled" in { + val f = fixture(_.isDebugEnabled, isEnabled = true) + import f._ + logger.debug(msg) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).debug(logMsg) + } + + "not call the underlying logger's debug method if the debug level is not enabled" in { + val f = fixture(_.isDebugEnabled, isEnabled = false) + import f._ + logger.debug(msg) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).debug(anyString) + } + } + + "Calling debug with a message and cause" should { + + "call the underlying logger's debug method if the debug level is enabled" in { + val f = fixture(_.isDebugEnabled, isEnabled = true) + import f._ + logger.debug(msg, cause) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).debug(logMsg, cause) + } + + "not call the underlying logger's debug method if the debug level is not enabled" in { + val f = fixture(_.isDebugEnabled, isEnabled = false) + import f._ + logger.debug(msg, cause) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).debug(anyString, anyObject) + } + } + + "Calling debug with a message and parameters" should { + + "call the underlying logger's debug method if the debug level is enabled" in { + val f = fixture(_.isDebugEnabled, isEnabled = true) + import f._ + logger.debug(msg, arg1) + verify(underlying).debug(logMsg, List(arg1): _*) + logger.debug(msg, arg1, arg2) + verify(underlying).debug(logMsg, List(arg1, arg2): _*) + logger.debug(msg, arg1, arg2, arg3) + verify(underlying).debug(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, times(3)).logMessage(msg, correlationId) + verify(canLogCorrelationId, times(3)).afterLog(correlationId) + } + + "not call the underlying logger's debug method if the debug level is not enabled" in { + val f = fixture(_.isDebugEnabled, isEnabled = false) + import f._ + logger.debug(msg, arg1) + verify(underlying, never).debug(logMsg, List(arg1): _*) + logger.debug(msg, arg1, arg2) + verify(underlying, never).debug(logMsg, List(arg1, arg2): _*) + logger.debug(msg, arg1, arg2, arg3) + verify(underlying, never).debug(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + } + } + + // Trace + + "Calling trace with a message" should { + + "call the underlying logger's trace method if the trace level is enabled" in { + val f = fixture(_.isTraceEnabled, isEnabled = true) + import f._ + logger.trace(msg) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).trace(logMsg) + } + + "not call the underlying logger's trace method if the trace level is not enabled" in { + val f = fixture(_.isTraceEnabled, isEnabled = false) + import f._ + logger.trace(msg) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).trace(anyString) + } + } + + "Calling trace with a message and cause" should { + + "call the underlying logger's trace method if the trace level is enabled" in { + val f = fixture(_.isTraceEnabled, isEnabled = true) + import f._ + logger.trace(msg, cause) + verify(canLogCorrelationId).logMessage(msg, correlationId) + verify(canLogCorrelationId).afterLog(correlationId) + verify(underlying).trace(logMsg, cause) + } + + "not call the underlying logger's trace method if the trace level is not enabled" in { + val f = fixture(_.isTraceEnabled, isEnabled = false) + import f._ + logger.trace(msg, cause) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + verify(underlying, never).trace(anyString, anyObject) + } + } + + "Calling trace with a message and parameters" should { + + "call the underlying logger's trace method if the trace level is enabled" in { + val f = fixture(_.isTraceEnabled, isEnabled = true) + import f._ + logger.trace(msg, arg1) + verify(underlying).trace(logMsg, List(arg1): _*) + logger.trace(msg, arg1, arg2) + verify(underlying).trace(logMsg, List(arg1, arg2): _*) + logger.trace(msg, arg1, arg2, arg3) + verify(underlying).trace(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, times(3)).logMessage(msg, correlationId) + verify(canLogCorrelationId, times(3)).afterLog(correlationId) + } + + "not call the underlying logger's trace method if the trace level is not enabled" in { + val f = fixture(_.isTraceEnabled, isEnabled = false) + import f._ + logger.trace(msg, arg1) + verify(underlying, never).trace(logMsg, List(arg1): _*) + logger.trace(msg, arg1, arg2) + verify(underlying, never).trace(logMsg, List(arg1, arg2): _*) + logger.trace(msg, arg1, arg2, arg3) + verify(underlying, never).trace(logMsg, arg1, arg2, arg3) + verify(canLogCorrelationId, never).logMessage(anyString, any[CorrelationId]) + verify(canLogCorrelationId, never).afterLog(any[CorrelationId]) + } + } + + def fixture(p: Underlying => Boolean, isEnabled: Boolean) = + new { + implicit val correlationId = CorrelationId("corrId") + implicit val canLogCorrelationId = mock[CanLog[CorrelationId]] + val msg = "msg" + val cause = new RuntimeException("cause") + val arg1 = "arg1" + val arg2 = new Integer(1) + val arg3 = "arg3" + val logMsg = "corrId - msg" + val underlying = mock[org.slf4j.Logger] + when(p(underlying)).thenReturn(isEnabled) + when(canLogCorrelationId.logMessage(anyString(), any[CorrelationId])).thenReturn(logMsg) + val logger = Logger.takingImplicit[CorrelationId](underlying) + } +}