zio / zio-logging

Powerful logging for ZIO 2.0 applications, with compatibility with many logging backends out-of-the-box.
https://zio.dev/zio-logging/
Apache License 2.0
174 stars 81 forks source link

Fallback to default logger when using runtime.unsafe #848

Closed seveneves closed 4 months ago

seveneves commented 4 months ago

It seems the correct (and only) way of enabling or disabling loggers is via ZLayer. As documentation states this is how a different logging implementation should be added to the application

import zio.*
import zio.logging.backend.SLF4J

object LoggingApp extends ZIOAppDefault {

  override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = Runtime.removeDefaultLoggers >>> SLF4J.slf4j

  override def run: ZIO[Environment, Any, Any] = ZIO.logInfo("This should support sl4j")
}

When running such App, the log entry produce follows slf4j pattern.

However, this does not work for any operation that relies on runtime.unsafe for example

import zio.*
import zio.logging.backend.SLF4J

object LoggingApp extends ZIOAppDefault {

  override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = Runtime.removeDefaultLoggers >>> SLF4J.slf4j

  override def run: ZIO[Environment, Any, Any] = ZIO.logInfo("This should support sl4j") *> unsafeLog

  private val unsafeLog = ZIO.fromFuture { _ =>
    Unsafe.unsafe(implicit u =>
      runtime.unsafe.runToFuture(ZIO.logInfo("Direct execution via unsafe does not use sl4j logger"))
    )
  }
}

And Direct execution via unsafe does not use sl4j logger will not follow sl4j structure.

This example is superficial and I could just use ZIO.logInfo directly of course. However it shows that all interoperability libraries that rely on runtime.unsafe to execute ZIO effects will not follow the desired logging. Such libraries may require zio.Runtime[Any] to be passed as an implicit.

This brings a question if it is possible to change logging in a zio.Runtime[Any] instead of providing it as ZLayer?

ZLayer works great when one has a ZIO application without any interoperability required. But in modern enterprise applications this is very hard to achieve and some interoperability is always required.

seveneves commented 4 months ago

Should be reading the documentation better 😅

import zio.*
import zio.logging.backend.SLF4J

object LoggingApp extends ZIOAppDefault {

  private val logging: RLayer[Any, Unit] = Runtime.removeDefaultLoggers >>> SLF4J.slf4j

  override def run: ZIO[Environment, Any, Any] = ZIO.logInfo("This should support sl4j") *> unsafeLog

  override def runtime: Runtime[Any] = Unsafe.unsafe(implicit unsafe => Runtime.unsafe.fromLayer(logging))

  private val unsafeLog = ZIO.fromFuture { _ =>
    Unsafe.unsafe(implicit u =>
      runtime.unsafe.runToFuture(ZIO.logInfo("Direct execution via unsafe does not use sl4j logger"))
    )
  }
}

works as expected