diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml new file mode 100644 index 00000000..84e2aac0 --- /dev/null +++ b/.github/workflows/pull-request.yaml @@ -0,0 +1,46 @@ +name: CI +on: + - pull_request +jobs: + format: + name: Format + strategy: + matrix: + os: + - ubuntu-20.04 + scala: + - 2.13.4 + java: + - adopt@1.11 + runs-on: ${{ matrix.os }} + steps: + - name: Branch Checkout + uses: actions/checkout@v2 + - name: Install Java And Sbt + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11 + - name: Check with Scalafmt + run: sbt ++${{ matrix.scala }} checkFormat + build: + name: Build + strategy: + matrix: + os: + - ubuntu-20.04 + scala: + - 2.12.12 + - 2.13.4 + java: + - adopt@1.11 + - adopt@1.15 + runs-on: ${{ matrix.os }} + steps: + - name: Branch Checkout + uses: actions/checkout@v2 + - name: Install Java And Sbt + uses: olafurpg/setup-scala@v10 + with: + java-version: ${{ matrix.java }} + - name: Test - [${{ matrix.java }}] [${{ matrix.scala }}] + run: sbt ++${{ matrix.scala }} ciBuild diff --git a/.travis.yml b/.travis.yml index 156882c9..f845dcfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ stages: if: type != pull_request AND tag IS present AND NOT fork scala_212: &scala_212 2.12.12 -scala_213: &scala_213 2.13.3 +scala_213: &scala_213 2.13.4 jdk_lts: &jdk_lts openjdk11 jdk_latest: &jdk_latest openjdk15 diff --git a/README.md b/README.md index c8be3ef4..afe6b729 100644 --- a/README.md +++ b/README.md @@ -43,38 +43,27 @@ Currently Log Effect supports the following backends ## Dependencies -|       | Cats | Fs2 | Cats Effect | Log Effect Core | +| | Cats | Fs2 | Cats Effect | Log Effect Core | | ------------------------:| ----:| ---:| -----------:| -----------------:| -| [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-fs2_2.13.svg?label=log-effect-fs2&colorB=2282c3)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-fs2_2.13) | 2.2.0 | 2.4.4 | 2.2.0 | [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.12.svg?label=%20&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.12) | -| v0.8.0 | 1.6.1 | 1.0.5 | 1.3.1 | v0.8.0 | -| v0.3.5 | 1.4.0 | 1.0.0-M5 | 1.0.0 | v0.3.5 | -| v0.2.2 | 1.2.0 | 0.10.5 | 0.10.1 | v0.2.2 | -| v0.1.14 | 1.2.0 | 0.10.5 | | v0.1.14 | +| [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-fs2_2.13.svg?label=log-effect-fs2&colorB=2282c3)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-fs2_2.13) | 2.3.0 | 2.4.6 | 2.3.0 | [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.12.svg?label=%20&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.12) |
-|       | Zio | Scalaz ZIO | Log Effect Core | -| ------------------------:| ---:| ----------:| -----------------:| -| [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-zio_2.13.svg?label=log-effect-zio&colorB=fb0005)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-zio_2.13) | 1.0.1 | | [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.12.svg?label=%20&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.12) | -| v0.8.0 | 1.0.0-RC9 | | v0.8.0 | -| v0.7.0 | | 1.0-RC4 | v0.7.0 | -| v0.3.5 | | 0.2.7 | v0.3.5 | +| | Zio | Log Effect Core | +| ------------------------:| ---:| ---------------:| +| [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-zio_2.13.svg?label=log-effect-zio&colorB=fb0005)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-zio_2.13) | 1.0.3 | [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.12.svg?label=%20&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.12) |
-|       | Log4cats | Log Effect Core | +| | Log4cats | Log Effect Core | | ------------------------:| --------:| -----------------:| | [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-interop_2.13.svg?label=log-effect-interop&colorB=009933)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-interop_2.13) | 1.1.1 | [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.13.svg?label=%20&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.13) |
-|       | Cats | Cats Effect | Log4s | Scribe | -| ------------------------:| -----:| -----------:| ------:| ------:| -| [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.13.svg?label=log-effect-core&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.13) | | | 1.8.2 | 2.7.12 | -| v0.8.0 | | | 1.8.2 | 2.7.8 | -| v0.3.5 | | | 1.6.1 | | -| v0.2.2 | 1.2.0 | | 1.6.1 | | -| v0.1.14 | 1.2.0 | 0.10.1 | 1.6.1 | | +| | Log4s | Scribe | +| ------------------------:| ------:| ------:| +| [![Maven Central](https://img.shields.io/maven-central/v/io.laserdisc/log-effect-core_2.13.svg?label=log-effect-core&colorB=9311fc)](https://maven-badges.herokuapp.com/maven-central/io.laserdisc/log-effect-core_2.13) | 1.9.0 | 3.1.3 |
@@ -83,153 +72,181 @@ Currently Log Effect supports the following backends ### Get Logs #### Cats Effect Sync -To get an instance of `LogWriter` for **Cats Effect**'s `Sync` the options below are available -```scala -import java.util.{ logging => jul } +To get an instance of `LogWriter` for **Cats Effect**'s `Sync` the options below are available (see [here](./fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala)) -import cats.effect.Sync -import cats.syntax.functor._ -import log.effect.fs2.SyncLogWriter._ -import log.effect.{ LogLevels, LogWriter } -import org.{ log4s => l4s } - -sealed abstract class App[F[_]](implicit F: Sync[F]) { - val log4s1: F[LogWriter[F]] = - F.delay(l4s.getLogger("test")) map log4sLog[F] +*full compiling example [here](./fs2/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala)* +```scala +val log4s1: F[LogWriter[F]] = +F.delay(l4s.getLogger("test")) map log4sLog[F] - val log4s2: F[LogWriter[F]] = log4sLog("a logger") +val log4s2: F[LogWriter[F]] = log4sLog("a logger") - val log4s3: F[LogWriter[F]] = { - case class LoggerClass() - log4sLog(classOf[LoggerClass]) - } +val log4s3: F[LogWriter[F]] = { +case class LoggerClass() +log4sLog(classOf[LoggerClass]) +} - val jul1: F[LogWriter[F]] = - F.delay(jul.Logger.getLogger("a logger")) map julLog[F] +val jul1: F[LogWriter[F]] = +F.delay(jul.Logger.getLogger("a logger")) map julLog[F] - val jul2: F[LogWriter[F]] = julLog +val jul2: F[LogWriter[F]] = julLog - val scribe1: F[LogWriter[F]] = - F.delay(scribe.Logger("a logger")) map scribeLog[F] +val scribe1: F[LogWriter[F]] = +F.delay(scribe.Logger("a logger")) map scribeLog[F] - val scribe2: F[LogWriter[F]] = scribeLog("a logger") +val scribe2: F[LogWriter[F]] = scribeLog("a logger") - val scribe3: F[LogWriter[F]] = { - case class LoggerClass() - scribeLog(classOf[LoggerClass]) - } +val scribe3: F[LogWriter[F]] = { +case class LoggerClass() +scribeLog(classOf[LoggerClass]) +} - val console1: LogWriter[F] = consoleLog +val console1: LogWriter[F] = consoleLog - val console2: LogWriter[F] = consoleLogUpToLevel(LogLevels.Warn) +val console2: LogWriter[F] = consoleLogUpToLevel(LogLevels.Warn) - val noOp: LogWriter[F] = noOpLog[F] -} +val noOp: LogWriter[F] = noOpLog[F] ``` #### Fs2 Stream -Simirarly, to get instances of `LogWriter` for **Fs2**'s `Stream` the constructors below are available -```scala -import java.util.{ logging => jul } +Similarly, to get instances of `LogWriter` for **Fs2**'s `Stream` the constructors below are available [here](./fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala) -import cats.effect.Sync -import cats.syntax.flatMap._ -import fs2.Stream -import log.effect.fs2.Fs2LogWriter._ -import log.effect.{ LogLevels, LogWriter } -import org.{ log4s => l4s } - -sealed abstract class App[F[_]](implicit F: Sync[F]) { - val log4s1: fs2.Stream[F, LogWriter[F]] = - Stream.eval(F.delay(l4s.getLogger("test"))) >>= log4sLogStream[F] +*full compiling example [here](./fs2/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala)* +```scala +val log4s1: fs2.Stream[F, LogWriter[F]] = +Stream.eval(F.delay(l4s.getLogger("test"))) >>= log4sLogStream[F] - val log4s2: fs2.Stream[F, LogWriter[F]] = log4sLogStream("a logger") +val log4s2: fs2.Stream[F, LogWriter[F]] = log4sLogStream("a logger") - val log4s3: fs2.Stream[F, LogWriter[F]] = { - case class LoggerClass() - log4sLogStream(classOf[LoggerClass]) - } +val log4s3: fs2.Stream[F, LogWriter[F]] = { +case class LoggerClass() +log4sLogStream(classOf[LoggerClass]) +} - val jul1: fs2.Stream[F, LogWriter[F]] = - Stream.eval(F.delay(jul.Logger.getLogger("a logger"))) >>= julLogStream[F] +val jul1: fs2.Stream[F, LogWriter[F]] = +Stream.eval(F.delay(jul.Logger.getLogger("a logger"))) >>= julLogStream[F] - val jul2: fs2.Stream[F, LogWriter[F]] = julLogStream +val jul2: fs2.Stream[F, LogWriter[F]] = julLogStream - val scribe1: fs2.Stream[F, LogWriter[F]] = - Stream.eval(F.delay(scribe.Logger("a logger"))) >>= scribeLogStream[F] +val scribe1: fs2.Stream[F, LogWriter[F]] = +Stream.eval(F.delay(scribe.Logger("a logger"))) >>= scribeLogStream[F] - val scribe2: fs2.Stream[F, LogWriter[F]] = scribeLogStream("a logger") +val scribe2: fs2.Stream[F, LogWriter[F]] = scribeLogStream("a logger") - val scribe3: fs2.Stream[F, LogWriter[F]] = { - case class LoggerClass() - scribeLogStream(classOf[LoggerClass]) - } +val scribe3: fs2.Stream[F, LogWriter[F]] = { +case class LoggerClass() +scribeLogStream(classOf[LoggerClass]) +} - val console1: fs2.Stream[F, LogWriter[F]] = consoleLogStream +val console1: fs2.Stream[F, LogWriter[F]] = consoleLogStream - val console2: fs2.Stream[F, LogWriter[F]] = consoleLogStreamUpToLevel(LogLevels.Warn) +val console2: fs2.Stream[F, LogWriter[F]] = consoleLogStreamUpToLevel(LogLevels.Warn) - val noOp: fs2.Stream[F, LogWriter[F]] = noOpLogStream -} +val noOp: fs2.Stream[F, LogWriter[F]] = noOpLogStream ``` *See [here](https://github.com/laserdisc-io/laserdisc#example-usage) for an example whit [Laserdisc](https://github.com/laserdisc-io/laserdisc)* #### Zio Task -To create instances for `ZIO` some useful constructors can be found [here](https://github.com/laserdisc-io/log-effect/blob/master/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala). Note as they exploit the power and expressiveness of the RIO pattern as shown below +To create instances for `ZIO` some useful constructors can be found [here](./zio/src/main/scala/log/effect/zio/ZioLogWriter.scala). Note as they exploit the power and expressiveness of `RLayer` an the `RIO` pattern as shown below. + +##### Create LogWriter as a Layer +*full compiling example [here](./zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala)* ```scala -import java.util.{ logging => jul } +// Case 1: from a possible config +val logNameLiveFromConfig: ULayer[ZLogName] = + aConfigLive >>> ZLayer.fromFunctionM { env => + ZIO.succeed(LogName(env.get[AConfig].logName)) + } -import log.effect.zio.ZioLogWriter._ -import log.effect.{ LogLevels, LogWriter } -import org.{ log4s => l4s } -import zio.{ RIO, Task } +val log4sCase1: TaskLayer[ZLogWriter] = + logNameLiveFromConfig >>> log4sLayerFromName -sealed abstract class App { - def someZioProgramUsingLogs: RIO[LogWriter[Task], Unit] +val scribeCase1: TaskLayer[ZLogWriter] = + logNameLiveFromConfig >>> scribeLayerFromName - val log4s1: Task[Unit] = - Task.effect(l4s.getLogger("a logger")) >>= { logger => - (log4sFromLogger >>> someZioProgramUsingLogs) provide logger - } +// Case 2: from a name +val log4sCase2: TaskLayer[ZLogWriter] = + logNameLive >>> log4sLayerFromName - val log4s2: Task[Unit] = - (log4sFromName >>> someZioProgramUsingLogs) provide "a logger name" +val scribeCase2: TaskLayer[ZLogWriter] = + logNameLive >>> scribeLayerFromName - val log4s3: Task[Unit] = { - case class LoggerClass(); - (log4sFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] - } +// Case 3: from a logger +val log4sCase3: TaskLayer[ZLogWriter] = + log4sLoggerLive >>> log4sLayerFromLogger - val jul1: Task[Unit] = - Task.effect(jul.Logger.getLogger("a logger")) >>= { logger => - (julFromLogger >>> someZioProgramUsingLogs) provide logger - } +val julCase3: TaskLayer[ZLogWriter] = + julLoggerLive >>> julLayerFromLogger - val jul2: Task[Unit] = - julGlobal >>> someZioProgramUsingLogs +val scribeCase3: TaskLayer[ZLogWriter] = + scribeLoggerLive >>> scribeLayerFromLogger - val scribe1: Task[Unit] = - Task.effect(scribe.Logger("a logger")) >>= { logger => - (scribeFromLogger >>> someZioProgramUsingLogs) provide logger - } +// Case 4: from a class +val log4sCase4: TaskLayer[ZLogWriter] = + classLive >>> log4sLayerFromClass - val scribe2: Task[Unit] = - (scribeFromName >>> someZioProgramUsingLogs) provide "a logger name" +val scribeCase4: TaskLayer[ZLogWriter] = + classLive >>> scribeLayerFromClass +``` - val scribe3: Task[Unit] = { - case class LoggerClass(); - (scribeFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] +##### Create LogWriter as RIO +*full compiling example [here](./zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala)* +```scala +// Case 1: from a possible config in a Layer (gives a Layer) +val log4sCase1: RLayer[Has[AConfig], ZLogWriter] = + ZLayer.fromServiceM { config => + log4sFromName.provide(config.logName) + } +val scribe4sCase1: RLayer[Has[AConfig], ZLogWriter] = + ZLayer.fromServiceM { config => + scribeFromName.provide(config.logName) } - val console1: Task[Unit] = - someZioProgramUsingLogs provide consoleLog - - val console2: Task[Unit] = - someZioProgramUsingLogs provide consoleLogUpToLevel(LogLevels.Warn) +// Case 2: from a name +val log4sCase2: Task[Unit] = + (log4sFromName >>> someZioProgramUsingLogs) provide aLogName + +val scribeCase2: Task[Unit] = + (scribeFromName >>> someZioProgramUsingLogs) provide aLogName + +// Case 3: from a logger +val log4sCase3: Task[Unit] = + Task.effect(l4s.getLogger(aLogName)) >>= { logger => + (log4sFromLogger >>> someZioProgramUsingLogs) provide logger + } +val julCase3: Task[Unit] = + Task.effect(jul.Logger.getLogger(aLogName)) >>= { logger => + (julFromLogger >>> someZioProgramUsingLogs) provide logger + } +val scribeCase3: Task[Unit] = + Task.effect(scribe.Logger(aLogName)) >>= { logger => + (scribeFromLogger >>> someZioProgramUsingLogs) provide logger + } - val noOp: Task[Unit] = - someZioProgramUsingLogs provide noOpLog +// Case 4: from a class +val log4sCase4: Task[Unit] = { + case class LoggerClass(); + (log4sFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] +} +val scribeCase4: Task[Unit] = { + case class LoggerClass(); + (scribeFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] } + +// Case 5 (Jul): from global logger object +val julCase5: Task[Unit] = + julGlobal >>> someZioProgramUsingLogs + +// Case 6: console logger +val console1: Task[Unit] = + someZioProgramUsingLogs provide consoleLog + +val console2: Task[Unit] = + someZioProgramUsingLogs provide consoleLogUpToLevel(LogLevels.Warn) + +// Case 7: No-op logger +val noOp: Task[Unit] = + someZioProgramUsingLogs provide noOpLog ``` diff --git a/build.sbt b/build.sbt index f8f36ea9..72cbdd0e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,17 @@ +lazy val versionOf = new { + val cats = "2.3.0" + val catsEffect = "2.3.0" + val fs2 = "2.4.6" + val kindProjector = "0.11.1" + val log4cats = "1.1.1" + val log4s = "1.9.0" + val scalaCheck = "1.15.1" + val scalaTest = "3.2.3" + val zio = "1.0.3" + val scribe = "3.1.3" + val silencer = "1.7.1" +} + lazy val scala212Options = Seq( "-deprecation", "-encoding", @@ -40,20 +54,6 @@ lazy val scala213Options = scala212Options diff Seq( "-Xfuture" ) -lazy val versionOf = new { - val cats = "2.2.0" - val catsEffect = "2.2.0" - val fs2 = "2.4.6" - val kindProjector = "0.11.1" - val log4cats = "1.1.1" - val log4s = "1.9.0" - val scalaCheck = "1.15.1" - val scalaTest = "3.2.3" - val zio = "1.0.3" - val scribe = "3.1.1" - val silencer = "1.7.1" -} - lazy val coreDependencies = Seq( "org.log4s" %% "log4s" % versionOf.log4s, "com.outr" %% "scribe" % versionOf.scribe @@ -134,7 +134,7 @@ lazy val root = project .settings( name := "log-effect", publishArtifact := false, - addCommandAlias("format", ";scalafmt;test:scalafmt;scalafmtSbt"), + addCommandAlias("fmt", ";scalafmt;test:scalafmt;scalafmtSbt"), addCommandAlias( "checkFormat", ";scalafmtCheck;test:scalafmtCheck;scalafmtSbtCheck" diff --git a/core/src/main/scala/log/effect/LogLevel.scala b/core/src/main/scala/log/effect/LogLevel.scala index 19a87dc6..c56788d6 100644 --- a/core/src/main/scala/log/effect/LogLevel.scala +++ b/core/src/main/scala/log/effect/LogLevel.scala @@ -9,56 +9,50 @@ sealed trait LogLevel extends Product with Serializable object LogLevel extends LogLevelSyntax { import LogLevels._ - implicit val logLevelShow: Show[LogLevel] = - new Show[LogLevel] { - def show(t: LogLevel): String = - t match { - case l: Trace => showFor(l).show(l) - case l: Debug => showFor(l).show(l) - case l: Info => showFor(l).show(l) - case l: Warn => showFor(l).show(l) - case l: Error => showFor(l).show(l) - } - } + implicit val logLevelShow: Show[LogLevel] = { + case l: Trace => showFor(l).show(l) + case l: Debug => showFor(l).show(l) + case l: Info => showFor(l).show(l) + case l: Warn => showFor(l).show(l) + case l: Error => showFor(l).show(l) + } implicit val logLevelOrdering: Ordering[LogLevel] = - new Ordering[LogLevel] { - def compare(x: LogLevel, y: LogLevel): Int = - x match { - case Trace => - y match { - case Trace => 0 - case Debug | Info | Warn | Error => -1 - } - - case Debug => - y match { - case Trace => 1 - case Debug => 0 - case Info | Warn | Error => -1 - } - - case Info => - y match { - case Trace | Debug => 1 - case Info => 0 - case Warn | Error => -1 - } - - case Warn => - y match { - case Trace | Debug | Info => 1 - case Warn => 0 - case Error => -1 - } - - case Error => - y match { - case Trace | Debug | Info | Warn => 1 - case Error => 0 - } - } - } + (x: LogLevel, y: LogLevel) => + x match { + case Trace => + y match { + case Trace => 0 + case Debug | Info | Warn | Error => -1 + } + + case Debug => + y match { + case Trace => 1 + case Debug => 0 + case Info | Warn | Error => -1 + } + + case Info => + y match { + case Trace | Debug => 1 + case Info => 0 + case Warn | Error => -1 + } + + case Warn => + y match { + case Trace | Debug | Info => 1 + case Warn => 0 + case Error => -1 + } + + case Error => + y match { + case Trace | Debug | Info | Warn => 1 + case Error => 0 + } + } @silent private def showFor[A](a: A)(implicit ev: Show[A]): Show[A] = ev } @@ -83,36 +77,26 @@ object LogLevels { case object Trace extends LogLevel { implicit val traceShow: Show[Trace] = - new Show[Trace] { - def show(t: Trace): String = "TRACE" - } + (_: Trace) => "TRACE" } case object Debug extends LogLevel { implicit val debugShow: Show[Debug] = - new Show[Debug] { - def show(t: Debug): String = "DEBUG" - } + (_: Debug) => "DEBUG" } case object Info extends LogLevel { implicit val infoShow: Show[Info] = - new Show[Info] { - def show(t: Info): String = "INFO" - } + (_: Info) => "INFO" } case object Warn extends LogLevel { implicit val warnShow: Show[Warn] = - new Show[Warn] { - def show(t: Warn): String = "WARN" - } + (_: Warn) => "WARN" } case object Error extends LogLevel { implicit val errorShow: Show[Error] = - new Show[Error] { - def show(t: Error): String = "ERROR" - } + (_: Error) => "ERROR" } } diff --git a/core/src/main/scala/log/effect/LogWriter.scala b/core/src/main/scala/log/effect/LogWriter.scala index 0c2c3de6..dc29396d 100644 --- a/core/src/main/scala/log/effect/LogWriter.scala +++ b/core/src/main/scala/log/effect/LogWriter.scala @@ -6,7 +6,7 @@ import log.effect.internal.{Id, Show} import scala.language.implicitConversions trait LogWriter[F[_]] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] + def write[A: Show](level: LogLevel, a: =>A): F[Unit] } object LogWriter extends LogWriterSyntax { diff --git a/core/src/main/scala/log/effect/LogWriterConstructor.scala b/core/src/main/scala/log/effect/LogWriterConstructor.scala index 03e1f771..4c4b9959 100644 --- a/core/src/main/scala/log/effect/LogWriterConstructor.scala +++ b/core/src/main/scala/log/effect/LogWriterConstructor.scala @@ -18,7 +18,7 @@ object LogWriterConstructor { val construction: G[l4s.Logger] => G[LogWriter[F]] = _ map { l4sLogger => new LogWriter[F] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = { + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = { val beLevel = level match { case LogLevels.Trace => l4s.Trace case LogLevels.Debug => l4s.Debug @@ -45,7 +45,7 @@ object LogWriterConstructor { val construction: G[jul.Logger] => G[LogWriter[F]] = _ map { julLogger => new LogWriter[F] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = { + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = { val beLevel = level match { case LogLevels.Trace => jul.Level.FINEST case LogLevels.Debug => jul.Level.FINE @@ -78,7 +78,7 @@ object LogWriterConstructor { val construction: G[scribe.Logger] => G[LogWriter[F]] = _ map { scribeLogger => new LogWriter[F] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = { + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = { val beLevel = level match { case LogLevels.Trace => scribe.Level.Trace case LogLevels.Debug => scribe.Level.Debug @@ -107,7 +107,7 @@ object LogWriterConstructor { new LogWriter[F] { private val minLogLevel = ll - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = if (level >= minLogLevel) F.suspend( println( @@ -123,7 +123,7 @@ object LogWriterConstructor { val construction: Unit => Id[LogWriter[Id]] = _ => new LogWriter[Id] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): Unit = () + def write[A: Show](level: LogLevel, a: =>A): Unit = () } } } diff --git a/fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala b/fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala index 87d60732..bbe5b421 100644 --- a/fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala +++ b/fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala @@ -62,8 +62,8 @@ object SyncLogWriter { implicit final class NoOpLogF(private val `_`: LogWriter[Id]) extends AnyVal { def liftF[F[_]: Applicative]: LogWriter[F] = new LogWriter[F] { - private[this] val unit = Applicative[F].unit - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = unit + private[this] val unit = Applicative[F].unit + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = unit } } } diff --git a/fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala b/fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala index ff5ac18b..bbe8863d 100644 --- a/fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala +++ b/fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala @@ -10,7 +10,7 @@ object readerT { implicit LW: LogWriter[F] ): LogWriter[ReaderT[F, Env, *]] = new LogWriter[ReaderT[F, Env, *]] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): ReaderT[F, Env, Unit] = + def write[A: Show](level: LogLevel, a: =>A): ReaderT[F, Env, Unit] = ReaderT.liftF[F, Env, Unit](LW.write(level, a)) } } diff --git a/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala b/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala index d5daf6ee..b10d39ab 100644 --- a/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala +++ b/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala @@ -9,7 +9,7 @@ import log.effect.internal.syntax._ private[interop] trait Log4catsInterop0 extends Log4catsInterop1 { implicit def logWriterFromLogger[F[_]](implicit ev: Logger[F]): LogWriter[F] = new LogWriter[F] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = (level, a) match { case (Trace, Failure(msg, th)) => ev.trace(th)(msg) case (Trace, other) => ev.trace(other.show) @@ -28,7 +28,7 @@ private[interop] trait Log4catsInterop0 extends Log4catsInterop1 { private[interop] trait Log4catsInterop1 { implicit def logWriterFromMessageLogger[F[_]](implicit ev: MessageLogger[F]): LogWriter[F] = new LogWriter[F] { - def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): F[Unit] = + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = level match { case Trace => ev.trace(a.show) case Debug => ev.debug(a.show) diff --git a/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala b/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala index ada9eb80..bf54d533 100644 --- a/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala +++ b/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala @@ -3,13 +3,23 @@ package zio import java.util.{logging => jul} -import _root_.zio.{IO, RIO, Task, UIO, URIO, ZIO} +import _root_.zio._ +import com.github.ghik.silencer.silent import log.effect.internal.{EffectSuspension, Id, Show} import org.{log4s => l4s} object ZioLogWriter { import instances._ + final case class LogName(x: String) extends AnyVal + + final type ZLogName = Has[LogName] + final type ZLogClass[A] = Has[Class[A]] + final type ZLog4sLogger = Has[l4s.Logger] + final type ZJulLogger = Has[jul.Logger] + final type ZScribeLogger = Has[scribe.Logger] + final type ZLogWriter = Has[LogWriter[Task]] + val log4sFromLogger: URIO[l4s.Logger, LogWriter[Task]] = ZIO.access(log4sLogger => LogWriter.pureOf[Task](log4sLogger)) @@ -46,6 +56,44 @@ object ZioLogWriter { val noOpLog: LogWriter[Task] = LogWriter.of[Id](()).liftT + // Needed for 2.12.12 where these warnings still appear + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + val log4sLayerFromName: RLayer[ZLogName, ZLogWriter] = + ZLayer.fromServiceM(name => log4sFromName provide name.x) + + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + val log4sLayerFromLogger: RLayer[ZLog4sLogger, ZLogWriter] = + ZLayer.fromServiceM(log4sLogger => log4sFromLogger provide log4sLogger) + + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + def log4sLayerFromClass[A: Tag]: RLayer[ZLogClass[A], ZLogWriter] = + ZLayer.fromServiceM(c => log4sFromClass provide c) + + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + val julLayerFromLogger: RLayer[ZJulLogger, ZLogWriter] = + ZLayer.fromServiceM(julLogger => julFromLogger provide julLogger) + + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + val scribeLayerFromName: RLayer[ZLogName, ZLogWriter] = + ZLayer.fromServiceM(name => scribeFromName provide name.x) + + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + val scribeLayerFromLogger: RLayer[ZScribeLogger, ZLogWriter] = + ZLayer.fromServiceM(scribeLogger => scribeFromLogger provide scribeLogger) + + @silent("a type was inferred to be `Any`; this may indicate a programming error.") + def scribeLayerFromClass[A: Tag]: RLayer[ZLogClass[A], ZLogWriter] = + ZLayer.fromServiceM(c => scribeFromClass provide c) + + val consoleLogLayer: ULayer[ZLogWriter] = + ZLayer.succeed(consoleLog) + + def consoleLogLayerUpToLevel[LL <: LogLevel](minLevel: LL): ULayer[ZLogWriter] = + ZLayer.succeed(consoleLogUpToLevel(minLevel)) + + val noOpLogLayer: ULayer[ZLogWriter] = + ZLayer.succeed(noOpLog) + private[this] object instances { private[zio] implicit final val taskEffectSuspension: EffectSuspension[Task] = new EffectSuspension[Task] { @@ -57,15 +105,18 @@ object ZioLogWriter { def suspend[A](a: =>A): UIO[A] = IO.effectTotal(a) } - private[zio] implicit final def functorInstances[R, E]: internal.Functor[ZIO[R, E, *]] = - new internal.Functor[ZIO[R, E, *]] { + private[zio] implicit final def functorInstances[ + R, + E + ]: log.effect.internal.Functor[ZIO[R, E, *]] = + new log.effect.internal.Functor[ZIO[R, E, *]] { def fmap[A, B](f: A => B): ZIO[R, E, A] => ZIO[R, E, B] = _ map f } implicit final class NoOpLogT(private val `_`: LogWriter[Id]) extends AnyVal { def liftT: LogWriter[Task] = new LogWriter[Task] { - override def write[A: Show, L <: LogLevel: Show](level: L, a: =>A): Task[Unit] = Task.unit + override def write[A: Show](level: LogLevel, a: =>A): Task[Unit] = Task.unit } } } diff --git a/zio/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala b/zio/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala deleted file mode 100644 index 76c1da48..00000000 --- a/zio/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala +++ /dev/null @@ -1,61 +0,0 @@ -import com.github.ghik.silencer.silent -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpecLike - -@silent final class ReadmeConstructionCodeSnippetsTest extends AnyWordSpecLike with Matchers { - "Zio construction snippets should compile" in { - import java.util.{logging => jul} - - import log.effect.zio.ZioLogWriter._ - import log.effect.{LogLevels, LogWriter} - import org.{log4s => l4s} - import zio.{RIO, Task} - - sealed abstract class App { - def someZioProgramUsingLogs: RIO[LogWriter[Task], Unit] - - val log4s1: Task[Unit] = - Task.effect(l4s.getLogger("a logger")) >>= { logger => - (log4sFromLogger >>> someZioProgramUsingLogs) provide logger - } - - val log4s2: Task[Unit] = - (log4sFromName >>> someZioProgramUsingLogs) provide "a logger name" - - val log4s3: Task[Unit] = { - case class LoggerClass(); - (log4sFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] - } - - val jul1: Task[Unit] = - Task.effect(jul.Logger.getLogger("a logger")) >>= { logger => - (julFromLogger >>> someZioProgramUsingLogs) provide logger - } - - val jul2: Task[Unit] = - julGlobal >>> someZioProgramUsingLogs - - val scribe1: Task[Unit] = - Task.effect(scribe.Logger("a logger")) >>= { logger => - (scribeFromLogger >>> someZioProgramUsingLogs) provide logger - } - - val scribe2: Task[Unit] = - (scribeFromName >>> someZioProgramUsingLogs) provide "a logger name" - - val scribe3: Task[Unit] = { - case class LoggerClass(); - (scribeFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] - } - - val console1: Task[Unit] = - someZioProgramUsingLogs provide consoleLog - - val console2: Task[Unit] = - someZioProgramUsingLogs provide consoleLogUpToLevel(LogLevels.Warn) - - val noOp: Task[Unit] = - someZioProgramUsingLogs provide noOpLog - } - } -} diff --git a/zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala b/zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala new file mode 100644 index 00000000..d986a8c8 --- /dev/null +++ b/zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala @@ -0,0 +1,81 @@ +import com.github.ghik.silencer.silent +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +@silent final class ReadmeLayerConstructionCodeSnippetsTest extends AnyWordSpecLike with Matchers { + "Zio Layer construction snippets should compile" in { + import java.util.{logging => jul} + + import log.effect.zio.ZioLogWriter._ + import org.{log4s => l4s} + import zio._ + + object Setup { + private val aLogName = "a logger" + private val aLog4sLogger = l4s.getLogger("a log4s logger") + private val aJulLogger = jul.Logger.getLogger("a jul logger") + private val aScribeLogger = scribe.Logger("a Scribe logger") + + final case class LoggerClass() + final case class AConfig(logName: String) + + final val aConfigLive: ULayer[ZEnv with Has[AConfig]] = + ZEnv.live ++ ZLayer.succeed(AConfig(aLogName)) + + final val logNameLive: ULayer[ZEnv with ZLogName] = + ZEnv.live ++ ZLayer.succeed(LogName(aLogName)) + + final val log4sLoggerLive: ULayer[ZEnv with ZLog4sLogger] = + ZEnv.live ++ ZLayer.succeed(aLog4sLogger) + + final val julLoggerLive: ULayer[ZEnv with ZJulLogger] = + ZEnv.live ++ ZLayer.succeed(aJulLogger) + + final val scribeLoggerLive: ULayer[ZEnv with ZScribeLogger] = + ZEnv.live ++ ZLayer.succeed(aScribeLogger) + + final val classLive: ULayer[ZEnv with ZLogClass[LoggerClass]] = + ZEnv.live ++ ZLayer.succeed(classOf[LoggerClass]) + } + + sealed abstract class App { + import Setup._ + + // Case 1: from a possible config + val logNameLiveFromConfig: ULayer[ZLogName] = + aConfigLive >>> ZLayer.fromFunctionM { env => + ZIO.succeed(LogName(env.get[AConfig].logName)) + } + + val log4sCase1: TaskLayer[ZLogWriter] = + logNameLiveFromConfig >>> log4sLayerFromName + + val scribeCase1: TaskLayer[ZLogWriter] = + logNameLiveFromConfig >>> scribeLayerFromName + + // Case 2: from a name + val log4sCase2: TaskLayer[ZLogWriter] = + logNameLive >>> log4sLayerFromName + + val scribeCase2: TaskLayer[ZLogWriter] = + logNameLive >>> scribeLayerFromName + + // Case 3: from a logger + val log4sCase3: TaskLayer[ZLogWriter] = + log4sLoggerLive >>> log4sLayerFromLogger + + val julCase3: TaskLayer[ZLogWriter] = + julLoggerLive >>> julLayerFromLogger + + val scribeCase3: TaskLayer[ZLogWriter] = + scribeLoggerLive >>> scribeLayerFromLogger + + // Case 4: from a class + val log4sCase4: TaskLayer[ZLogWriter] = + classLive >>> log4sLayerFromClass + + val scribeCase4: TaskLayer[ZLogWriter] = + classLive >>> scribeLayerFromClass + } + } +} diff --git a/zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala b/zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala new file mode 100644 index 00000000..a7c4fbfe --- /dev/null +++ b/zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala @@ -0,0 +1,80 @@ +import com.github.ghik.silencer.silent +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +@silent final class ReadmeRioConstructionCodeSnippetsTest extends AnyWordSpecLike with Matchers { + "Zio RIO construction snippets should compile" in { + import java.util.{logging => jul} + + import log.effect.zio.ZioLogWriter._ + import log.effect.{LogLevels, LogWriter} + import org.{log4s => l4s} + import zio._ + + object Setup { + final case class AConfig(logName: String) + final val aLogName = "a logger" + final def someZioProgramUsingLogs: RIO[LogWriter[Task], Unit] = ??? + } + + sealed abstract class App { + import Setup._ + + // Case 1: from a possible config in a Layer (gives a Layer) + val log4sCase1: RLayer[Has[AConfig], ZLogWriter] = + ZLayer.fromServiceM { config => + log4sFromName.provide(config.logName) + } + val scribe4sCase1: RLayer[Has[AConfig], ZLogWriter] = + ZLayer.fromServiceM { config => + scribeFromName.provide(config.logName) + } + + // Case 2: from a name + val log4sCase2: Task[Unit] = + (log4sFromName >>> someZioProgramUsingLogs) provide aLogName + + val scribeCase2: Task[Unit] = + (scribeFromName >>> someZioProgramUsingLogs) provide aLogName + + // Case 3: from a logger + val log4sCase3: Task[Unit] = + Task.effect(l4s.getLogger(aLogName)) >>= { logger => + (log4sFromLogger >>> someZioProgramUsingLogs) provide logger + } + val julCase3: Task[Unit] = + Task.effect(jul.Logger.getLogger(aLogName)) >>= { logger => + (julFromLogger >>> someZioProgramUsingLogs) provide logger + } + val scribeCase3: Task[Unit] = + Task.effect(scribe.Logger(aLogName)) >>= { logger => + (scribeFromLogger >>> someZioProgramUsingLogs) provide logger + } + + // Case 4: from a class + val log4sCase4: Task[Unit] = { + case class LoggerClass(); + (log4sFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] + } + val scribeCase4: Task[Unit] = { + case class LoggerClass(); + (scribeFromClass >>> someZioProgramUsingLogs) provide classOf[LoggerClass] + } + + // Case 5 (Jul): from global logger object + val julCase5: Task[Unit] = + julGlobal >>> someZioProgramUsingLogs + + // Case 6: console logger + val console1: Task[Unit] = + someZioProgramUsingLogs provide consoleLog + + val console2: Task[Unit] = + someZioProgramUsingLogs provide consoleLogUpToLevel(LogLevels.Warn) + + // Case 7: No-op logger + val noOp: Task[Unit] = + someZioProgramUsingLogs provide noOpLog + } + } +}