Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LoggerFactory typeclass #629

Merged
merged 15 commits into from
Apr 21, 2022
8 changes: 7 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.typesafe.tools.mima.core._

val Scala213 = "2.13.8"
val Scala212 = "2.12.15"
val Scala3 = "3.0.2"
Expand Down Expand Up @@ -79,7 +81,11 @@ lazy val slf4j = project
libraryDependencies ++= {
if (tlIsScala3.value) Seq.empty
else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided)
}
},
mimaBinaryIssueFilters ++= Seq(
ProblemFilters.exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.slf4j.internal.GetLoggerMacros.createImpl"),
ProblemFilters.exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.slf4j.internal.GetLoggerMacros.getLoggerImpl")
)
)

lazy val commonSettings = Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.typelevel.log4cats.slf4j

import internal.GetLoggerMacros

object LoggerName {
implicit def name: LoggerName = macro GetLoggerMacros.getLoggerName
}

final case class LoggerName(value: String)
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ package org.typelevel.log4cats.slf4j

import cats.effect.Sync
import org.typelevel.log4cats.SelfAwareStructuredLogger
import org.typelevel.log4cats.slf4j.internal._
import org.typelevel.log4cats.slf4j.internal.{_}
import org.slf4j.{Logger => JLogger}
import scala.annotation.nowarn

object Slf4jLogger {

def getLogger[F[_]: Sync]: SelfAwareStructuredLogger[F] =
macro GetLoggerMacros.unsafeCreateImpl[F[_]]
@nowarn("cat=unused")
private def getLogger[F[_]: Sync]: SelfAwareStructuredLogger[F] = ???
hamnis marked this conversation as resolved.
Show resolved Hide resolved
@nowarn("cat=unused")
private def create[F[_]: Sync]: F[SelfAwareStructuredLogger[F]] = ???

def getLogger[F[_]: Sync](implicit name: LoggerName): SelfAwareStructuredLogger[F] =
getLoggerFromName(name.value.stripSuffix("$"))
hamnis marked this conversation as resolved.
Show resolved Hide resolved

def getLoggerFromName[F[_]: Sync](name: String): SelfAwareStructuredLogger[F] =
getLoggerFromSlf4j(org.slf4j.LoggerFactory.getLogger(name))
Expand All @@ -35,8 +41,8 @@ object Slf4jLogger {
def getLoggerFromSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] =
new Slf4jLoggerInternal.Slf4jLogger(logger)

def create[F[_]: Sync]: F[SelfAwareStructuredLogger[F]] =
macro GetLoggerMacros.safeCreateImpl[F[_]]
def create[F[_]: Sync](implicit name: LoggerName): F[SelfAwareStructuredLogger[F]] =
Sync[F].delay(getLoggerFromName(name.value.stripSuffix("$")))

def fromName[F[_]: Sync](name: String): F[SelfAwareStructuredLogger[F]] =
Sync[F].delay(getLoggerFromName(name))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.typelevel.log4cats
package slf4j

import cats.effect.Sync
import org.slf4j.{Logger => JLogger}

trait Slf4jLoggerFactory[F[_]] {
def getLogger(implicit name: LoggerName): SelfAwareStructuredLogger[F]
def getLoggerFromName(name: String): SelfAwareStructuredLogger[F]
def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F]
def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F]
def create(implicit name: LoggerName): F[SelfAwareStructuredLogger[F]]
def fromName(name: String): F[SelfAwareStructuredLogger[F]]
def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]]
def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]]
}

object Slf4jLoggerFactory {
def apply[F[_]](lf: Slf4jLoggerFactory[F]): Slf4jLoggerFactory[F] = lf
hamnis marked this conversation as resolved.
Show resolved Hide resolved

implicit def forSync[F[_]: Sync]: Slf4jLoggerFactory[F] = new Slf4jLoggerFactory[F] {
override def getLogger(implicit name: LoggerName): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLogger

override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLoggerFromName(name)
override def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLoggerFromClass(clazz)
override def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLoggerFromSlf4j(logger)

def create(implicit name: LoggerName): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.create

override def fromName(name: String): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.fromName(name)

override def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.fromClass(clazz)

override def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.fromSlf4j(logger)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,10 @@ import scala.reflect.macros.blackbox
*/
private[slf4j] class GetLoggerMacros(val c: blackbox.Context) {

final def safeCreateImpl[F](f: c.Expr[F]) = getLoggerImpl[F](f, true)

final def unsafeCreateImpl[F](f: c.Expr[F]) = getLoggerImpl[F](f, false)
final def getLoggerName = getLoggerNameImpl

/** Get a logger by reflecting the enclosing class name. */
private def getLoggerImpl[F](f: c.Expr[F], delayed: Boolean) = {
private def getLoggerNameImpl = {
import c.universe._

@tailrec def findEnclosingClass(sym: c.universe.Symbol): c.universe.Symbol = {
Expand All @@ -53,15 +51,6 @@ private[slf4j] class GetLoggerMacros(val c: blackbox.Context) {

assert(cls.isModule || cls.isClass, "Enclosing class is always either a module or a class")

def loggerByParam(param: c.Tree) = {
val unsafeCreate =
q"_root_.org.typelevel.log4cats.slf4j.Slf4jLogger.getLoggerFromSlf4j(_root_.org.slf4j.LoggerFactory.getLogger(...${List(param)}))($f)"
if (delayed)
q"_root_.cats.effect.Sync.apply($f).delay(...$unsafeCreate)"
else
unsafeCreate
}

def loggerBySymbolName(s: Symbol) = {
hamnis marked this conversation as resolved.
Show resolved Hide resolved
def fullName(s: Symbol): String = {
@inline def isPackageObject = (
Expand All @@ -81,15 +70,15 @@ private[slf4j] class GetLoggerMacros(val c: blackbox.Context) {
fullName(s.owner)
}
}
loggerByParam(q"${fullName(s)}")
q"new _root_.org.typelevel.log4cats.slf4j.LoggerName(${fullName(s)})"
}

def loggerByType(s: Symbol) = {
val typeSymbol: ClassSymbol = (if (s.isModule) s.asModule.moduleClass else s).asClass
val typeParams = typeSymbol.typeParams

if (typeParams.isEmpty) {
loggerByParam(q"_root_.scala.Predef.classOf[$typeSymbol]")
q"new _root_.org.typelevel.log4cats.slf4j.LoggerName(_root_.scala.Predef.classOf[$typeSymbol].getName)"
} else {
if (typeParams.exists(_.asType.typeParams.nonEmpty)) {
/* We have at least one higher-kinded type: fall back to by-name logger construction, as
Expand All @@ -98,7 +87,7 @@ private[slf4j] class GetLoggerMacros(val c: blackbox.Context) {
} else {
val typeArgs = List.fill(typeParams.length)(WildcardType)
val typeConstructor = tq"$typeSymbol[..${typeArgs}]"
loggerByParam(q"_root_.scala.Predef.classOf[$typeConstructor]")
q"new _root_.org.typelevel.log4cats.slf4j.LoggerName(_root_.scala.Predef.classOf[$typeConstructor].getName)"
}
}
}
Expand All @@ -107,9 +96,7 @@ private[slf4j] class GetLoggerMacros(val c: blackbox.Context) {
s.isClass && !(s.owner.isPackage)

val instanceByName =
Slf4jLoggerInternal.singletonsByName && (cls.isModule || cls.isModuleClass) || cls.isClass && isInnerClass(
cls
)
(Slf4jLoggerInternal.singletonsByName && cls.isModule || cls.isModuleClass) || (cls.isClass && isInnerClass(cls))

if (instanceByName) {
loggerBySymbolName(cls)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.typelevel.log4cats.slf4j

import org.typelevel.log4cats.slf4j.internal.GetLoggerMacros

import scala.quoted.*

object LoggerName {
implicit inline def name: LoggerName =
${GetLoggerMacros.getLoggerName }
}

final case class LoggerName(value: String)
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ package org.typelevel.log4cats.slf4j
import cats.effect.Sync
import org.typelevel.log4cats.SelfAwareStructuredLogger
import org.typelevel.log4cats.slf4j.internal._
import org.slf4j.{Logger => JLogger}
import org.slf4j.Logger as JLogger
import scala.annotation.nowarn

object Slf4jLogger {

inline def getLogger[F[_]](implicit F: Sync[F]): SelfAwareStructuredLogger[F] =
${ GetLoggerMacros.getLoggerImpl('F) }
//for binary compability
@nowarn("cat=unused")
private def create[F[_]: Sync]: F[SelfAwareStructuredLogger[F]] = ???

@nowarn("cat=unused")
private def getLogger[F[_]](using F: Sync[F]): SelfAwareStructuredLogger[F] = ???

def getLogger[F[_]: Sync](using n: LoggerName): SelfAwareStructuredLogger[F] =
getLoggerFromName(n.value.stripSuffix("$"))

def getLoggerFromName[F[_]: Sync](name: String): SelfAwareStructuredLogger[F] =
getLoggerFromSlf4j(org.slf4j.LoggerFactory.getLogger(name))
Expand All @@ -35,8 +43,8 @@ object Slf4jLogger {
def getLoggerFromSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] =
new Slf4jLoggerInternal.Slf4jLogger(logger)

inline def create[F[_]](implicit F: Sync[F]): F[SelfAwareStructuredLogger[F]] =
${ GetLoggerMacros.createImpl('F) }
def create[F[_]: Sync](using n: LoggerName): F[SelfAwareStructuredLogger[F]] =
Sync[F].delay(getLoggerFromName(n.value.stripSuffix("$")))

def fromName[F[_]: Sync](name: String): F[SelfAwareStructuredLogger[F]] =
Sync[F].delay(getLoggerFromName(name))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.typelevel.log4cats.slf4j

import cats.effect.Sync
import org.typelevel.log4cats.SelfAwareStructuredLogger
import org.typelevel.log4cats.slf4j.internal.*
import org.slf4j.Logger as JLogger
import org.typelevel.log4cats.slf4j.Slf4jLogger.{
getLoggerFromClass,
getLoggerFromName,
getLoggerFromSlf4j
}

trait Slf4jLoggerFactory[F[_]] {
hamnis marked this conversation as resolved.
Show resolved Hide resolved
def getLogger(using name: LoggerName): SelfAwareStructuredLogger[F]
def getLoggerFromName(name: String): SelfAwareStructuredLogger[F]
def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F]
def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F]
def create(using name: LoggerName): F[SelfAwareStructuredLogger[F]]
def fromName(name: String): F[SelfAwareStructuredLogger[F]]
def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]]
def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]]
}

object Slf4jLoggerFactory {
hamnis marked this conversation as resolved.
Show resolved Hide resolved
def apply[F[_]](lf: Slf4jLoggerFactory[F]): Slf4jLoggerFactory[F] = lf

implicit def forSync[F[_]](implicit F: Sync[F]): Slf4jLoggerFactory[F] =
new Slf4jLoggerFactory[F] {
override def getLogger(using name: LoggerName): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLogger
override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLoggerFromName(name)
override def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLoggerFromClass(clazz)
override def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] =
Slf4jLogger.getLoggerFromSlf4j(logger)
override def create(using name: LoggerName): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.create
override def fromName(name: String): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.fromName(name)

override def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.fromClass(clazz)

override def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] =
Slf4jLogger.fromSlf4j(logger)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@ package org.typelevel.log4cats.slf4j.internal

import cats.effect.Sync
import org.slf4j.LoggerFactory
import org.typelevel.log4cats.slf4j.Slf4jLogger
import org.typelevel.log4cats.slf4j.{LoggerName, Slf4jLogger}
import org.typelevel.log4cats.SelfAwareStructuredLogger

import scala.annotation.tailrec
import scala.quoted._
import scala.quoted.*

private[slf4j] object GetLoggerMacros {

def getLoggerImpl[F[_]: Type](
F: Expr[Sync[F]]
)(using qctx: Quotes): Expr[SelfAwareStructuredLogger[F]] = {
def getLoggerName(using qctx: Quotes): Expr[LoggerName] = {
val name = getLoggerNameImpl
'{new LoggerName($name)}
}

def getLoggerNameImpl(using qctx: Quotes): Expr[String] = {
import qctx.reflect._

@tailrec def findEnclosingClass(sym: Symbol): Symbol = {
Expand All @@ -42,7 +46,7 @@ private[slf4j] object GetLoggerMacros {
}
}

def logger(s: Symbol): Expr[SelfAwareStructuredLogger[F]] = {
def logger(s: Symbol): Expr[String] = {
def fullName(s: Symbol): String = {
val flags = s.flags
if (flags.is(Flags.Package)) {
Expand All @@ -63,18 +67,10 @@ private[slf4j] object GetLoggerMacros {
}
}

val name = Expr(fullName(s))
'{ Slf4jLogger.getLoggerFromSlf4j(LoggerFactory.getLogger($name))($F) }
Expr(fullName(s))
}

val cls = findEnclosingClass(Symbol.spliceOwner)
logger(cls)
}

def createImpl[F[_]: Type](
F: Expr[Sync[F]]
)(using qctx: Quotes): Expr[F[SelfAwareStructuredLogger[F]]] = {
val logger = getLoggerImpl(F)
'{ $F.delay($logger) }
}
}