From 1fef04e71a7f178dbad13b3bcc8fa125c600f1cd Mon Sep 17 00:00:00 2001 From: Lorand Szakacs Date: Sun, 27 May 2018 15:34:08 +0300 Subject: [PATCH 1/8] Integrate iolog4s macro implementation in slf4j module The log4cats-slf4j module now uses the macro based implementation from iolog4s: https://iolog4s.github.io/iolog4s/ This one is essentially identical to the log4s approach, but macros generate code that wrap statements in F.delay when appropriate. --- .../log4cats/slf4j/LoggerMacros.scala | 208 +++++++++++++++++ .../log4cats/slf4j/Slf4jLogger.scala | 211 +++++++++++++----- .../log4cats/slf4j/logLevels.scala | 74 ++++++ 3 files changed, 435 insertions(+), 58 deletions(-) create mode 100644 slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala create mode 100644 slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala new file mode 100644 index 00000000..596c5554 --- /dev/null +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala @@ -0,0 +1,208 @@ +/** + * Copyright 2013-2017 Sarah Gerweck + * see: https://github.com/Log4s/log4s + * + * Modifications copyright (C) 2018 Lorand Szakacs + * + * 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 io.chrisdavenport.log4cats.slf4j + +import scala.annotation.tailrec +import scala.reflect.macros.{blackbox, whitebox} + +/** Macros that support the logging system. + * + * See for handling call-by-name-parameters in macros + * https://issues.scala-lang.org/browse/SI-5778 + * + * @author Sarah Gerweck + */ +private[slf4j] object LoggerMacros { + + /** Get a logger by reflecting the enclosing class name. */ + final def getLoggerImpl[F: c.WeakTypeTag](c: blackbox.Context)(f: c.Expr[F]) = { + import c.universe._ + + @tailrec def findEnclosingClass(sym: c.universe.Symbol): c.universe.Symbol = { + sym match { + case NoSymbol => + c.abort(c.enclosingPosition, s"Couldn't find an enclosing class or module for the logger") + case s if s.isModule || s.isClass => + s + case other => + /* We're not in a module or a class, so we're probably inside a member definition. Recurse upward. */ + findEnclosingClass(other.owner) + } + } + + val cls = findEnclosingClass(c.internal.enclosingOwner) + + assert(cls.isModule || cls.isClass, "Enclosing class is always either a module or a class") + + def loggerByParam(param: c.Tree)(f: c.Expr[F]) = + q"new _root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger(_root_.org.slf4j.LoggerFactory.getLogger(...${List( + param)}))($f)" + + def loggerBySymbolName(s: Symbol)(f: c.Expr[F]) = { + def fullName(s: Symbol): String = { + @inline def isPackageObject = ( + (s.isModule || s.isModuleClass) + && s.owner.isPackage + && s.name.decodedName.toString == termNames.PACKAGE.decodedName.toString + ) + if (s.isModule || s.isClass) { + if (isPackageObject) { + s.owner.fullName + } else if (s.owner.isStatic) { + s.fullName + } else { + fullName(s.owner) + "." + s.name.encodedName.toString + } + } else { + fullName(s.owner) + } + } + loggerByParam(q"${fullName(s)}")(f) + } + + def loggerByType(s: Symbol)(f: c.Expr[F]) = { + val typeSymbol: ClassSymbol = (if (s.isModule) s.asModule.moduleClass else s).asClass + val typeParams = typeSymbol.typeParams + + if (typeParams.isEmpty) { + loggerByParam(q"classOf[$typeSymbol]")(f) + } else { + if (typeParams.exists(_.asType.typeParams.nonEmpty)) { + /* We have at least one higher-kinded type: fall back to by-name logger construction, as + * there's no simple way to declare a higher-kinded type with an "any" parameter. */ + loggerBySymbolName(s)(f) + } else { + val typeArgs = List.fill(typeParams.length)(WildcardType) + val typeConstructor = tq"$typeSymbol[..${typeArgs}]" + loggerByParam(q"classOf[$typeConstructor]")(f) + } + } + } + + @inline def isInnerClass(s: Symbol) = + s.isClass && !(s.owner.isPackage) + + val instanceByName = Slf4jLogger.singletonsByName && (cls.isModule || cls.isModuleClass) || cls.isClass && isInnerClass( + cls + ) + + if (instanceByName) { + loggerBySymbolName(cls)(f) + } else { + loggerByType(cls)(f) + } + } + + /** A macro context that represents a method call on a Logger instance. */ + private[this] type LogCtx[F[_]] = whitebox.Context { type PrefixType = Slf4jLogger[F] } + + /** Log a message reflectively at a given level. + * + * This is the internal workhorse method that does most of the logging for real applications. + * + * @param msg the message that the user wants to log + * @param error the `Throwable` that we're logging along with the message, if any + * @param logLevel the level of the logging + */ + private[this] def reflectiveLog[F[_]]( + c: LogCtx[F] + )(msg: c.Tree, error: Option[c.Expr[Throwable]], context: Seq[c.Expr[(String, String)]])( + logLevel: LogLevel) = { + import c.universe._ + + val logger = q"${c.prefix.tree}.logger" + val F = q"${c.prefix}.F" + val logValues = error match { + case None => List(msg) + case Some(e) => List(msg, e.tree) + } + val logExpr = q"$logger.${TermName(logLevel.methodName)}(..$logValues)" + val checkExpr = q"$logger.${TermName(s"is${logLevel.name}Enabled")}" + + def errorIsSimple = { + error match { + case None | Some(c.Expr(Ident(_))) => true + case _ => false + } + } + + msg match { + case _ if context.nonEmpty => + val MDC = q"org.slf4j.MDC" + val Seq = q"scala.collection.Seq" + val backup = TermName(c.freshName("mdcBackup")) + q""" + if ($checkExpr) $F.delay { + val $backup = $MDC.getCopyOfContextMap + try { + for ((k, v) <- $Seq(..$context)) $MDC.put(k, v) + $logExpr + } finally { + if ($backup eq null) $MDC.clear() + else $MDC.setContextMap($backup) + } + } else $F.unit + """ + case c.Expr(Literal(Constant(_))) if errorIsSimple => + q"$F.delay($logExpr)" + case _ => + q"if ($checkExpr) $F.delay($logExpr) else $F.unit" + } + } + + def traceTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = + reflectiveLog(c)(msg, Some(t), Nil)(Trace) + + def traceM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Trace) + + def traceCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = + reflectiveLog(c)(msg, None, ctx)(Trace) + + def debugTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = + reflectiveLog(c)(msg, Some(t), Nil)(Debug) + + def debugM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Debug) + + def debugCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = + reflectiveLog(c)(msg, None, ctx)(Debug) + + def infoTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = + reflectiveLog(c)(msg, Some(t), Nil)(Info) + + def infoM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Info) + + def infoCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = + reflectiveLog(c)(msg, None, ctx)(Info) + + def warnTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = + reflectiveLog(c)(msg, Some(t), Nil)(Warn) + + def warnM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Warn) + + def warnCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = + reflectiveLog(c)(msg, None, ctx)(Warn) + + def errorTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = + reflectiveLog(c)(msg, Some(t), Nil)(Error) + + def errorM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Error) + + def errorCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = + reflectiveLog(c)(msg, None, ctx)(Error) +} diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index 38a94bc1..002be8b1 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -1,68 +1,163 @@ +/** + * Copyright 2013-2017 Sarah Gerweck + * see: https://github.com/Log4s/log4s + * + * Modifications copyright (C) 2018 Lorand Szakacs + * + * 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 io.chrisdavenport.log4cats.slf4j -import cats.implicits._ import cats.effect.Sync import io.chrisdavenport.log4cats.Logger -import org.slf4j.{Logger => Base, LoggerFactory} + +import language.experimental.macros +import org.slf4j.{Logger => JLogger} object Slf4jLogger { - def fromName[F[_]: Sync](name: String): Logger[F] = - fromLogger[F](LoggerFactory.getLogger(name)) + def create[F[_]: Sync]: Logger[F] = macro LoggerMacros.getLoggerImpl[F[_]] + + def fromName[F[_]: Sync](name: String): Logger[F] = + new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name)) def fromClass[F[_]: Sync](clazz: Class[_]): Logger[F] = - fromLogger[F](LoggerFactory.getLogger(clazz)) - - // Do we want a macro here for reflection? - - def fromLogger[F[_]: Sync](logger: Base): Logger[F] = new Logger[F]{ - - def isDebugEnabled: F[Boolean] = Sync[F].delay(logger.isDebugEnabled) - def isErrorEnabled: F[Boolean] = Sync[F].delay(logger.isErrorEnabled) - def isInfoEnabled: F[Boolean] = Sync[F].delay(logger.isInfoEnabled) - def isTraceEnabled: F[Boolean] = Sync[F].delay(logger.isTraceEnabled) - def isWarnEnabled: F[Boolean] = Sync[F].delay(logger.isWarnEnabled) - - def debug(t: Throwable)(message: => String): F[Unit] = isDebugEnabled.ifM( - Sync[F].delay(logger.debug(message, t)), - Sync[F].unit - ) - def debug(message: => String): F[Unit] = isDebugEnabled.ifM( - Sync[F].delay(logger.debug(message)), - Sync[F].unit - ) - def error(t: Throwable)(message: => String): F[Unit] = isErrorEnabled.ifM( - Sync[F].delay(logger.error(message, t)), - Sync[F].unit - ) - def error(message: => String): F[Unit] = isErrorEnabled.ifM( - Sync[F].delay(logger.error(message)), - Sync[F].unit - ) - def info(t: Throwable)(message: => String): F[Unit] = isInfoEnabled.ifM( - Sync[F].delay(logger.info(message, t)), - Sync[F].unit - ) - def info(message: => String): F[Unit] = isInfoEnabled.ifM( - Sync[F].delay(logger.info(message)), - Sync[F].unit - ) - - def trace(t: Throwable)(message: => String): F[Unit] = isTraceEnabled.ifM( - Sync[F].delay(logger.trace(message, t)), - Sync[F].unit - ) - def trace(message: => String): F[Unit] = isTraceEnabled.ifM( - Sync[F].delay(logger.trace(message)), - Sync[F].unit - ) - def warn(t: Throwable)(message: => String): F[Unit] = isWarnEnabled.ifM( - Sync[F].delay(logger.warn(message, t)), - Sync[F].unit - ) - def warn(message: => String): F[Unit] = isWarnEnabled.ifM( - Sync[F].delay(logger.warn(message)), - Sync[F].unit - ) + new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(clazz)) + + final val singletonsByName = true + final val trailingDollar = false + + sealed trait LevelLogger[F[_]] extends Any { + def isEnabled: F[Boolean] + + def apply(msg: => String): F[Unit] + def apply(t: Throwable)(msg: => String): F[Unit] } -} \ No newline at end of file +// +// final class TraceLevelLogger[F[_]: Sync] private[slf4j] (val logger: JLogger) +// extends AnyVal +// with LevelLogger[F] { +// @inline def isEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) +// @inline def apply(msg: => String): F[Unit] = isEnabled.flatMap {isEnabled => if(isEnabled) $F.delay($logExpr) else $F.unit +// @inline def apply(t: Throwable)(msg: => String) = if (isEnabled) logger.trace(msg, t) +// } + +// final class DebugLevelLogger private[slf4j] (val logger: JLogger) +// extends AnyVal +// with LevelLogger { +// @inline def isEnabled: Boolean = logger.isDebugEnabled +// @inline def apply(msg: => String): Unit = if (isEnabled) logger.debug(msg) +// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.debug(msg, t) +// } +// +// final class InfoLevelLogger private[slf4j] (val logger: JLogger) +// extends AnyVal +// with LevelLogger { +// @inline def isEnabled: Boolean = logger.isInfoEnabled +// @inline def apply(msg: => String): Unit = if (isEnabled) logger.info(msg) +// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.info(msg, t) +// } +// +// final class WarnLevelLogger private[slf4j] (val logger: JLogger) +// extends AnyVal +// with LevelLogger { +// @inline def isEnabled: Boolean = logger.isWarnEnabled +// @inline def apply(msg: => String): Unit = if (isEnabled) logger.warn(msg) +// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.warn(msg, t) +// } +// +// final class ErrorLevelLogger private[slf4j] (val logger: JLogger) +// extends AnyVal +// with LevelLogger { +// @inline def isEnabled: Boolean = logger.isErrorEnabled +// @inline def apply(msg: => String): Unit = if (isEnabled) logger.error(msg) +// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.error(msg, t) +// } + + /** + * The existance of this class is due to the in-ability of macros to + * override abstract methods, only methods directly. + * + * See: + * https://github.com/scala/scala/commit/ef979c02da887b7c56bc1da9c4eb888e92af570f + */ + private[Slf4jLogger] class Slf4jLoggerDummyMacro[F[_]] extends Logger[F] { + + @inline override def isTraceEnabled: F[Boolean] = ??? + + @inline override def isDebugEnabled: F[Boolean] = ??? + + @inline override def isInfoEnabled: F[Boolean] = ??? + + @inline override def isWarnEnabled: F[Boolean] = ??? + + @inline override def isErrorEnabled: F[Boolean] = ??? + + override def trace(t: Throwable)(msg: => String): F[Unit] = ??? + override def trace(msg: => String): F[Unit] = ??? + def trace(ctx: (String, String)*)(msg: => String): F[Unit] = ??? + + override def debug(t: Throwable)(msg: => String): F[Unit] = ??? + override def debug(msg: => String): F[Unit] = ??? + def debug(ctx: (String, String)*)(msg: => String): F[Unit] = ??? + + override def info(t: Throwable)(msg: => String): F[Unit] = ??? + override def info(msg: => String): F[Unit] = ??? + def info(ctx: (String, String)*)(msg: => String): F[Unit] = ??? + + override def warn(t: Throwable)(msg: => String): F[Unit] = ??? + override def warn(msg: => String): F[Unit] = ??? + def warn(ctx: (String, String)*)(msg: => String): F[Unit] = ??? + + override def error(t: Throwable)(msg: => String): F[Unit] = ??? + override def error(msg: => String): F[Unit] = ??? + def error(ctx: (String, String)*)(msg: => String): F[Unit] = ??? + } +} + +final class Slf4jLogger[F[_]: Sync](val logger: JLogger) extends Slf4jLogger.Slf4jLoggerDummyMacro[F] { + val F: Sync[F] = Sync[F] + @inline override def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) + + @inline override def isDebugEnabled: F[Boolean] = F.delay(logger.isDebugEnabled) + + @inline override def isInfoEnabled: F[Boolean] = F.delay(logger.isInfoEnabled) + + @inline override def isWarnEnabled: F[Boolean] = F.delay(logger.isWarnEnabled) + + @inline override def isErrorEnabled: F[Boolean] = F.delay(logger.isErrorEnabled) + + import LoggerMacros._ + + override def trace(t: Throwable)(msg: => String): F[Unit] = macro traceTM[F] + override def trace(msg: => String): F[Unit] = macro traceM[F] + override def trace(ctx: (String, String)*)(msg: => String): F[Unit] = macro traceCM[F] + + override def debug(t: Throwable)(msg: => String): F[Unit] = macro debugTM[F] + override def debug(msg: => String): F[Unit] = macro debugM[F] + override def debug(ctx: (String, String)*)(msg: => String): F[Unit] = macro debugCM[F] + + override def info(t: Throwable)(msg: => String): F[Unit] = macro infoTM[F] + override def info(msg: => String): F[Unit] = macro infoM[F] + override def info(ctx: (String, String)*)(msg: => String): F[Unit] = macro infoCM[F] + + override def warn(t: Throwable)(msg: => String): F[Unit] = macro warnTM[F] + override def warn(msg: => String): F[Unit] = macro warnM[F] + override def warn(ctx: (String, String)*)(msg: => String): F[Unit] = macro warnCM[F] + + override def error(t: Throwable)(msg: => String): F[Unit] = macro errorTM[F] + override def error(msg: => String): F[Unit] = macro errorM[F] + override def error(ctx: (String, String)*)(msg: => String): F[Unit] = macro errorCM[F] + +} + diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala new file mode 100644 index 00000000..2ab83ea8 --- /dev/null +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala @@ -0,0 +1,74 @@ +/** + * Copyright 2013-2017 Sarah Gerweck + * see: https://github.com/Log4s/log4s + * + * Modifications copyright (C) 2018 Lorand Szakacs + * + * 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 io.chrisdavenport.log4cats.slf4j + +/** A severity level that can be assigned to log statements. */ +sealed trait LogLevel { + + /** The name of this log level. It is spelled with initial capitals */ + def name: String = this.toString + + /** The name of the SLF4J method that does logging at this level */ + private[slf4j] def methodName = name.toLowerCase +} + +object LogLevel { + + def forName(name: String): LogLevel = { + name.toLowerCase match { + case "trace" => Trace + case "debug" => Debug + case "info" => Info + case "warn" => Warn + case "error" => Error + case _ => + throw new IllegalArgumentException(s"No log level named $name") + } + } +} + +/** The highest logging severity. This generally indicates an + * application or system error that causes undesired outcomes. + * An error generally indicates a bug or an environment + * problem that warrants some kind of immediate intervention. + */ +case object Error extends LogLevel + +/** Generally indicates something is not expected but the system is + * able to continue operating. This generally indicates a bug or + * environment problem that does not require urgent intervention. + */ +case object Warn extends LogLevel + +/** Indicates normal high-level activity. Generally a single user– or + * system-initiated activity will trigger one or two info-level statements. + * (E.g., one when starting and one when finishing for complex requests.) + */ +case object Info extends LogLevel + +/** Log statements that provide the ability to trace the progress and + * behavior involved in tracking a single activity. These are useful for + * debugging general issues, identifying how modules are interacting, etc. + */ +case object Debug extends LogLevel + +/** Highly localized log statements useful for tracking the decisions made + * inside a single unit of code. These may occur at a very high frequency. + */ +case object Trace extends LogLevel From 3dae97bfc28e92a3974102e54511a0ab45537f31 Mon Sep 17 00:00:00 2001 From: Lorand Szakacs Date: Sun, 27 May 2018 16:32:16 +0300 Subject: [PATCH 2/8] Make Slf4jLogger no longer crash when its used with the Logger[F] type This solution fixes a problem in the previous commit where the program would crash with ??? when the logger was being referenced from the `Logger[F]` interface. What this implementation effectively achives is that when the user uses the the the slf4j backend wrapper via the subtype io.chrisdavenport.log4cats.slf4j.Slf4jLogger then they get macro-based optimizations like you see in iolog4s. But when they switch to the abstract algebra of `Logger[F]` then they lose this macro-based optimization and they get something that is equivalent to the old log4cats wrapper. --- .../log4cats/slf4j/Slf4jLogger.scala | 93 ++++++++++--------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index 002be8b1..80cc8a8d 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -26,12 +26,12 @@ import org.slf4j.{Logger => JLogger} object Slf4jLogger { - def create[F[_]: Sync]: Logger[F] = macro LoggerMacros.getLoggerImpl[F[_]] + def create[F[_]: Sync]: Slf4jLogger[F] = macro LoggerMacros.getLoggerImpl[F[_]] - def fromName[F[_]: Sync](name: String): Logger[F] = + def fromName[F[_]: Sync](name: String): Slf4jLogger[F] = new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name)) - def fromClass[F[_]: Sync](clazz: Class[_]): Logger[F] = + def fromClass[F[_]: Sync](clazz: Class[_]): Slf4jLogger[F] = new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(clazz)) final val singletonsByName = true @@ -85,48 +85,56 @@ object Slf4jLogger { // } /** - * The existance of this class is due to the in-ability of macros to - * override abstract methods, only methods directly. - * - * See: - * https://github.com/scala/scala/commit/ef979c02da887b7c56bc1da9c4eb888e92af570f - */ - private[Slf4jLogger] class Slf4jLoggerDummyMacro[F[_]] extends Logger[F] { - - @inline override def isTraceEnabled: F[Boolean] = ??? - - @inline override def isDebugEnabled: F[Boolean] = ??? - - @inline override def isInfoEnabled: F[Boolean] = ??? - - @inline override def isWarnEnabled: F[Boolean] = ??? - - @inline override def isErrorEnabled: F[Boolean] = ??? - - override def trace(t: Throwable)(msg: => String): F[Unit] = ??? - override def trace(msg: => String): F[Unit] = ??? - def trace(ctx: (String, String)*)(msg: => String): F[Unit] = ??? - - override def debug(t: Throwable)(msg: => String): F[Unit] = ??? - override def debug(msg: => String): F[Unit] = ??? - def debug(ctx: (String, String)*)(msg: => String): F[Unit] = ??? - - override def info(t: Throwable)(msg: => String): F[Unit] = ??? - override def info(msg: => String): F[Unit] = ??? - def info(ctx: (String, String)*)(msg: => String): F[Unit] = ??? - - override def warn(t: Throwable)(msg: => String): F[Unit] = ??? - override def warn(msg: => String): F[Unit] = ??? - def warn(ctx: (String, String)*)(msg: => String): F[Unit] = ??? - - override def error(t: Throwable)(msg: => String): F[Unit] = ??? - override def error(msg: => String): F[Unit] = ??? - def error(ctx: (String, String)*)(msg: => String): F[Unit] = ??? + * The existance of this class is due to the in-ability of macros to + * override abstract methods, only methods directly. + * + * See: + * https://github.com/scala/scala/commit/ef979c02da887b7c56bc1da9c4eb888e92af570f + */ + private[Slf4jLogger] class NonMacroSlf4jWrapper[F[_]](val logger: JLogger)( + implicit val F: Sync[F]) + extends Logger[F] { + + @inline override def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) + + @inline override def isDebugEnabled: F[Boolean] = F.delay(logger.isDebugEnabled) + + @inline override def isInfoEnabled: F[Boolean] = F.delay(logger.isInfoEnabled) + + @inline override def isWarnEnabled: F[Boolean] = F.delay(logger.isWarnEnabled) + + @inline override def isErrorEnabled: F[Boolean] = F.delay(logger.isErrorEnabled) + + override def trace(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.trace(msg, t)) + override def trace(msg: => String): F[Unit] = F.delay(logger.trace(msg)) + def trace(ctx: (String, String)*)(msg: => String): F[Unit] = + ??? //implement if it makes it into the Logger[F] interface + + override def debug(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.debug(msg, t)) + override def debug(msg: => String): F[Unit] = F.delay(logger.debug(msg)) + def debug(ctx: (String, String)*)(msg: => String): F[Unit] = + ??? //implement if it makes it into the Logger[F] interface + + override def info(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.info(msg, t)) + override def info(msg: => String): F[Unit] = F.delay(logger.info(msg)) + def info(ctx: (String, String)*)(msg: => String): F[Unit] = + ??? //implement if it makes it into the Logger[F] interface + + override def warn(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.warn(msg, t)) + override def warn(msg: => String): F[Unit] = F.delay(logger.warn(msg)) + def warn(ctx: (String, String)*)(msg: => String): F[Unit] = + ??? //implement if it makes it into the Logger[F] interface + + override def error(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.error(msg, t)) + override def error(msg: => String): F[Unit] = F.delay(logger.error(msg)) + def error(ctx: (String, String)*)(msg: => String): F[Unit] = + ??? //implement if it makes it into the Logger[F] interface } } -final class Slf4jLogger[F[_]: Sync](val logger: JLogger) extends Slf4jLogger.Slf4jLoggerDummyMacro[F] { - val F: Sync[F] = Sync[F] +final class Slf4jLogger[F[_]](override val logger: JLogger)(implicit override val F: Sync[F]) + extends Slf4jLogger.NonMacroSlf4jWrapper[F](logger)(F) { + @inline override def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) @inline override def isDebugEnabled: F[Boolean] = F.delay(logger.isDebugEnabled) @@ -160,4 +168,3 @@ final class Slf4jLogger[F[_]: Sync](val logger: JLogger) extends Slf4jLogger.Slf override def error(ctx: (String, String)*)(msg: => String): F[Unit] = macro errorCM[F] } - From b34dfa0bedef4a16b181c9bb1367ec9783c52aa3 Mon Sep 17 00:00:00 2001 From: Chris Davenport Date: Tue, 29 May 2018 11:46:13 -0400 Subject: [PATCH 3/8] Alternative Proposal for Algebra and Macro Implementation --- build.sbt | 11 +- .../log4cats/LogLevelAware.scala | 9 + .../io/chrisdavenport/log4cats/Logger.scala | 11 -- .../chrisdavenport/log4cats/MDCLogger.scala | 9 + .../log4cats/log4s/Log4sLogger.scala | 2 +- .../log4cats/scribe/ScribeLogger.scala | 32 +--- .../slf4j/internal}/LoggerMacros.scala | 8 +- .../slf4j/internal/Slf4jLoggerInternal.scala | 55 ++++++ .../log4cats/slf4j/internal}/logLevels.scala | 2 +- .../log4cats/slf4j/Slf4jLogger.scala | 176 ++++-------------- .../log4cats/testing/TestingLogger.scala | 4 +- 11 files changed, 128 insertions(+), 191 deletions(-) create mode 100644 core/shared/src/main/scala/io/chrisdavenport/log4cats/LogLevelAware.scala create mode 100644 core/shared/src/main/scala/io/chrisdavenport/log4cats/MDCLogger.scala rename {slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j => slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal}/LoggerMacros.scala (95%) create mode 100644 slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala rename {slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j => slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal}/logLevels.scala (98%) diff --git a/build.sbt b/build.sbt index 268a4161..7f3c201c 100644 --- a/build.sbt +++ b/build.sbt @@ -12,6 +12,7 @@ lazy val log4cats = project.in(file(".")) scribeJVM, scribeJS, slf4j, + `slf4j-internal`, log4sJVM, log4sJS, docs @@ -57,13 +58,21 @@ lazy val log4s = crossProject.in(file("log4s")) lazy val slf4j = project.in(file("slf4j")) .settings(commonSettings, releaseSettings) + .dependsOn(`slf4j-internal`) + .settings( + name := "log4cats-slf4j" + ) + +lazy val `slf4j-internal` = project.in(file("slf4j-internal")) +.settings(commonSettings, releaseSettings) .dependsOn(core.jvm) .settings( - name := "log4cats-slf4j", + name := "log4cats-slf4j-internal", libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.25" ) ) + lazy val log4sJVM = log4s.jvm lazy val log4sJS = log4s.js diff --git a/core/shared/src/main/scala/io/chrisdavenport/log4cats/LogLevelAware.scala b/core/shared/src/main/scala/io/chrisdavenport/log4cats/LogLevelAware.scala new file mode 100644 index 00000000..6c22bf6d --- /dev/null +++ b/core/shared/src/main/scala/io/chrisdavenport/log4cats/LogLevelAware.scala @@ -0,0 +1,9 @@ +package io.chrisdavenport.log4cats + +trait LogLevelAware[F[_]]{ + def isTraceEnabled: F[Boolean] + def isDebugEnabled: F[Boolean] + def isInfoEnabled: F[Boolean] + def isWarnEnabled: F[Boolean] + def isErrorEnabled: F[Boolean] +} \ No newline at end of file diff --git a/core/shared/src/main/scala/io/chrisdavenport/log4cats/Logger.scala b/core/shared/src/main/scala/io/chrisdavenport/log4cats/Logger.scala index b8334b0b..5c2c4262 100644 --- a/core/shared/src/main/scala/io/chrisdavenport/log4cats/Logger.scala +++ b/core/shared/src/main/scala/io/chrisdavenport/log4cats/Logger.scala @@ -3,12 +3,6 @@ package io.chrisdavenport.log4cats trait Logger[F[_]]{ import Logger.{withModifiedString => wMS} - def isTraceEnabled: F[Boolean] - def isDebugEnabled: F[Boolean] - def isInfoEnabled: F[Boolean] - def isWarnEnabled: F[Boolean] - def isErrorEnabled: F[Boolean] - def error(message: => String): F[Unit] def error(t: Throwable)(message: => String): F[Unit] def warn(message: => String): F[Unit] @@ -27,11 +21,6 @@ object Logger { def apply[F[_]](implicit ev: Logger[F]) = ev private def withModifiedString[F[_]](l: Logger[F], f: String => String): Logger[F] = new Logger[F]{ - def isTraceEnabled: F[Boolean] = l.isTraceEnabled - def isDebugEnabled: F[Boolean] = l.isDebugEnabled - def isInfoEnabled: F[Boolean] = l.isInfoEnabled - def isWarnEnabled: F[Boolean] = l.isWarnEnabled - def isErrorEnabled: F[Boolean] = l.isErrorEnabled def error(message: => String): F[Unit] = l.error(f(message)) def error(t: Throwable)(message: => String): F[Unit] = l.error(t)(f(message)) def warn(message: => String): F[Unit] = l.warn(f(message)) diff --git a/core/shared/src/main/scala/io/chrisdavenport/log4cats/MDCLogger.scala b/core/shared/src/main/scala/io/chrisdavenport/log4cats/MDCLogger.scala new file mode 100644 index 00000000..59fa97fb --- /dev/null +++ b/core/shared/src/main/scala/io/chrisdavenport/log4cats/MDCLogger.scala @@ -0,0 +1,9 @@ +package io.chrisdavenport.log4cats + +trait MDCLogger[F[_]]{ + def trace(ctx: (String, String)*)(msg: => String): F[Unit] + def debug(ctx: (String, String)*)(msg: => String): F[Unit] + def info(ctx: (String, String)*)(msg: => String): F[Unit] + def warn(ctx: (String, String)*)(msg: => String): F[Unit] + def error(ctx: (String, String)*)(msg: => String): F[Unit] +} \ No newline at end of file diff --git a/log4s/shared/src/main/scala/io/chrisdavenport/log4cats/log4s/Log4sLogger.scala b/log4s/shared/src/main/scala/io/chrisdavenport/log4cats/log4s/Log4sLogger.scala index bbfb5296..abc973af 100644 --- a/log4s/shared/src/main/scala/io/chrisdavenport/log4cats/log4s/Log4sLogger.scala +++ b/log4s/shared/src/main/scala/io/chrisdavenport/log4cats/log4s/Log4sLogger.scala @@ -10,7 +10,7 @@ object Log4sLogger { def createByName[F[_]: Sync](name: String) = fromLog4s[F](org.log4s.getLogger(name)) def createByClass[F[_]: Sync](clazz: Class[_]) = fromLog4s[F](org.log4s.getLogger(clazz)) - def fromLog4s[F[_]: Sync](logger: Base): Logger[F] = new Logger[F]{ + def fromLog4s[F[_]: Sync](logger: Base): Logger[F] with LogLevelAware[F] = new Logger[F] with LogLevelAware[F] { override def isTraceEnabled: F[Boolean] = Sync[F].delay(logger.isTraceEnabled) diff --git a/scribe/shared/src/main/scala/io/chrisdavenport/log4cats/scribe/ScribeLogger.scala b/scribe/shared/src/main/scala/io/chrisdavenport/log4cats/scribe/ScribeLogger.scala index 160eec47..b0ab7d99 100644 --- a/scribe/shared/src/main/scala/io/chrisdavenport/log4cats/scribe/ScribeLogger.scala +++ b/scribe/shared/src/main/scala/io/chrisdavenport/log4cats/scribe/ScribeLogger.scala @@ -1,7 +1,7 @@ package io.chrisdavenport.log4cats.scribe import io.chrisdavenport.log4cats.Logger -import _root_.scribe.{Logger => Base, Level} +import _root_.scribe.{Logger => Base} import cats.effect.Sync object ScribeLogger { @@ -12,27 +12,6 @@ object ScribeLogger { def fromLogger[F[_]: Sync](logger: Base): Logger[F] = new Logger[F]{ - def isTraceEnabled: F[Boolean] = - Sync[F].delay( - checkLogLevelEnabled(logger, Level.Trace) - ) - def isDebugEnabled: F[Boolean] = - Sync[F].delay( - checkLogLevelEnabled(logger, Level.Debug) - ) - def isInfoEnabled: F[Boolean] = - Sync[F].delay( - checkLogLevelEnabled(logger, Level.Info) - ) - def isWarnEnabled: F[Boolean] = - Sync[F].delay( - checkLogLevelEnabled(logger, Level.Warn) - ) - def isErrorEnabled: F[Boolean] = - Sync[F].delay( - checkLogLevelEnabled(logger, Level.Error) - ) - def error(message: => String): F[Unit] = Sync[F].delay(logger.error(message)) def error(t: Throwable)(message: => String): F[Unit] = @@ -55,13 +34,4 @@ object ScribeLogger { Sync[F].delay(logger.trace(message, t)) } - /** - * Almost Certain this Behavior is incorrectly in place - * @param l Underlying Scribe Logger - * @param level Level to Check if it is enabled - */ - private[scribe] def checkLogLevelEnabled(l: Base, level: Level): Boolean = { - l.handlers.forall(_.modifiers.forall(_.priority.value <= level.value)) - } - } \ No newline at end of file diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala similarity index 95% rename from slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala rename to slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala index 596c5554..abdf7296 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/LoggerMacros.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.chrisdavenport.log4cats.slf4j +package io.chrisdavenport.log4cats.slf4j.internal import scala.annotation.tailrec import scala.reflect.macros.{blackbox, whitebox} @@ -51,7 +51,7 @@ private[slf4j] object LoggerMacros { assert(cls.isModule || cls.isClass, "Enclosing class is always either a module or a class") def loggerByParam(param: c.Tree)(f: c.Expr[F]) = - q"new _root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger(_root_.org.slf4j.LoggerFactory.getLogger(...${List( + q"new _root_.io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal(_root_.org.slf4j.LoggerFactory.getLogger(...${List( param)}))($f)" def loggerBySymbolName(s: Symbol)(f: c.Expr[F]) = { @@ -98,7 +98,7 @@ private[slf4j] object LoggerMacros { @inline def isInnerClass(s: Symbol) = s.isClass && !(s.owner.isPackage) - val instanceByName = Slf4jLogger.singletonsByName && (cls.isModule || cls.isModuleClass) || cls.isClass && isInnerClass( + val instanceByName = Slf4jLoggerInternal.singletonsByName && (cls.isModule || cls.isModuleClass) || cls.isClass && isInnerClass( cls ) @@ -110,7 +110,7 @@ private[slf4j] object LoggerMacros { } /** A macro context that represents a method call on a Logger instance. */ - private[this] type LogCtx[F[_]] = whitebox.Context { type PrefixType = Slf4jLogger[F] } + private[this] type LogCtx[F[_]] = whitebox.Context { type PrefixType = Slf4jLoggerInternal[F] } /** Log a message reflectively at a given level. * diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala new file mode 100644 index 00000000..30f43d51 --- /dev/null +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala @@ -0,0 +1,55 @@ +package io.chrisdavenport.log4cats.slf4j.internal + +import language.experimental.macros +import org.slf4j.{Logger => JLogger} +import cats.effect.Sync + +object Slf4jLoggerInternal { + + final val singletonsByName = true + final val trailingDollar = false + + sealed trait LevelLogger[F[_]] extends Any { + def isEnabled: F[Boolean] + + def apply(msg: => String): F[Unit] + def apply(t: Throwable)(msg: => String): F[Unit] + } +} + +private[slf4j] final class Slf4jLoggerInternal[F[_]](val logger: JLogger)(implicit val F: Sync[F]){ + + @inline def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) + + @inline def isDebugEnabled: F[Boolean] = F.delay(logger.isDebugEnabled) + + @inline def isInfoEnabled: F[Boolean] = F.delay(logger.isInfoEnabled) + + @inline def isWarnEnabled: F[Boolean] = F.delay(logger.isWarnEnabled) + + @inline def isErrorEnabled: F[Boolean] = F.delay(logger.isErrorEnabled) + + import LoggerMacros._ + + // Internal Methods To Not Run into Macro Abstract implementation issues. + def internalTraceTM(t: Throwable)(msg: => String): F[Unit] = macro traceTM[F] + def internalTraceM(msg: => String): F[Unit] = macro traceM[F] + def internalTraceMDC(ctx: (String, String)*)(msg: => String): F[Unit] = macro traceCM[F] + + def internalDebugTM(t: Throwable)(msg: => String): F[Unit] = macro debugTM[F] + def internalDebugM(msg: => String): F[Unit] = macro debugM[F] + def internalDebugMDC(ctx: (String, String)*)(msg: => String): F[Unit] = macro debugCM[F] + + def internalInfoTM(t: Throwable)(msg: => String): F[Unit] = macro infoTM[F] + def internalInfoM(msg: => String): F[Unit] = macro infoM[F] + def internalInfoMDC(ctx: (String, String)*)(msg: => String): F[Unit] = macro infoCM[F] + + def internalWarnTM(t: Throwable)(msg: => String): F[Unit] = macro warnTM[F] + def internalWarnM(msg: => String): F[Unit] = macro warnM[F] + def internalWarnMDC(ctx: (String, String)*)(msg: => String): F[Unit] = macro warnCM[F] + + def internalErrorTM(t: Throwable)(msg: => String): F[Unit] = macro errorTM[F] + def internalErrorM(msg: => String): F[Unit] = macro errorM[F] + def internalErrorMDC(ctx: (String, String)*)(msg: => String): F[Unit] = macro errorCM[F] + +} diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/logLevels.scala similarity index 98% rename from slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala rename to slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/logLevels.scala index 2ab83ea8..5069c7ca 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/logLevels.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/logLevels.scala @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.chrisdavenport.log4cats.slf4j +package io.chrisdavenport.log4cats.slf4j.internal /** A severity level that can be assigned to log statements. */ sealed trait LogLevel { diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index 80cc8a8d..d2535ab0 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -19,152 +19,48 @@ package io.chrisdavenport.log4cats.slf4j import cats.effect.Sync -import io.chrisdavenport.log4cats.Logger +import io.chrisdavenport.log4cats.{Logger, LogLevelAware, MDCLogger} +import io.chrisdavenport.log4cats.slf4j.internal._ import language.experimental.macros -import org.slf4j.{Logger => JLogger} +// import org.slf4j.{Logger => JLogger} object Slf4jLogger { - def create[F[_]: Sync]: Slf4jLogger[F] = macro LoggerMacros.getLoggerImpl[F[_]] + def create[F[_]: Sync]: Logger[F] with LogLevelAware[F] with MDCLogger[F] = + macro LoggerMacros.getLoggerImpl[F[_]] + + def fromName[F[_]: Sync](name: String): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(name))) + + def fromClass[F[_]: Sync](clazz: Class[_]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(clazz))) + + private def fromSlf4jLogger[F[_]: Sync](s: Slf4jLoggerInternal[F]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + new Logger[F] with LogLevelAware[F] with MDCLogger[F] { + @inline override def isTraceEnabled: F[Boolean] = s.isTraceEnabled + @inline override def isDebugEnabled: F[Boolean] = s.isDebugEnabled + @inline override def isInfoEnabled: F[Boolean] = s.isInfoEnabled + @inline override def isWarnEnabled: F[Boolean] = s.isWarnEnabled + @inline override def isErrorEnabled: F[Boolean] = s.isErrorEnabled + + override def trace(t: Throwable)(msg: => String): F[Unit] = s.internalTraceTM(t)(msg) + override def trace(msg: => String): F[Unit] = s.internalTraceM(msg) + override def trace(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalTraceMDC(ctx:_*)(msg) + override def debug(t: Throwable)(msg: => String): F[Unit] = s.internalDebugTM(t)(msg) + override def debug(msg: => String): F[Unit] = s.internalDebugM(msg) + override def debug(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalDebugMDC(ctx:_*)(msg) + override def info(t: Throwable)(msg: => String): F[Unit] = s.internalInfoTM(t)(msg) + override def info(msg: => String): F[Unit] = s.internalInfoM(msg) + override def info(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalInfoMDC(ctx:_*)(msg) + override def warn(t: Throwable)(msg: => String): F[Unit] = s.internalWarnTM(t)(msg) + override def warn(msg: => String): F[Unit] = s.internalWarnM(msg) + override def warn(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalWarnMDC(ctx:_*)(msg) + override def error(t: Throwable)(msg: => String): F[Unit] = s.internalErrorTM(t)(msg) + override def error(msg: => String): F[Unit] = s.internalErrorM(msg) + override def error(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalErrorMDC(ctx:_*)(msg) + } - def fromName[F[_]: Sync](name: String): Slf4jLogger[F] = - new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name)) - def fromClass[F[_]: Sync](clazz: Class[_]): Slf4jLogger[F] = - new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(clazz)) - - final val singletonsByName = true - final val trailingDollar = false - - sealed trait LevelLogger[F[_]] extends Any { - def isEnabled: F[Boolean] - - def apply(msg: => String): F[Unit] - def apply(t: Throwable)(msg: => String): F[Unit] - } -// -// final class TraceLevelLogger[F[_]: Sync] private[slf4j] (val logger: JLogger) -// extends AnyVal -// with LevelLogger[F] { -// @inline def isEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) -// @inline def apply(msg: => String): F[Unit] = isEnabled.flatMap {isEnabled => if(isEnabled) $F.delay($logExpr) else $F.unit -// @inline def apply(t: Throwable)(msg: => String) = if (isEnabled) logger.trace(msg, t) -// } - -// final class DebugLevelLogger private[slf4j] (val logger: JLogger) -// extends AnyVal -// with LevelLogger { -// @inline def isEnabled: Boolean = logger.isDebugEnabled -// @inline def apply(msg: => String): Unit = if (isEnabled) logger.debug(msg) -// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.debug(msg, t) -// } -// -// final class InfoLevelLogger private[slf4j] (val logger: JLogger) -// extends AnyVal -// with LevelLogger { -// @inline def isEnabled: Boolean = logger.isInfoEnabled -// @inline def apply(msg: => String): Unit = if (isEnabled) logger.info(msg) -// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.info(msg, t) -// } -// -// final class WarnLevelLogger private[slf4j] (val logger: JLogger) -// extends AnyVal -// with LevelLogger { -// @inline def isEnabled: Boolean = logger.isWarnEnabled -// @inline def apply(msg: => String): Unit = if (isEnabled) logger.warn(msg) -// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.warn(msg, t) -// } -// -// final class ErrorLevelLogger private[slf4j] (val logger: JLogger) -// extends AnyVal -// with LevelLogger { -// @inline def isEnabled: Boolean = logger.isErrorEnabled -// @inline def apply(msg: => String): Unit = if (isEnabled) logger.error(msg) -// @inline def apply(t: Throwable)(msg: => String): Unit = if (isEnabled) logger.error(msg, t) -// } - - /** - * The existance of this class is due to the in-ability of macros to - * override abstract methods, only methods directly. - * - * See: - * https://github.com/scala/scala/commit/ef979c02da887b7c56bc1da9c4eb888e92af570f - */ - private[Slf4jLogger] class NonMacroSlf4jWrapper[F[_]](val logger: JLogger)( - implicit val F: Sync[F]) - extends Logger[F] { - - @inline override def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) - - @inline override def isDebugEnabled: F[Boolean] = F.delay(logger.isDebugEnabled) - - @inline override def isInfoEnabled: F[Boolean] = F.delay(logger.isInfoEnabled) - - @inline override def isWarnEnabled: F[Boolean] = F.delay(logger.isWarnEnabled) - - @inline override def isErrorEnabled: F[Boolean] = F.delay(logger.isErrorEnabled) - - override def trace(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.trace(msg, t)) - override def trace(msg: => String): F[Unit] = F.delay(logger.trace(msg)) - def trace(ctx: (String, String)*)(msg: => String): F[Unit] = - ??? //implement if it makes it into the Logger[F] interface - - override def debug(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.debug(msg, t)) - override def debug(msg: => String): F[Unit] = F.delay(logger.debug(msg)) - def debug(ctx: (String, String)*)(msg: => String): F[Unit] = - ??? //implement if it makes it into the Logger[F] interface - - override def info(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.info(msg, t)) - override def info(msg: => String): F[Unit] = F.delay(logger.info(msg)) - def info(ctx: (String, String)*)(msg: => String): F[Unit] = - ??? //implement if it makes it into the Logger[F] interface - - override def warn(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.warn(msg, t)) - override def warn(msg: => String): F[Unit] = F.delay(logger.warn(msg)) - def warn(ctx: (String, String)*)(msg: => String): F[Unit] = - ??? //implement if it makes it into the Logger[F] interface - - override def error(t: Throwable)(msg: => String): F[Unit] = F.delay(logger.error(msg, t)) - override def error(msg: => String): F[Unit] = F.delay(logger.error(msg)) - def error(ctx: (String, String)*)(msg: => String): F[Unit] = - ??? //implement if it makes it into the Logger[F] interface - } } -final class Slf4jLogger[F[_]](override val logger: JLogger)(implicit override val F: Sync[F]) - extends Slf4jLogger.NonMacroSlf4jWrapper[F](logger)(F) { - - @inline override def isTraceEnabled: F[Boolean] = F.delay(logger.isTraceEnabled) - - @inline override def isDebugEnabled: F[Boolean] = F.delay(logger.isDebugEnabled) - - @inline override def isInfoEnabled: F[Boolean] = F.delay(logger.isInfoEnabled) - - @inline override def isWarnEnabled: F[Boolean] = F.delay(logger.isWarnEnabled) - - @inline override def isErrorEnabled: F[Boolean] = F.delay(logger.isErrorEnabled) - - import LoggerMacros._ - - override def trace(t: Throwable)(msg: => String): F[Unit] = macro traceTM[F] - override def trace(msg: => String): F[Unit] = macro traceM[F] - override def trace(ctx: (String, String)*)(msg: => String): F[Unit] = macro traceCM[F] - - override def debug(t: Throwable)(msg: => String): F[Unit] = macro debugTM[F] - override def debug(msg: => String): F[Unit] = macro debugM[F] - override def debug(ctx: (String, String)*)(msg: => String): F[Unit] = macro debugCM[F] - - override def info(t: Throwable)(msg: => String): F[Unit] = macro infoTM[F] - override def info(msg: => String): F[Unit] = macro infoM[F] - override def info(ctx: (String, String)*)(msg: => String): F[Unit] = macro infoCM[F] - - override def warn(t: Throwable)(msg: => String): F[Unit] = macro warnTM[F] - override def warn(msg: => String): F[Unit] = macro warnM[F] - override def warn(ctx: (String, String)*)(msg: => String): F[Unit] = macro warnCM[F] - - override def error(t: Throwable)(msg: => String): F[Unit] = macro errorTM[F] - override def error(msg: => String): F[Unit] = macro errorM[F] - override def error(ctx: (String, String)*)(msg: => String): F[Unit] = macro errorCM[F] - -} diff --git a/testing/shared/src/main/scala/io/chrisdavenport/log4cats/testing/TestingLogger.scala b/testing/shared/src/main/scala/io/chrisdavenport/log4cats/testing/TestingLogger.scala index 0e84dc32..d7daa4cd 100644 --- a/testing/shared/src/main/scala/io/chrisdavenport/log4cats/testing/TestingLogger.scala +++ b/testing/shared/src/main/scala/io/chrisdavenport/log4cats/testing/TestingLogger.scala @@ -1,12 +1,12 @@ package io.chrisdavenport.log4cats.testing -import io.chrisdavenport.log4cats.Logger +import io.chrisdavenport.log4cats.{Logger, LogLevelAware} import cats.effect.Sync import cats.implicits._ import java.util.concurrent.atomic.AtomicReference import scala.annotation.tailrec -trait TestingLogger[F[_]] extends Logger[F] { +trait TestingLogger[F[_]] extends Logger[F] with LogLevelAware[F]{ import TestingLogger.LogMessage def logged: F[Vector[LogMessage]] } From 287a6875c4f23a9f03c9451ebbd5106186e95536 Mon Sep 17 00:00:00 2001 From: Chris Davenport Date: Tue, 29 May 2018 12:04:00 -0400 Subject: [PATCH 4/8] Fix Macro Expansion for Generation --- .../chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala | 4 ++-- .../scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala index abdf7296..cdac518c 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala @@ -51,8 +51,8 @@ private[slf4j] object LoggerMacros { assert(cls.isModule || cls.isClass, "Enclosing class is always either a module or a class") def loggerByParam(param: c.Tree)(f: c.Expr[F]) = - q"new _root_.io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal(_root_.org.slf4j.LoggerFactory.getLogger(...${List( - param)}))($f)" + q"_root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger.fromSlf4jLogger(new _root_.io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal(_root_.org.slf4j.LoggerFactory.getLogger(...${List( + param)}))($f))" def loggerBySymbolName(s: Symbol)(f: c.Expr[F]) = { def fullName(s: Symbol): String = { diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index d2535ab0..7be65c14 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -23,12 +23,11 @@ import io.chrisdavenport.log4cats.{Logger, LogLevelAware, MDCLogger} import io.chrisdavenport.log4cats.slf4j.internal._ import language.experimental.macros -// import org.slf4j.{Logger => JLogger} object Slf4jLogger { def create[F[_]: Sync]: Logger[F] with LogLevelAware[F] with MDCLogger[F] = - macro LoggerMacros.getLoggerImpl[F[_]] + macro LoggerMacros.getLoggerImpl[F[_]] def fromName[F[_]: Sync](name: String): Logger[F] with LogLevelAware[F] with MDCLogger[F] = fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(name))) From 955d1de5df1e7d45dc9a07d15a16e12ff3a74992 Mon Sep 17 00:00:00 2001 From: Chris Davenport Date: Tue, 29 May 2018 12:52:29 -0400 Subject: [PATCH 5/8] Fully Internalize Macro Implementation --- .../{logLevels.scala => LogLevel.scala} | 57 ++++++++++--------- .../slf4j/internal/LoggerMacros.scala | 34 +++++------ .../slf4j/internal/Slf4jLoggerInternal.scala | 2 +- .../log4cats/slf4j/Slf4jLogger.scala | 6 +- 4 files changed, 52 insertions(+), 47 deletions(-) rename slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/{logLevels.scala => LogLevel.scala} (50%) diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/logLevels.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala similarity index 50% rename from slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/logLevels.scala rename to slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala index 5069c7ca..146cbbbe 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/logLevels.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala @@ -19,7 +19,7 @@ package io.chrisdavenport.log4cats.slf4j.internal /** A severity level that can be assigned to log statements. */ -sealed trait LogLevel { +private[slf4j] sealed trait LogLevel { /** The name of this log level. It is spelled with initial capitals */ def name: String = this.toString @@ -28,7 +28,7 @@ sealed trait LogLevel { private[slf4j] def methodName = name.toLowerCase } -object LogLevel { +private[slf4j] object LogLevel { def forName(name: String): LogLevel = { name.toLowerCase match { @@ -41,34 +41,35 @@ object LogLevel { throw new IllegalArgumentException(s"No log level named $name") } } -} + /** The highest logging severity. This generally indicates an + * application or system error that causes undesired outcomes. + * An error generally indicates a bug or an environment + * problem that warrants some kind of immediate intervention. + */ + case object Error extends LogLevel -/** The highest logging severity. This generally indicates an - * application or system error that causes undesired outcomes. - * An error generally indicates a bug or an environment - * problem that warrants some kind of immediate intervention. - */ -case object Error extends LogLevel + /** Generally indicates something is not expected but the system is + * able to continue operating. This generally indicates a bug or + * environment problem that does not require urgent intervention. + */ + case object Warn extends LogLevel -/** Generally indicates something is not expected but the system is - * able to continue operating. This generally indicates a bug or - * environment problem that does not require urgent intervention. - */ -case object Warn extends LogLevel + /** Indicates normal high-level activity. Generally a single user– or + * system-initiated activity will trigger one or two info-level statements. + * (E.g., one when starting and one when finishing for complex requests.) + */ + case object Info extends LogLevel -/** Indicates normal high-level activity. Generally a single user– or - * system-initiated activity will trigger one or two info-level statements. - * (E.g., one when starting and one when finishing for complex requests.) - */ -case object Info extends LogLevel + /** Log statements that provide the ability to trace the progress and + * behavior involved in tracking a single activity. These are useful for + * debugging general issues, identifying how modules are interacting, etc. + */ + case object Debug extends LogLevel + + /** Highly localized log statements useful for tracking the decisions made + * inside a single unit of code. These may occur at a very high frequency. + */ + case object Trace extends LogLevel +} -/** Log statements that provide the ability to trace the progress and - * behavior involved in tracking a single activity. These are useful for - * debugging general issues, identifying how modules are interacting, etc. - */ -case object Debug extends LogLevel -/** Highly localized log statements useful for tracking the decisions made - * inside a single unit of code. These may occur at a very high frequency. - */ -case object Trace extends LogLevel diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala index cdac518c..e249e968 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala @@ -51,8 +51,8 @@ private[slf4j] object LoggerMacros { assert(cls.isModule || cls.isClass, "Enclosing class is always either a module or a class") def loggerByParam(param: c.Tree)(f: c.Expr[F]) = - q"_root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger.fromSlf4jLogger(new _root_.io.chrisdavenport.log4cats.slf4j.internal.Slf4jLoggerInternal(_root_.org.slf4j.LoggerFactory.getLogger(...${List( - param)}))($f))" + q"_root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger.fromSlf4j(_root_.org.slf4j.LoggerFactory.getLogger(...${List( + param)}))($f)" def loggerBySymbolName(s: Symbol)(f: c.Expr[F]) = { def fullName(s: Symbol): String = { @@ -167,42 +167,42 @@ private[slf4j] object LoggerMacros { } def traceTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = - reflectiveLog(c)(msg, Some(t), Nil)(Trace) + reflectiveLog(c)(msg, Some(t), Nil)(LogLevel.Trace) - def traceM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Trace) + def traceM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(LogLevel.Trace) def traceCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = - reflectiveLog(c)(msg, None, ctx)(Trace) + reflectiveLog(c)(msg, None, ctx)(LogLevel.Trace) def debugTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = - reflectiveLog(c)(msg, Some(t), Nil)(Debug) + reflectiveLog(c)(msg, Some(t), Nil)(LogLevel.Debug) - def debugM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Debug) + def debugM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(LogLevel.Debug) def debugCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = - reflectiveLog(c)(msg, None, ctx)(Debug) + reflectiveLog(c)(msg, None, ctx)(LogLevel.Debug) def infoTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = - reflectiveLog(c)(msg, Some(t), Nil)(Info) + reflectiveLog(c)(msg, Some(t), Nil)(LogLevel.Info) - def infoM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Info) + def infoM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(LogLevel.Info) def infoCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = - reflectiveLog(c)(msg, None, ctx)(Info) + reflectiveLog(c)(msg, None, ctx)(LogLevel.Info) def warnTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = - reflectiveLog(c)(msg, Some(t), Nil)(Warn) + reflectiveLog(c)(msg, Some(t), Nil)(LogLevel.Warn) - def warnM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Warn) + def warnM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(LogLevel.Warn) def warnCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = - reflectiveLog(c)(msg, None, ctx)(Warn) + reflectiveLog(c)(msg, None, ctx)(LogLevel.Warn) def errorTM[F[_]](c: LogCtx[F])(t: c.Expr[Throwable])(msg: c.Tree) = - reflectiveLog(c)(msg, Some(t), Nil)(Error) + reflectiveLog(c)(msg, Some(t), Nil)(LogLevel.Error) - def errorM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(Error) + def errorM[F[_]](c: LogCtx[F])(msg: c.Tree) = reflectiveLog(c)(msg, None, Nil)(LogLevel.Error) def errorCM[F[_]](c: LogCtx[F])(ctx: c.Expr[(String, String)]*)(msg: c.Tree) = - reflectiveLog(c)(msg, None, ctx)(Error) + reflectiveLog(c)(msg, None, ctx)(LogLevel.Error) } diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala index 30f43d51..aa23800a 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/Slf4jLoggerInternal.scala @@ -4,7 +4,7 @@ import language.experimental.macros import org.slf4j.{Logger => JLogger} import cats.effect.Sync -object Slf4jLoggerInternal { +private[slf4j] object Slf4jLoggerInternal { final val singletonsByName = true final val trailingDollar = false diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index 7be65c14..a1556f6e 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -21,6 +21,7 @@ package io.chrisdavenport.log4cats.slf4j import cats.effect.Sync import io.chrisdavenport.log4cats.{Logger, LogLevelAware, MDCLogger} import io.chrisdavenport.log4cats.slf4j.internal._ +import org.slf4j.{Logger => JLogger} import language.experimental.macros @@ -29,12 +30,15 @@ object Slf4jLogger { def create[F[_]: Sync]: Logger[F] with LogLevelAware[F] with MDCLogger[F] = macro LoggerMacros.getLoggerImpl[F[_]] - def fromName[F[_]: Sync](name: String): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + def fromName[F[_]: Sync](name: String):Logger[F] with LogLevelAware[F] with MDCLogger[F] = fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(name))) def fromClass[F[_]: Sync](clazz: Class[_]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(clazz))) + def fromSlf4j[F[_]: Sync](s: JLogger): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + fromSlf4jLogger(new Slf4jLoggerInternal[F](s)) + private def fromSlf4jLogger[F[_]: Sync](s: Slf4jLoggerInternal[F]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = new Logger[F] with LogLevelAware[F] with MDCLogger[F] { @inline override def isTraceEnabled: F[Boolean] = s.isTraceEnabled From b8367a3cac0081e0b92ec6817d895ac75d0f9cad Mon Sep 17 00:00:00 2001 From: Chris Davenport Date: Tue, 29 May 2018 13:27:21 -0400 Subject: [PATCH 6/8] Attempt to Inline To the Macro Implementations --- .../log4cats/slf4j/Slf4jLogger.scala | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index a1556f6e..ed96921b 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -47,21 +47,21 @@ object Slf4jLogger { @inline override def isWarnEnabled: F[Boolean] = s.isWarnEnabled @inline override def isErrorEnabled: F[Boolean] = s.isErrorEnabled - override def trace(t: Throwable)(msg: => String): F[Unit] = s.internalTraceTM(t)(msg) - override def trace(msg: => String): F[Unit] = s.internalTraceM(msg) - override def trace(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalTraceMDC(ctx:_*)(msg) - override def debug(t: Throwable)(msg: => String): F[Unit] = s.internalDebugTM(t)(msg) - override def debug(msg: => String): F[Unit] = s.internalDebugM(msg) - override def debug(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalDebugMDC(ctx:_*)(msg) - override def info(t: Throwable)(msg: => String): F[Unit] = s.internalInfoTM(t)(msg) - override def info(msg: => String): F[Unit] = s.internalInfoM(msg) - override def info(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalInfoMDC(ctx:_*)(msg) - override def warn(t: Throwable)(msg: => String): F[Unit] = s.internalWarnTM(t)(msg) - override def warn(msg: => String): F[Unit] = s.internalWarnM(msg) - override def warn(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalWarnMDC(ctx:_*)(msg) - override def error(t: Throwable)(msg: => String): F[Unit] = s.internalErrorTM(t)(msg) - override def error(msg: => String): F[Unit] = s.internalErrorM(msg) - override def error(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalErrorMDC(ctx:_*)(msg) + @inline override def trace(t: Throwable)(msg: => String): F[Unit] = s.internalTraceTM(t)(msg) + @inline override def trace(msg: => String): F[Unit] = s.internalTraceM(msg) + @inline override def trace(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalTraceMDC(ctx:_*)(msg) + @inline override def debug(t: Throwable)(msg: => String): F[Unit] = s.internalDebugTM(t)(msg) + @inline override def debug(msg: => String): F[Unit] = s.internalDebugM(msg) + @inline override def debug(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalDebugMDC(ctx:_*)(msg) + @inline override def info(t: Throwable)(msg: => String): F[Unit] = s.internalInfoTM(t)(msg) + @inline override def info(msg: => String): F[Unit] = s.internalInfoM(msg) + @inline override def info(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalInfoMDC(ctx:_*)(msg) + @inline override def warn(t: Throwable)(msg: => String): F[Unit] = s.internalWarnTM(t)(msg) + @inline override def warn(msg: => String): F[Unit] = s.internalWarnM(msg) + @inline override def warn(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalWarnMDC(ctx:_*)(msg) + @inline override def error(t: Throwable)(msg: => String): F[Unit] = s.internalErrorTM(t)(msg) + @inline override def error(msg: => String): F[Unit] = s.internalErrorM(msg) + @inline override def error(ctx: (String, String)*)(msg: => String): F[Unit] = s.internalErrorMDC(ctx:_*)(msg) } From c50d146ae74be28580ef6d5e2cc5da1a0a414cb8 Mon Sep 17 00:00:00 2001 From: Chris Davenport Date: Wed, 30 May 2018 09:58:47 -0400 Subject: [PATCH 7/8] Add Safe/Unsafe Variants --- .../slf4j/internal/LoggerMacros.scala | 2 +- .../log4cats/slf4j/Slf4jLogger.scala | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala index e249e968..e338eb61 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala @@ -51,7 +51,7 @@ private[slf4j] object LoggerMacros { assert(cls.isModule || cls.isClass, "Enclosing class is always either a module or a class") def loggerByParam(param: c.Tree)(f: c.Expr[F]) = - q"_root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger.fromSlf4j(_root_.org.slf4j.LoggerFactory.getLogger(...${List( + q"_root_.io.chrisdavenport.log4cats.slf4j.Slf4jLogger.unsafeFromSlf4j(_root_.org.slf4j.LoggerFactory.getLogger(...${List( param)}))($f)" def loggerBySymbolName(s: Symbol)(f: c.Expr[F]) = { diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index ed96921b..7b4c54c3 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -27,17 +27,29 @@ import language.experimental.macros object Slf4jLogger { - def create[F[_]: Sync]: Logger[F] with LogLevelAware[F] with MDCLogger[F] = + def create[F[_]: Sync]: F[Logger[F] with LogLevelAware[F] with MDCLogger[F]] = + Sync[F].delay(unsafeCreate) + + def fromName[F[_]: Sync](name: String): F[Logger[F] with LogLevelAware[F] with MDCLogger[F]] = + Sync[F].delay(unsafeFromName(name)) + + def fromClass[F[_]: Sync](clazz: Class[_]): F[Logger[F] with LogLevelAware[F] with MDCLogger[F]] = + Sync[F].delay(unsafeFromClass(clazz)) + + def fromSlf4j[F[_]: Sync](logger: JLogger): F[Logger[F] with LogLevelAware[F] with MDCLogger[F]] = + Sync[F].delay(unsafeFromSlf4j(logger)) + + def unsafeCreate[F[_]: Sync]: Logger[F] with LogLevelAware[F] with MDCLogger[F] = macro LoggerMacros.getLoggerImpl[F[_]] - def fromName[F[_]: Sync](name: String):Logger[F] with LogLevelAware[F] with MDCLogger[F] = + def unsafeFromName[F[_]: Sync](name: String):Logger[F] with LogLevelAware[F] with MDCLogger[F] = fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(name))) - def fromClass[F[_]: Sync](clazz: Class[_]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + def unsafeFromClass[F[_]: Sync](clazz: Class[_]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = fromSlf4jLogger(new Slf4jLoggerInternal[F](org.slf4j.LoggerFactory.getLogger(clazz))) - def fromSlf4j[F[_]: Sync](s: JLogger): Logger[F] with LogLevelAware[F] with MDCLogger[F] = - fromSlf4jLogger(new Slf4jLoggerInternal[F](s)) + def unsafeFromSlf4j[F[_]: Sync](logger: JLogger): Logger[F] with LogLevelAware[F] with MDCLogger[F] = + fromSlf4jLogger(new Slf4jLoggerInternal[F](logger)) private def fromSlf4jLogger[F[_]: Sync](s: Slf4jLoggerInternal[F]): Logger[F] with LogLevelAware[F] with MDCLogger[F] = new Logger[F] with LogLevelAware[F] with MDCLogger[F] { From 586c37b96bca13fdf4cc7e2c0c3504ff408f4613 Mon Sep 17 00:00:00 2001 From: Chris Davenport Date: Wed, 30 May 2018 10:02:58 -0400 Subject: [PATCH 8/8] Change for consistent copyright, add contributor --- build.sbt | 3 ++- .../io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala | 2 +- .../chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala | 2 +- .../scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 7f3c201c..ff35be44 100644 --- a/build.sbt +++ b/build.sbt @@ -90,7 +90,8 @@ lazy val scribeJVM = scribe.jvm lazy val scribeJS = scribe.js lazy val contributors = Seq( - "ChristopherDavenport" -> "Christopher Davenport" + "ChristopherDavenport" -> "Christopher Davenport", + "lorandszakacs" -> "Loránd Szakács" ) lazy val commonSettings = Seq( diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala index 146cbbbe..2dc39b78 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LogLevel.scala @@ -2,7 +2,7 @@ * Copyright 2013-2017 Sarah Gerweck * see: https://github.com/Log4s/log4s * - * Modifications copyright (C) 2018 Lorand Szakacs + * Modifications copyright (C) 2018 Christopher Davenport * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala index e338eb61..2665596e 100644 --- a/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala +++ b/slf4j-internal/src/main/scala/io/chrisdavenport/log4cats/slf4j/internal/LoggerMacros.scala @@ -2,7 +2,7 @@ * Copyright 2013-2017 Sarah Gerweck * see: https://github.com/Log4s/log4s * - * Modifications copyright (C) 2018 Lorand Szakacs + * Modifications copyright (C) 2018 Christopher Davenport * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala index 7b4c54c3..4d4febc4 100644 --- a/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/io/chrisdavenport/log4cats/slf4j/Slf4jLogger.scala @@ -2,7 +2,7 @@ * Copyright 2013-2017 Sarah Gerweck * see: https://github.com/Log4s/log4s * - * Modifications copyright (C) 2018 Lorand Szakacs + * Modifications copyright (C) 2018 Christopher Davenport * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.