From 33c5e7f8fd220ca678fc2de7727953dc59f88141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Mon, 5 Apr 2021 10:46:13 +0300 Subject: [PATCH 01/15] Add dependency on com.lorandszakacs.enclosure --- build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 8baa10d9..7f592076 100644 --- a/build.sbt +++ b/build.sbt @@ -80,6 +80,7 @@ val catsEffectV = "2.4.1" val slf4jV = "1.7.30" val munitCatsEffectV = "1.0.1" val logbackClassicV = "1.2.3" +val enclosureV = "0.1.0" Global / onChangedBuildSource := ReloadOnSourceChanges @@ -111,7 +112,8 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) .settings( name := "log4cats-core", libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-core" % catsV + "org.typelevel" %%% "cats-core" % catsV, + "com.lorandszakacs" %%% "enclosure" % enclosureV, ) ) lazy val coreJVM = core.jvm From ba1c236f4a67dc54fa573279226461651520c16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Mon, 5 Apr 2021 10:47:04 +0300 Subject: [PATCH 02/15] Add logging capabilities Includes both generic, and fixed in the type of logger --- .../log4cats/loggingCapabilities.scala | 90 +++++++++++++++++++ .../org/typelevel/log4cats/package.scala | 23 +++++ .../typelevel/log4cats/noop/NoopLogging.scala | 49 ++++++++++ .../log4cats/slf4j/Slf4jLogging.scala | 60 +++++++++++++ .../Slf4jLoggerMacroCompilationTests.scala | 8 ++ .../internal/Slf4jLoggerInternalSuite.scala | 10 ++- 6 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/package.scala create mode 100644 noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala create mode 100644 slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala new file mode 100644 index 00000000..92c8d99e --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala @@ -0,0 +1,90 @@ +/* + * Copyright 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. + * 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 com.lorandszakacs.enclosure.Enclosure + +/** + * Logging capability trait, or put crudeley "logger factory". + * + * The recommended way of creating loggers is through this capability trait. + * You instantiate it once in your application (dependent on the specific + * logging backend you use), and pass this around in your application. + * + * This has several advantages: + * - you no longer pass around _very powerful_ `F[_]: Sync` constraints that can do + * almost anything, when you just need logging + * - you are in control of how loggers are created, and you can even add in whatever + * custom functionality you need for your own applications here. e.g. create loggers + * that also send logs to some external providers by giving an implementation to this + * trait. + */ +trait LoggingGen[F[_], LoggerType <: Logger[F]] + extends LoggingGenId[F, LoggerType] + with LoggingGenF[F, LoggerType] + +object LoggingGen { + def apply[F[_], LoggerType <: Logger[F]](implicit + l: LoggingGen[F, LoggerType] + ): LoggingGen[F, LoggerType] = l +} + +/** + * Use when the creation of your loggers can be a pure operation. + * + * If you need to various side effects (init some state, make external calss, etc) + * when instantiating a logger then use [[LoggingGenF]] instead. + */ +trait LoggingGenId[F[_], LoggerType <: Logger[F]] { + def getLogger(implicit enc: Enclosure): LoggerType + + def getLoggerFromName(name: String): LoggerType + + def getLoggerFromClass(clazz: Class[_]): LoggerType +} + +object LoggingGenId { + def apply[F[_], LoggerType <: Logger[F]](implicit + l: LoggingGenId[F, LoggerType] + ): LoggingGenId[F, LoggerType] = l +} + +trait LoggingGenF[F[_], LoggerType <: Logger[F]] { + def create(implicit enc: Enclosure): F[LoggerType] + + def fromName(name: String): F[LoggerType] + + def fromClass(clazz: Class[_]): F[LoggerType] +} + +object LoggingGenF { + def apply[F[_], LoggerType <: Logger[F]](implicit + l: LoggingGenF[F, LoggerType] + ): LoggingGenF[F, LoggerType] = l +} + +object Logging { + def apply[F[_]](implicit l: Logging[F]): Logging[F] = l +} + +object LoggingId { + def apply[F[_]](implicit l: LoggingId[F]): LoggingId[F] = l +} + +object LoggingF { + def apply[F[_]](implicit l: LoggingF[F]): LoggingF[F] = l +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala new file mode 100644 index 00000000..adfcd739 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala @@ -0,0 +1,23 @@ +/* + * Copyright 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. + * 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 + +package object log4cats { + type Logging[F[_]] = LoggingGen[F, SelfAwareStructuredLogger[F]] + type LoggingId[F[_]] = LoggingGenId[F, SelfAwareStructuredLogger[F]] + type LoggingF[F[_]] = LoggingGenF[F, SelfAwareStructuredLogger[F]] +} diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala new file mode 100644 index 00000000..73bc67be --- /dev/null +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala @@ -0,0 +1,49 @@ +/* + * Copyright 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. + * 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.noop + +import com.lorandszakacs.enclosure.Enclosure +import cats.syntax.all._ +import cats.Applicative +import org.typelevel.log4cats._ + +trait NoopLogging[F[_]] extends Logging[F] { + implicit protected def applicative: Applicative[F] + + override def getLogger(implicit enc: Enclosure): SelfAwareStructuredLogger[F] = NoOpLogger.impl[F] + + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = NoOpLogger.impl[F] + + override def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F] = + NoOpLogger.impl[F] + + override def create(implicit enc: Enclosure): F[SelfAwareStructuredLogger[F]] = + NoOpLogger.impl[F].pure[F] + + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = NoOpLogger.impl[F].pure[F] + + override def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]] = + NoOpLogger.impl[F].pure[F] + +} + +object NoopLogging { + def apply[F[_]](implicit logging: NoopLogging[F]): NoopLogging[F] = logging + def create[F[_]](implicit F: Applicative[F]) = new NoopLogging[F] { + override implicit protected val applicative: Applicative[F] = F + } +} diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala new file mode 100644 index 00000000..fce0197e --- /dev/null +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala @@ -0,0 +1,60 @@ +/* + * Copyright 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. + * 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 org.slf4j.{Logger => JLogger} +import cats.effect.Sync +import org.typelevel.log4cats._ + +import com.lorandszakacs.enclosure.Enclosure + +trait Slf4jLogging[F[_]] extends Logging[F] { + + implicit protected def sync: Sync[F] + + override def getLogger(implicit enc: Enclosure): SelfAwareStructuredLogger[F] = + Slf4jLogger.getLoggerFromName[F](enc.fullModuleName) + + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = + Slf4jLogger.getLoggerFromName[F](name) + + override def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F] = + Slf4jLogger.getLoggerFromClass[F](clazz) + + def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] = + Slf4jLogger.getLoggerFromSlf4j[F](logger) + + override def create(implicit enc: Enclosure): F[SelfAwareStructuredLogger[F]] = + Slf4jLogger.fromName[F](enc.fullModuleName) + + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = + Slf4jLogger.fromName[F](name) + + override def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]] = + Slf4jLogger.fromClass[F](clazz) + + def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] = + Slf4jLogger.fromSlf4j[F](logger) +} + +object Slf4jLogging { + def apply[F[_]](implicit logging: Slf4jLogging[F]): Slf4jLogging[F] = logging + + def createForSync[F[_]](implicit F: Sync[F]): Slf4jLogging[F] = new Slf4jLogging[F] { + override protected val sync: Sync[F] = F + } +} 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 edb2e91e..2a891d05 100644 --- a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala @@ -71,3 +71,11 @@ object LoggingBaseline { val errorCM = logger[IO].error(Map.empty[String, String])("") } + +object LoggingCapabilityTest { + val t = new Throwable + implicit val logging: Slf4jLogging[IO] = Slf4jLogging.createForSync[IO] + + val explicit = logging.getLogger.trace("") + val summoned = Slf4jLogging[IO].getLogger.trace(t)("") +} diff --git a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala index b9f6fc99..8a3b7552 100644 --- a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala @@ -66,8 +66,9 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite { val initial = "yellow" MDC.put(variable, initial) - Slf4jLogger - .getLogger[IO] + Slf4jLogging + .createForSync[IO] + .getLogger .info(Map(variable -> "bar"))("A log went here") .as(MDC.get(variable)) .assertEquals(initial) @@ -91,8 +92,9 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite { val logCanceled = new CountDownLatch(1) val finishedLog = new CountDownLatch(1) - val performLogging = Slf4jLogger - .getLogger[IO] + val performLogging = Slf4jLogging + .createForSync[IO] + .getLogger .info(Map(variable -> "modified")) { startedLog.countDown() logCanceled.await() From 8c557594a5443725fea1acea921f7c5a9740cb38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Mon, 5 Apr 2021 11:38:16 +0300 Subject: [PATCH 03/15] Rename NoopLogging -> NoOpLogging --- .../noop/{NoopLogging.scala => NoOpLogging.scala} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename noop/shared/src/main/scala/org/typelevel/log4cats/noop/{NoopLogging.scala => NoOpLogging.scala} (88%) diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala similarity index 88% rename from noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala rename to noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala index 73bc67be..a7403c6e 100644 --- a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoopLogging.scala +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala @@ -21,7 +21,7 @@ import cats.syntax.all._ import cats.Applicative import org.typelevel.log4cats._ -trait NoopLogging[F[_]] extends Logging[F] { +trait NoOpLogging[F[_]] extends Logging[F] { implicit protected def applicative: Applicative[F] override def getLogger(implicit enc: Enclosure): SelfAwareStructuredLogger[F] = NoOpLogger.impl[F] @@ -41,9 +41,9 @@ trait NoopLogging[F[_]] extends Logging[F] { } -object NoopLogging { - def apply[F[_]](implicit logging: NoopLogging[F]): NoopLogging[F] = logging - def create[F[_]](implicit F: Applicative[F]) = new NoopLogging[F] { +object NoOpLogging { + def apply[F[_]](implicit logging: NoOpLogging[F]): NoOpLogging[F] = logging + def create[F[_]](implicit F: Applicative[F]) = new NoOpLogging[F] { override implicit protected val applicative: Applicative[F] = F } } From b25c55950a3cb2bd4414f2159426e425db017369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Mon, 5 Apr 2021 12:20:34 +0300 Subject: [PATCH 04/15] Implement methods in logging traits in terms for fromName --- .../typelevel/log4cats/loggingCapabilities.scala | 16 ++++++++++------ .../typelevel/log4cats/noop/NoOpLogging.scala | 15 --------------- .../typelevel/log4cats/slf4j/Slf4jLogging.scala | 14 -------------- 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala index 92c8d99e..ad1419af 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala @@ -50,11 +50,13 @@ object LoggingGen { * when instantiating a logger then use [[LoggingGenF]] instead. */ trait LoggingGenId[F[_], LoggerType <: Logger[F]] { - def getLogger(implicit enc: Enclosure): LoggerType - def getLoggerFromName(name: String): LoggerType - def getLoggerFromClass(clazz: Class[_]): LoggerType + def getLogger(implicit enc: Enclosure): LoggerType = + getLoggerFromName(enc.fullModuleName) + + def getLoggerFromClass(clazz: Class[_]): LoggerType = + getLoggerFromName(clazz.getName()) //N.B. .getCanonicalName does not exist on scala JS. } object LoggingGenId { @@ -64,11 +66,13 @@ object LoggingGenId { } trait LoggingGenF[F[_], LoggerType <: Logger[F]] { - def create(implicit enc: Enclosure): F[LoggerType] - def fromName(name: String): F[LoggerType] - def fromClass(clazz: Class[_]): F[LoggerType] + def create(implicit enc: Enclosure): F[LoggerType] = + fromName(enc.fullModuleName) + + def fromClass(clazz: Class[_]): F[LoggerType] = + fromName(clazz.getName) //N.B. .getCanonicalName does not exist on scala JS. } object LoggingGenF { diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala index a7403c6e..12f7a87c 100644 --- a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala @@ -16,29 +16,14 @@ package org.typelevel.log4cats.noop -import com.lorandszakacs.enclosure.Enclosure import cats.syntax.all._ import cats.Applicative import org.typelevel.log4cats._ trait NoOpLogging[F[_]] extends Logging[F] { implicit protected def applicative: Applicative[F] - - override def getLogger(implicit enc: Enclosure): SelfAwareStructuredLogger[F] = NoOpLogger.impl[F] - override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = NoOpLogger.impl[F] - - override def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F] = - NoOpLogger.impl[F] - - override def create(implicit enc: Enclosure): F[SelfAwareStructuredLogger[F]] = - NoOpLogger.impl[F].pure[F] - override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = NoOpLogger.impl[F].pure[F] - - override def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]] = - NoOpLogger.impl[F].pure[F] - } object NoOpLogging { diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala index fce0197e..2de3f8fa 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala @@ -20,33 +20,19 @@ import org.slf4j.{Logger => JLogger} import cats.effect.Sync import org.typelevel.log4cats._ -import com.lorandszakacs.enclosure.Enclosure - trait Slf4jLogging[F[_]] extends Logging[F] { implicit protected def sync: Sync[F] - override def getLogger(implicit enc: Enclosure): SelfAwareStructuredLogger[F] = - Slf4jLogger.getLoggerFromName[F](enc.fullModuleName) - override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = Slf4jLogger.getLoggerFromName[F](name) - override def getLoggerFromClass(clazz: Class[_]): SelfAwareStructuredLogger[F] = - Slf4jLogger.getLoggerFromClass[F](clazz) - def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] = Slf4jLogger.getLoggerFromSlf4j[F](logger) - override def create(implicit enc: Enclosure): F[SelfAwareStructuredLogger[F]] = - Slf4jLogger.fromName[F](enc.fullModuleName) - override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = Slf4jLogger.fromName[F](name) - override def fromClass(clazz: Class[_]): F[SelfAwareStructuredLogger[F]] = - Slf4jLogger.fromClass[F](clazz) - def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] = Slf4jLogger.fromSlf4j[F](logger) } From 02660d8a6672e0528cbbd59e1511201baaa9e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Mon, 5 Apr 2021 20:44:54 +0300 Subject: [PATCH 05/15] Remove variance constraints on LoggingGen* --- .../log4cats/loggingCapabilities.scala | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala index ad1419af..3256cb7e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala @@ -33,14 +33,12 @@ import com.lorandszakacs.enclosure.Enclosure * that also send logs to some external providers by giving an implementation to this * trait. */ -trait LoggingGen[F[_], LoggerType <: Logger[F]] +trait LoggingGen[F[_], LoggerType] extends LoggingGenId[F, LoggerType] with LoggingGenF[F, LoggerType] object LoggingGen { - def apply[F[_], LoggerType <: Logger[F]](implicit - l: LoggingGen[F, LoggerType] - ): LoggingGen[F, LoggerType] = l + def apply[F[_], LoggerType](implicit l: LoggingGen[F, LoggerType]): LoggingGen[F, LoggerType] = l } /** @@ -49,7 +47,7 @@ object LoggingGen { * If you need to various side effects (init some state, make external calss, etc) * when instantiating a logger then use [[LoggingGenF]] instead. */ -trait LoggingGenId[F[_], LoggerType <: Logger[F]] { +trait LoggingGenId[F[_], LoggerType] { def getLoggerFromName(name: String): LoggerType def getLogger(implicit enc: Enclosure): LoggerType = @@ -60,12 +58,12 @@ trait LoggingGenId[F[_], LoggerType <: Logger[F]] { } object LoggingGenId { - def apply[F[_], LoggerType <: Logger[F]](implicit + def apply[F[_], LoggerType](implicit l: LoggingGenId[F, LoggerType] ): LoggingGenId[F, LoggerType] = l } -trait LoggingGenF[F[_], LoggerType <: Logger[F]] { +trait LoggingGenF[F[_], LoggerType] { def fromName(name: String): F[LoggerType] def create(implicit enc: Enclosure): F[LoggerType] = @@ -76,9 +74,8 @@ trait LoggingGenF[F[_], LoggerType <: Logger[F]] { } object LoggingGenF { - def apply[F[_], LoggerType <: Logger[F]](implicit - l: LoggingGenF[F, LoggerType] - ): LoggingGenF[F, LoggerType] = l + def apply[F[_], LoggerType](implicit l: LoggingGenF[F, LoggerType]): LoggingGenF[F, LoggerType] = + l } object Logging { From 5022e11fdc9b03660861a5d65fc440f93610afa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Mon, 5 Apr 2021 21:31:45 +0300 Subject: [PATCH 06/15] Flip Gen <-> Logging to be consistent with CE3 naming --- .../log4cats/loggingCapabilities.scala | 24 +++++++++---------- .../org/typelevel/log4cats/package.scala | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala index 3256cb7e..350fe3f1 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala @@ -33,12 +33,12 @@ import com.lorandszakacs.enclosure.Enclosure * that also send logs to some external providers by giving an implementation to this * trait. */ -trait LoggingGen[F[_], LoggerType] - extends LoggingGenId[F, LoggerType] - with LoggingGenF[F, LoggerType] +trait GenLogging[F[_], LoggerType] + extends GenLoggingId[F, LoggerType] + with GenLoggingF[F, LoggerType] -object LoggingGen { - def apply[F[_], LoggerType](implicit l: LoggingGen[F, LoggerType]): LoggingGen[F, LoggerType] = l +object GenLogging { + def apply[F[_], LoggerType](implicit l: GenLogging[F, LoggerType]): GenLogging[F, LoggerType] = l } /** @@ -47,7 +47,7 @@ object LoggingGen { * If you need to various side effects (init some state, make external calss, etc) * when instantiating a logger then use [[LoggingGenF]] instead. */ -trait LoggingGenId[F[_], LoggerType] { +trait GenLoggingId[F[_], LoggerType] { def getLoggerFromName(name: String): LoggerType def getLogger(implicit enc: Enclosure): LoggerType = @@ -57,13 +57,13 @@ trait LoggingGenId[F[_], LoggerType] { getLoggerFromName(clazz.getName()) //N.B. .getCanonicalName does not exist on scala JS. } -object LoggingGenId { +object GenLoggingId { def apply[F[_], LoggerType](implicit - l: LoggingGenId[F, LoggerType] - ): LoggingGenId[F, LoggerType] = l + l: GenLoggingId[F, LoggerType] + ): GenLoggingId[F, LoggerType] = l } -trait LoggingGenF[F[_], LoggerType] { +trait GenLoggingF[F[_], LoggerType] { def fromName(name: String): F[LoggerType] def create(implicit enc: Enclosure): F[LoggerType] = @@ -73,8 +73,8 @@ trait LoggingGenF[F[_], LoggerType] { fromName(clazz.getName) //N.B. .getCanonicalName does not exist on scala JS. } -object LoggingGenF { - def apply[F[_], LoggerType](implicit l: LoggingGenF[F, LoggerType]): LoggingGenF[F, LoggerType] = +object GenLoggingF { + def apply[F[_], LoggerType](implicit l: GenLoggingF[F, LoggerType]): GenLoggingF[F, LoggerType] = l } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala index adfcd739..e5a2445d 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala @@ -17,7 +17,7 @@ package org.typelevel package object log4cats { - type Logging[F[_]] = LoggingGen[F, SelfAwareStructuredLogger[F]] - type LoggingId[F[_]] = LoggingGenId[F, SelfAwareStructuredLogger[F]] - type LoggingF[F[_]] = LoggingGenF[F, SelfAwareStructuredLogger[F]] + type Logging[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] + type LoggingId[F[_]] = GenLoggingId[F, SelfAwareStructuredLogger[F]] + type LoggingF[F[_]] = GenLoggingF[F, SelfAwareStructuredLogger[F]] } From 45f8bbaf4897b82c8e0aa99c0e5a4e8256ecf66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Tue, 6 Apr 2021 09:52:35 +0300 Subject: [PATCH 07/15] Reencode Logging hierarchy starting from GenLogging[F[_], LoggerType] --- build.sbt | 7 ++- .../log4cats/loggingCapabilities.scala | 59 +++++-------------- .../LoggingImplicitNotFoundTests.scala | 39 ++++++++++++ .../typelevel/log4cats/noop/NoOpLogging.scala | 9 +-- .../log4cats/slf4j/Slf4jLogging.scala | 46 --------------- .../log4cats/slf4j/slf4jLogging.scala | 57 ++++++++++++++++++ .../Slf4jGenLoggingCompilationTest.scala | 21 +++++-- .../Slf4jLoggerMacroCompilationTests.scala | 10 +--- .../internal/Slf4jLoggerInternalSuite.scala | 8 +-- 9 files changed, 141 insertions(+), 115 deletions(-) create mode 100644 core/shared/src/test/scala/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala delete mode 100644 slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala create mode 100644 slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala rename core/shared/src/main/scala/org/typelevel/log4cats/package.scala => slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala (51%) diff --git a/build.sbt b/build.sbt index 7f592076..4e5c7fec 100644 --- a/build.sbt +++ b/build.sbt @@ -117,7 +117,12 @@ lazy val core = crossProject(JSPlatform, JVMPlatform) ) ) lazy val coreJVM = core.jvm -lazy val coreJS = core.js +lazy val coreJS = core + .settings(dottyJsSettings(ThisBuild / crossScalaVersions)) + .jsSettings( + scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) + ) + .js lazy val testing = crossProject(JSPlatform, JVMPlatform) .settings(commonSettings, releaseSettings) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala index 350fe3f1..9ac1873d 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala @@ -32,60 +32,31 @@ import com.lorandszakacs.enclosure.Enclosure * custom functionality you need for your own applications here. e.g. create loggers * that also send logs to some external providers by giving an implementation to this * trait. - */ -trait GenLogging[F[_], LoggerType] - extends GenLoggingId[F, LoggerType] - with GenLoggingF[F, LoggerType] - -object GenLogging { - def apply[F[_], LoggerType](implicit l: GenLogging[F, LoggerType]): GenLogging[F, LoggerType] = l -} - -/** - * Use when the creation of your loggers can be a pure operation. * - * If you need to various side effects (init some state, make external calss, etc) - * when instantiating a logger then use [[LoggingGenF]] instead. + * @tparam F[_] + * the effect type in which the loggers are constructed. + * e.g. make cats.Id if logger creation can be done as a pure computation */ -trait GenLoggingId[F[_], LoggerType] { - def getLoggerFromName(name: String): LoggerType - - def getLogger(implicit enc: Enclosure): LoggerType = - getLoggerFromName(enc.fullModuleName) - - def getLoggerFromClass(clazz: Class[_]): LoggerType = - getLoggerFromName(clazz.getName()) //N.B. .getCanonicalName does not exist on scala JS. -} +trait GenLogging[G[_], LoggerType] { + def fromName(name: String): G[LoggerType] -object GenLoggingId { - def apply[F[_], LoggerType](implicit - l: GenLoggingId[F, LoggerType] - ): GenLoggingId[F, LoggerType] = l -} - -trait GenLoggingF[F[_], LoggerType] { - def fromName(name: String): F[LoggerType] - - def create(implicit enc: Enclosure): F[LoggerType] = - fromName(enc.fullModuleName) + def create(implicit enc: Enclosure): G[LoggerType] = fromName(enc.fullModuleName) - def fromClass(clazz: Class[_]): F[LoggerType] = + def fromClass(clazz: Class[_]): G[LoggerType] = fromName(clazz.getName) //N.B. .getCanonicalName does not exist on scala JS. } -object GenLoggingF { - def apply[F[_], LoggerType](implicit l: GenLoggingF[F, LoggerType]): GenLoggingF[F, LoggerType] = +object GenLogging { + def apply[G[_], LoggerType](implicit l: GenLogging[G, LoggerType]): GenLogging[G, LoggerType] = l } +// N.B made it a trait, because otherwise on Scala 3 the implicitNotFound annotation would not be picked up :/ +@scala.annotation.implicitNotFound( + "Not found for Logging[${F}], keep in mind that, that ${F} is the effect in which logging is done. e.g. `Logging[${F}].create.info(message) : ${F}[Unit]`.\nAdditionally Logging[${F}] is defined as creating only loggers of type SelfAwareStructuredLogger[${F}].\nThe Logging[${F}] type itself described logging creation to be pure, i.e. cats.Id.\nSo your problem might be:\n\t1) you only have a GenLogger[G[_], ...] for some G[_] type other than cats.Id\n\t2) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}].\nIf you are unsure how to create a Logging[${F}], then you need to look at the `log4cats-slf4j` module, or `log4cats-noop` for concrete implementations. Example for slf4j:\nimplicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] // we create out Logging[IO]\nLogging[IO].create //we summon our instance, and create a SelfAwareStructuredLogger[${F}]." +) +trait Logging[F[_]] extends GenLogging[cats.Id, SelfAwareStructuredLogger[F]] + object Logging { def apply[F[_]](implicit l: Logging[F]): Logging[F] = l } - -object LoggingId { - def apply[F[_]](implicit l: LoggingId[F]): LoggingId[F] = l -} - -object LoggingF { - def apply[F[_]](implicit l: LoggingF[F]): LoggingF[F] = l -} diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala b/core/shared/src/test/scala/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala new file mode 100644 index 00000000..b929d6f6 --- /dev/null +++ b/core/shared/src/test/scala/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala @@ -0,0 +1,39 @@ +/* + * Copyright 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. + * 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 + +// TODO: figure out why the implicitNotFound annotation does not work on Scala 3. +class LoggingImplicitNotFoundTests extends munit.FunSuite { + + test("receive proper implicitNotFound error message on Logging type") { + val errors = compileErrors( + "import cats.effect.IO;import org.typelevel.log4cats.Logging;Logging[IO].create" + ) + assert( + errors.contains( + "you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger" + ), + clue = s"""|Actual compiler error was: + | + |$errors + | + | + |""".stripMargin + ) + } + +} diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala index 12f7a87c..bc9a9ef5 100644 --- a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala @@ -16,19 +16,16 @@ package org.typelevel.log4cats.noop -import cats.syntax.all._ import cats.Applicative import org.typelevel.log4cats._ trait NoOpLogging[F[_]] extends Logging[F] { - implicit protected def applicative: Applicative[F] - override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = NoOpLogger.impl[F] - override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = NoOpLogger.impl[F].pure[F] + override def fromName(name: String): SelfAwareStructuredLogger[F] } object NoOpLogging { def apply[F[_]](implicit logging: NoOpLogging[F]): NoOpLogging[F] = logging - def create[F[_]](implicit F: Applicative[F]) = new NoOpLogging[F] { - override implicit protected val applicative: Applicative[F] = F + def create[F[_]: Applicative] = new NoOpLogging[F] { + override def fromName(name: String): SelfAwareStructuredLogger[F] = NoOpLogger[F] } } diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala deleted file mode 100644 index 2de3f8fa..00000000 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/Slf4jLogging.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 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. - * 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 org.slf4j.{Logger => JLogger} -import cats.effect.Sync -import org.typelevel.log4cats._ - -trait Slf4jLogging[F[_]] extends Logging[F] { - - implicit protected def sync: Sync[F] - - override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = - Slf4jLogger.getLoggerFromName[F](name) - - def getLoggerFromSlf4j(logger: JLogger): SelfAwareStructuredLogger[F] = - Slf4jLogger.getLoggerFromSlf4j[F](logger) - - override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = - Slf4jLogger.fromName[F](name) - - def fromSlf4j(logger: JLogger): F[SelfAwareStructuredLogger[F]] = - Slf4jLogger.fromSlf4j[F](logger) -} - -object Slf4jLogging { - def apply[F[_]](implicit logging: Slf4jLogging[F]): Slf4jLogging[F] = logging - - def createForSync[F[_]](implicit F: Sync[F]): Slf4jLogging[F] = new Slf4jLogging[F] { - override protected val sync: Sync[F] = F - } -} diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala new file mode 100644 index 00000000..59aacc45 --- /dev/null +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala @@ -0,0 +1,57 @@ +/* + * Copyright 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. + * 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 org.slf4j.{Logger => JLogger} +import cats.effect.Sync +import org.typelevel.log4cats._ +import cats.{Applicative, Id, Defer} + +// format: off +trait Slf4jGenLogging[G[_], F[_]] extends GenLogging[G, SelfAwareStructuredLogger[F]] { + protected implicit def syncF: Sync[F] + protected def lift(f: => SelfAwareStructuredLogger[F]): G[SelfAwareStructuredLogger[F]] + + override def fromName(name: String): G[SelfAwareStructuredLogger[F]] = lift(Slf4jLogger.getLoggerFromName[F](name)) + def fromSlf4j(logger: JLogger): G[SelfAwareStructuredLogger[F]] = lift(Slf4jLogger.getLoggerFromSlf4j[F](logger)) +} +// format: off + +object Slf4jGenLogging { + def apply[G[_], F[_]](implicit logging: Slf4jGenLogging[G, F]): Slf4jGenLogging[G, F] = logging + + // format: off + def forSync[G[_], F[_]](implicit F: Sync[F], G: Applicative[G], GD: Defer[G]): Slf4jGenLogging[G, F] = + new Slf4jGenLogging[G, F] { + override protected val syncF: Sync[F] = F + override protected def lift(l: => SelfAwareStructuredLogger[F]): G[SelfAwareStructuredLogger[F]] = GD.defer(G.pure(l)) + } + // format: on +} + +trait Slf4jLogging[F[_]] extends Slf4jGenLogging[Id, F] + +object Slf4jLogging { + def apply[F[_]](implicit logging: Slf4jLogging[F]): Slf4jLogging[F] = logging + + // format: off + def forSync[F[_]](implicit F: Sync[F]): Slf4jLogging[F] = new Slf4jLogging[F] { + override protected def lift(f: => SelfAwareStructuredLogger[F]): Id[SelfAwareStructuredLogger[F]] = f + override protected def syncF: Sync[F] = F + } + // format: on +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala similarity index 51% rename from core/shared/src/main/scala/org/typelevel/log4cats/package.scala rename to slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala index e5a2445d..3acec7e8 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala @@ -14,10 +14,21 @@ * limitations under the License. */ -package org.typelevel +package org.typelevel.log4cats.slf4j + +import cats.effect._ + +object Slf4jGenLoggingCompilationTest { + implicit val slf4jGenLogging: Slf4jGenLogging[IO, IO] = Slf4jGenLogging.forSync[IO, IO] + implicit val slf4jLogging: Slf4jLogging[IO] = Slf4jLogging.forSync[IO] + + def justCompile: IO[Unit] = { + for { + _ <- slf4jGenLogging.create.flatMap(_.info("it compiled!")) + _ <- Slf4jGenLogging[IO, IO].create.flatMap(_.info("it compiled!")) + _ <- slf4jLogging.create.info("it compiled!") + _ <- Slf4jLogging[IO].create.info("it compiled!") + } yield () + } -package object log4cats { - type Logging[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] - type LoggingId[F[_]] = GenLoggingId[F, SelfAwareStructuredLogger[F]] - type LoggingF[F[_]] = GenLoggingF[F, SelfAwareStructuredLogger[F]] } 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 2a891d05..ba018025 100644 --- a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jLoggerMacroCompilationTests.scala @@ -17,7 +17,7 @@ package org.typelevel.log4cats.slf4j import cats.effect._ -import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats._ class Slf4jLoggerSimpleClassMacroTest { def loggerF[F[_]: Sync]: F[SelfAwareStructuredLogger[F]] = Slf4jLogger.create[F] @@ -71,11 +71,3 @@ object LoggingBaseline { val errorCM = logger[IO].error(Map.empty[String, String])("") } - -object LoggingCapabilityTest { - val t = new Throwable - implicit val logging: Slf4jLogging[IO] = Slf4jLogging.createForSync[IO] - - val explicit = logging.getLogger.trace("") - val summoned = Slf4jLogging[IO].getLogger.trace(t)("") -} diff --git a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala index 8a3b7552..f9e9994e 100644 --- a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternalSuite.scala @@ -67,8 +67,8 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite { MDC.put(variable, initial) Slf4jLogging - .createForSync[IO] - .getLogger + .forSync[IO] + .create .info(Map(variable -> "bar"))("A log went here") .as(MDC.get(variable)) .assertEquals(initial) @@ -93,8 +93,8 @@ class Slf4jLoggerInternalSuite extends CatsEffectSuite { val finishedLog = new CountDownLatch(1) val performLogging = Slf4jLogging - .createForSync[IO] - .getLogger + .forSync[IO] + .create .info(Map(variable -> "modified")) { startedLog.countDown() logCanceled.await() From 2f3a4f8d0fdc8e298e5969f2fbbaecfb4804bdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Tue, 6 Apr 2021 11:52:56 +0300 Subject: [PATCH 08/15] Apply scalafmt --- .../main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala index 59aacc45..0ea1bdd8 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala @@ -19,7 +19,7 @@ package org.typelevel.log4cats.slf4j import org.slf4j.{Logger => JLogger} import cats.effect.Sync import org.typelevel.log4cats._ -import cats.{Applicative, Id, Defer} +import cats.{Applicative, Defer, Id} // format: off trait Slf4jGenLogging[G[_], F[_]] extends GenLogging[G, SelfAwareStructuredLogger[F]] { From 0ef7a9e897a9ca2dd1aa3a48951562b387607d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Tue, 6 Apr 2021 12:36:54 +0300 Subject: [PATCH 09/15] Make Logging back into a type alias, add LoggingF --- .../log4cats/loggingCapabilities.scala | 12 +++--- .../org/typelevel/log4cats/package.scala | 43 +++++++++++++++++++ .../Slf4jGenLoggingCompilationTest.scala | 3 ++ 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/package.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala index 9ac1873d..86e43b34 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala @@ -33,7 +33,7 @@ import com.lorandszakacs.enclosure.Enclosure * that also send logs to some external providers by giving an implementation to this * trait. * - * @tparam F[_] + * @tparam G[_] * the effect type in which the loggers are constructed. * e.g. make cats.Id if logger creation can be done as a pure computation */ @@ -51,12 +51,10 @@ object GenLogging { l } -// N.B made it a trait, because otherwise on Scala 3 the implicitNotFound annotation would not be picked up :/ -@scala.annotation.implicitNotFound( - "Not found for Logging[${F}], keep in mind that, that ${F} is the effect in which logging is done. e.g. `Logging[${F}].create.info(message) : ${F}[Unit]`.\nAdditionally Logging[${F}] is defined as creating only loggers of type SelfAwareStructuredLogger[${F}].\nThe Logging[${F}] type itself described logging creation to be pure, i.e. cats.Id.\nSo your problem might be:\n\t1) you only have a GenLogger[G[_], ...] for some G[_] type other than cats.Id\n\t2) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}].\nIf you are unsure how to create a Logging[${F}], then you need to look at the `log4cats-slf4j` module, or `log4cats-noop` for concrete implementations. Example for slf4j:\nimplicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] // we create out Logging[IO]\nLogging[IO].create //we summon our instance, and create a SelfAwareStructuredLogger[${F}]." -) -trait Logging[F[_]] extends GenLogging[cats.Id, SelfAwareStructuredLogger[F]] - object Logging { def apply[F[_]](implicit l: Logging[F]): Logging[F] = l } + +object LoggingF { + def apply[F[_]](implicit l: LoggingF[F]): LoggingF[F] = l +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala new file mode 100644 index 00000000..c83c1c04 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala @@ -0,0 +1,43 @@ +/* + * Copyright 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. + * 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 + +package object log4cats { + + /** + * Convenience type for describing Logging where creation of the loggers itself is a pure operation + * + * @tparam F[_] + * the effect type in which logging is done. + */ + @scala.annotation.implicitNotFound( + "Not found for Logging[${F}], keep in mind that ${F} is the effect in which logging is done. e.g. `Logging[${F}].create.info(message) : ${F}[Unit]`.\nAdditionally Logging[${F}] is defined as creating only loggers of type SelfAwareStructuredLogger[${F}].\nThe Logging[${F}] type itself described logging creation to be pure, i.e. cats.Id.\nSo your problem might be:\n\t1) you only have a GenLogger[G[_], ...] for some G[_] type other than cats.Id\n\t2) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}].\nIf you are unsure how to create a Logging[${F}], then you can to look at the `log4cats-slf4j` module, or `log4cats-noop` for concrete implementations. Example for slf4j:\nimplicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] // we create our Logging[IO]\nLogging[IO].create //we summon our instance, and create a SelfAwareStructuredLogger[IO]." + ) + type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] + + /** + * Convenience type + * + * @tparam F[_] + * the effect type of both logger creation, and logging. + */ + @scala.annotation.implicitNotFound( + "Not found for LoggingF[${F}], keep in mind that ${F} is the effect in which logging is done, and the effect in which loggers are created. e.g. `LoggingF[${F}].create.flatMap(_.info(message)) : ${F}[Unit]`.\nAdditionally LoggingF[${F}] is defined as creating only loggers of type SelfAwareStructuredLogger[${F}].\nSo your problem might be:\n\t1) you only have a GenLogger[G[_], ...] for some G[_] type other than ${F}\n\t2) you do actually have a GenLogger[${F}, L], but where L is not SelfAwareStructuredLogger[${F}].\nIf you are unsure how to create a LoggingF[${F}], then you can to look at the `log4cats-slf4j` module for concrete implementations. Example for slf4j:\nimplicit val logging: LoggingF[IO] = Slf4jGenLogging.forSync[IO, IO] // we create our Logging[IO]\nLogging[IO].create //we summon our instance, and create an IO[SelfAwareStructuredLogger[IO]]." + ) + type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] + +} diff --git a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala index 3acec7e8..d9332549 100644 --- a/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala +++ b/slf4j/src/test/scala/org/typelevel/log4cats/slf4j/Slf4jGenLoggingCompilationTest.scala @@ -16,11 +16,14 @@ package org.typelevel.log4cats.slf4j +import org.typelevel.log4cats._ import cats.effect._ object Slf4jGenLoggingCompilationTest { implicit val slf4jGenLogging: Slf4jGenLogging[IO, IO] = Slf4jGenLogging.forSync[IO, IO] implicit val slf4jLogging: Slf4jLogging[IO] = Slf4jLogging.forSync[IO] + implicit val loggingF: LoggingF[IO] = slf4jGenLogging + implicit val logging: Logging[IO] = slf4jLogging def justCompile: IO[Unit] = { for { From 751fe46d54065913f90194c432dda6ae7e836afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Tue, 6 Apr 2021 12:39:44 +0300 Subject: [PATCH 10/15] Hack build to allow scala-X folders in JS/JVM cross projects --- build.sbt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build.sbt b/build.sbt index 4e5c7fec..9f40c14d 100644 --- a/build.sbt +++ b/build.sbt @@ -166,6 +166,20 @@ lazy val commonSettings = Seq( "org.typelevel" %%% "munit-cats-effect-2" % munitCatsEffectV % Test, ), testFrameworks += new TestFramework("munit.Framework"), + // major scala version folders do not work properly for shared sources between JS/JVM + // see: https://github.com/sbt/sbt/issues/5895#issuecomment-739510744 + Compile / unmanagedSourceDirectories ++= { + val major = if (isDotty.value) "-3" else "-2" + List(CrossType.Pure, CrossType.Full).flatMap( + _.sharedSrcDir(baseDirectory.value, "main").toList.map(f => file(f.getPath + major)) + ) + }, + Test / unmanagedSourceDirectories ++= { + val major = if (isDotty.value) "-3" else "-2" + List(CrossType.Pure, CrossType.Full).flatMap( + _.sharedSrcDir(baseDirectory.value, "test").toList.map(f => file(f.getPath + major)) + ) + } ) lazy val releaseSettings = { From 0f9e5679a36663637307986c669b955c98ed978b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Tue, 6 Apr 2021 12:50:38 +0300 Subject: [PATCH 11/15] Move implicitNotFound test to scala-2 only --- .../org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/shared/src/test/{scala => scala-2}/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala (100%) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala b/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala similarity index 100% rename from core/shared/src/test/scala/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala rename to core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala From e3b59f06d859ab4f30a5fa9619a8982c1c82ceac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Tue, 6 Apr 2021 13:38:25 +0300 Subject: [PATCH 12/15] Improve implicitNotFound messages on Logging and LoggingF --- .../org/typelevel/log4cats/package.scala | 84 ++++++++++++++++++- .../LoggingImplicitNotFoundTests.scala | 23 ++++- 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala index c83c1c04..1cd1d5cc 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala @@ -25,7 +25,47 @@ package object log4cats { * the effect type in which logging is done. */ @scala.annotation.implicitNotFound( - "Not found for Logging[${F}], keep in mind that ${F} is the effect in which logging is done. e.g. `Logging[${F}].create.info(message) : ${F}[Unit]`.\nAdditionally Logging[${F}] is defined as creating only loggers of type SelfAwareStructuredLogger[${F}].\nThe Logging[${F}] type itself described logging creation to be pure, i.e. cats.Id.\nSo your problem might be:\n\t1) you only have a GenLogger[G[_], ...] for some G[_] type other than cats.Id\n\t2) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}].\nIf you are unsure how to create a Logging[${F}], then you can to look at the `log4cats-slf4j` module, or `log4cats-noop` for concrete implementations. Example for slf4j:\nimplicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] // we create our Logging[IO]\nLogging[IO].create //we summon our instance, and create a SelfAwareStructuredLogger[IO]." + """ +Implicit not found for Logging[${F}]. Which is a convenience type alias. + +Keep in mind that: + +a) Logging[${F}] has to be created _explicitely_ once (at least) in your code + +b) for the type Logging, ${F} is _only_ the effect in which logging is done. + ``` + Logging[${F}].create.info(message) : ${F}[Unit] + ``` + +c) The Logging[${F}] type itself described logging creation as being pure, i.e. in cats.Id + +d) Logging[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. + +The full definition of Logging is: + ``` + type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] + ``` + +Therefore, your problem might be: + 1) you didn't create a Logging[${F}] to begin with, or didn't mark it as implicit + 2) you only have a GenLogger[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than cats.Id + 3) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}]. + +If you are unsure how to create a Logging[${F}], then you can to look at the `log4cats-slf4j`, +or `log4cats-noop` modules for concrete implementations. + +Example for slf4j: + ``` + import cats.effect.IO + + // we create our Logging[IO] + implicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] + + //we summon our instance, and create our logger + val logger: SelfAwareStructuredLogger[IO] = Logging[IO].create + logger.info("logging in IO!"): IO[Unit] + ``` +""" ) type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] @@ -36,7 +76,47 @@ package object log4cats { * the effect type of both logger creation, and logging. */ @scala.annotation.implicitNotFound( - "Not found for LoggingF[${F}], keep in mind that ${F} is the effect in which logging is done, and the effect in which loggers are created. e.g. `LoggingF[${F}].create.flatMap(_.info(message)) : ${F}[Unit]`.\nAdditionally LoggingF[${F}] is defined as creating only loggers of type SelfAwareStructuredLogger[${F}].\nSo your problem might be:\n\t1) you only have a GenLogger[G[_], ...] for some G[_] type other than ${F}\n\t2) you do actually have a GenLogger[${F}, L], but where L is not SelfAwareStructuredLogger[${F}].\nIf you are unsure how to create a LoggingF[${F}], then you can to look at the `log4cats-slf4j` module for concrete implementations. Example for slf4j:\nimplicit val logging: LoggingF[IO] = Slf4jGenLogging.forSync[IO, IO] // we create our Logging[IO]\nLogging[IO].create //we summon our instance, and create an IO[SelfAwareStructuredLogger[IO]]." + """ +Implicit not found for LoggingF[${F}]. Which is a convenience type alias. + +Keep in mind that: + +a) LoggingF[${F}] has to be created _explicitely_ once (at least) in your code + +b) for the type LoggingF, ${F} is _both_ the effect in which logger creation is done + _and_ the effect in which logging is done + ``` + val loggerF: ${F}[SelfAwareStructuredLogger] = Logging[${F}].create + loggerF.flatMap{logger => logger.info("the result of the .info is in F"): ${F}[Unit] }: ${F}[Unit] + ``` + +c) LoggingF[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. + +The full definition of Logging is: + ``` + type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] + ``` +Therefore, your problem might be: + 1) you didn't create a LoggingF[${F}] to begin with, or didn't mark it as implicit + 2) you only have a GenLoggerF[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than type ${F} + 3) you do actually have a GenLogger[${F}, L], but where L is not SelfAwareStructuredLogger[${F}]. + +If you are unsure how to create a LoggingF[${F}], then you can to look at the +`log4cats-slf4j` module for concrete implementations. + +Example for slf4j: + ``` + import cats.effect.IO + + // we use IO for both logger creation effect, and logging effect. Otherwise it wouldn't be a LoggingF + implicit val loggingF: LoggingF[IO] = Slf4jGenLogging.forSync[IO, IO] + + //we summon our instance, and create our logger + val loggerIO: IO[SelfAwareStructuredLogger[IO]] = LoggingF[IO].create + + loggerIO.flatMap{logger => logger.info("logging in IO"): IO[Unit]}: IO[Unit] + ``` +""" ) type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] diff --git a/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala b/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala index b929d6f6..34b785cd 100644 --- a/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala +++ b/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala @@ -19,13 +19,30 @@ package org.typelevel.log4cats // TODO: figure out why the implicitNotFound annotation does not work on Scala 3. class LoggingImplicitNotFoundTests extends munit.FunSuite { - test("receive proper implicitNotFound error message on Logging type") { + test("receive proper implicitNotFound error message on Logging") { val errors = compileErrors( - "import cats.effect.IO;import org.typelevel.log4cats.Logging;Logging[IO].create" + "import cats.effect.IO;import org.typelevel.log4cats.Logging;Logging[IO]" ) assert( errors.contains( - "you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger" + "you didn't create a Logging[cats.effect.IO] to begin with, or didn't mark it as implicit" + ), + clue = s"""|Actual compiler error was: + | + |$errors + | + | + |""".stripMargin + ) + } + + test("receive proper implicitNotFound error message on LoggingF") { + val errors = compileErrors( + "import cats.effect.IO;import org.typelevel.log4cats.LoggingF;LoggingF[IO]" + ) + assert( + errors.contains( + "you didn't create a LoggingF[cats.effect.IO] to begin with, or didn't mark it as implicit" ), clue = s"""|Actual compiler error was: | From 895e6bfe7508e45cc7cd880df2143cf466b1d606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Fri, 16 Apr 2021 09:29:41 +0300 Subject: [PATCH 13/15] Add slf4j.implicits + tweak implicitNotFound messages --- .../org/typelevel/log4cats/package.scala | 120 ++++++++++++------ .../LoggingImplicitNotFoundTests.scala | 4 +- .../typelevel/log4cats/slf4j/implicits.scala | 21 +++ .../internal/Slf4jLoggingInstances.scala | 30 +++++ 4 files changed, 131 insertions(+), 44 deletions(-) create mode 100644 slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala create mode 100644 slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala index 1cd1d5cc..b794cb58 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/package.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/package.scala @@ -28,35 +28,27 @@ package object log4cats { """ Implicit not found for Logging[${F}]. Which is a convenience type alias. -Keep in mind that: - -a) Logging[${F}] has to be created _explicitely_ once (at least) in your code - -b) for the type Logging, ${F} is _only_ the effect in which logging is done. - ``` - Logging[${F}].create.info(message) : ${F}[Unit] - ``` - -c) The Logging[${F}] type itself described logging creation as being pure, i.e. in cats.Id +If you are unsure how to create a Logging[${F}], then you can to look at the `log4cats-slf4j`, +or `log4cats-noop` modules for concrete implementations. -d) Logging[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. +Quickest fix might be to import the implicits: -The full definition of Logging is: ``` - type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] + // assumes dependency on log4cats-slf4j module + import org.typelevel.log4cats._ + import org.typelevel.log4cats.slf4j.implicits._ + val logger: SelfAwareStructuredLogger[IO] = Logging[IO].create + // or: + def anyFSyncLogger[F[_]: Sync]: SelfAwareStructuredLogger[F] = Logging[F].create ``` -Therefore, your problem might be: - 1) you didn't create a Logging[${F}] to begin with, or didn't mark it as implicit - 2) you only have a GenLogger[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than cats.Id - 3) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}]. - -If you are unsure how to create a Logging[${F}], then you can to look at the `log4cats-slf4j`, -or `log4cats-noop` modules for concrete implementations. +Alternatively, a mutually exclusive solution, is to explicitely create your own Logging[F] instances +and pass them around implicitely: -Example for slf4j: ``` import cats.effect.IO + import org.typelevel.log4cats._ + import org.typelevel.log4cats.slf4j._ // we create our Logging[IO] implicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] @@ -64,7 +56,31 @@ Example for slf4j: //we summon our instance, and create our logger val logger: SelfAwareStructuredLogger[IO] = Logging[IO].create logger.info("logging in IO!"): IO[Unit] + + def useLogging[F[_]: Logging] = Logging[F].create.info("yay! effect polymorphic code") + + useLogging[IO] ``` + +If neither of those things work, keep in mind that: +a) for the type Logging, ${F} is _only_ the effect in which logging is done. + ``` + Logging[${F}].create.info(message) : ${F}[Unit] + ``` + +b) The Logging[${F}] type itself described logging creation as being pure, i.e. in cats.Id + +c) Logging[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. + +The full definition of Logging is: + ``` + type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] + ``` + +Therefore, your problem might be: + 1) you `import org.typelevel.log4cats.slf4j.implicits._` AND created your own instance. Do one or the either. + 2) you only have a GenLogger[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than cats.Id + 3) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}]. """ ) type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] @@ -79,43 +95,63 @@ Example for slf4j: """ Implicit not found for LoggingF[${F}]. Which is a convenience type alias. -Keep in mind that: +If you are unsure how to create a LoggingF[${F}], then you can to look at the +`log4cats-slf4j` module for concrete implementations. + +Quickest fix might be to import the implicits: + + ``` + // assumes dependency on log4cats-slf4j module + import org.typelevel.log4cats._ + import org.typelevel.log4cats.slf4j.implicits._ + import cats.effect.{Sync, IO} + + //we summon our instance, and create our logger + val loggerIO: IO[SelfAwareStructuredLogger[IO]] = LoggingF[IO].create + + def anyFSyncLogger[F[_]: Sync]: F[SelfAwareStructuredLogger[F]] = LoggingF[F].create + ``` + +Alternatively, a mutually exclusive solution, is to explicitely create your own LoggingF[F] instances +and pass them around implicitely: + + ``` + // assumes dependency on log4cats-slf4j module + import org.typelevel.log4cats._ + import org.typelevel.log4cats.slf4j._ + import cats.effect.{Sync, IO} + + // we use IO for both logger creation effect, and logging effect. Otherwise it wouldn't be a LoggingF + implicit val loggingF: LoggingF[IO] = Slf4jGenLogging.forSync[IO, IO] + + //we summon our instance, and create our logger + val loggerIO: IO[SelfAwareStructuredLogger[IO]] = LoggingF[IO].create + + loggerIO.flatMap{logger => logger.info("logging in IO"): IO[Unit]}: IO[Unit] -a) LoggingF[${F}] has to be created _explicitely_ once (at least) in your code + def useLogging[F[_]: LoggingF] = LoggingF[F].create.flatMap{logger => logger.info("yay! effect polymorphic code")} + + useLogging[IO] + ``` -b) for the type LoggingF, ${F} is _both_ the effect in which logger creation is done +If neither of those things work, keep in mind that: +a) for the type LoggingF, ${F} is _both_ the effect in which logger creation is done _and_ the effect in which logging is done ``` val loggerF: ${F}[SelfAwareStructuredLogger] = Logging[${F}].create loggerF.flatMap{logger => logger.info("the result of the .info is in F"): ${F}[Unit] }: ${F}[Unit] ``` -c) LoggingF[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. +b) LoggingF[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. The full definition of Logging is: ``` type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] ``` Therefore, your problem might be: - 1) you didn't create a LoggingF[${F}] to begin with, or didn't mark it as implicit + 1) you `import org.typelevel.log4cats.slf4j.implicits._` AND created your own instance. Do one or the either. 2) you only have a GenLoggerF[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than type ${F} 3) you do actually have a GenLogger[${F}, L], but where L is not SelfAwareStructuredLogger[${F}]. - -If you are unsure how to create a LoggingF[${F}], then you can to look at the -`log4cats-slf4j` module for concrete implementations. - -Example for slf4j: - ``` - import cats.effect.IO - - // we use IO for both logger creation effect, and logging effect. Otherwise it wouldn't be a LoggingF - implicit val loggingF: LoggingF[IO] = Slf4jGenLogging.forSync[IO, IO] - - //we summon our instance, and create our logger - val loggerIO: IO[SelfAwareStructuredLogger[IO]] = LoggingF[IO].create - - loggerIO.flatMap{logger => logger.info("logging in IO"): IO[Unit]}: IO[Unit] - ``` """ ) type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] diff --git a/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala b/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala index 34b785cd..efe21d9c 100644 --- a/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala +++ b/core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala @@ -25,7 +25,7 @@ class LoggingImplicitNotFoundTests extends munit.FunSuite { ) assert( errors.contains( - "you didn't create a Logging[cats.effect.IO] to begin with, or didn't mark it as implicit" + "Logging[cats.effect.IO] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[cats.effect.IO]" ), clue = s"""|Actual compiler error was: | @@ -42,7 +42,7 @@ class LoggingImplicitNotFoundTests extends munit.FunSuite { ) assert( errors.contains( - "you didn't create a LoggingF[cats.effect.IO] to begin with, or didn't mark it as implicit" + "val loggerF: cats.effect.IO[SelfAwareStructuredLogger] = Logging[cats.effect.IO].create" ), clue = s"""|Actual compiler error was: | diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala new file mode 100644 index 00000000..fe60ddbc --- /dev/null +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala @@ -0,0 +1,21 @@ +/* + * Copyright 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. + * 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 org.typelevel.log4cats.slf4j.internal.Slf4jLoggingInstances + +object implicits extends Slf4jLoggingInstances \ No newline at end of file diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala new file mode 100644 index 00000000..e376a9cf --- /dev/null +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala @@ -0,0 +1,30 @@ +/* + * Copyright 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. + * 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.internal + +import cats.effect.Sync +import cats.{Applicative, Defer} +import org.typelevel.log4cats.slf4j._ + +trait Slf4jLoggingInstances { + implicit def log4catsSummonSLF4jLoggingForSync[F[_]: Sync]: Slf4jLogging[F] = Slf4jLogging.forSync[F] + + implicit def log4catsSummonSLF4jGenLoggingForSync[ + G[_]: Applicative: Defer, + F[_]: Sync + ]: Slf4jGenLogging[G, F] = Slf4jGenLogging.forSync[G, F] +} From 81422b2aeee11915fe054e2e199eb4904ef5bd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Fri, 16 Apr 2021 10:50:35 +0300 Subject: [PATCH 14/15] Create NoOpLogging/F/Gen traits + add implicits --- .../{NoOpLogging.scala => implicits.scala} | 14 +---- .../noop/internal/NoOpLoggingInstances.scala | 27 ++++++++++ .../noop/noOpLoggingCapabilities.scala | 52 +++++++++++++++++++ 3 files changed, 81 insertions(+), 12 deletions(-) rename noop/shared/src/main/scala/org/typelevel/log4cats/noop/{NoOpLogging.scala => implicits.scala} (60%) create mode 100644 noop/shared/src/main/scala/org/typelevel/log4cats/noop/internal/NoOpLoggingInstances.scala create mode 100644 noop/shared/src/main/scala/org/typelevel/log4cats/noop/noOpLoggingCapabilities.scala diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/implicits.scala similarity index 60% rename from noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala rename to noop/shared/src/main/scala/org/typelevel/log4cats/noop/implicits.scala index bc9a9ef5..4285c1a3 100644 --- a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/implicits.scala @@ -16,16 +16,6 @@ package org.typelevel.log4cats.noop -import cats.Applicative -import org.typelevel.log4cats._ +import org.typelevel.log4cats.noop.internal.NoOpLoggingInstances -trait NoOpLogging[F[_]] extends Logging[F] { - override def fromName(name: String): SelfAwareStructuredLogger[F] -} - -object NoOpLogging { - def apply[F[_]](implicit logging: NoOpLogging[F]): NoOpLogging[F] = logging - def create[F[_]: Applicative] = new NoOpLogging[F] { - override def fromName(name: String): SelfAwareStructuredLogger[F] = NoOpLogger[F] - } -} +object implicits extends NoOpLoggingInstances diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/internal/NoOpLoggingInstances.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/internal/NoOpLoggingInstances.scala new file mode 100644 index 00000000..03bf0429 --- /dev/null +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/internal/NoOpLoggingInstances.scala @@ -0,0 +1,27 @@ +/* + * Copyright 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. + * 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.noop.internal + +import cats.Applicative +import org.typelevel.log4cats.noop._ + +trait NoOpLoggingInstances { + implicit def log4catsSummonNoOpGenLogging[G[_]: Applicative, F[_]: Applicative]: NoOpLoggingGen[ + G, + F + ] = NoOpLoggingGen[G, F] +} diff --git a/noop/shared/src/main/scala/org/typelevel/log4cats/noop/noOpLoggingCapabilities.scala b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/noOpLoggingCapabilities.scala new file mode 100644 index 00000000..149b700b --- /dev/null +++ b/noop/shared/src/main/scala/org/typelevel/log4cats/noop/noOpLoggingCapabilities.scala @@ -0,0 +1,52 @@ +/* + * Copyright 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. + * 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.noop + +import cats._ +import org.typelevel.log4cats._ + +trait NoOpLoggingGen[G[_], F[_]] extends GenLogging[G, SelfAwareStructuredLogger[F]] +trait NoOpLogging[F[_]] extends NoOpLoggingGen[cats.Id, F] +trait NoOpLoggingF[F[_]] extends NoOpLoggingGen[F, F] + +object NoOpLoggingGen { + def apply[G[_], F[_]](implicit logging: NoOpLoggingGen[G, F]): NoOpLoggingGen[G, F] = logging + + implicit def create[G[_]: Applicative, F[_]: Applicative]: NoOpLoggingGen[G, F] = + new NoOpLoggingGen[G, F] { + override def fromName(name: String): G[SelfAwareStructuredLogger[F]] = + Applicative[G].map(Applicative[G].pure(name))(_ => NoOpLogger[F]) + } +} + +object NoOpLogging { + def apply[F[_]](implicit l: NoOpLogging[F]): NoOpLogging[F] = l + + def create[F[_]: Applicative]: NoOpLogging[F] = new NoOpLogging[F] { + override def fromName(name: String): SelfAwareStructuredLogger[F] = + Applicative[Id].map(Applicative[Id].pure(name))(_ => NoOpLogger[F]) + } +} + +object NoOpLoggingF { + def apply[F[_]](implicit l: NoOpLoggingF[F]): NoOpLoggingF[F] = l + + def create[F[_]: Applicative]: NoOpLoggingF[F] = new NoOpLoggingF[F] { + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = + Applicative[F].map(Applicative[F].pure(name))(_ => NoOpLogger[F]) + } +} From 696f17ed67e6b9a00ff07d62f1fab81302bafe93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lor=C3=A1nd=20Szak=C3=A1cs?= Date: Fri, 16 Apr 2021 10:59:22 +0300 Subject: [PATCH 15/15] Add missing Slf4jLoggingF trait --- .../typelevel/log4cats/slf4j/implicits.scala | 2 +- .../slf4j/internal/Slf4jLoggingInstances.scala | 3 ++- .../typelevel/log4cats/slf4j/slf4jLogging.scala | 17 ++++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala index fe60ddbc..8abdc651 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/implicits.scala @@ -18,4 +18,4 @@ package org.typelevel.log4cats.slf4j import org.typelevel.log4cats.slf4j.internal.Slf4jLoggingInstances -object implicits extends Slf4jLoggingInstances \ No newline at end of file +object implicits extends Slf4jLoggingInstances diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala index e376a9cf..c02e1d40 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggingInstances.scala @@ -21,7 +21,8 @@ import cats.{Applicative, Defer} import org.typelevel.log4cats.slf4j._ trait Slf4jLoggingInstances { - implicit def log4catsSummonSLF4jLoggingForSync[F[_]: Sync]: Slf4jLogging[F] = Slf4jLogging.forSync[F] + implicit def log4catsSummonSLF4jLoggingForSync[F[_]: Sync]: Slf4jLogging[F] = + Slf4jLogging.forSync[F] implicit def log4catsSummonSLF4jGenLoggingForSync[ G[_]: Applicative: Defer, diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala index 0ea1bdd8..a7f9dce7 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala @@ -50,8 +50,23 @@ object Slf4jLogging { // format: off def forSync[F[_]](implicit F: Sync[F]): Slf4jLogging[F] = new Slf4jLogging[F] { + override protected val syncF: Sync[F] = F override protected def lift(f: => SelfAwareStructuredLogger[F]): Id[SelfAwareStructuredLogger[F]] = f - override protected def syncF: Sync[F] = F + + } + // format: on +} + +trait Slf4jLoggingF[F[_]] extends Slf4jGenLogging[F, F] + +object Slf4jLoggingF { + def apply[F[_]](implicit logging: Slf4jLoggingF[F]): Slf4jLoggingF[F] = logging + + // format: off + def forSync[F[_]](implicit F: Sync[F]): Slf4jLoggingF[F] = new Slf4jLoggingF[F] { + override protected val syncF: Sync[F] = F + override protected def lift(f: => SelfAwareStructuredLogger[F]): F[SelfAwareStructuredLogger[F]] = F.defer(F.pure(f)) + } // format: on }