diff --git a/build.sbt b/build.sbt index 1540d07b..b00112b0 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,10 @@ +import com.typesafe.tools.mima.core._ + val Scala213 = "2.13.8" val Scala212 = "2.12.15" val Scala3 = "3.0.2" -ThisBuild / tlBaseVersion := "1.6" +ThisBuild / tlBaseVersion := "1.7" ThisBuild / crossScalaVersions := Seq(Scala213, Scala212, Scala3) ThisBuild / scalaVersion := Scala213 ThisBuild / startYear := Some(2018) @@ -50,7 +52,11 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) name := "log4cats-core", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV - ) + ), + libraryDependencies ++= { + if (tlIsScala3.value) Seq.empty + else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided) + } ) lazy val testing = crossProject(JSPlatform, JVMPlatform) diff --git a/core/shared/src/main/scala-2/org/typelevel/log4cats/LoggerNameCompat.scala b/core/shared/src/main/scala-2/org/typelevel/log4cats/LoggerNameCompat.scala new file mode 100644 index 00000000..b43323bc --- /dev/null +++ b/core/shared/src/main/scala-2/org/typelevel/log4cats/LoggerNameCompat.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +import org.typelevel.log4cats.internal.LoggerNameMacro + +trait LoggerNameCompat { + implicit def name: LoggerName = macro LoggerNameMacro.getLoggerName +} diff --git a/core/shared/src/main/scala-2/org/typelevel/log4cats/internal/LoggerNameMacro.scala b/core/shared/src/main/scala-2/org/typelevel/log4cats/internal/LoggerNameMacro.scala new file mode 100644 index 00000000..b865d60d --- /dev/null +++ b/core/shared/src/main/scala-2/org/typelevel/log4cats/internal/LoggerNameMacro.scala @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats.internal + +import scala.annotation.tailrec +import scala.reflect.macros.blackbox + +private[log4cats] class LoggerNameMacro(val c: blackbox.Context) { + final def getLoggerName = SharedLoggerNameMacro.getLoggerNameImpl(c) +} + +private[log4cats] object SharedLoggerNameMacro { + + /** Get a logger by reflecting the enclosing class name. */ + private[log4cats] def getLoggerNameImpl(c: blackbox.Context) = { + 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 nameBySymbol(s: Symbol) = { + 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) + } + } + + q"new _root_.org.typelevel.log4cats.LoggerName(${fullName(s)})" + } + + def nameByType(s: Symbol) = { + val typeSymbol: ClassSymbol = (if (s.isModule) s.asModule.moduleClass else s).asClass + val typeParams = typeSymbol.typeParams + + if (typeParams.isEmpty) { + q"new _root_.org.typelevel.log4cats.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 + * there's no simple way to declare a higher-kinded type with an "any" parameter. */ + nameBySymbol(s) + } else { + val typeArgs = List.fill(typeParams.length)(WildcardType) + val typeConstructor = tq"$typeSymbol[..${typeArgs}]" + q"new _root_.org.typelevel.log4cats.LoggerName(_root_.scala.Predef.classOf[$typeConstructor].getName)" + } + } + } + + @inline def isInnerClass(s: Symbol) = + s.isClass && !(s.owner.isPackage) + + val instanceByName = + (true && cls.isModule || cls.isModuleClass) || (cls.isClass && isInnerClass( + cls + )) + + if (instanceByName) { + nameBySymbol(cls) + } else { + nameByType(cls) + } + } +} diff --git a/core/shared/src/main/scala-3/org/typelevel/log4cats/LoggerNameCompat.scala b/core/shared/src/main/scala-3/org/typelevel/log4cats/LoggerNameCompat.scala new file mode 100644 index 00000000..27a4ef51 --- /dev/null +++ b/core/shared/src/main/scala-3/org/typelevel/log4cats/LoggerNameCompat.scala @@ -0,0 +1,26 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +import org.typelevel.log4cats.internal.LoggerNameMacro + +import scala.quoted.* + +trait LoggerNameCompat { + implicit inline def name: LoggerName = + ${ LoggerNameMacro.getLoggerName } +} diff --git a/core/shared/src/main/scala-3/org/typelevel/log4cats/internal/LoggerNameMacro.scala b/core/shared/src/main/scala-3/org/typelevel/log4cats/internal/LoggerNameMacro.scala new file mode 100644 index 00000000..5db94194 --- /dev/null +++ b/core/shared/src/main/scala-3/org/typelevel/log4cats/internal/LoggerNameMacro.scala @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats +package internal + +import scala.annotation.tailrec +import scala.quoted.* + +private[log4cats] object LoggerNameMacro { + 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 = { + sym match { + case s if s.isNoSymbol => + report.throwError("Couldn't find an enclosing class or module for the logger") + case s if s.isClassDef => + 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) + } + } + + def symbolName(s: Symbol): Expr[String] = { + def fullName(s: Symbol): String = { + val flags = s.flags + if (flags.is(Flags.Package)) { + s.fullName + } else if (s.isClassDef) { + if (flags.is(Flags.Module)) { + if (s.name == "package$") { + fullName(s.owner) + } else { + val chomped = s.name.stripSuffix("$") + fullName(s.owner) + "." + chomped + } + } else { + fullName(s.owner) + "." + s.name + } + } else { + fullName(s.owner) + } + } + + Expr(fullName(s).stripSuffix("$")) + } + + val cls = findEnclosingClass(Symbol.spliceOwner) + symbolName(cls) + } +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala new file mode 100644 index 00000000..c71d61fb --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +import scala.annotation.implicitNotFound + +@implicitNotFound(""" +Implicit not found for LoggerFactory[${F}]. +Learn about LoggerFactory at https://typelevel.org/log4cats/#logging-using-capabilities +""") +trait LoggerFactory[F[_]] extends LoggerFactoryGen[F] { + type LoggerType = SelfAwareStructuredLogger[F] +} + +object LoggerFactory extends LoggerFactoryGenCompanion { + def apply[F[_]: LoggerFactory]: LoggerFactory[F] = implicitly +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactoryGen.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactoryGen.scala new file mode 100644 index 00000000..0c45eb00 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactoryGen.scala @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +trait LoggerFactoryGen[F[_]] { + type LoggerType <: Logger[F] + def getLogger(implicit name: LoggerName): LoggerType = getLoggerFromName(name.value) + def getLoggerFromClass(clazz: Class[_]): LoggerType = getLoggerFromName(clazz.getName) + def create(implicit name: LoggerName): F[LoggerType] = fromName(name.value) + def fromClass(clazz: Class[_]): F[LoggerType] = fromName(clazz.getName) + def getLoggerFromName(name: String): LoggerType + def fromName(name: String): F[LoggerType] +} + +private[log4cats] trait LoggerFactoryGenCompanion { + def getLogger[F[_]](implicit lf: LoggerFactoryGen[F], name: LoggerName): lf.LoggerType = + lf.getLogger + def getLoggerFromName[F[_]](name: String)(implicit lf: LoggerFactoryGen[F]): lf.LoggerType = + lf.getLoggerFromName(name) + def getLoggerFromClass[F[_]](clazz: Class[_])(implicit lf: LoggerFactoryGen[F]): lf.LoggerType = + lf.getLoggerFromClass(clazz) + def create[F[_]](implicit lf: LoggerFactoryGen[F], name: LoggerName): F[lf.LoggerType] = + lf.create + def fromName[F[_]](name: String)(implicit lf: LoggerFactoryGen[F]): F[lf.LoggerType] = + lf.fromName(name) + def fromClass[F[_]](clazz: Class[_])(implicit lf: LoggerFactoryGen[F]): F[lf.LoggerType] = + lf.fromClass(clazz) +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerName.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerName.scala new file mode 100644 index 00000000..801dcadc --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerName.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +object LoggerName extends LoggerNameCompat + +final case class LoggerName(value: String) extends AnyVal diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/LoggerNameTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/LoggerNameTest.scala new file mode 100644 index 00000000..6ae11a5c --- /dev/null +++ b/core/shared/src/test/scala/org/typelevel/log4cats/LoggerNameTest.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +import munit.FunSuite + +class LoggerNameTest extends FunSuite { + class FooA { + val name = LoggerName.name + } + + object FooB { + val name = LoggerName.name + } + + test("names") { + val name1 = new FooA().name + val name2 = FooB.name + + assertEquals(name1.value, "org.typelevel.log4cats.LoggerNameTest.FooA") + assertEquals(name2.value, "org.typelevel.log4cats.LoggerNameTest.FooB") + } +} diff --git a/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/Slf4jLoggerCompat.scala b/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/Slf4jLoggerCompat.scala new file mode 100644 index 00000000..651fd5d9 --- /dev/null +++ b/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/Slf4jLoggerCompat.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats.slf4j + +import cats.effect.Sync +import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats.slf4j.internal._ + +trait Slf4jLoggerCompat { + + private[slf4j] def getLogger[F[_]](implicit f: Sync[F]): SelfAwareStructuredLogger[F] = + macro GetLoggerMacros.unsafeCreateImpl[F[_]] + + private[slf4j] def create[F[_]](implicit f: Sync[F]): F[SelfAwareStructuredLogger[F]] = + macro GetLoggerMacros.safeCreateImpl[F[_]] +} diff --git a/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala b/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala index b5596a53..14856e24 100644 --- a/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala +++ b/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala @@ -16,7 +16,8 @@ package org.typelevel.log4cats.slf4j.internal -import scala.annotation.{nowarn, tailrec} +import org.typelevel.log4cats.internal.SharedLoggerNameMacro + import scala.reflect.macros.blackbox /** @@ -28,32 +29,14 @@ import scala.reflect.macros.blackbox * Sarah Gerweck */ private[slf4j] class GetLoggerMacros(val c: blackbox.Context) { + final def safeCreateImpl[F](f: c.Expr[F]) = getLoggerImpl[F](f, true) - final def safeCreateImpl[F: c.WeakTypeTag](f: c.Expr[F]) = getLoggerImpl[F](f, true) - - final def unsafeCreateImpl[F: c.WeakTypeTag](f: c.Expr[F]) = getLoggerImpl[F](f, false) + final def unsafeCreateImpl[F](f: c.Expr[F]) = getLoggerImpl[F](f, false) - /** Get a logger by reflecting the enclosing class name. */ - @nowarn("cat=unused") - private def getLoggerImpl[F: c.WeakTypeTag](f: c.Expr[F], delayed: Boolean) = { + private def getLoggerImpl[F](f: c.Expr[F], delayed: Boolean) = { + val loggerName = SharedLoggerNameMacro.getLoggerNameImpl(c) 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) = { val unsafeCreate = q"_root_.org.typelevel.log4cats.slf4j.Slf4jLogger.getLoggerFromSlf4j(_root_.org.slf4j.LoggerFactory.getLogger(...${List(param)}))($f)" @@ -62,60 +45,6 @@ private[slf4j] class GetLoggerMacros(val c: blackbox.Context) { else unsafeCreate } - - def loggerBySymbolName(s: Symbol) = { - 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)}") - } - - 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]") - } 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) - } else { - val typeArgs = List.fill(typeParams.length)(WildcardType) - val typeConstructor = tq"$typeSymbol[..${typeArgs}]" - loggerByParam(q"_root_.scala.Predef.classOf[$typeConstructor]") - } - } - } - - @inline def isInnerClass(s: Symbol) = - s.isClass && !(s.owner.isPackage) - - val instanceByName = - Slf4jLoggerInternal.singletonsByName && (cls.isModule || cls.isModuleClass) || cls.isClass && isInnerClass( - cls - ) - - if (instanceByName) { - loggerBySymbolName(cls) - } else { - loggerByType(cls) - } + loggerByParam(q"$loggerName.value") } } diff --git a/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/Slf4jLogger.scala deleted file mode 100644 index 8d4f41f4..00000000 --- a/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/Slf4jLogger.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2018 Typelevel - * - * 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 org.typelevel.log4cats.slf4j - -import cats.effect.Sync -import org.typelevel.log4cats.SelfAwareStructuredLogger -import org.typelevel.log4cats.slf4j.internal._ -import org.slf4j.{Logger => JLogger} - -object Slf4jLogger { - - inline def getLogger[F[_]](implicit F: Sync[F]): SelfAwareStructuredLogger[F] = - ${ GetLoggerMacros.getLoggerImpl('F) } - - @deprecated("0.3.0", "Use getLogger instead") - inline def unsafeCreate[F[_]](implicit F: Sync[F]): SelfAwareStructuredLogger[F] = - ${ GetLoggerMacros.getLoggerImpl('F) } - - def getLoggerFromName[F[_]: Sync](name: String): SelfAwareStructuredLogger[F] = - getLoggerFromSlf4j(org.slf4j.LoggerFactory.getLogger(name)) - - @deprecated("0.3.0", "Use getLoggerFromName") - def unsafeFromName[F[_]: Sync](name: String): SelfAwareStructuredLogger[F] = - getLoggerFromName[F](name) - - def getLoggerFromClass[F[_]: Sync](clazz: Class[_]): SelfAwareStructuredLogger[F] = - getLoggerFromSlf4j[F](org.slf4j.LoggerFactory.getLogger(clazz)) - - @deprecated("0.3.0", "Use getLoggerFromClass") - def unsafeFromClass[F[_]: Sync](clazz: Class[_]): SelfAwareStructuredLogger[F] = - getLoggerFromClass[F](clazz) - - def getLoggerFromSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] = - new Slf4jLoggerInternal.Slf4jLogger(logger) - - @deprecated("0.3.0", "Use getLoggerFromSlf4J instead") - def unsafeFromSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] = - getLoggerFromSlf4j[F](logger) - - inline def create[F[_]](implicit F: Sync[F]): F[SelfAwareStructuredLogger[F]] = - ${ GetLoggerMacros.createImpl('F) } - - def fromName[F[_]: Sync](name: String): F[SelfAwareStructuredLogger[F]] = - Sync[F].delay(getLoggerFromName(name)) - - def fromClass[F[_]: Sync](clazz: Class[_]): F[SelfAwareStructuredLogger[F]] = - Sync[F].delay(getLoggerFromClass(clazz)) - - def fromSlf4j[F[_]: Sync](logger: JLogger): F[SelfAwareStructuredLogger[F]] = - Sync[F].delay(getLoggerFromSlf4j[F](logger)) - -} diff --git a/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/Slf4jLoggerCompat.scala b/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/Slf4jLoggerCompat.scala new file mode 100644 index 00000000..9f79cab0 --- /dev/null +++ b/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/Slf4jLoggerCompat.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2018 Typelevel + * + * 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 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 scala.annotation.nowarn + +trait Slf4jLoggerCompat { + + // for binary compability + private[slf4j] inline def createMacro[F[_]](F: Sync[F]): F[SelfAwareStructuredLogger[F]] = + ${ GetLoggerMacros.createImpl('F) } + + private[slf4j] inline def getLoggerMacro[F[_]](using F: Sync[F]): SelfAwareStructuredLogger[F] = + ${ GetLoggerMacros.getLoggerImpl('F) } + +} diff --git a/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala b/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala index 26129b96..f611ad97 100644 --- a/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala +++ b/slf4j/src/main/scala-3/org/typelevel/log4cats/slf4j/internal/GetLoggerMacros.scala @@ -20,55 +20,18 @@ import cats.effect.Sync import org.slf4j.LoggerFactory import org.typelevel.log4cats.slf4j.Slf4jLogger import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats.internal.LoggerNameMacro + 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]] = { - import qctx.reflect._ - - @tailrec def findEnclosingClass(sym: Symbol): Symbol = { - sym match { - case s if s.isNoSymbol => - report.throwError("Couldn't find an enclosing class or module for the logger") - case s if s.isClassDef => - 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) - } - } - - def logger(s: Symbol): Expr[SelfAwareStructuredLogger[F]] = { - def fullName(s: Symbol): String = { - val flags = s.flags - if (flags.is(Flags.Package)) { - s.fullName - } else if (s.isClassDef) { - if (flags.is(Flags.Module)) { - if (s.name == "package$") { - fullName(s.owner) - } else { - val chomped = s.name.stripSuffix("$") - fullName(s.owner) + "." + chomped - } - } else { - fullName(s.owner) + "." + s.name - } - } else { - fullName(s.owner) - } - } - - val name = Expr(fullName(s)) - '{ Slf4jLogger.getLoggerFromSlf4j(LoggerFactory.getLogger($name))($F) } - } - - val cls = findEnclosingClass(Symbol.spliceOwner) - logger(cls) + val name = LoggerNameMacro.getLoggerNameImpl + '{ Slf4jLogger.getLoggerFromSlf4j(LoggerFactory.getLogger($name))($F) } } def createImpl[F[_]: Type]( diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jFactory.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jFactory.scala new file mode 100644 index 00000000..b210e9a3 --- /dev/null +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jFactory.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats +package slf4j + +import org.slf4j.{Logger => JLogger} + +trait Slf4jFactory[F[_]] extends LoggerFactory[F] { + def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] + + def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] +} + +object Slf4jFactory extends LoggerFactoryGenCompanion { + def apply[F[_]: Slf4jFactory]: Slf4jFactory[F] = implicitly + + def getLoggerFromSlf4j[F[_]](logger: JLogger)(implicit + lf: Slf4jFactory[F] + ): SelfAwareStructuredLogger[F] = lf.getLoggerFromSlf4j(logger) + + def fromSlf4j[F[_]](logger: JLogger)(implicit + lf: Slf4jFactory[F] + ): F[SelfAwareStructuredLogger[F]] = + lf.fromSlf4j(logger) +} diff --git a/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/Slf4jLogger.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala similarity index 79% rename from slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/Slf4jLogger.scala rename to slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala index fa89a091..7a01124a 100644 --- a/slf4j/src/main/scala-2/org/typelevel/log4cats/slf4j/Slf4jLogger.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogger.scala @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.typelevel.log4cats.slf4j +package org.typelevel.log4cats +package slf4j import cats.effect.Sync -import org.typelevel.log4cats.SelfAwareStructuredLogger -import org.typelevel.log4cats.slf4j.internal._ +import org.typelevel.log4cats.slf4j.internal.Slf4jLoggerInternal import org.slf4j.{Logger => JLogger} -object Slf4jLogger { +object Slf4jLogger extends Slf4jLoggerCompat { - def getLogger[F[_]: Sync]: SelfAwareStructuredLogger[F] = - macro GetLoggerMacros.unsafeCreateImpl[F[_]] + def getLogger[F[_]](implicit f: Sync[F], name: LoggerName): SelfAwareStructuredLogger[F] = + getLoggerFromName(name.value) @deprecated("0.3.0", "Use getLogger instead") - def unsafeCreate[F[_]: Sync]: SelfAwareStructuredLogger[F] = - macro GetLoggerMacros.unsafeCreateImpl[F[_]] + def unsafeCreate[F[_]: Sync](implicit name: LoggerName): SelfAwareStructuredLogger[F] = + getLogger[F] def getLoggerFromName[F[_]: Sync](name: String): SelfAwareStructuredLogger[F] = getLoggerFromSlf4j(org.slf4j.LoggerFactory.getLogger(name)) @@ -51,8 +51,8 @@ object Slf4jLogger { def unsafeFromSlf4j[F[_]: Sync](logger: JLogger): SelfAwareStructuredLogger[F] = getLoggerFromSlf4j[F](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)) def fromName[F[_]: Sync](name: String): F[SelfAwareStructuredLogger[F]] = Sync[F].delay(getLoggerFromName(name)) @@ -62,5 +62,4 @@ object Slf4jLogger { def fromSlf4j[F[_]: Sync](logger: JLogger): F[SelfAwareStructuredLogger[F]] = Sync[F].delay(getLoggerFromSlf4j[F](logger)) - } diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/package.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/package.scala new file mode 100644 index 00000000..95cfd9f1 --- /dev/null +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/package.scala @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats + +import cats.effect.Sync +import org.slf4j.{Logger => JLogger} + +package object slf4j { + implicit def loggerFactoryforSync[F[_]: Sync]: Slf4jFactory[F] = new Slf4jFactory[F] { + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = + Slf4jLogger.getLoggerFromName(name) + + override def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] = + Slf4jLogger.getLoggerFromSlf4j(logger) + + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = + Slf4jLogger.fromName(name) + + override def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] = + Slf4jLogger.fromSlf4j(logger) + } +} diff --git a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala index 566413e7..ce3c1ab7 100644 --- a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala @@ -48,7 +48,7 @@ class Slf4jLoggerOuterClassMacroTest { object LoggingBaseline { val t = new Throwable - def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] = Slf4jLogger.getLogger[F] + def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] = Slf4jFactory.getLogger[F] val traceM = logger[IO].trace("") val traceTM = logger[IO].trace(t)("")