Skip to content

Commit

Permalink
Merge pull request #656 from FunFunFine/series/1.x-backport-logger-fa…
Browse files Browse the repository at this point in the history
…ctory

Backport LoggerFactory to 1.x series
  • Loading branch information
rossabaker committed Jun 17, 2022
2 parents be01031 + ac8a327 commit 42fb409
Show file tree
Hide file tree
Showing 18 changed files with 526 additions and 200 deletions.
10 changes: 8 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
21 changes: 21 additions & 0 deletions core/shared/src/main/scala/org/typelevel/log4cats/LoggerName.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading

0 comments on commit 42fb409

Please sign in to comment.