From 116cb6cada1b8fb67d41429adb7c68188013d87c Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 29 Apr 2025 10:42:16 -0700 Subject: [PATCH 1/9] Add a ConsoleLogger and ConsoleLoggerFactory --- .../log4cats/extras/ConsoleLogger.scala | 323 ++++++++++++++++++ .../extras/ConsoleLoggerFactory.scala | 193 +++++++++++ 2 files changed, 516 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala new file mode 100644 index 00000000..ea206954 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala @@ -0,0 +1,323 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats.extras + +import cats.{~>, Show} +import cats.effect.kernel.{Async, Ref} +import cats.effect.std.Console +import cats.syntax.all.* +import org.typelevel.log4cats.SelfAwareStructuredLogger + +import java.io.{ByteArrayOutputStream, PrintStream} + +/** + * A simple logger that prints logs to standard error output. + * + * The intended use-case is for one-off scripts, examples, and other situations where a more + * fully-featured and performance-optimized logger would be overkill + * + * Log output format is roughly: + * {{{ + * %level %name - %message + * %ctx + * %throwable + * }}} + */ +trait ConsoleLogger[F[_]] extends SelfAwareStructuredLogger[F] { + + /** + * Because there is no `log4j` backend, the log level can be set directly + */ + def setLogLevel(level: LogLevel): F[Unit] + + override def mapK[G[_]](fk: F ~> G): ConsoleLogger[G] = + ConsoleLogger.mapK(this, fk) + + override def addContext(ctx: Map[String, String]): ConsoleLogger[F] = + ConsoleLogger.withContext(this)(ctx) + + override def addContext(pairs: (String, Show.Shown)*): ConsoleLogger[F] = + ConsoleLogger.withContext(this)( + pairs.map { case (k, v) => (k, v.toString) }.toMap + ) + + override def withModifiedString(f: String => String): ConsoleLogger[F] = + ConsoleLogger.withModifiedString(this, f) +} +object ConsoleLogger { + def apply[F[_]](name: String, initialLogLevel: LogLevel)(implicit + console: Console[F], + F: Async[F] + ): F[ConsoleLogger[F]] = + Ref[F].of(initialLogLevel).map { logLevelRef => + new ConsoleLogger[F] { + override def setLogLevel(level: LogLevel): F[Unit] = logLevelRef.set(level) + + private def isLevelEnabled(level: LogLevel): F[Boolean] = logLevelRef.get.map(_ >= level) + + override def isTraceEnabled: F[Boolean] = isLevelEnabled(LogLevel.Trace) + override def isDebugEnabled: F[Boolean] = isLevelEnabled(LogLevel.Debug) + override def isInfoEnabled: F[Boolean] = isLevelEnabled(LogLevel.Info) + override def isWarnEnabled: F[Boolean] = isLevelEnabled(LogLevel.Warn) + override def isErrorEnabled: F[Boolean] = isLevelEnabled(LogLevel.Error) + + private def renderThrowable(t: Throwable): F[String] = + F.delay { + val baos = new ByteArrayOutputStream() + val ps = new PrintStream(baos) + t.printStackTrace(ps) + baos.toString.linesIterator.map(s => s" $s").mkString("\n") + } + + private def renderContext(ctx: Map[String, String]): String = + if (ctx.isEmpty) "" + else { + val builder = new StringBuilder() + ctx.toVector.foreach { case (key, value) => + builder.append(" ").append(key).append(" = ").append(value).append("\n") + } + builder.result() + } + + private def log(level: LogLevel, msg: => String): F[Unit] = + isLevelEnabled(level).ifF(console.errorln(s"$level $name - $msg"), F.unit) + + private def log(level: LogLevel, msg: => String, throwable: Throwable): F[Unit] = + isLevelEnabled(level).ifF( + renderThrowable(throwable).flatMap(t => console.errorln(s"$level $name - $msg\n$t")), + F.unit + ) + + private def log(level: LogLevel, msg: => String, ctx: Map[String, String]): F[Unit] = + isLevelEnabled(level).ifF( + console.errorln(s"$level $name - $msg${renderContext(ctx)}"), + F.unit + ) + + private def log( + level: LogLevel, + msg: => String, + throwable: Throwable, + ctx: Map[String, String] + ): F[Unit] = + isLevelEnabled(level).ifF( + renderThrowable(throwable) + .flatMap(t => console.errorln(s"$level $name - $msg${renderContext(ctx)}\n$t")), + F.unit + ) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Error, msg, ctx) + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Warn, msg, ctx) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Info, msg, ctx) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Debug, msg, ctx) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Trace, msg, ctx) + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Error, msg, t, ctx) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Warn, msg, t, ctx) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Info, msg, t, ctx) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Debug, msg, t, ctx) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Trace, msg, t, ctx) + + override def error(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Error, message, t) + override def warn(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Warn, message, t) + override def info(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Info, message, t) + override def debug(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Debug, message, t) + override def trace(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Trace, message, t) + + override def error(message: => String): F[Unit] = log(LogLevel.Error, message) + override def warn(message: => String): F[Unit] = log(LogLevel.Warn, message) + override def info(message: => String): F[Unit] = log(LogLevel.Info, message) + override def debug(message: => String): F[Unit] = log(LogLevel.Debug, message) + override def trace(message: => String): F[Unit] = log(LogLevel.Trace, message) + } + } + + private def mapK[F[_], G[_]]( + logger: ConsoleLogger[F], + fk: F ~> G + ): ConsoleLogger[G] = + new ConsoleLogger[G] { + override def setLogLevel(level: LogLevel): G[Unit] = fk(logger.setLogLevel(level)) + + override def isTraceEnabled: G[Boolean] = fk(logger.isTraceEnabled) + override def isDebugEnabled: G[Boolean] = fk(logger.isDebugEnabled) + override def isInfoEnabled: G[Boolean] = fk(logger.isInfoEnabled) + override def isWarnEnabled: G[Boolean] = fk(logger.isWarnEnabled) + override def isErrorEnabled: G[Boolean] = fk(logger.isErrorEnabled) + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): G[Unit] = fk( + logger.trace(ctx, t)(msg) + ) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): G[Unit] = fk( + logger.debug(ctx, t)(msg) + ) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): G[Unit] = fk( + logger.info(ctx, t)(msg) + ) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): G[Unit] = fk( + logger.warn(ctx, t)(msg) + ) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): G[Unit] = fk( + logger.error(ctx, t)(msg) + ) + + override def trace(ctx: Map[String, String])(msg: => String): G[Unit] = fk( + logger.trace(ctx)(msg) + ) + override def debug(ctx: Map[String, String])(msg: => String): G[Unit] = fk( + logger.debug(ctx)(msg) + ) + override def info(ctx: Map[String, String])(msg: => String): G[Unit] = fk( + logger.info(ctx)(msg) + ) + override def warn(ctx: Map[String, String])(msg: => String): G[Unit] = fk( + logger.warn(ctx)(msg) + ) + override def error(ctx: Map[String, String])(msg: => String): G[Unit] = fk( + logger.error(ctx)(msg) + ) + + override def trace(t: Throwable)(message: => String): G[Unit] = fk(logger.trace(t)(message)) + override def debug(t: Throwable)(message: => String): G[Unit] = fk(logger.debug(t)(message)) + override def info(t: Throwable)(message: => String): G[Unit] = fk(logger.info(t)(message)) + override def warn(t: Throwable)(message: => String): G[Unit] = fk(logger.warn(t)(message)) + override def error(t: Throwable)(message: => String): G[Unit] = fk(logger.error(t)(message)) + + override def trace(message: => String): G[Unit] = fk(logger.trace(message)) + override def debug(message: => String): G[Unit] = fk(logger.debug(message)) + override def info(message: => String): G[Unit] = fk(logger.info(message)) + override def warn(message: => String): G[Unit] = fk(logger.warn(message)) + override def error(message: => String): G[Unit] = fk(logger.error(message)) + } + + def withContext[F[_]]( + logger: ConsoleLogger[F] + )(baseCtx: Map[String, String]): ConsoleLogger[F] = + new ConsoleLogger[F] { + private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx + + override def setLogLevel(level: LogLevel): F[Unit] = logger.setLogLevel(level) + + override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled + override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled + override def isInfoEnabled: F[Boolean] = logger.isInfoEnabled + override def isWarnEnabled: F[Boolean] = logger.isWarnEnabled + override def isErrorEnabled: F[Boolean] = logger.isErrorEnabled + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.trace(addCtx(ctx), t)(msg) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.debug(addCtx(ctx), t)(msg) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.info(addCtx(ctx), t)(msg) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.warn(addCtx(ctx), t)(msg) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.error(addCtx(ctx), t)(msg) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.trace(addCtx(ctx))(msg) + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.debug(addCtx(ctx))(msg) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.info(addCtx(ctx))(msg) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.warn(addCtx(ctx))(msg) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.error(addCtx(ctx))(msg) + + override def trace(t: Throwable)(message: => String): F[Unit] = + logger.trace(baseCtx, t)(message) + override def debug(t: Throwable)(message: => String): F[Unit] = + logger.debug(baseCtx, t)(message) + override def info(t: Throwable)(message: => String): F[Unit] = + logger.info(baseCtx, t)(message) + override def warn(t: Throwable)(message: => String): F[Unit] = + logger.warn(baseCtx, t)(message) + override def error(t: Throwable)(message: => String): F[Unit] = + logger.error(baseCtx, t)(message) + + override def trace(message: => String): F[Unit] = logger.trace(baseCtx)(message) + override def debug(message: => String): F[Unit] = logger.debug(baseCtx)(message) + override def info(message: => String): F[Unit] = logger.info(baseCtx)(message) + override def warn(message: => String): F[Unit] = logger.warn(baseCtx)(message) + override def error(message: => String): F[Unit] = logger.error(baseCtx)(message) + } + + def withModifiedString[F[_]]( + logger: ConsoleLogger[F], + f: String => String + ): ConsoleLogger[F] = + new ConsoleLogger[F] { + override def setLogLevel(level: LogLevel): F[Unit] = logger.setLogLevel(level) + + override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled + override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled + override def isInfoEnabled: F[Boolean] = logger.isInfoEnabled + override def isWarnEnabled: F[Boolean] = logger.isWarnEnabled + override def isErrorEnabled: F[Boolean] = logger.isErrorEnabled + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.trace(ctx, t)(f(msg)) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.debug(ctx, t)(f(msg)) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.info(ctx, t)(f(msg)) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.warn(ctx, t)(f(msg)) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + logger.error(ctx, t)(f(msg)) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.trace(ctx)(f(msg)) + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.debug(ctx)(f(msg)) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.info(ctx)(f(msg)) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.warn(ctx)(f(msg)) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = + logger.error(ctx)(f(msg)) + + override def trace(t: Throwable)(message: => String): F[Unit] = logger.trace(t)(f(message)) + override def debug(t: Throwable)(message: => String): F[Unit] = logger.debug(t)(f(message)) + override def info(t: Throwable)(message: => String): F[Unit] = logger.info(t)(f(message)) + override def warn(t: Throwable)(message: => String): F[Unit] = logger.warn(t)(f(message)) + override def error(t: Throwable)(message: => String): F[Unit] = logger.error(t)(f(message)) + + override def trace(message: => String): F[Unit] = logger.trace(f(message)) + override def debug(message: => String): F[Unit] = logger.debug(f(message)) + override def info(message: => String): F[Unit] = logger.info(f(message)) + override def warn(message: => String): F[Unit] = logger.warn(f(message)) + override def error(message: => String): F[Unit] = logger.error(f(message)) + } +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala new file mode 100644 index 00000000..9986d678 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala @@ -0,0 +1,193 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats.extras + +import cats.Show.Shown +import cats.data.Chain +import cats.effect.kernel.{Async, Ref, Resource} +import cats.effect.std.{Console, Dispatcher} +import cats.syntax.all.* +import cats.{~>, Functor} +import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} + +/** + * Creates simple loggers that print logs to standard error output. + * + * The intended use-case is for one-off scripts, examples, and other situations where a more + * fully-featured and performance-optimized logger would be overkill + */ +trait ConsoleLoggerFactory[F[_]] extends LoggerFactory[F] { + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] + + /** + * Set the initial log level for loggers that will be created. + * + * @param logLevel + * Sets the log level such that logs at or above this level will be logged. + * @note + * This does not propagate to loggers which have already been created. + */ + def setGlobalLogLevel(logLevel: LogLevel): F[Unit] + + /** + * Add an override for loggers that will be created which have a matching prefix. + * + * @param prefix + * Similar to how `name` works in a `log4j` config file, this is a simple prefix match. + * @note + * This does not propagate to loggers which have already been created. + * @note + * These are applied on a first-match basis: + * {{{ + * logger.addLogLevelOverride("", LogLevel.Warn) *> + * logger.addLogLevelOverride("foo", LogLevel.Info) // This is effectively dead code + * }}} + */ + def addLogLevelOverride(prefix: String, logLevel: LogLevel): F[Unit] + + /** + * Remove an existing log level override. + * + * @param prefix + * This must match exactly, if overrides for `foo.bar` and `foo` have been added, then removing + * `foo` will not remove the override for `foo.bar` + * @note + * This does not propagate to loggers which have already been created. + */ + def removeLogLevelOverride(prefix: String): F[Unit] + + override def addContext(ctx: Map[String, String])(implicit + F: Functor[F] + ): ConsoleLoggerFactory[F] = + ConsoleLoggerFactory.addContext(this, ctx) + + override def addContext(pairs: (String, Shown)*)(implicit + F: Functor[F] + ): ConsoleLoggerFactory[F] = + ConsoleLoggerFactory.addContext(this, pairs.map { case (k, v) => (k, v.toString) }.toMap) + + override def withModifiedString(f: String => String)(implicit + F: Functor[F] + ): ConsoleLoggerFactory[F] = + ConsoleLoggerFactory.withModifiedString(this, f) + + override def mapK[G[_]](fk: F ~> G)(implicit F: Functor[F]): ConsoleLoggerFactory[G] = + ConsoleLoggerFactory.mapK[F, G](fk)(this) +} +object ConsoleLoggerFactory { + + def apply[F[_]: Async: Console]( + defaultLogLevel: LogLevel, + logLevelOverrides: (String, LogLevel)* + ): Resource[F, ConsoleLoggerFactory[F]] = + Dispatcher.sequential[F].evalMap { dispatcher => + ( + Ref[F].of(defaultLogLevel), + Ref[F].of(Chain.fromSeq(logLevelOverrides.map((new LogLevelOverride(_, _)).tupled))) + ).mapN { (globalLogLevelRef, logLevelOverrides) => + new ConsoleLoggerFactory[F] { + override def setGlobalLogLevel(logLevel: LogLevel): F[Unit] = + globalLogLevelRef.set(logLevel) + + override def addLogLevelOverride(prefix: String, logLevel: LogLevel): F[Unit] = + logLevelOverrides.update(_.append(new LogLevelOverride(prefix, logLevel))) + + override def removeLogLevelOverride(prefix: String): F[Unit] = + logLevelOverrides.update(_.filterNot(_.prefix == prefix)) + + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = + (globalLogLevelRef.get, logLevelOverrides.get).flatMapN { + (globalLogLevel, logLevelOverrides) => + val logLevel = + logLevelOverrides.find(_.matches(name)).fold(globalLogLevel)(_.logLevel) + ConsoleLogger[F](name, logLevel) + }.widen + + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = + dispatcher.unsafeRunSync(fromName(name)) + } + } + } + + private class LogLevelOverride(val prefix: String, val logLevel: LogLevel) { + def matches(name: String): Boolean = name.startsWith(prefix) + } + + private def mapK[F[_]: Functor, G[_]]( + fk: F ~> G + )(lf: ConsoleLoggerFactory[F]): ConsoleLoggerFactory[G] = + new ConsoleLoggerFactory[G] { + override def setGlobalLogLevel(logLevel: LogLevel): G[Unit] = fk( + lf.setGlobalLogLevel(logLevel) + ) + + override def addLogLevelOverride(prefix: String, logLevel: LogLevel): G[Unit] = + fk(lf.addLogLevelOverride(prefix, logLevel)) + + override def removeLogLevelOverride(prefix: String): G[Unit] = fk( + lf.removeLogLevelOverride(prefix) + ) + + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[G] = + lf.getLoggerFromName(name).mapK(fk) + + override def fromName(name: String): G[SelfAwareStructuredLogger[G]] = fk( + lf.fromName(name).map(_.mapK(fk)) + ) + } + + private def addContext[F[_]: Functor]( + lf: ConsoleLoggerFactory[F], + ctx: Map[String, String] + ): ConsoleLoggerFactory[F] = + new ConsoleLoggerFactory[F] { + override def setGlobalLogLevel(logLevel: LogLevel): F[Unit] = lf.setGlobalLogLevel(logLevel) + + override def addLogLevelOverride(prefix: String, logLevel: LogLevel): F[Unit] = + lf.addLogLevelOverride(prefix, logLevel) + + override def removeLogLevelOverride(prefix: String): F[Unit] = + lf.removeLogLevelOverride(prefix) + + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = + lf.getLoggerFromName(name).addContext(ctx) + + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = + lf.fromName(name).map(_.addContext(ctx)) + } + + private def withModifiedString[F[_]: Functor]( + lf: ConsoleLoggerFactory[F], + f: String => String + ): ConsoleLoggerFactory[F] = + new ConsoleLoggerFactory[F] { + override def setGlobalLogLevel(logLevel: LogLevel): F[Unit] = lf.setGlobalLogLevel(logLevel) + + override def addLogLevelOverride(prefix: String, logLevel: LogLevel): F[Unit] = + lf.addLogLevelOverride(prefix, logLevel) + + override def removeLogLevelOverride(prefix: String): F[Unit] = + lf.removeLogLevelOverride(prefix) + + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = + lf.getLoggerFromName(name).withModifiedString(f) + + override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = + lf.fromName(name).map(_.withModifiedString(f)) + } + +} From 6717c2e42d3bfaac2b1b471a9cd8fb04d80a78c3 Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 29 Apr 2025 11:06:12 -0700 Subject: [PATCH 2/9] Fix accidental ifF to ifM --- .../org/typelevel/log4cats/extras/ConsoleLogger.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala index ea206954..0d6dd207 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala @@ -94,16 +94,16 @@ object ConsoleLogger { } private def log(level: LogLevel, msg: => String): F[Unit] = - isLevelEnabled(level).ifF(console.errorln(s"$level $name - $msg"), F.unit) + isLevelEnabled(level).ifM(console.errorln(s"$level $name - $msg"), F.unit) private def log(level: LogLevel, msg: => String, throwable: Throwable): F[Unit] = - isLevelEnabled(level).ifF( + isLevelEnabled(level).ifM( renderThrowable(throwable).flatMap(t => console.errorln(s"$level $name - $msg\n$t")), F.unit ) private def log(level: LogLevel, msg: => String, ctx: Map[String, String]): F[Unit] = - isLevelEnabled(level).ifF( + isLevelEnabled(level).ifM( console.errorln(s"$level $name - $msg${renderContext(ctx)}"), F.unit ) @@ -114,7 +114,7 @@ object ConsoleLogger { throwable: Throwable, ctx: Map[String, String] ): F[Unit] = - isLevelEnabled(level).ifF( + isLevelEnabled(level).ifM( renderThrowable(throwable) .flatMap(t => console.errorln(s"$level $name - $msg${renderContext(ctx)}\n$t")), F.unit From b09d316b3e3234d12ef7f10324e228a35d600206 Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 29 Apr 2025 11:15:42 -0700 Subject: [PATCH 3/9] Avoid using pre 2.13 method --- .../typelevel/log4cats/extras/ConsoleLoggerFactory.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala index 9986d678..6cda0b32 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala @@ -24,6 +24,9 @@ import cats.syntax.all.* import cats.{~>, Functor} import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} +import scala.concurrent.Await +import scala.concurrent.duration.Duration + /** * Creates simple loggers that print logs to standard error output. * @@ -117,8 +120,10 @@ object ConsoleLoggerFactory { ConsoleLogger[F](name, logLevel) }.widen - override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = - dispatcher.unsafeRunSync(fromName(name)) + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = { + // Note: switch to dispatcher.unsafeRunSync when 2.12 support is dropped + Await.result(dispatcher.unsafeToFuture(fromName(name)), Duration.Inf) + } } } } From f21ba99450de64caa252db06bdf8d560f3c904df Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 29 Apr 2025 11:21:54 -0700 Subject: [PATCH 4/9] Fix formatting --- .../org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala index 6cda0b32..0a91fc81 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala @@ -120,10 +120,9 @@ object ConsoleLoggerFactory { ConsoleLogger[F](name, logLevel) }.widen - override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = { + override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = // Note: switch to dispatcher.unsafeRunSync when 2.12 support is dropped Await.result(dispatcher.unsafeToFuture(fromName(name)), Duration.Inf) - } } } } From de72914121e1319d8d939bdee37cc1c49df091f5 Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 6 May 2025 08:59:53 -0700 Subject: [PATCH 5/9] Fix miswired log levels --- .../log4cats/extras/ConsoleLogger.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala index 0d6dd207..13c22b25 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala @@ -121,26 +121,26 @@ object ConsoleLogger { ) override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Error, msg, ctx) + log(LogLevel.Trace, msg, ctx) override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Warn, msg, ctx) + log(LogLevel.Debug, msg, ctx) override def info(ctx: Map[String, String])(msg: => String): F[Unit] = log(LogLevel.Info, msg, ctx) override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Debug, msg, ctx) + log(LogLevel.Warn, msg, ctx) override def error(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Trace, msg, ctx) + log(LogLevel.Error, msg, ctx) override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Error, msg, t, ctx) + log(LogLevel.Trace, msg, t, ctx) override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Warn, msg, t, ctx) + log(LogLevel.Debug, msg, t, ctx) override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = log(LogLevel.Info, msg, t, ctx) override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Debug, msg, t, ctx) + log(LogLevel.Warn, msg, t, ctx) override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Trace, msg, t, ctx) + log(LogLevel.Error, msg, t, ctx) override def error(t: Throwable)(message: => String): F[Unit] = log(LogLevel.Error, message, t) From b09cf4714c4af139ac4151436bff2cc874feca9c Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 6 May 2025 09:43:23 -0700 Subject: [PATCH 6/9] Allow ConsoleLogger to specify format --- .../log4cats/extras/ConsoleLogFormat.scala | 92 +++++++++++++++++++ .../log4cats/extras/ConsoleLogger.scala | 45 ++++----- .../extras/ConsoleLoggerFactory.scala | 9 +- 3 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala new file mode 100644 index 00000000..6cfc5078 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala @@ -0,0 +1,92 @@ +/* + * Copyright 2018 Typelevel + * + * 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 org.typelevel.log4cats.extras + +import java.io.{ByteArrayOutputStream, PrintStream} +import java.time.Instant +import java.time.format.DateTimeFormatter + +trait ConsoleLogFormat { + def format(loggerName: String, level: LogLevel, timestamp: Instant, msg: String): String + def format( + loggerName: String, + level: LogLevel, + timestamp: Instant, + msg: String, + throwable: Throwable + ): String + def format( + loggerName: String, + level: LogLevel, + timestamp: Instant, + msg: String, + ctx: Map[String, String] + ): String + def format( + loggerName: String, + level: LogLevel, + timestamp: Instant, + msg: String, + ctx: Map[String, String], + throwable: Throwable + ): String +} +object ConsoleLogFormat { + val Default: ConsoleLogFormat = new ConsoleLogFormat { + private def fmt(i: Instant): String = DateTimeFormatter.ISO_INSTANT.format(i) + private def fmt(ctx: Map[String, String]): String = { + val builder = new StringBuilder() + ctx.toVector.foreach { case (key, value) => + builder.append(" ").append(key).append(" = ").append(value).append("\n") + } + builder.result() + } + private def fmt(t: Throwable): String = { + val baos = new ByteArrayOutputStream() + val ps = new PrintStream(baos) + t.printStackTrace(ps) + baos.toString.linesIterator.map(s => s" $s").mkString("\n") + } + + override def format(n: String, l: LogLevel, ts: Instant, m: String): String = + s"${fmt(ts)} $l $n - $m" + + override def format(n: String, l: LogLevel, ts: Instant, m: String, t: Throwable): String = + s"${fmt(ts)} $l $n - $m\n${fmt(t)}" + + override def format( + n: String, + l: LogLevel, + ts: Instant, + m: String, + ctx: Map[String, String] + ): String = + if (ctx.isEmpty) format(n, l, ts, m) + else s"${fmt(ts)} $l $n - $m\n${fmt(ctx)}" + + override def format( + n: String, + l: LogLevel, + ts: Instant, + m: String, + ctx: Map[String, String], + t: Throwable + ): String = + if (ctx.isEmpty) format(n, l, ts, m, t) + else s"${fmt(ts)} $l $n - $m\n${fmt(ctx)}\t${fmt(t)}" + } +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala index 13c22b25..164e9a6a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala @@ -16,14 +16,12 @@ package org.typelevel.log4cats.extras -import cats.{~>, Show} import cats.effect.kernel.{Async, Ref} import cats.effect.std.Console import cats.syntax.all.* +import cats.{~>, Show} import org.typelevel.log4cats.SelfAwareStructuredLogger -import java.io.{ByteArrayOutputStream, PrintStream} - /** * A simple logger that prints logs to standard error output. * @@ -59,7 +57,10 @@ trait ConsoleLogger[F[_]] extends SelfAwareStructuredLogger[F] { ConsoleLogger.withModifiedString(this, f) } object ConsoleLogger { - def apply[F[_]](name: String, initialLogLevel: LogLevel)(implicit + def apply[F[_]: Console: Async](name: String, initialLogLevel: LogLevel): F[ConsoleLogger[F]] = + apply[F](name, initialLogLevel, ConsoleLogFormat.Default) + + def apply[F[_]](name: String, initialLogLevel: LogLevel, format: ConsoleLogFormat)(implicit console: Console[F], F: Async[F] ): F[ConsoleLogger[F]] = @@ -75,36 +76,25 @@ object ConsoleLogger { override def isWarnEnabled: F[Boolean] = isLevelEnabled(LogLevel.Warn) override def isErrorEnabled: F[Boolean] = isLevelEnabled(LogLevel.Error) - private def renderThrowable(t: Throwable): F[String] = - F.delay { - val baos = new ByteArrayOutputStream() - val ps = new PrintStream(baos) - t.printStackTrace(ps) - baos.toString.linesIterator.map(s => s" $s").mkString("\n") - } - - private def renderContext(ctx: Map[String, String]): String = - if (ctx.isEmpty) "" - else { - val builder = new StringBuilder() - ctx.toVector.foreach { case (key, value) => - builder.append(" ").append(key).append(" = ").append(value).append("\n") - } - builder.result() - } - private def log(level: LogLevel, msg: => String): F[Unit] = - isLevelEnabled(level).ifM(console.errorln(s"$level $name - $msg"), F.unit) + isLevelEnabled(level).ifM( + F.realTimeInstant.map(format.format(name, level, _, msg)).flatMap(console.errorln(_)), + F.unit + ) private def log(level: LogLevel, msg: => String, throwable: Throwable): F[Unit] = isLevelEnabled(level).ifM( - renderThrowable(throwable).flatMap(t => console.errorln(s"$level $name - $msg\n$t")), + F.realTimeInstant + .map(format.format(name, level, _, msg, throwable)) + .flatMap(console.errorln(_)), F.unit ) private def log(level: LogLevel, msg: => String, ctx: Map[String, String]): F[Unit] = isLevelEnabled(level).ifM( - console.errorln(s"$level $name - $msg${renderContext(ctx)}"), + F.realTimeInstant + .map(format.format(name, level, _, msg, ctx)) + .flatMap(console.errorln(_)), F.unit ) @@ -115,8 +105,9 @@ object ConsoleLogger { ctx: Map[String, String] ): F[Unit] = isLevelEnabled(level).ifM( - renderThrowable(throwable) - .flatMap(t => console.errorln(s"$level $name - $msg${renderContext(ctx)}\n$t")), + F.realTimeInstant + .map(format.format(name, level, _, msg, ctx, throwable)) + .flatMap(console.errorln(_)), F.unit ) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala index 0a91fc81..3a447506 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala @@ -96,6 +96,13 @@ object ConsoleLoggerFactory { def apply[F[_]: Async: Console]( defaultLogLevel: LogLevel, logLevelOverrides: (String, LogLevel)* + ): Resource[F, ConsoleLoggerFactory[F]] = + apply[F](defaultLogLevel, ConsoleLogFormat.Default, logLevelOverrides*) + + def apply[F[_]: Async: Console]( + defaultLogLevel: LogLevel, + format: ConsoleLogFormat, + logLevelOverrides: (String, LogLevel)* ): Resource[F, ConsoleLoggerFactory[F]] = Dispatcher.sequential[F].evalMap { dispatcher => ( @@ -117,7 +124,7 @@ object ConsoleLoggerFactory { (globalLogLevel, logLevelOverrides) => val logLevel = logLevelOverrides.find(_.matches(name)).fold(globalLogLevel)(_.logLevel) - ConsoleLogger[F](name, logLevel) + ConsoleLogger[F](name, logLevel, format) }.widen override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = From 4ed0fd51d4f9ceb864192d92716684b00b5ccaa4 Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 6 May 2025 10:10:21 -0700 Subject: [PATCH 7/9] Use method that exists for JS --- .../log4cats/extras/ConsoleLogger.scala | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala index 164e9a6a..4b4cad47 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala @@ -22,6 +22,8 @@ import cats.syntax.all.* import cats.{~>, Show} import org.typelevel.log4cats.SelfAwareStructuredLogger +import java.time.Instant + /** * A simple logger that prints logs to standard error output. * @@ -76,25 +78,22 @@ object ConsoleLogger { override def isWarnEnabled: F[Boolean] = isLevelEnabled(LogLevel.Warn) override def isErrorEnabled: F[Boolean] = isLevelEnabled(LogLevel.Error) + private def now: F[Instant] = F.realTime.map(d => Instant.EPOCH.plusNanos(d.toNanos)) private def log(level: LogLevel, msg: => String): F[Unit] = isLevelEnabled(level).ifM( - F.realTimeInstant.map(format.format(name, level, _, msg)).flatMap(console.errorln(_)), + now.map(format.format(name, level, _, msg)).flatMap(console.errorln(_)), F.unit ) private def log(level: LogLevel, msg: => String, throwable: Throwable): F[Unit] = isLevelEnabled(level).ifM( - F.realTimeInstant - .map(format.format(name, level, _, msg, throwable)) - .flatMap(console.errorln(_)), + now.map(format.format(name, level, _, msg, throwable)).flatMap(console.errorln(_)), F.unit ) private def log(level: LogLevel, msg: => String, ctx: Map[String, String]): F[Unit] = isLevelEnabled(level).ifM( - F.realTimeInstant - .map(format.format(name, level, _, msg, ctx)) - .flatMap(console.errorln(_)), + now.map(format.format(name, level, _, msg, ctx)).flatMap(console.errorln(_)), F.unit ) @@ -105,9 +104,7 @@ object ConsoleLogger { ctx: Map[String, String] ): F[Unit] = isLevelEnabled(level).ifM( - F.realTimeInstant - .map(format.format(name, level, _, msg, ctx, throwable)) - .flatMap(console.errorln(_)), + now.map(format.format(name, level, _, msg, ctx, throwable)).flatMap(console.errorln(_)), F.unit ) From f517111c0a3cc8aa537d32696c80df1ebac4bb5a Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Mon, 26 May 2025 10:31:55 -0700 Subject: [PATCH 8/9] Rename ConsoleLogger to StdErrLogger --- ...soleLogFormat.scala => LogFormatter.scala} | 6 +-- ...ConsoleLogger.scala => StdErrLogger.scala} | 48 +++++++++---------- ...actory.scala => StdErrLoggerFactory.scala} | 48 +++++++++---------- 3 files changed, 51 insertions(+), 51 deletions(-) rename core/shared/src/main/scala/org/typelevel/log4cats/extras/{ConsoleLogFormat.scala => LogFormatter.scala} (96%) rename core/shared/src/main/scala/org/typelevel/log4cats/extras/{ConsoleLogger.scala => StdErrLogger.scala} (93%) rename core/shared/src/main/scala/org/typelevel/log4cats/extras/{ConsoleLoggerFactory.scala => StdErrLoggerFactory.scala} (85%) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogFormatter.scala similarity index 96% rename from core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala rename to core/shared/src/main/scala/org/typelevel/log4cats/extras/LogFormatter.scala index 6cfc5078..1becf5d1 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogFormat.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogFormatter.scala @@ -20,7 +20,7 @@ import java.io.{ByteArrayOutputStream, PrintStream} import java.time.Instant import java.time.format.DateTimeFormatter -trait ConsoleLogFormat { +trait LogFormatter { def format(loggerName: String, level: LogLevel, timestamp: Instant, msg: String): String def format( loggerName: String, @@ -45,8 +45,8 @@ trait ConsoleLogFormat { throwable: Throwable ): String } -object ConsoleLogFormat { - val Default: ConsoleLogFormat = new ConsoleLogFormat { +object LogFormatter { + val Default: LogFormatter = new LogFormatter { private def fmt(i: Instant): String = DateTimeFormatter.ISO_INSTANT.format(i) private def fmt(ctx: Map[String, String]): String = { val builder = new StringBuilder() diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala similarity index 93% rename from core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala rename to core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala index 4b4cad47..6b495bf0 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala @@ -37,37 +37,37 @@ import java.time.Instant * %throwable * }}} */ -trait ConsoleLogger[F[_]] extends SelfAwareStructuredLogger[F] { +trait StdErrLogger[F[_]] extends SelfAwareStructuredLogger[F] { /** * Because there is no `log4j` backend, the log level can be set directly */ def setLogLevel(level: LogLevel): F[Unit] - override def mapK[G[_]](fk: F ~> G): ConsoleLogger[G] = - ConsoleLogger.mapK(this, fk) + override def mapK[G[_]](fk: F ~> G): StdErrLogger[G] = + StdErrLogger.mapK(this, fk) - override def addContext(ctx: Map[String, String]): ConsoleLogger[F] = - ConsoleLogger.withContext(this)(ctx) + override def addContext(ctx: Map[String, String]): StdErrLogger[F] = + StdErrLogger.withContext(this)(ctx) - override def addContext(pairs: (String, Show.Shown)*): ConsoleLogger[F] = - ConsoleLogger.withContext(this)( + override def addContext(pairs: (String, Show.Shown)*): StdErrLogger[F] = + StdErrLogger.withContext(this)( pairs.map { case (k, v) => (k, v.toString) }.toMap ) - override def withModifiedString(f: String => String): ConsoleLogger[F] = - ConsoleLogger.withModifiedString(this, f) + override def withModifiedString(f: String => String): StdErrLogger[F] = + StdErrLogger.withModifiedString(this, f) } -object ConsoleLogger { - def apply[F[_]: Console: Async](name: String, initialLogLevel: LogLevel): F[ConsoleLogger[F]] = - apply[F](name, initialLogLevel, ConsoleLogFormat.Default) +object StdErrLogger { + def apply[F[_]: Console: Async](name: String, initialLogLevel: LogLevel): F[StdErrLogger[F]] = + apply[F](name, initialLogLevel, LogFormatter.Default) - def apply[F[_]](name: String, initialLogLevel: LogLevel, format: ConsoleLogFormat)(implicit + def apply[F[_]](name: String, initialLogLevel: LogLevel, format: LogFormatter)(implicit console: Console[F], F: Async[F] - ): F[ConsoleLogger[F]] = + ): F[StdErrLogger[F]] = Ref[F].of(initialLogLevel).map { logLevelRef => - new ConsoleLogger[F] { + new StdErrLogger[F] { override def setLogLevel(level: LogLevel): F[Unit] = logLevelRef.set(level) private def isLevelEnabled(level: LogLevel): F[Boolean] = logLevelRef.get.map(_ >= level) @@ -150,10 +150,10 @@ object ConsoleLogger { } private def mapK[F[_], G[_]]( - logger: ConsoleLogger[F], + logger: StdErrLogger[F], fk: F ~> G - ): ConsoleLogger[G] = - new ConsoleLogger[G] { + ): StdErrLogger[G] = + new StdErrLogger[G] { override def setLogLevel(level: LogLevel): G[Unit] = fk(logger.setLogLevel(level)) override def isTraceEnabled: G[Boolean] = fk(logger.isTraceEnabled) @@ -208,9 +208,9 @@ object ConsoleLogger { } def withContext[F[_]]( - logger: ConsoleLogger[F] - )(baseCtx: Map[String, String]): ConsoleLogger[F] = - new ConsoleLogger[F] { + logger: StdErrLogger[F] + )(baseCtx: Map[String, String]): StdErrLogger[F] = + new StdErrLogger[F] { private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx override def setLogLevel(level: LogLevel): F[Unit] = logger.setLogLevel(level) @@ -262,10 +262,10 @@ object ConsoleLogger { } def withModifiedString[F[_]]( - logger: ConsoleLogger[F], + logger: StdErrLogger[F], f: String => String - ): ConsoleLogger[F] = - new ConsoleLogger[F] { + ): StdErrLogger[F] = + new StdErrLogger[F] { override def setLogLevel(level: LogLevel): F[Unit] = logger.setLogLevel(level) override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala similarity index 85% rename from core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala rename to core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala index 3a447506..cc536c3e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/ConsoleLoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala @@ -33,7 +33,7 @@ import scala.concurrent.duration.Duration * The intended use-case is for one-off scripts, examples, and other situations where a more * fully-featured and performance-optimized logger would be overkill */ -trait ConsoleLoggerFactory[F[_]] extends LoggerFactory[F] { +trait StdErrLoggerFactory[F[_]] extends LoggerFactory[F] { override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] /** @@ -75,41 +75,41 @@ trait ConsoleLoggerFactory[F[_]] extends LoggerFactory[F] { override def addContext(ctx: Map[String, String])(implicit F: Functor[F] - ): ConsoleLoggerFactory[F] = - ConsoleLoggerFactory.addContext(this, ctx) + ): StdErrLoggerFactory[F] = + StdErrLoggerFactory.addContext(this, ctx) override def addContext(pairs: (String, Shown)*)(implicit F: Functor[F] - ): ConsoleLoggerFactory[F] = - ConsoleLoggerFactory.addContext(this, pairs.map { case (k, v) => (k, v.toString) }.toMap) + ): StdErrLoggerFactory[F] = + StdErrLoggerFactory.addContext(this, pairs.map { case (k, v) => (k, v.toString) }.toMap) override def withModifiedString(f: String => String)(implicit F: Functor[F] - ): ConsoleLoggerFactory[F] = - ConsoleLoggerFactory.withModifiedString(this, f) + ): StdErrLoggerFactory[F] = + StdErrLoggerFactory.withModifiedString(this, f) - override def mapK[G[_]](fk: F ~> G)(implicit F: Functor[F]): ConsoleLoggerFactory[G] = - ConsoleLoggerFactory.mapK[F, G](fk)(this) + override def mapK[G[_]](fk: F ~> G)(implicit F: Functor[F]): StdErrLoggerFactory[G] = + StdErrLoggerFactory.mapK[F, G](fk)(this) } -object ConsoleLoggerFactory { +object StdErrLoggerFactory { def apply[F[_]: Async: Console]( defaultLogLevel: LogLevel, logLevelOverrides: (String, LogLevel)* - ): Resource[F, ConsoleLoggerFactory[F]] = - apply[F](defaultLogLevel, ConsoleLogFormat.Default, logLevelOverrides*) + ): Resource[F, StdErrLoggerFactory[F]] = + apply[F](defaultLogLevel, LogFormatter.Default, logLevelOverrides*) def apply[F[_]: Async: Console]( defaultLogLevel: LogLevel, - format: ConsoleLogFormat, + format: LogFormatter, logLevelOverrides: (String, LogLevel)* - ): Resource[F, ConsoleLoggerFactory[F]] = + ): Resource[F, StdErrLoggerFactory[F]] = Dispatcher.sequential[F].evalMap { dispatcher => ( Ref[F].of(defaultLogLevel), Ref[F].of(Chain.fromSeq(logLevelOverrides.map((new LogLevelOverride(_, _)).tupled))) ).mapN { (globalLogLevelRef, logLevelOverrides) => - new ConsoleLoggerFactory[F] { + new StdErrLoggerFactory[F] { override def setGlobalLogLevel(logLevel: LogLevel): F[Unit] = globalLogLevelRef.set(logLevel) @@ -124,7 +124,7 @@ object ConsoleLoggerFactory { (globalLogLevel, logLevelOverrides) => val logLevel = logLevelOverrides.find(_.matches(name)).fold(globalLogLevel)(_.logLevel) - ConsoleLogger[F](name, logLevel, format) + StdErrLogger[F](name, logLevel, format) }.widen override def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = @@ -140,8 +140,8 @@ object ConsoleLoggerFactory { private def mapK[F[_]: Functor, G[_]]( fk: F ~> G - )(lf: ConsoleLoggerFactory[F]): ConsoleLoggerFactory[G] = - new ConsoleLoggerFactory[G] { + )(lf: StdErrLoggerFactory[F]): StdErrLoggerFactory[G] = + new StdErrLoggerFactory[G] { override def setGlobalLogLevel(logLevel: LogLevel): G[Unit] = fk( lf.setGlobalLogLevel(logLevel) ) @@ -162,10 +162,10 @@ object ConsoleLoggerFactory { } private def addContext[F[_]: Functor]( - lf: ConsoleLoggerFactory[F], + lf: StdErrLoggerFactory[F], ctx: Map[String, String] - ): ConsoleLoggerFactory[F] = - new ConsoleLoggerFactory[F] { + ): StdErrLoggerFactory[F] = + new StdErrLoggerFactory[F] { override def setGlobalLogLevel(logLevel: LogLevel): F[Unit] = lf.setGlobalLogLevel(logLevel) override def addLogLevelOverride(prefix: String, logLevel: LogLevel): F[Unit] = @@ -182,10 +182,10 @@ object ConsoleLoggerFactory { } private def withModifiedString[F[_]: Functor]( - lf: ConsoleLoggerFactory[F], + lf: StdErrLoggerFactory[F], f: String => String - ): ConsoleLoggerFactory[F] = - new ConsoleLoggerFactory[F] { + ): StdErrLoggerFactory[F] = + new StdErrLoggerFactory[F] { override def setGlobalLogLevel(logLevel: LogLevel): F[Unit] = lf.setGlobalLogLevel(logLevel) override def addLogLevelOverride(prefix: String, logLevel: LogLevel): F[Unit] = From 6ae7f0b7a37028ef4c5c8430f8647220a4b92db7 Mon Sep 17 00:00:00 2001 From: Morgen Peschke Date: Tue, 10 Jun 2025 13:52:48 -0700 Subject: [PATCH 9/9] Remove StdErrLogger.setLogLevel --- .../log4cats/extras/StdErrLogger.scala | 183 ++++++++---------- .../log4cats/extras/StdErrLoggerFactory.scala | 2 +- 2 files changed, 87 insertions(+), 98 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala index 6b495bf0..e3393cdb 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLogger.scala @@ -16,7 +16,7 @@ package org.typelevel.log4cats.extras -import cats.effect.kernel.{Async, Ref} +import cats.effect.kernel.Async import cats.effect.std.Console import cats.syntax.all.* import cats.{~>, Show} @@ -39,11 +39,6 @@ import java.time.Instant */ trait StdErrLogger[F[_]] extends SelfAwareStructuredLogger[F] { - /** - * Because there is no `log4j` backend, the log level can be set directly - */ - def setLogLevel(level: LogLevel): F[Unit] - override def mapK[G[_]](fk: F ~> G): StdErrLogger[G] = StdErrLogger.mapK(this, fk) @@ -59,94 +54,94 @@ trait StdErrLogger[F[_]] extends SelfAwareStructuredLogger[F] { StdErrLogger.withModifiedString(this, f) } object StdErrLogger { - def apply[F[_]: Console: Async](name: String, initialLogLevel: LogLevel): F[StdErrLogger[F]] = - apply[F](name, initialLogLevel, LogFormatter.Default) + def apply[F[_]: Console: Async](name: String, logLevel: LogLevel): StdErrLogger[F] = + apply[F](name, logLevel, LogFormatter.Default) - def apply[F[_]](name: String, initialLogLevel: LogLevel, format: LogFormatter)(implicit + def apply[F[_]](name: String, logLevel: LogLevel, format: LogFormatter)(implicit console: Console[F], F: Async[F] - ): F[StdErrLogger[F]] = - Ref[F].of(initialLogLevel).map { logLevelRef => - new StdErrLogger[F] { - override def setLogLevel(level: LogLevel): F[Unit] = logLevelRef.set(level) - - private def isLevelEnabled(level: LogLevel): F[Boolean] = logLevelRef.get.map(_ >= level) - - override def isTraceEnabled: F[Boolean] = isLevelEnabled(LogLevel.Trace) - override def isDebugEnabled: F[Boolean] = isLevelEnabled(LogLevel.Debug) - override def isInfoEnabled: F[Boolean] = isLevelEnabled(LogLevel.Info) - override def isWarnEnabled: F[Boolean] = isLevelEnabled(LogLevel.Warn) - override def isErrorEnabled: F[Boolean] = isLevelEnabled(LogLevel.Error) - - private def now: F[Instant] = F.realTime.map(d => Instant.EPOCH.plusNanos(d.toNanos)) - private def log(level: LogLevel, msg: => String): F[Unit] = - isLevelEnabled(level).ifM( - now.map(format.format(name, level, _, msg)).flatMap(console.errorln(_)), - F.unit - ) - - private def log(level: LogLevel, msg: => String, throwable: Throwable): F[Unit] = - isLevelEnabled(level).ifM( - now.map(format.format(name, level, _, msg, throwable)).flatMap(console.errorln(_)), - F.unit - ) - - private def log(level: LogLevel, msg: => String, ctx: Map[String, String]): F[Unit] = - isLevelEnabled(level).ifM( - now.map(format.format(name, level, _, msg, ctx)).flatMap(console.errorln(_)), - F.unit - ) - - private def log( - level: LogLevel, - msg: => String, - throwable: Throwable, - ctx: Map[String, String] - ): F[Unit] = - isLevelEnabled(level).ifM( - now.map(format.format(name, level, _, msg, ctx, throwable)).flatMap(console.errorln(_)), - F.unit - ) - - override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Trace, msg, ctx) - override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Debug, msg, ctx) - override def info(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Info, msg, ctx) - override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Warn, msg, ctx) - override def error(ctx: Map[String, String])(msg: => String): F[Unit] = - log(LogLevel.Error, msg, ctx) - - override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Trace, msg, t, ctx) - override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Debug, msg, t, ctx) - override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Info, msg, t, ctx) - override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Warn, msg, t, ctx) - override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - log(LogLevel.Error, msg, t, ctx) - - override def error(t: Throwable)(message: => String): F[Unit] = - log(LogLevel.Error, message, t) - override def warn(t: Throwable)(message: => String): F[Unit] = - log(LogLevel.Warn, message, t) - override def info(t: Throwable)(message: => String): F[Unit] = - log(LogLevel.Info, message, t) - override def debug(t: Throwable)(message: => String): F[Unit] = - log(LogLevel.Debug, message, t) - override def trace(t: Throwable)(message: => String): F[Unit] = - log(LogLevel.Trace, message, t) - - override def error(message: => String): F[Unit] = log(LogLevel.Error, message) - override def warn(message: => String): F[Unit] = log(LogLevel.Warn, message) - override def info(message: => String): F[Unit] = log(LogLevel.Info, message) - override def debug(message: => String): F[Unit] = log(LogLevel.Debug, message) - override def trace(message: => String): F[Unit] = log(LogLevel.Trace, message) - } + ): StdErrLogger[F] = + new StdErrLogger[F] { + private def isLevelEnabled(level: LogLevel): Boolean = logLevel >= level + + override def isTraceEnabled: F[Boolean] = isLevelEnabled(LogLevel.Trace).pure[F] + + override def isDebugEnabled: F[Boolean] = isLevelEnabled(LogLevel.Debug).pure[F] + + override def isInfoEnabled: F[Boolean] = isLevelEnabled(LogLevel.Info).pure[F] + + override def isWarnEnabled: F[Boolean] = isLevelEnabled(LogLevel.Warn).pure[F] + + override def isErrorEnabled: F[Boolean] = isLevelEnabled(LogLevel.Error).pure[F] + + private def now: F[Instant] = F.realTime.map(d => Instant.EPOCH.plusNanos(d.toNanos)) + + private def log(level: LogLevel, msg: => String): F[Unit] = + now + .map(format.format(name, level, _, msg)) + .flatMap(console.errorln(_)) + .whenA(isLevelEnabled(level)) + + private def log(level: LogLevel, msg: => String, throwable: Throwable): F[Unit] = + now + .map(format.format(name, level, _, msg, throwable)) + .flatMap(console.errorln(_)) + .whenA(isLevelEnabled(level)) + + private def log(level: LogLevel, msg: => String, ctx: Map[String, String]): F[Unit] = + now + .map(format.format(name, level, _, msg, ctx)) + .flatMap(console.errorln(_)) + .whenA(isLevelEnabled(level)) + + private def log( + level: LogLevel, + msg: => String, + throwable: Throwable, + ctx: Map[String, String] + ): F[Unit] = + now + .map(format.format(name, level, _, msg, ctx, throwable)) + .flatMap(console.errorln(_)) + .whenA(isLevelEnabled(level)) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Trace, msg, ctx) + + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Debug, msg, ctx) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Info, msg, ctx) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Warn, msg, ctx) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = + log(LogLevel.Error, msg, ctx) + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Trace, msg, t, ctx) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Debug, msg, t, ctx) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Info, msg, t, ctx) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Warn, msg, t, ctx) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + log(LogLevel.Error, msg, t, ctx) + + override def error(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Error, message, t) + override def warn(t: Throwable)(message: => String): F[Unit] = log(LogLevel.Warn, message, t) + override def info(t: Throwable)(message: => String): F[Unit] = log(LogLevel.Info, message, t) + override def debug(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Debug, message, t) + override def trace(t: Throwable)(message: => String): F[Unit] = + log(LogLevel.Trace, message, t) + + override def error(message: => String): F[Unit] = log(LogLevel.Error, message) + override def warn(message: => String): F[Unit] = log(LogLevel.Warn, message) + override def info(message: => String): F[Unit] = log(LogLevel.Info, message) + override def debug(message: => String): F[Unit] = log(LogLevel.Debug, message) + override def trace(message: => String): F[Unit] = log(LogLevel.Trace, message) } private def mapK[F[_], G[_]]( @@ -154,8 +149,6 @@ object StdErrLogger { fk: F ~> G ): StdErrLogger[G] = new StdErrLogger[G] { - override def setLogLevel(level: LogLevel): G[Unit] = fk(logger.setLogLevel(level)) - override def isTraceEnabled: G[Boolean] = fk(logger.isTraceEnabled) override def isDebugEnabled: G[Boolean] = fk(logger.isDebugEnabled) override def isInfoEnabled: G[Boolean] = fk(logger.isInfoEnabled) @@ -213,8 +206,6 @@ object StdErrLogger { new StdErrLogger[F] { private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx - override def setLogLevel(level: LogLevel): F[Unit] = logger.setLogLevel(level) - override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled override def isInfoEnabled: F[Boolean] = logger.isInfoEnabled @@ -266,8 +257,6 @@ object StdErrLogger { f: String => String ): StdErrLogger[F] = new StdErrLogger[F] { - override def setLogLevel(level: LogLevel): F[Unit] = logger.setLogLevel(level) - override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled override def isInfoEnabled: F[Boolean] = logger.isInfoEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala index cc536c3e..eb884df9 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StdErrLoggerFactory.scala @@ -120,7 +120,7 @@ object StdErrLoggerFactory { logLevelOverrides.update(_.filterNot(_.prefix == prefix)) override def fromName(name: String): F[SelfAwareStructuredLogger[F]] = - (globalLogLevelRef.get, logLevelOverrides.get).flatMapN { + (globalLogLevelRef.get, logLevelOverrides.get).mapN { (globalLogLevel, logLevelOverrides) => val logLevel = logLevelOverrides.find(_.matches(name)).fold(globalLogLevel)(_.logLevel)