Log Effect is available for Scala 2.12, 2.13 and 3. Helper constructors are provided for Cats Effect's Sync
F[_]
, for Fs2's Stream
and for ZIO's Task
.
Add
libraryDependencies += "io.laserdisc" %% "log-effect-fs2" % <latest-fs2-version>
for Fs2 or Cats Effect. Add instead
libraryDependencies += "io.laserdisc" %% "log-effect-zio" % <latest-zio-version>
for ZIO. If the intention is, instead, to create your own implementation of the typeclass, adding this
libraryDependencies += "io.laserdisc" %% "log-effect-core" % <latest-core-version>
will be enough. For the latest versions available please refer to the badges below the title.
Currently Log Effect supports the following backends
- Log4s
- Java Logging (Jul)
- Scribe
- Console
- No log sink
Cats | Fs2 | Cats Effect | Log Effect Core | |
---|---|---|---|---|
2.9.0 | 3.4.0 | 3.4.1 |
Zio | Log Effect Core | |
---|---|---|
2.0.4 |
Log4cats | Log Effect Core | |
---|---|---|
2.5.0 |
Log4s | Scribe | |
---|---|---|
1.10.0 | 3.10.5 |
To get an instance of LogWriter
for Cats Effect's Sync
the options below are available (see here)
full compiling example here
val log4s1: F[LogWriter[F]] =
F.delay(l4s.getLogger("test")) map log4sLog[F]
val log4s2: F[LogWriter[F]] = log4sLog("a logger")
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 jul2: F[LogWriter[F]] = julLog
val scribe1: F[LogWriter[F]] =
F.delay(scribe.Logger("a logger")) map scribeLog[F]
val scribe2: F[LogWriter[F]] = scribeLog("a logger")
val scribe3: F[LogWriter[F]] = {
case class LoggerClass()
scribeLog(classOf[LoggerClass])
}
val console1: LogWriter[F] = consoleLog
val console2: LogWriter[F] = consoleLogUpToLevel(LogLevels.Warn)
val noOp: LogWriter[F] = noOpLog[F]
Similarly, to get instances of LogWriter
for Fs2's Stream
the constructors below are available here
full compiling example here
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 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 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 scribe2: fs2.Stream[F, LogWriter[F]] = scribeLogStream("a logger")
val scribe3: fs2.Stream[F, LogWriter[F]] = {
case class LoggerClass()
scribeLogStream(classOf[LoggerClass])
}
val console1: fs2.Stream[F, LogWriter[F]] = consoleLogStream
val console2: fs2.Stream[F, LogWriter[F]] = consoleLogStreamUpToLevel(LogLevels.Warn)
val noOp: fs2.Stream[F, LogWriter[F]] = noOpLogStream
See here for an example with Laserdisc
To create instances for ZIO
some useful constructors can be found here. Note as they exploit the power and expressiveness of RLayer
an the RIO
pattern as shown below.
full compiling example here
// Case 1: from a possible config
val logNameLiveFromConfig: ULayer[ZLogName] =
aConfigLive >>> ZLayer(ZIO.service[AConfig].map(c => LogName(c.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
full compiling example here
// Case 1: from a possible config in a Layer (gives a Layer)
val log4sCase1: RLayer[AConfig, ZLogWriter] =
ZLayer(ZIO.serviceWithZIO { c =>
log4sFromName.provideEnvironment(ZEnvironment(c.logName))
})
val scribe4sCase1: RLayer[AConfig, ZLogWriter] =
ZLayer(ZIO.serviceWithZIO { c =>
scribeFromName.provideEnvironment(ZEnvironment(c.logName))
})
// Case 2: from a name
val log4sCase2: Task[Unit] =
log4sFromName.provideEnvironment(ZEnvironment(aLogName)).flatMap { logger =>
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logger))
}
val scribeCase2: Task[Unit] =
scribeFromName.provideEnvironment(ZEnvironment(aLogName)).flatMap { logger =>
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logger))
}
// Case 3: from a logger
val log4sCase3: Task[Unit] =
for {
logger <- ZIO.attempt(l4s.getLogger(aLogName))
logWriter <- log4sFromLogger.provideEnvironment(ZEnvironment(logger))
_ <- someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logWriter))
} yield ()
val julCase3: Task[Unit] =
for {
logger <- ZIO.attempt(jul.Logger.getLogger(aLogName))
logWriter <- julFromLogger.provideEnvironment(ZEnvironment(logger))
_ <- someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logWriter))
} yield ()
val scribeCase3: Task[Unit] =
for {
logger <- ZIO.attempt(scribe.Logger(aLogName))
logWriter <- scribeFromLogger.provideEnvironment(ZEnvironment(logger))
_ <- someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logWriter))
} yield ()
// Case 4: from a class
val log4sCase4: Task[Unit] = {
case class LoggerClass();
log4sFromClass.provideEnvironment(ZEnvironment(classOf[LoggerClass])).flatMap { logger =>
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logger))
}
}
val scribeCase4: Task[Unit] = {
case class LoggerClass();
scribeFromClass.provideEnvironment(ZEnvironment(classOf[LoggerClass])).flatMap { logger =>
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logger))
}
}
// Case 5 (Jul): from global logger object
val julCase5: Task[Unit] =
julGlobal.flatMap(logger =>
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(logger))
)
// Case 6: console logger
val console1: Task[Unit] =
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(consoleLog))
val console2: Task[Unit] =
someZioProgramUsingLogs.provideEnvironment(
ZEnvironment(consoleLogUpToLevel(LogLevels.Warn))
)
// Case 7: No-op logger
val noOp: Task[Unit] =
someZioProgramUsingLogs.provideEnvironment(ZEnvironment(noOpLog))
The following ways of submitting logs are supported:
- in a monadic sequence of effects
import cats.effect.Sync
import cats.syntax.flatMap._
import cats.syntax.functor._
import log.effect.LogWriter
def process[F[_]](implicit F: Sync[F], log: LogWriter[F]): F[(Int, Int)] =
for {
_ <- log.trace("We start")
a <- F.delay(10)
_ <- log.trace("Keep going")
b <- F.delay(20)
_ <- log.trace("We reached this point")
_ <- log.info(s"Process complete: ${(a, b)}")
} yield (a, b)
- in a streaming environment using
LogWriter
's syntax
import java.nio.channels.AsynchronousChannelGroup
import cats.