Skip to content

A simple macro-less logging typeclass with some common backends

License

Notifications You must be signed in to change notification settings

laserdisc-io/log-effect

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Log Effect

Continuous Integration Known Vulnerabilities Join the chat at https://gitter.im/laserdisc-io/laserdisc License: MIT Scala Steward badge

log-effect-fs2 Scala version support

log-effect-zio Scala version support

log-effect-core Scala version support

log-effect-interop Scala version support

Start

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.

Backends

Currently Log Effect supports the following backends

  • Log4s
  • Java Logging (Jul)
  • Scribe
  • Console
  • No log sink

Dependencies

Cats Fs2 Cats Effect Log Effect Core
Maven Central 2.9.0 3.4.0 3.4.1 Maven Central

Zio Log Effect Core
Maven Central 2.0.4 Maven Central

Log4cats Log Effect Core
Maven Central 2.5.0 Maven Central

Log4s Scribe
Maven Central 1.10.0 3.10.5

Examples

Get Logs

Cats Effect Sync

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]

Fs2 Stream

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

Zio Task

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.

Create LogWriter as a Layer

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
Create LogWriter as RIO

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))

Submit Logs

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.