Giter VIP home page Giter VIP logo

zio-logging's Introduction

zio-logging

ZIO Logging is simple logging for ZIO apps, with correlation, context, and pluggable backends out of the box with integrations for common logging backends.

Production Ready CI Badge Sonatype Releases Sonatype Snapshots javadoc zio-logging

Introduction

When we are writing our applications using ZIO effects, to log easy way we need a ZIO native solution for logging.

Key features of ZIO Logging:

  • ZIO Native — Other than it is a type-safe and purely functional solution, it leverages ZIO's features.
  • Multi-Platform - It supports both JVM and JS platforms.
  • Composable — Loggers are composable together via contraMap.
  • Pluggable Backends — Support multiple backends like ZIO Console, SLF4j, JPL.
  • Logger Context — It has a first citizen Logger Context implemented on top of FiberRef. The Logger Context maintains information like logger name, filters, correlation id, and so forth across different fibers. It supports Mapped Diagnostic Context (MDC) which manages contextual information across fibers in a concurrent environment.
  • Richly integrated into ZIO 2's built-in logging facilities
  • ZIO Console, SLF4j, and other backends

Installation

In order to use this library, we need to add the following line in our build.sbt file:

// ZIO Logging backends
libraryDependencies += "dev.zio" %% "zio-logging" % "2.2.2"

The main module contains the following features:

Other modules:

  • SLF4J backend - if you like to use SLF4J logging backends (e.g. java.util.logging, logback, log4j), add the one of following lines to your build.sbt file:

    // SLF4j v1 integration
    libraryDependencies += "dev.zio" %% "zio-logging-slf4j" % "2.2.2"
    
    // SLF4j v2 integration
    libraryDependencies += "dev.zio" %% "zio-logging-slf4j2" % "2.2.2"

    When to use this module: you are already using SLF4J logger in some other project, and you like to have same log outputs. See SLF4J v2 or v1 section for more details.

  • SLF4J bridge - with this logging bridge, it is possible to use zio-logging for SLF4J loggers (usually third-party non-ZIO libraries), add the one of following lines to your build.sbt file:

    // Using ZIO Logging for SLF4j v1 loggers, usually third-party non-ZIO libraries
    libraryDependencies += "dev.zio" %% "zio-logging-slf4j-bridge" % "2.2.2"
    
    // Using ZIO Logging for SLF4j v2 loggers, usually third-party non-ZIO libraries
    libraryDependencies += "dev.zio" %% "zio-logging-slf4j2-bridge" % "2.2.2"

    When to use this module: you want to use some zio-logger implementation, but also you are using some java library which using SLF4J interface for logging. See SLF4J bridge v2 or v1 section for more details.

  • Java Platform/System Logger backend - if you like to use Java Platform/System Logger logging backend, add the following line to your build.sbt file:

    // JPL integration
    libraryDependencies += "dev.zio" %% "zio-logging-jpl" % "2.2.2"

    When to use this module: you are already using Java Platform/System Logger in some other project, and you like to have same log outputs. See Java Platform/System Logger section for more details.

Example

Let's try an example of ZIO Logging which demonstrates a simple application of ZIO logging.

The recommended place for setting the logger is application boostrap. In this case, custom logger will be set for whole application runtime (also application failures will be logged with specified logger).

package zio.logging.example

import zio.logging.consoleLogger
import zio.{ ExitCode, Runtime, Scope, ZIO, ZIOAppArgs, ZIOAppDefault, ZLayer }

object SimpleApp extends ZIOAppDefault {

  override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
    Runtime.removeDefaultLoggers >>> consoleLogger()

  override def run: ZIO[Scope, Any, ExitCode] =
    for {
      _ <- ZIO.logInfo("Start")
      _ <- ZIO.fail("FAILURE")
      _ <- ZIO.logInfo("Done")
    } yield ExitCode.success

}

Expected console output:

timestamp=2023-03-15T08:36:24.421098+01:00 level=INFO thread=zio-fiber-4 message="Start"
timestamp=2023-03-15T08:36:24.440181+01:00 level=ERROR thread=zio-fiber-0 message="" cause=Exception in thread "zio-fiber-4" java.lang.String: FAILURE
	at zio.logging.example.SimpleApp.run(SimpleApp.scala:29)

You can find the source code of examples here

Documentation

Learn more on the zio-logging homepage!

Contributing

For the general guidelines, see ZIO contributor's guide.

Code of Conduct

See the Code of Conduct

Support

Come chat with us on Badge-Discord.

License

License

zio-logging's People

Contributors

987nabil avatar aashish2054 avatar adamgfraser avatar davidlar avatar erdeszt avatar ghostdogpr avatar github-actions[bot] avatar guersam avatar jdegoes avatar justcoon avatar khajavi avatar kornev avatar landlockedsurfer avatar mijicd avatar mschuwalow avatar peterhajdu avatar pierangeloc avatar pshemass avatar reibitto avatar renovate[bot] avatar rituraj2342 avatar saurabh-maheshwari avatar scala-steward avatar shsongs avatar sideeffffect avatar softinio avatar thijsbroersen avatar tusharmath avatar vigoo avatar wtrlmmrs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zio-logging's Issues

release version built against zio-1.0.13

I ran into problems cause this library is still depending on zio 1.0.12 and pulling in 1.0.12 versions of zio-macros that for some reason didn't get replaced with the 1.0.13 version by sbt. I ended up having two versions of zio-test and their zio-test-macros and got runtime errors in IntelliJ when trying to run my tests.

Please release a version that depends on 1.0.13 so others don't run into the same problem.
Thanks!

provide something similar to zio.logging.log.locally for zstreams

we need something like zio.logging.log.locally for zstreams.. at the moment I'm doing this like this and it took me quite a while to figure it out:

for {
              _ <- ZStream.unit

              enhancedLogging <- ZStream.fromEffect(
                zio.logging.log.derive(_.annotate(RTDLogging.ValidFrom, Some(v))
                  .annotate(RTDLogging.CalculationId, Some(calcId))))
              s <- stream.updateService[zio.logging.Logger[String]](_ => enhancedLogging)
            } yield s

LogFormat's value is missing in v2.0.0-RC2

image

we can simply fix it by modify label method in file LogFormat.scala.

def label(label: => String, value: LogFormat): LogFormat =
    LogFormat.make { (builder, trace, fiberId, logLevel, message, context, spans, location) =>
      builder.openKey()
      try builder.appendText(label)
      finally builder.closeKeyOpenValue()

      try value.unsafeFormat(builder)(trace, fiberId, logLevel, message, context, spans, location)
      finally builder.closeValue()
    }

image

Wrong logger used for uncaught failures

Hi, I tried get my application to only log through SLF4J with the latest RC versions of zio and zio-logging. It works fine so far except for one use case: When the application, which is created with ZIOAppDefault, crashes without catching any error, I would still expect the output to be logged through SLF4J. However, it uses ZIO's built-in logger instead although it is deactivated.

Not entirely sure if this behaviour is by design, but in a production environment I would really appreciate it if all logs use the same format without any extra boilerplate code like tapping or catching in the effect.

Here is some scala-cli code to reproduce the issue:

//> using scala "2.13"
//> using lib "dev.zio::zio:2.0.0-RC5"
//> using lib "dev.zio::zio-logging:2.0.0-RC8"
//> using lib "dev.zio::zio-logging-slf4j:2.0.0-RC8"
//> using lib "ch.qos.logback:logback-classic:1.2.11"

import zio._
import zio.logging.backend._

object App extends ZIOAppDefault {
  def run = ZIO.withRuntimeConfig(defaultLoggerDisabled @@ slf4jLogging)(program)

  lazy val defaultLoggerDisabled = RuntimeConfig.default.copy(logger = ZLogger.none)
  lazy val slf4jLogging = SLF4J.slf4j(LogLevel.Info)

  lazy val program = ZIO.logInfo("Hey there!") *> ZIO.attempt(throw new RuntimeException("Oh no!"))
}

App.main(Array()) // ignore this line, it is just here to work around a scala-cli bug which prevents ZIO apps from being run

The output produced:

16:54:25.716 [ZScheduler-12] INFO zio-slf4j-logger - timestamp=2022-04-20T16:54:25.661218+02:00 level=INFO thread=zio-fiber-2 message="Hey there!"
timestamp=2022-04-20T14:54:25.742614Z level=ERROR thread=#zio-fiber-0 message="" cause="Exception in thread "zio-fiber-2" java.lang.RuntimeException: java.lang.RuntimeException: Oh no!
	at <empty>.Main.App.program(Main.scala:21)
	at <empty>.Main.App.run(Main.scala:16)"

The first log entry shows that SLF4J logging works, the second shows logging through the built-in logger.

FR: slf4j bridge improvement

Slf4j docs show a pattern of using {} in log format message (see http://www.slf4j.org/manual.html#typical_usage)
However, the bridge implementation uses String.format everywhere (see eg. https://github.com/zio/zio-logging/blob/master/slf4j-bridge/src/main/scala-2/org/slf4j/impl/ZioLogger.scala#L20), which doesn't substitute the arguments' .toString result in place of that when logging. The result for many cases is logs filled with stuff like org.eclipse.jetty.util.component.AbstractLifeCycle] STARTED @{}ms {}, which is less than helpful :)

Please support positional arguments? Perhaps just replacing {} by %s in the format string would be sufficient?

ZIOSpecDefault integration

The following code works with ZIOAppDefault but the same idiom does not work with ZIOSpecDefault. Apologies if this is incorrect but I haven't been able to figure out any other way to use the current version of zio-logging.

import zio.logging.LogFormat
import zio.logging.backend.SLF4J
import zio.{ LogLevel, RuntimeConfigAspect, Scope, ZIO, ZIOAppArgs, ZIOAppDefault }

object HelloWorld extends ZIOAppDefault {
  val slf4j =
    SLF4J.slf4j(LogLevel.Info, LogFormat.default, _ => "zio-slf4j-logger")
  override def hook: RuntimeConfigAspect = slf4j

  val logic = for {
    _ <- ZIO.logInfo("Hello world!")
  } yield ()

  override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = logic.exit
}

with the following logback configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
  </appender>


  <appender name="zio-slf4j-logger" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
  </appender>

  <root level="trace" class="ch.qos.logback.core.ConsoleAppender">
    <appender-ref  ref="zio-slf4j-logger" />
  </root>

</configuration>

.. and the following dependencies:

"dev.zio" %% "zio" % "2.0.0-RC5"
 "dev.zio" %% "zio-logging" % "2.0.0-RC8"
 "dev.zio" %% "zio-logging-slf4j" % "2.0.0-RC8"
 "ch.qos.logback" % "logback-classic" % "1.2.11",
 "net.logstash.logback" % "logstash-logback-encoder" % "6.6"

outputs:

timestamp=2022-04-17T18:45:55.991567Z level=INFO thread=#zio-fiber-2 message="Hello world!" location=com.example.HelloWorld.logic file=HelloWorld.scala line=12
19:45:56.104 [ZScheduler-7] INFO zio-slf4j-logger - timestamp=2022-04-17T19:45:56.032862+01:00 level=INFO thread=zio-fiber-2 message="Hello world!"

The following does not log anything except :
+ Just log something

import zio.logging.LogFormat
import zio.logging.backend.SLF4J
import zio.test.{ TestEnvironment, ZIOSpecDefault, ZSpec, assertCompletes }
import zio.{ LogLevel, RuntimeConfigAspect, Scope, ZIO }

object HelloSpec extends ZIOSpecDefault {

  val slf4j =
    SLF4J.slf4j(LogLevel.Info, LogFormat.default, _ => "zio-slf4j-logger")
  override def hook: RuntimeConfigAspect = slf4j

  override def spec: ZSpec[TestEnvironment with Scope, Any] =
    test("Just log something") {
      for {
        _ <- ZIO.logInfo("Hello SpecWorld")
      } yield assertCompletes

    }
}

SBT Exit throws an sl4j error

I am using the Console logger for my use case and yet I keep getting the following error

2020-06-14 13:05:41,430 shutdown-hooks-run-all ERROR No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: Cannot find preset's package (github>whitesource/merge-confidence:beta)

Api discuss

def locally[R <: Logging, E, A1](fn: LogContext => LogContext)(zio: ZIO[R, E, A1]): ZIO[Logging with R, E, A1] =
Logging.locally(fn)(zio)
def locallyM[R <: Logging, E, A1](
fn: LogContext => URIO[R, LogContext]
)(zio: ZIO[R, E, A1]): ZIO[Logging with R, E, A1] =
Logging.locallyM(fn)(zio)

Here R is already <: Logging, is it need to declare return type to ZIO[Logging with R, E, A1] ? or ZIO[R, E, A1] is OK ?
Now I'm using nat programming to rebuild a new zio api and rebuild the api only log.scala in zio-logging. I find that just use XIO[R, E, A1] is no problem in my code. Just create this issue.

Here's my code

https://github.com/djx314/xio/blob/052c663846a7380610e1027625a6b2975d4c62b4/a05-XIO/xio-logging/src/main/scala/xio/logging/Log.scala#L57-L62

Create apply method on LogAnnotation for nicer syntax

Create apply method on LogAnnotation:

final case class LogAnnotation[A: ClassTag](name: String, neutral: A, combine: (A, A) => A, render: A => String) { self =>
  def apply[A](value: A): LogContext  => LogContext = _.annotate(self, combine(neutral, value))
}

Then it can be used like so, logger.locally(Level(LogLevel.Info)) { ... }.

Recommended usage in combination with ZLayer

I'm currently writing my first application based on ZLayer based on Pavels Sisojevs blog post "From idea to product with ZLayer". In his blog post he makes use of a self rolled logging service. Instead of rolling it myself, I used this project. So far so good. I create my own implementation classes with the following example dependencies:

class Live(httpClient: HttpClient.Service, logging: Logging.Service) extends GithubService.Service {
  ...
}

However, I find the syntax used for basic debug logging very convoluted. It looks like this:

def getRepos: Task[List[Repo]] = for {
  ...
  _ <- logging.logger.log(LogLevel.Debug)(body)
  ...
} yield repos

Something so simple requires me to look at the service definition again and again because I expect something simple as logging.debug(body). Currently, there aren't any log level specific definitions on both the Logging.Service and the LoggerLike traits. They are however defined on the log package object. Using this would get my something like the following:

def getRepos: Task[List[Repo]] = (for {
  ...
  _ <- log.debug(body)
  ...
} yield repos).provideLayer(ZLayer.succeed(logging))

This also doesn't look like something that was intended. Am I completely missing something? Without ZLayer this would have looked like:

def getRepos: RIO[Logging with HttpClient, List[Repo]] = for {
  ...
  _ <- log.debug(body)
  ...
} yield repos

Should there be methods like .debug living in the Logging.Service definition or the LoggerLike definition?

Check & create parent directory before calling FileOutputStream

In addition to #372 please check that when opening the FileOutputStream the parent directory of the file exists (and create it if it doesn't). Have a look in commons-io at FileUtils.openOutputStream (here) or PathUtils.newOutputStream (here).

Both of those functions:

  1. Check that the parent directory of the file exists, if it does not then create it.
  2. Check that the file exists
  3. Check that the file is writable (not possible with 'path' API)

add zio.logging.Logging.any

add this similar to what Clock, Console etc. have so a user can defer construction of the logging Layer

    val any: ZLayer[Logging, Nothing, Logging] =  ZLayer.requires[Logging]

Could you please explain me a little bit of your design?

Hey, I just quickly reviewed your code and a little bit be shocked! I didn't find and monad in your core code. I saw a class called writer, but when I clicked in, I saw java io writer? It seems nobody knows functional programming log is made by writer monad? John provided state monad already and you can easily turn it as a good writer for logging. Maybe I am wrong but seems this is zio official site and why I cannot find a FP logger?

Slf4jLogger name broken in 0.5.0

Looks like somehow the changes in encoding broke the magic to set the logger name to the class name.
Now the logger name is always zio.logging.LogAppender$$anon$2
At a first glance I cannot tell why that changed.

[feature request] ConsoleLogWriter should write to the standard error output

Currently, SimpleConsoleLogWriter and ColoredLogWriter output log messages the the standard output of the console. It would be more in-line with other logging frameworks, and more helpful IMHO to direct them to the dedicated error console also known as StdErr.

AFAICT this can be achieved by simply changing the 2 calls to zio.console.putStrLn in LogWriter.scala to zio.console.putStrLnErr. (zio/zio#3925)

Fiber failure when used with Play 2.8

I'm wanting to upgrade some Play apps to use the new ZLayer and have additionally upgraded zio-logging to 0.2.3. Sbt libraryDependencies are minimal - I'm letting zio-logging pull the zip version it wants to.

I've stripped back to the most basic case here by firing up a play-scala-seed app with the following controller code:

class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {

  val layer = (Clock.live ++ Console.live) >>> Logging.console((_, msg) => msg)

  def index() = Action.async { implicit request: Request[AnyContent] =>
    val io = log(LogLevel.Info)("Does this work?") *> UIO(Ok(views.html.index()))
    Runtime.default.unsafeRunToFuture(io.provideLayer(layer))
  }
}

If I hit localhost:9000/ a few times it will fairly regularly (but not every request) dump the following trace:

2020-03-09T20:13:07.186Z INFO  Does this work?
2020-03-09 20:13:07 ERROR p.api.http.DefaultHttpErrorHandler

! @7f4nk6442 - Internal server error, for (GET) [/] ->

play.api.UnexpectedException: Unexpected exception[InterruptedException: Interrupted by fibers: #120]
	at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:328)
	at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:251)
	at play.core.server.AkkaHttpServer$$anonfun$2.applyOrElse(AkkaHttpServer.scala:421)
	at play.core.server.AkkaHttpServer$$anonfun$2.applyOrElse(AkkaHttpServer.scala:417)
	at scala.concurrent.impl.Promise$Transformation.run(Promise.scala:453)
	at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
	at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:92)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:94)
	at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:92)
	at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:47)
	at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:47)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.InterruptedException: Interrupted by fibers: #120
	at zio.Cause.$anonfun$squashWith$1(Cause.scala:348)
	at scala.Option.orElse(Option.scala:477)
	at zio.Cause.squashWith(Cause.scala:345)
	at zio.Cause.squashWith$(Cause.scala:343)
	at zio.Cause$Internal$Interrupt.squashWith(Cause.scala:648)
	at zio.Cause.squashWithTrace(Cause.scala:370)
	at zio.Cause.squashWithTrace$(Cause.scala:369)
	at zio.Cause$Internal$Interrupt.squashWithTrace(Cause.scala:648)
	at zio.Fiber.$anonfun$toFutureWith$2(Fiber.scala:297)
	at zio.internal.FiberContext.evaluateNow(FiberContext.scala:383)
	at zio.internal.FiberContext.$anonfun$evaluateLater$1(FiberContext.scala:679)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: zio.Cause$FiberTrace: Fiber failed.
An interrupt was produced by #120.
No ZIO Trace available.

Base on log4zio?

At @jdegoes 's request, I have opened this as its own issue.

I have a logging library https://github.com/leigh-perry/log4zio that might be a useful starting point for zio-logging. Its main selling point is contravariant-functor-style composition of loggers. It is ZIO-based without dependencies on Cats or Scalaz.

Modules

Currently there are two modules:

  1. core contains the main abstraction, dependent only on ZIO.

  2. slf4j provides for logging over conventional JVM-style frameworks.

Design

There are two design points.

Error-free

The interface assumes that the user doesn't want to experience logging failures. Logging is most important under failure conditions, so it is best to log via a fallback mechanism rather than fail altogether.

To convey that the interface will not emit errors, all operations are in UIO.

Update: I have exposed error-ful versions too - the error-free versions build on these, adding fallback handling to avoid emitting errors.

Contravariant composition

The library lets the user create a new logging capability by composing refinements on top of a base logging implementation.

For example, if you have a simple-text console logger, Contravariant composition allows you to create a logger that includes a timestamp on every message. Or, it means that you can create a logger that encodes as JSON, writing the JSON data out to a string logger. Alternatively, you can create a logger that accepts only a specialised SafeString data type for its log messages. Implementing this is easy since the SafeString can be converted to String for the log-writing phase. Or perhaps all your log messages take the form of an integer code, and you want to be able to merely call log.info(101) to write whatever that means to your log.

final case class LogMedium[+E, A](log: A => IO[E, Unit]) {
  def contramap[B](f: B => A): LogMedium[E, B] =
    LogMedium[E, B](b => log(f(b)))

  def contramapM[E1 >: E, B](f: B => IO[E1, A]): LogMedium[E1, B] =
    LogMedium[E1, B](b => f(b).flatMap(log))

  def orElse[E2](that: => LogMedium[E2, A]): LogMedium[E2, A] =
    LogMedium[E2, A](a => log(a).catchAll(_ => that.log(a)))
}

Components

LogMedium

LogMedium conveys the basic abstraction of logging: a function from some A to Unit, for example taking a String and (effectfully) writing it somewhere.

LogMedium[A] is a contravariant functor, so it can be reused to implement another LogMedium[B] via contramap, so long as B can be converted to an A.

TaggedLogMedium

TaggedLogMedium is a derived LogMedium that encapsulates the conventional logging pattern, with a logging level (such as INFO) and timestamp. Addition of the level and timestamp info is also achieved by contramap-ing the additional level and timestamp tags onto a raw logger.

The implementation is here.

Log

The second main component is Log. It is an implementation of the ZIO service pattern, and encapsulates a conventional logging API over the active configured LogMedium:

trait Log[E, A] {
  def log: Log.Service[E, A]
}

object Log {
  type SafeLog[A] = Log[Nothing, A]

  def log[E, A]: ZIO[Log[E, A], Nothing, Log.Service[E, A]] =
    ZIO.access[Log[E, A]](_.log)

  /**
   * An implementation of conventional logging with timestamp and logging level
   */
  trait Service[E, A] {
    def error(s: => A): IO[E, Unit]
    def warn(s: => A): IO[E, Unit]
    def info(s: => A): IO[E, Unit]
    def debug(s: => A): IO[E, Unit]
  }
  :

NOTE: logging is in A, allowing SafeString or even Int logging. In a fatuous examples of Int Logging:

object AppMain extends zio.App {

  def intLogger: UIO[Log[Int]] =
    Log.make[Int](intRendered(RawLogMedium.console))

  def intRendered(base: LogMedium[String]): LogMedium[Tagged[Int]] =
    base.contramap {
      m: Tagged[Int] =>
        val n: Int = m.message()
        "%-5s - %d:%s".format(m.level.name, n, "x" * n)
    }

  final case class AppEnv(log: Log.Service[Int]) extends Log[Int]

  override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, Int] =
    for {
      logsvc <- intLogger
      log = logsvc.log

      pgm = Application.execute.provide(AppEnv(log))

      exitCode <- pgm.foldM(
        e => log.error(11) *> ZIO.succeed(1),
        _ => log.info(10) *> ZIO.succeed(0)
      )
    } yield exitCode
}

// The core application
object Application {
  val doSomething: ZIO[Log[Int], Nothing, Unit] =
    for {
      log <- Log.log[Int]
      _ <- log.info(1)
      _ <- log.info(2)
    } yield ()

  val execute: ZIO[Log[Int], Nothing, Unit] =
    for {
      log <- Log.log[Int]
      _ <- log.info(3)
      _ <- doSomething
      _ <- log.info(4)
    } yield ()
}

yields the output

INFO  - 3:xxx
INFO  - 1:x
INFO  - 2:xx
INFO  - 4:xxxx
INFO  - 10:xxxxxxxxxx

In this example, the Int version of a LogMedium is created by taking a raw LogMedium over ZIO Console and contramapping it into a LogMedium[Tagged[Int]]:

  def intRendered(base: LogMedium[String]): LogMedium[Tagged[Int]] =
    base.contramap {
      m: Tagged[Int] =>
        val n: Int = m.message()
        "%-5s - %d:%s".format(m.level.name, n, "x" * n)
    }

This silly rendering prints the integer value plus an 'x' that number of times, but it is meant to illustrate the idea that the Int value is available to the LogMedium logic and that Contravariant implementation of that logger can render it anyway it wants. Looking up an error code dictionary would be a more realistic way to go!

Opinions welcome :-)

Provide version for zio-2.0.0-M6-2

I'm trying to migrate a small service to zio 2 M6-2 to figure out what needs to be done..
Looks like the new baked-in logging implementation is not yet capable of handling the equivalent of locally... so at least for my projects, I'll probably have to rely on a version of zio-logging for zio-2.
would be great if we could have a first version for those of us that used zio-logging in 1.x and can't simply migrate to the new logging version cause of missing features.

Provide empty logger as instance for .updateService

today I needed to deactivate logging for a certain effect and noticed that we only have the Logging.ignore layer but not a simple EmptyLogger instance I can use with .updateService

import zio.{UIO, ZIO}
import zio.logging.{LogContext, Logger}

object EmptyLogger extends Logger[String] {
  override def locally[R1, E, A1](f: LogContext => LogContext)(in: ZIO[R1, E, A1]): ZIO[R1, E, A1] = in
  override def log(line: => String): UIO[Unit]                                                     = ZIO.unit
  override def logContext: UIO[LogContext]                                                         = ZIO.succeed(LogContext.empty)
}

I use it in my streams like this:

cockpitZR <-
                ZStream
                  .fromEffect(
                    (for {
                      result <- effectWithDisabledLogging(....)
                        .updateService[zio.logging.Logger[String]](_ => EmptyLogger)
                      _ <- someOtherEffectWithNormalLogging(...)
                    } yield result)
                      .tapCause(e => zio.logging.log.error("failed to calculate ...", e))
                  )

Maybe we can include this in a straightforward way into the library?

Unsafe Logging (without ZIO dependency)

I am thinking of adding some logging to ZIO Http's backend. The purpose is to help debug issues in the library as a contributor and also as a consumer of the library I would sometime want to know what's going on behind the scenes.

A lot of the backend doesn't run on ZIO or FP, so it's a bit hard to use ZIO Logging for such type of code. eg:

trait ChannelHandler {
  override def channelRead(ctx: ChannelHandlerContext, msg: Any): Unit = {
    Logger.debug("Message refCount: ${msg.refCount()}") // returns a ZIO, doesn't log anything

   // .. .. 

   Logger.debug("Message refCount: ${msg.refCount()}") // returns a ZIO, doesn't log anything
  }
}

Proposal
Support and UnsafeLogger that logs without depending on ZIO.

New design for ZIO Logging

package zio

import scala.reflect.ClassTag

package object logging {
}

package logging {
  /**
   * A log level defines the level at which an element is logged.
   */
  sealed trait LogLevel { self => 
    def render: String
    def level: Int

    def > (that: LogLevel) = self.level > that.level 
    def >= (that: LogLevel) = self.level >= that.level 
    def < (that: LogLevel) = self.level < that.level 
    def <= (that: LogLevel) = self.level <= that.level 

    def max(that: LogLevel): LogLevel = if (self < that) that else self

    def min(that: LogLevel): LogLevel = if (self > that) that else self
  }
  object LogLevel {
    case object All   extends LogLevel { def level = 7; def render = "ALL" }
    case object Fatal extends LogLevel { def level = 6; def render = "FATAL" }
    case object Error extends LogLevel { def level = 5; def render = "ERROR" }
    case object Warn  extends LogLevel { def level = 4; def render = "WARN" }
    case object Info  extends LogLevel { def level = 3; def render = "INFO" }
    case object Debug extends LogLevel { def level = 2; def render = "DEBUG" }
    case object Trace extends LogLevel { def level = 1; def render = "TRACE" }
    case object Off   extends LogLevel { def level = 0; def render = "OFF" }
  }

  trait LoggerLike[-R] { self =>
    /**
     * Produces a new logger by adapting a different input type to the input 
     * type of this logger.
     */
    final def contramap[R1](f: R1 => R): LoggerLike[R1] = 
      new LoggerLike[R1] {
        def locallyAnnotate[R, E, A](f: LogContext => LogContext)(zio: ZIO[R, E, A]): ZIO[R, E, A] =
          self.locallyAnnotate(f)(zio)

        def log(line: => R1): UIO[Unit] = self.log(f(line))

        def logContext: UIO[LogContext] = self.logContext
      }

    /**
     * Derives a new logger from this one, by applying the specified decorator
     * to the logger context.
     */
    def derive(f: LogContext => LogContext): LoggerLike[R] = 
      new LoggerLike[R] { 
        def locallyAnnotate[R, E, A](f: LogContext => LogContext)(zio: ZIO[R, E, A]): ZIO[R, E, A] =
          self.locallyAnnotate(f)(zio)

        def log(line: => R): UIO[Unit] = locallyAnnotate(f)(self.log(line))

        def logContext: UIO[LogContext] = self.logContext
      }

    /**
     * Produces a logger that logs at the specified level.
     */
    def level(level: LogLevel): LoggerLike[R] = 
      derive(_.annotate(LogAnnotation.Level, level))

    /**
     * Modifies the log context in the scope of the specified effect.
     */
    def locallyAnnotate[R, E, A](f: LogContext => LogContext)(zio: ZIO[R, E, A]): ZIO[R, E, A]

    /**
     * Logs the specified element using an inherited log level.
     */
    def log(line: => R): UIO[Unit]

    /**
     * Retrieves the log context.
     */
    def logContext: UIO[LogContext]

    /**
     * Logs the specified element at the specified level. Implementations may 
     * override this for greater efficiency.
     */
    def log(level: LogLevel)(line: => R): UIO[Unit] = self.level(level).log(line)

    /**
     * Produces a named logger.
     */
    def name(name: String): LoggerLike[R] = {
      val named = name :: Nil 

      derive(_.annotate(LogAnnotation.Name, named))
    }
  }

  /**
   * A logger of strings.
   */
  trait Logger extends LoggerLike[String]
  object Logger {
    /**
     * Constructs a logger provided the specified sink.
     */
    def make(logger: (LogContext, => String) => UIO[Unit]): UIO[Logger] = 
      FiberRef.make(LogContext.empty).map { ref =>
        new Logger {
          def locallyAnnotate[R, E, A](f: LogContext => LogContext)(zio: ZIO[R, E, A]): ZIO[R, E, A] =
            ref.get.flatMap(context => ref.locally(f(context))(zio))

          def log(line: => String): UIO[Unit] = ref.get.flatMap(context => logger(context, line))

          def logContext: UIO[LogContext] = ref.get
        }
      }
  }

  /**
   * A `LogAnnotation` describes a particular type of annotation applied to log 
   * lines.
   */
  final case class LogAnnotation[A: ClassTag](name: String, neutral: A, combine: (A, A) => A, render: A => String) { self =>
    def id: (String, ClassTag[A]) = (name, classTag)

    /**
     * The class tag of the annotation type, used for disambiguation purposes only.
     */
    def classTag: ClassTag[A] = implicitly[ClassTag[A]]

    override def hashCode: Int = id.hashCode 

    override def equals(that: Any): Boolean = that match {
      case that : LogAnnotation[_] => self.id == that.id
      case _ => false
    }
  }
  object LogAnnotation {
    /**
     * The `Level` annotation keeps track of log levels.
     */
    val Level = LogAnnotation[LogLevel]("level", LogLevel.Off, (_, r) => r, _.render)

    /**
     * The `Name` annotation keeps track of logger names.
     */
    val Name = LogAnnotation[List[String]]("name", Nil, _ ++ _, _.mkString("."))
  }

  /**
   * A `LogContext` stores context associated with logging operations.
   */
  final case class LogContext private (private val map: Map[LogAnnotation[_], Any]) { self =>
    /**
     * Merges this context with the specified context.
     */
    def ++ (that: LogContext): LogContext = self merge that

    /**
     * Annotates the context with the specified annotation and value, returning
     * the new context.
     */
    def annotate[A](annotation: LogAnnotation[A], newA: A): LogContext = {
      val oldA = get(annotation)

      new LogContext(map + (annotation -> annotation.combine(oldA, newA)))
    }

    /**
     * Retrieves the specified annotation from the context.
     */
    def get[A](annotation: LogAnnotation[A]): A =
      map.get(annotation).fold(annotation.neutral)(_.asInstanceOf[A])

    /**
     * Merges this context with the specified context.
     */
    def merge(that: LogContext): LogContext = {
      val allKeys = self.map.keySet ++ that.map.keySet

      new LogContext(allKeys.foldLeft(Map.empty[LogAnnotation[_], Any]) {
        case (map, annotation) => 
          map + (annotation -> annotation.combine(self.get(annotation), that.get(annotation)))
      })
    }
  }
  object LogContext {
    /**
     * An empty context.
     */
    val empty: LogContext = new LogContext(Map())
  }
}

Release compiled for Scala 3

Currently having an issue with using zio-logging Scala 2 version and the zio Scala 3 version together in a Scala 3 project. The error I receive is:

[error] An existential type that came from a Scala-2 classfile cannot be
[error] mapped accurately to to a Scala-3 equivalent.
[error] original type    : (S[X], X => cats.free.Free[S, A]) forSome X
[error] reduces to       : (S[X], X => cats.free.Free[S, A])
[error] type used instead: (S[Any], Any => cats.free.Free[S, A])
[error] This choice can cause follow-on type errors or hide type errors.
[error] Proceed at own risk.
[error] one error found
[error] one error found
[error] (Compile / compileIncremental) Compilation failed

Unfortunately I cannot use zio Scala 2 compiled version for another issue with Tag in that version of zio. Do you plan to release a Scala 3 version of zio-logging anytime soon?

zio

4.0.0 dev.zio zio-logging-slf4j-bridge_2.13 jar zio-logging-slf4j-bridge https://zio.github.io/zio-logging/ 0.5.8 Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 repo zio-logging-slf4j-bridge dev.zio https://zio.github.io/zio-logging/ https://github.com/zio/zio-logging/ scm:git:[email protected]:zio/zio-logging.git jdegoes John De Goes http://degoes.net [email protected] pshemass Przemyslaw Wierzbicki https://github.com/pshemass [email protected] org.scala-lang scala-library 2.13.4 dev.zio zio-logging_2.13 0.5.8 com.github.ghik silencer-lib_2.13.4 1.7.3 provided org.slf4j slf4j-api 1.7.30 dev.zio zio-test_2.13 1.0.5 test dev.zio zio-test-sbt_2.13 1.0.5 test

Idea: sampling of logs

Hi!

Something I often struggle with is getting the log levels in production right. Either I get way too much noise, or run the risk of not having enough context to efficiently debug production errors.

One approach to improve this that I've used in the past is to do log sampling on a per-request-basis. It more or less works by buffering all log calls during the duration of the request, but only actually emits ~1% of requests' logs if nothing goes wrong. But, if a log line with level warn >= is logged, emit everything that's been buffered. This gives you the ability to use liberal debug logging in production while still keeping your log volumes at manageable levels.

I realize that this may very well be outside of the scope for this project, but wanted to open an issue for discussion!

Adding logging environmental effect

Here's a quick sketch of a logging API.

trait Logging {
  def logging: Logging.Service[Any]
}
object Logging {
  trait Service[-R] {
    // TODO: Define methods here
    def log(line: => String): ZIO[R, Nothing, Unit]
    def info(line: => String): ZIO[R, Nothing, Unit]
    def warning(line: => String): ZIO[R, Nothing, Unit]
    def error(line: => String): ZIO[R, Nothing, Unit]
    def errorCause(cause: Cause[Any]): ZIO[R, Nothing, Unit]
  }
}

package object logging extends Logging.Service[Logging] {
  ...
}

I don't think this is quite sufficient. We probably need to think about FiberRef integration and making sure we have a small set of operations that can be implemented by many different logging libraries (e.g. LogStage, all the ZIO loggers, slf4j, etc.).

Support for structured logging

Hi!

I've been trying to (in a very hacky manner) implement a PoC logging service on top of zio-logging with support for structured logging and run into some issues/questions along the way.

Ideally I wanted to grab all the pieces of metadata off the logging context and expose it when the logline is constructed. That's currently not doable since the map holding all the data isn't part of the public API.

However, given the current design of LogContext and friends I think this would be quite easy to add support for structured logging with some small design tweaks!

Another discussion to be had is how we'd actually expose the metadata in each logging backend, perhaps via [logstash-logback]'s(https://github.com/logstash/logstash-logback-encoder#event-specific-custom-fields) support or something else.

Construct logging layer within effectful computation.

Since ZLayer is not a monad, I have real troubles with creating a logging layer.

All the methods which allow to create a logging layer are similar to:

def console(
    logLevel: LogLevel = LogLevel.Info,
    format: LogFormat[String] = LogFormat.ColoredLogFormat((_, s) => s)
  ): ZLayer[Console with Clock, Nothing, Logging]

Now, consider I would like to read logLevel from the config file or environment variable. It would be an effectful computation (or a layer), but since I can't lift ZLayer, there is no flatMap or something, I can't construct ZLayer[Logging] from Task[LogLevel] or ZLayer[Config]. Or do I miss something?

`context.annotate` gets evaluated for every logging call

It appears that every call on the logger reevaluates the context and thus triggers unwanted side-effects.

I used this code to annotate elements of my stream

import zio._
import zio.logging.LogAnnotation
import zio.logging.slf4j.Slf4jLogger
import zio.stream.ZStream

import java.util.UUID
object LoggingSample {
  final val env = Slf4jLogger.makeWithAllAnnotationsAsMdc()

  final val UserId = LogAnnotation[Option[String]](
    name = "user-id",
    initialValue = None,
    combine = (_, r) => r,
    render = _.map(_.toString).getOrElse("undefined-user-id")
  )

  final val CalculationId = LogAnnotation[Option[UUID]](
    name = "calculation-id",
    initialValue = None,
    combine = (_, r) => r,
    render = _.map(_.toString).getOrElse("undefined-calculation-id")
  )

  final val CalculationNumber = LogAnnotation[Int](
    name = "calculation-number",
    initialValue = 0,
    combine = (_, r) => r,
    render = _.toString
  )

}

object SampleApp extends zio.App {

  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = {

    val initialStream = ZStream.fromIterable(Seq(1, 2, 3, 4, 5))

    val s = for {
      calcNumber <- initialStream

      subStream = (for {
        _ <- ZStream.fromEffect(zio.logging.log.debug(s"would log first line for calculation# ${calcNumber}"))
        _ <- ZStream.fromEffect(zio.logging.log.debug(s"would log second line for calculation# ${calcNumber}"))
      } yield ())

      updatedLoggingDep <- ZStream.fromEffect(
        zio.logging.log.derive(_.annotate(LoggingSample.CalculationId, Some(UUID.randomUUID()))
          .annotate(LoggingSample.CalculationNumber, calcNumber)))

      x <- subStream.updateService[zio.logging.Logger[String]](_ => updatedLoggingDep)

    } yield calcNumber

    val e = for {
      _ <- ZIO.unit
      _ <- s.runDrain
    } yield ()

    e.provideSomeLayer(LoggingSample.env).exitCode
  }
}

When running this code, one sees that for every zio.logging.log.debug call, a new CalculationId (UUID) is generated, although it should stay fixed.

When I run this on my machine, it outputs the following:

[info] {"@timestamp":"2021-05-16T19:31:47.537+02:00","@version":"1","message":"would log first line for calculation# 1","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-5","level":"DEBUG","level_value":10000,"calculation-number":"1","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"e6844b43-8ab0-482b-8fad-f3a72c4f193b","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.540+02:00","@version":"1","message":"would log second line for calculation# 1","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-5","level":"DEBUG","level_value":10000,"calculation-number":"1","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"c68a9310-92da-49cb-8713-636ec9da9daf","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.630+02:00","@version":"1","message":"would log first line for calculation# 2","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-5","level":"DEBUG","level_value":10000,"calculation-number":"2","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"9fc90b98-8c21-47ea-9ddc-9845c749901e","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.631+02:00","@version":"1","message":"would log second line for calculation# 2","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-5","level":"DEBUG","level_value":10000,"calculation-number":"2","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"aa67949a-712b-43ea-b05e-78dc2840e894","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.633+02:00","@version":"1","message":"would log first line for calculation# 3","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-5","level":"DEBUG","level_value":10000,"calculation-number":"3","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"228c088b-a15a-43bc-8f4a-06e82188c1e2","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.634+02:00","@version":"1","message":"would log second line for calculation# 3","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-5","level":"DEBUG","level_value":10000,"calculation-number":"3","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"4f0c7d78-8114-4999-a8ae-9d632667371d","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.637+02:00","@version":"1","message":"would log first line for calculation# 4","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-6","level":"DEBUG","level_value":10000,"calculation-number":"4","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"720b1180-055b-483f-967d-23a8914af43c","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.638+02:00","@version":"1","message":"would log second line for calculation# 4","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-6","level":"DEBUG","level_value":10000,"calculation-number":"4","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"cf6d67a2-029c-442d-9338-548088f9ba54","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.639+02:00","@version":"1","message":"would log first line for calculation# 5","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-6","level":"DEBUG","level_value":10000,"calculation-number":"5","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"df0857cc-c679-41f2-9b32-665aeb3e7317","level":"debug"}
[info] {"@timestamp":"2021-05-16T19:31:47.640+02:00","@version":"1","message":"would log second line for calculation# 5","logger_name":"com.dominikdorn.bugs.SampleApp$","thread_name":"zio-default-async-6","level":"DEBUG","level_value":10000,"calculation-number":"5","name":"com.dominikdorn.bugs.SampleApp$","calculation-id":"843021cd-e245-43e0-bf5b-231b53173cb6","level":"debug"}

Expected behavior:
I would expect the "calculation-id" to stay constant for every logging call with the same calculation-number

Actual behavior:
The calculation-id is reevaluated for every call to a logging method. This is (imho) unwanted behavior. Also it (imho) has a negative effect on performance.

I can provide a sbt-project if necessary.

Missing error in logger.error(String, Cause[Any])

.tapCause(logger.error("Error in logging slack message", _))

Logs just "Error in logging slack message"
But this logs also the error

.tapError(logger.throwable("Error in logging slack message", _))

Readd message to logError

Currently, logError only supports providing a Cause. In the previous version both a Cause and String could be provided, which I believe is far nicer for including additional information.

What was the motivation for removing it?

zio-logging not working with ZIO 2.x

Getting this error while using zio-logging with zio 2.0.0-M3, any plans on supporting it soon?

Caused by: java.lang.ClassNotFoundException: zio.ZIO$AccessMPartiallyApplied$
        at zio.logging.Logging$.<clinit>(Logging.scala:43)
        at zio.logging.slf4j.Slf4jLogger$.make(Slf4jLogger.scala:40)
        at io.km8.Server$.run$$anonfun$1(Server.scala:16)
        at zio.ZIO.provideLayer$$anonfun$1(ZIO.scala:1389)
        at zio.ZIO$.suspendSucceed$$anonfun$1(ZIO.scala:4936)
        at zio.internal.FiberContext.runUntil(FiberContext.scala:496)
        at zio.internal.FiberContext.run(FiberContext.scala:314)
        at zio.internal.ZScheduler$$anon$2.run(ZScheduler.scala:149)

Add Microsite

we should follow the same pattern as in other project to provide documentation via microsite. This documentation should consist of description of library purpose, design choices and code examples

Provide a ZManaged variant of locally

It would be nice to be able to apply log annotations when constructing a managed resource using logging in the acquire/release effects.

log.locallyManaged(LogAnnotations.Name("x" :: Nil)) {
  ZManaged.make(log.info("x"))(_ => log.info("y")).use { _ => log.info("z") }
}

all three log lines should have the applied annotations

`java.lang.StackOverflowError` when adding `initializeSlf4jBridge` in my project

Here's how I initialize my logging layer:

  val logLayerLive: ULayer[Logging] =
    Slf4jLogger.make((context, message) => "[correlation-id = %s] %s".format(context(correlationId), message))

When I launch my app with this configuration, the project start without any issue.

When I add >>> initializeSlf4jBridge, then the service generate a `` when booting:

  val logLayerLive: ULayer[Logging] =
    Slf4jLogger.make((context, message) => "[correlation-id = %s] %s".format(context(correlationId), message)) >>> initializeSlf4jBridge

Error:

[IJ]app-embedded[ERROR] java.lang.StackOverflowError
app-embedded[ERROR] 	at zio.internal.PlatformSpecific$$anon$2.fatal(PlatformSpecific.scala:88)
app-embedded[ERROR] 	at zio.internal.Platform$Proxy.fatal(Platform.scala:110)
app-embedded[ERROR] 	at zio.internal.FiberContext.evaluateNow(FiberContext.scala:653)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunWith(Runtime.scala:214)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync(Runtime.scala:83)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync$(Runtime.scala:80)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRunSync(Runtime.scala:280)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun(Runtime.scala:58)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun$(Runtime.scala:57)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRun(Runtime.scala:280)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLoggerFactory.run(ZioLoggerFactory.scala:19)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.run(ZioLogger.scala:11)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.info(ZioLogger.scala:73)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2(Slf4jLogger.scala:32)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2$adapted(Slf4jLogger.scala:26)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4438)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4436)
app-embedded[ERROR] 	at zio.internal.FiberContext.evaluateNow(FiberContext.scala:351)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunWith(Runtime.scala:214)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync(Runtime.scala:83)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync$(Runtime.scala:80)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRunSync(Runtime.scala:280)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun(Runtime.scala:58)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun$(Runtime.scala:57)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRun(Runtime.scala:280)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLoggerFactory.run(ZioLoggerFactory.scala:19)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.run(ZioLogger.scala:11)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.info(ZioLogger.scala:73)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2(Slf4jLogger.scala:32)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2$adapted(Slf4jLogger.scala:26)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4438)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4436)
app-embedded[ERROR] 	at zio.internal.FiberContext.evaluateNow(FiberContext.scala:351)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunWith(Runtime.scala:214)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync(Runtime.scala:83)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync$(Runtime.scala:80)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRunSync(Runtime.scala:280)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun(Runtime.scala:58)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun$(Runtime.scala:57)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRun(Runtime.scala:280)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLoggerFactory.run(ZioLoggerFactory.scala:19)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.run(ZioLogger.scala:11)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.info(ZioLogger.scala:73)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2(Slf4jLogger.scala:32)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2$adapted(Slf4jLogger.scala:26)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4438)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4436)
app-embedded[ERROR] 	at zio.internal.FiberContext.evaluateNow(FiberContext.scala:351)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunWith(Runtime.scala:214)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync(Runtime.scala:83)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync$(Runtime.scala:80)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRunSync(Runtime.scala:280)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun(Runtime.scala:58)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun$(Runtime.scala:57)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRun(Runtime.scala:280)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLoggerFactory.run(ZioLoggerFactory.scala:19)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.run(ZioLogger.scala:11)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.info(ZioLogger.scala:73)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2(Slf4jLogger.scala:32)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2$adapted(Slf4jLogger.scala:26)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4438)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4436)
app-embedded[ERROR] 	at zio.internal.FiberContext.evaluateNow(FiberContext.scala:351)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunWith(Runtime.scala:214)
app-embedded **** WARNING ***
app-embedded Catastrophic JVM error encountered. Application not safely interrupted. Resources may be leaked. Check the logs for more details and consider overriding `Platform.reportFatal` to capture context.
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync(Runtime.scala:83)
app-embedded[ERROR] 	at zio.Runtime.unsafeRunSync$(Runtime.scala:80)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRunSync(Runtime.scala:280)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun(Runtime.scala:58)
app-embedded[ERROR] 	at zio.Runtime.unsafeRun$(Runtime.scala:57)
app-embedded[ERROR] 	at zio.Runtime$$anon$3.unsafeRun(Runtime.scala:280)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLoggerFactory.run(ZioLoggerFactory.scala:19)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.run(ZioLogger.scala:11)
app-embedded[ERROR] 	at org.slf4j.impl.ZioLogger.info(ZioLogger.scala:73)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2(Slf4jLogger.scala:32)
app-embedded[ERROR] 	at zio.logging.slf4j.Slf4jLogger$.$anonfun$make$2$adapted(Slf4jLogger.scala:26)
app-embedded[ERROR] 	at zio.ZIO$MapFn.apply(ZIO.scala:4438)
...

FileOutputStream in LogWriter needs to be opened in `append` mode.

In LogWriter, need to change this:

val output = new OutputStreamWriter(new FileOutputStream(destination.toFile), charset)

To this:

val output = new OutputStreamWriter(new FileOutputStream(destination.toFile, true), charset)

Otherwise all file streams opened by the writer will override previous files.

Backend for ZIO 2.0 Logging

I will flesh this ticket out more as I get more experience in this area, but in short-

This project has done some excellent proof-of-concept work, that has largely been assimilated into ZIO Core.
Moving forward, this project can focus on providing back-end implementations of the new Logging facades defined in ZIO 2.0.

More information about the 2.0 logging changes is available in John's blog post:
https://ziverge.com/blog/a-preview-of-logging-in-zio-2/

Just wanted to get this on the radar!

Add support for logging Throwable as warning

I would like to log a message as a warning and include an exception in the log. So similar to the:

def error(line: => String, cause: Cause[Any])

I'd like to have a:

def warn(line: => String, cause: Cause[Any]) 

That would log at the Warn level but it would include the error trace similarly to error.

If this is already possible and I just missed it somehow I'm sorry, otherwise if there's no objection I'd be happy to work on a PR for this.

Suggestion: ZIO Log Ops to improve usability

I find it that logging sometimes make your code more difficult to read, since you normally interleave commands with logging operations. In my most recent code I've created a ZIOLogOps implicit class that gives us what I consider a more readable alternative to that.

implicit class ZIOLogOps[R <: Logging with ZUUID, E, A](val z: ZIO[R, E, A]) extends AnyVal {
    def withCorrelationId: ZIO[R, E, A] =
      for {
        uuid  <- ZUUID.uuid
        result <- log.locally(_.annotate(LogAnnotation.CorrelationId, Some(uuid)))(z)
      } yield result

    def withLoggerName(name: String): ZIO[R, E, A] =
      log.locally(_.annotate(LogAnnotation.Name, name :: Nil))(z)

    def logInfo(f: A => String): ZIO[R, E, A] =
      z.tap(a => log.info(f(a)))

    def logError(f: E => String): ZIO[R, E, A] =
      z.tapError(e => log.error(f(e)))

    def logFold(fe: E => String, fa: A => String): ZIO[R, E, A] =
      z.logError(fe).logInfo(fa)
  }

A usage example would be:

val pipe = for {
  user <- UserRepository.findById(userId).logError(e => s"Error finding user: $e")
  credit <- BalanceRepository.getBalanceByUserId(userId).logInfo(b => s"Balance of $b found")
} yield ()

pipe
  .withLoggerName("UserBalanceService")
  .withCorrelationId

The current alternatives without this syntax would be:

val pipe = for {
  user <- UserRepository.findById(userId).tapError(e => log.error(s"Error finding user: $e"))
  credit <- BalanceRepository.getBalanceByUserId(userId).tap(b => log.info(s"Balance of $b found"))
} yield ()

for {
  correlationId <- ZUUID.uuid
  _ <- log.locally(_.annotate(LogAnnotation.Name, "UserBalanceService" :: Nil)
.annotate(LogAnnotation.CorrelationId, Some(uuid)))(pipe)  
} yield ()

I find the first alternative cleaner. What do you think? Would it be interesting to have some zio.logging.syntax package shipped with the library?

Idea: ability to dump logging context into MDC on arbitrary effect

Hi!

Sometimes you have to wrap a classic Scala library / a Java library which uses slf4j directly. Sadly, in these situations, the context of a logger will never reach MDC, so you won't see anything unless you explicitly wrap the interop with something roughly like:

def withLoggingContext[A](eff: => A): ZIO[Logging, Throwable, A] =
  log.context.flatMap { ctx =>
    ZIO.effect {
      import scala.jdk.CollectionConverters._
      val previous =
        Option(MDC.getCopyOfContextMap().asScala)
          .getOrElse(Map.empty[String, String])

      try {
        ctx.renderContext.foreach((MDC.put _).tupled)
        eff
      } finally MDC.setContextMap(previous.asJava)
    }
  }

I don't know that much about what ZIO lets us do in this situation, but do you think this would be possible to do automatically on every effect / effectAsync, e.g. with a custom executor that accesses the logger's FiberRef somehow and dumps its variables to MDC?

Additional helper methods for Cause parameter

Currently only Logger.error has an optional Cause parameter. I was expecting this helper method for all log levels. For example, I'm trying to print a failure with Warn level.

Also seemingly tied with this design decision is:

def throwable(line: => A, t: Throwable)

Which is hardcoded to print with LogLevel.Error. I don't know if that should also accept an optional LogLevel or more overloads to warn, info, etc. helpers.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.