All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zio.logging.package.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019-2023 John A. De Goes and the ZIO Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package zio

import zio.metrics.{ Metric, MetricLabel }

import java.io.PrintStream
import java.nio.charset.{ Charset, StandardCharsets }
import java.nio.file.Path

package object logging {

  /**
   * The [[logContext]] fiber reference is used to store typed, structured log
   * annotations, which can be utilized by backends to enrich log messages.
   *
   * Because [[logContext]] is an ordinary [[zio.FiberRef]], it may be get, set,
   * and updated like any other fiber reference. However, the idiomatic way to
   * interact with [[logContext]] is by using [[zio.logging.LogAnnotation]].
   *
   * For example:
   *
   * {{{
   * myResponseHandler(request) @@ UserId(request.userId)
   * }}}
   *
   * This code would add the structured log annotation [[LogAnnotation.UserId]]
   * to all log messages emitted by the `myResponseHandler(request)` effect.
   */
  val logContext: FiberRef[LogContext] =
    zio.Unsafe.unsafe { implicit u =>
      FiberRef.unsafe.make(LogContext.empty, ZIO.identityFn[LogContext], (old, newV) => old ++ newV)
    }

  /**
   * log aspect annotation key for logger name
   */
  val loggerNameAnnotationKey = "logger_name"

  private[logging] val logLevelMetricLabel = "level"

  private[logging] val loggedTotalMetric =
    Metric.counter(name = "zio_log_total")

  /**
   * Logger name aspect, by this aspect is possible to set logger name (in general, logger name is extracted from [[Trace]])
   *
   * annotation key: [[zio.logging.loggerNameAnnotationKey]]
   */
  def loggerName(value: String): ZIOAspect[Nothing, Any, Nothing, Any, Nothing, Any] =
    ZIOAspect.annotated(loggerNameAnnotationKey, value)

  def console(
    format: LogFormat = LogFormat.colored,
    logLevel: LogLevel = LogLevel.Info
  ): ZLayer[Any, Nothing, Unit] =
    console(format, LogFilter.logLevel(logLevel))

  def console(
    format: LogFormat,
    logFilter: LogFilter[String]
  ): ZLayer[Any, Nothing, Unit] =
    makeConsole(format.toLogger, java.lang.System.out, logFilter)

  def consoleJson(
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info
  ): ZLayer[Any, Nothing, Unit] =
    consoleJson(format, LogFilter.logLevel(logLevel))

  def consoleJson(
    format: LogFormat,
    logFilter: LogFilter[String]
  ): ZLayer[Any, Nothing, Unit] =
    makeConsole(format.toJsonLogger, java.lang.System.out, logFilter)

  def consoleErr(
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info
  ): ZLayer[Any, Nothing, Unit] =
    consoleErr(format, LogFilter.logLevel(logLevel))

  def consoleErr(
    format: LogFormat,
    logFilter: LogFilter[String]
  ): ZLayer[Any, Nothing, Unit] =
    makeConsole(format.toLogger, java.lang.System.err, logFilter)

  def consoleErrJson(
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info
  ): ZLayer[Any, Nothing, Unit] =
    consoleErrJson(format, LogFilter.logLevel(logLevel))

  def consoleErrJson(
    format: LogFormat,
    logFilter: LogFilter[String]
  ): ZLayer[Any, Nothing, Unit] =
    makeConsole(format.toJsonLogger, java.lang.System.err, logFilter)

  def file(
    destination: Path,
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info,
    charset: Charset = StandardCharsets.UTF_8,
    autoFlushBatchSize: Int = 1,
    bufferedIOSize: Option[Int] = None
  ): ZLayer[Any, Nothing, Unit] =
    file(destination, format, LogFilter.logLevel(logLevel), charset, autoFlushBatchSize, bufferedIOSize)

  def file(
    destination: Path,
    format: LogFormat,
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int]
  ): ZLayer[Any, Nothing, Unit] =
    Runtime.addLogger(
      makeStringLogger(
        destination,
        format.toLogger,
        logFilter,
        charset,
        autoFlushBatchSize,
        bufferedIOSize
      )
    )

  def fileJson(
    destination: Path,
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info,
    charset: Charset = StandardCharsets.UTF_8,
    autoFlushBatchSize: Int = 1,
    bufferedIOSize: Option[Int] = None
  ): ZLayer[Any, Nothing, Unit] =
    fileJson(destination, format, LogFilter.logLevel(logLevel), charset, autoFlushBatchSize, bufferedIOSize)

  def fileJson(
    destination: Path,
    format: LogFormat,
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int]
  ): ZLayer[Any, Nothing, Unit] =
    Runtime.addLogger(
      makeStringLogger(
        destination,
        format.toJsonLogger,
        logFilter,
        charset,
        autoFlushBatchSize,
        bufferedIOSize
      )
    )

  def fileAsync(
    destination: Path,
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info,
    charset: Charset = StandardCharsets.UTF_8,
    autoFlushBatchSize: Int = 1,
    bufferedIOSize: Option[Int] = None
  ): ZLayer[Any, Nothing, Unit] =
    fileAsync(
      destination,
      format,
      LogFilter.logLevel(logLevel),
      charset,
      autoFlushBatchSize,
      bufferedIOSize
    )

  def fileAsync(
    destination: Path,
    format: LogFormat,
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int]
  ): ZLayer[Any, Nothing, Unit] =
    makeFileAsync(
      destination,
      format.toLogger,
      logFilter,
      charset,
      autoFlushBatchSize,
      bufferedIOSize
    )

  def fileAsyncJson(
    destination: Path,
    format: LogFormat = LogFormat.default,
    logLevel: LogLevel = LogLevel.Info,
    charset: Charset = StandardCharsets.UTF_8,
    autoFlushBatchSize: Int = 1,
    bufferedIOSize: Option[Int] = None
  ): ZLayer[Any, Nothing, Unit] =
    fileAsyncJson(
      destination,
      format,
      LogFilter.logLevel(logLevel),
      charset,
      autoFlushBatchSize,
      bufferedIOSize
    )

  def fileAsyncJson(
    destination: Path,
    format: LogFormat,
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int]
  ): ZLayer[Any, Nothing, Unit] =
    makeFileAsync(
      destination,
      format.toJsonLogger,
      logFilter,
      charset,
      autoFlushBatchSize,
      bufferedIOSize
    )

  val removeDefaultLoggers: ZLayer[Any, Nothing, Unit] = Runtime.removeDefaultLoggers

  private def makeConsole(
    logger: ZLogger[String, String],
    stream: PrintStream,
    logFilter: LogFilter[String]
  ): ZLayer[Any, Nothing, Unit] = {

    val stringLogger = logFilter.filter(logger.map { line =>
      try stream.println(line)
      catch {
        case t: VirtualMachineError => throw t
        case _: Throwable           => ()
      }
    })

    Runtime.addLogger(stringLogger)
  }

  private def makeStringLogger(
    destination: Path,
    logger: ZLogger[String, String],
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int]
  ): ZLogger[String, Any] = {
    val logWriter = new internal.FileWriter(destination, charset, autoFlushBatchSize, bufferedIOSize)

    val stringLogger: ZLogger[String, Any] = logFilter.filter(logger.map { (line: String) =>
      try logWriter.writeln(line)
      catch {
        case t: VirtualMachineError => throw t
        case _: Throwable           => ()
      }
    })

    stringLogger
  }

  private def makeFileAsync(
    destination: Path,
    logger: ZLogger[String, String],
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int]
  ): ZLayer[Any, Nothing, Unit] =
    ZLayer.scoped {
      for {
        queue       <- Queue.bounded[UIO[Any]](1000)
        stringLogger =
          makeAsyncStringLogger(destination, logger, logFilter, charset, autoFlushBatchSize, bufferedIOSize, queue)
        _           <- FiberRef.currentLoggers.locallyScopedWith(_ + stringLogger)
        _           <- queue.take.flatMap(task => task.ignore).forever.forkScoped
      } yield ()
    }

  private def makeAsyncStringLogger(
    destination: Path,
    logger: ZLogger[String, String],
    logFilter: LogFilter[String],
    charset: Charset,
    autoFlushBatchSize: Int,
    bufferedIOSize: Option[Int],
    queue: Queue[UIO[Any]]
  ): ZLogger[String, Any] = {
    val logWriter = new internal.FileWriter(destination, charset, autoFlushBatchSize, bufferedIOSize)

    val stringLogger: ZLogger[String, Any] = logFilter.filter(logger.map { (line: String) =>
      zio.Unsafe.unsafe { implicit u =>
        Runtime.default.unsafe.run(queue.offer(ZIO.succeed {
          try logWriter.writeln(line)
          catch {
            case t: VirtualMachineError => throw t
            case _: Throwable           => ()
          }
        }))
      }
    })
    stringLogger
  }

  private def metricLogger(counter: Metric.Counter[Long], logLevelLabel: String) = new ZLogger[String, Unit] {
    override def apply(
      trace: Trace,
      fiberId: FiberId,
      logLevel: LogLevel,
      message: () => String,
      cause: Cause[Any],
      context: FiberRefs,
      spans: List[LogSpan],
      annotations: Map[String, String]
    ): Unit = {
      val tags = context.get(FiberRef.currentTags).getOrElse(Set.empty)
      counter.unsafe.update(1, tags + MetricLabel(logLevelLabel, logLevel.label.toLowerCase))(Unsafe.unsafe)
      ()
    }
  }

  val logMetrics: ZLayer[Any, Nothing, Unit] =
    Runtime.addLogger(metricLogger(loggedTotalMetric, logLevelMetricLabel))

  def logMetricsWith(name: String, logLevelLabel: String): ZLayer[Any, Nothing, Unit] =
    Runtime.addLogger(metricLogger(Metric.counter(name), logLevelLabel))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy