From 485b98bd24959ac8568f73387e6b0fc7f66324ca Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 15:50:38 +0000 Subject: [PATCH 01/62] Added Logger Kernel Initial File --- .../org/typelevel/log4cats/Context.scala | 62 ++++++++ .../org/typelevel/log4cats/JsonLike.scala | 43 +++++ .../scala/org/typelevel/log4cats/Log.scala | 150 ++++++++++++++++++ .../org/typelevel/log4cats/LogLevel.scala | 59 +++++++ .../org/typelevel/log4cats/LoggerKernel.scala | 53 +++++++ 5 files changed, 367 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/Context.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/Log.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala new file mode 100644 index 00000000..781e462c --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala @@ -0,0 +1,62 @@ +/* + * 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 + +import scala.concurrent.duration.FiniteDuration +import org.typelevel.log4cats.JsonLike.Aux + +/** A value that can be written into a json-like construct, provided a visitor. + */ +trait Context { + def capture[J](jsonLike: JsonLike.Aux[J]): J +} + +object Context { + trait Encoder[A] { + def encode[J](json: JsonLike.Aux[J], a: A): J + } + + object Encoder { + def apply[A](implicit ev: Encoder[A]): ev.type = ev + + implicit val stringEncoder: Encoder[String] = new Encoder[String] { + def encode[J](json: JsonLike.Aux[J], a: String) = json.string(a) + } + + implicit val intEncoder: Encoder[Int] = new Encoder[Int] { + def encode[J](json: JsonLike.Aux[J], a: Int) = json.int(a) + } + + implicit val booleanEncoder: Encoder[Boolean] = new Encoder[Boolean] { + def encode[J](json: Aux[J], a: Boolean): J = json.bool(a) + } + + implicit val timestampEncoder: Encoder[FiniteDuration] = + new Encoder[FiniteDuration] { + def encode[J](json: JsonLike.Aux[J], a: FiniteDuration) = + json.timestamp(a) + } + } + + implicit def toContext[A: Encoder](a: A): Context = + DeferredRecord(a, Encoder[A]) + + private case class DeferredRecord[A](a: A, encoder: Encoder[A]) + extends Context { + def capture[J](jsonLike: JsonLike.Aux[J]): J = encoder.encode(jsonLike, a) + } +} \ No newline at end of file diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala new file mode 100644 index 00000000..e0a7b723 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala @@ -0,0 +1,43 @@ +/* + * 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 + +import scala.concurrent.duration.FiniteDuration + +/** A visitor-like construct that allows for capturing contextual values of + * several types, without enforcing an in-memory representation or a third-party dependency. + */ +trait JsonLike { + type J + + def nul: J + def bool(value: Boolean): J + def int(value: Int): J + def short(value: Int): J + def long(value: Int): J + def double(value: Double): J + def timestamp(ts: FiniteDuration): J + def string(value: String): J + def obj(bindings: (String, J)*): J + def arr(elems: J*): J +} + +object JsonLike { + type Aux[Json] = JsonLike { + type J = Json + } +} \ No newline at end of file diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala new file mode 100644 index 00000000..c062a4a7 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -0,0 +1,150 @@ +/* + * 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 + +import scala.collection.{Map => MapLike} +import scala.collection.mutable.{Map => MMap} +import scala.concurrent.duration.FiniteDuration + +/** Low-level interface exposing methods to enrich a log record with relevant + * information. The methods are designed to capture elements that cannot be + * easily captured from a monadic context (or by running an effect). Elements + * such as timestamps should be provided by means of middlewares. + */ +trait Log { + def timestamp: Option[FiniteDuration] + def level: LogLevel + def levelValue: Double + def message: String + def throwable: Option[Throwable] + def context: Map[String, Context] + def fileName: Option[String] + def className: Option[String] + def methodName: Option[String] + def line: Option[Int] + + def unsafeThrowable: Throwable + def unsafeContext: MapLike[String, Context] +} + +object Log { + trait Builder { + def withTimestamp(value: FiniteDuration): Builder + def withLevel(level: LogLevel): Builder + def withLevelValue(levelValue: Double): Builder + def withMessage(message: => String): Builder + def withThrowable(throwable: Throwable): Builder + def withContext(name: String)(f: Context): Builder + def withFileName(name: String): Builder + def withClassName(name: String): Builder + def withLine(line: Int): Builder + + final def withContextMap[A: Context.Encoder]( + mdc: Map[String, A] + ): Builder = { + var builder = this + mdc.foreach { case (k, v) => + builder = withContext(k)(v) + } + builder + } + + def build(): Log + } + + def mutableBuilder(): Builder = new MutableBuilder() + + private class MutableBuilder private[Log] () extends Builder with Log { + def build(): Log = this + + def timestamp: Option[FiniteDuration] = Option(_timestamp) + def level: LogLevel = if (_level == null) LogLevel.Debug else _level + def levelValue: Double = + if (_levelValue < 0) level.value else _levelValue + def message: String = if (_message == null) "" else _message + def throwable: Option[Throwable] = Option(_throwable) + def context: Map[String, Context] = + if (_context == null) Map.empty else _context.toMap + + def className: Option[String] = Option(_className) + def fileName: Option[String] = Option(_fileName) + def methodName: Option[String] = Option(_methodName) + def line: Option[Int] = Some(_line).filter(_ > 0) + + def unsafeThrowable: Throwable = _throwable + def unsafeContext: MapLike[String, Context] = _context + + private var _timestamp: FiniteDuration = null + private var _level: LogLevel = null + private var _levelValue: Double = -1 + private var _message: String = null + private var _throwable: Throwable = null + private var _context: MMap[String, Context] = null + private var _fileName: String = null + private var _className: String = null + private var _methodName: String = null + private var _line: Int = -1 + + def withTimestamp(value: FiniteDuration): this.type = { + this._timestamp = value + this + } + + def withLevel(level: LogLevel): this.type = { + this._level = level + this + } + + def withLevelValue(levelValue: Double): this.type = { + this._levelValue = levelValue + this + } + + def withMessage(message: => String): this.type = { + this._message = message + this + } + + def withThrowable(throwable: Throwable): this.type = { + this._throwable = throwable + this + } + + def withContext(name: String)(value: Context): this.type = { + if (this._context == null) { + this._context = MMap.empty[String, Context] + } + this._context += name -> value + this + } + + def withFileName(name: String): this.type = { + this._fileName = name + this + } + + def withClassName(name: String): this.type = { + this._className = name + this + } + + def withLine(line: Int): this.type = { + this._line = line + this + } + } +} \ No newline at end of file diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala new file mode 100644 index 00000000..7043741c --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala @@ -0,0 +1,59 @@ +/* + * 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 + +final case class LogLevel(name: String, value: Double) { + def namePadded: String = LogLevel.padded(this) + + LogLevel.add(this) +} + +object LogLevel { + private var maxLength = 0 + + private var map = Map.empty[String, LogLevel] + private var padded = Map.empty[LogLevel, String] + + implicit final val LevelOrdering: Ordering[LogLevel] = + Ordering.by[LogLevel, Double](_.value).reverse + + val Trace: LogLevel = LogLevel("TRACE", 100.0) + val Debug: LogLevel = LogLevel("DEBUG", 200.0) + val Info: LogLevel = LogLevel("INFO", 300.0) + val Warn: LogLevel = LogLevel("WARN", 400.0) + val Error: LogLevel = LogLevel("ERROR", 500.0) + val Fatal: LogLevel = LogLevel("FATAL", 600.0) + + def add(level: LogLevel): Unit = synchronized { + val length = level.name.length + map += level.name.toLowerCase -> level + if (length > maxLength) { + maxLength = length + padded = map.map { case (_, level) => + level -> level.name.padTo(maxLength, ' ').mkString + } + } else { + padded += level -> level.name.padTo(maxLength, ' ').mkString + } + } + + def get(name: String): Option[LogLevel] = map.get(name.toLowerCase) + + def apply(name: String): LogLevel = get(name).getOrElse( + throw new RuntimeException(s"Level not found by name: $name") + ) +} \ No newline at end of file diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala new file mode 100644 index 00000000..6445ed60 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala @@ -0,0 +1,53 @@ +/* + * 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 + +/** This is the fundamental abstraction: a single-abstract-method interface + * that has the following properties: + * + * - Doesn't enforce a specific memory layout. More specific interfaces can + * back `LogRecord` with mutables/immutables values, and avoid storing + * things that are not important. + * + * - LogRecord allows to precisely capture a lot of information. In + * particular, it does not enforce a `Map[String, String]` representation + * of context values that is not sufficient to leverage all the power from + * logging backends query engines, and without pulling a third-party JSON + * library. + * + * - The SAM-like nature of the construct makes it inherently middleware + * friendly, as a single method call needs to be intercepted/proxied in + * order to amend the behaviour of the logger. + * + * This also means that different libraries can use wrappers on top of this + * kernel interface to use whatever UX is preferred without necessarily + * imposing constraints on the underlying implementation. + */ +trait LoggerKernel[F[_]] { + def log(level: LogLevel, record: Log.Builder => Log.Builder): F[Unit] + + final def logTrace(record: Log.Builder => Log.Builder): F[Unit] = + log(LogLevel.Trace, record) + final def logDebug(record: Log.Builder => Log.Builder): F[Unit] = + log(LogLevel.Debug, record) + final def logInfo(record: Log.Builder => Log.Builder): F[Unit] = + log(LogLevel.Info, record) + final def logWarn(record: Log.Builder => Log.Builder): F[Unit] = + log(LogLevel.Warn, record) + final def logError(record: Log.Builder => Log.Builder): F[Unit] = + log(LogLevel.Error, record) +} \ No newline at end of file From 8d8223f521d66f76ddf7fc770dfc3bccbe3da0fd Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 16:14:19 +0000 Subject: [PATCH 02/62] sbt scalafmt --- .../org/typelevel/log4cats/Context.scala | 12 +++--- .../org/typelevel/log4cats/JsonLike.scala | 9 +++-- .../scala/org/typelevel/log4cats/Log.scala | 13 +++--- .../org/typelevel/log4cats/LogLevel.scala | 2 +- .../org/typelevel/log4cats/LoggerKernel.scala | 40 +++++++++---------- 5 files changed, 37 insertions(+), 39 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala index 781e462c..11fbb878 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala @@ -19,8 +19,9 @@ package org.typelevel.log4cats import scala.concurrent.duration.FiniteDuration import org.typelevel.log4cats.JsonLike.Aux -/** A value that can be written into a json-like construct, provided a visitor. - */ +/** + * A value that can be written into a json-like construct, provided a visitor. + */ trait Context { def capture[J](jsonLike: JsonLike.Aux[J]): J } @@ -29,7 +30,7 @@ object Context { trait Encoder[A] { def encode[J](json: JsonLike.Aux[J], a: A): J } - + object Encoder { def apply[A](implicit ev: Encoder[A]): ev.type = ev @@ -55,8 +56,7 @@ object Context { implicit def toContext[A: Encoder](a: A): Context = DeferredRecord(a, Encoder[A]) - private case class DeferredRecord[A](a: A, encoder: Encoder[A]) - extends Context { + private case class DeferredRecord[A](a: A, encoder: Encoder[A]) extends Context { def capture[J](jsonLike: JsonLike.Aux[J]): J = encoder.encode(jsonLike, a) } -} \ No newline at end of file +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala index e0a7b723..2d1e27bf 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala @@ -18,9 +18,10 @@ package org.typelevel.log4cats import scala.concurrent.duration.FiniteDuration -/** A visitor-like construct that allows for capturing contextual values of - * several types, without enforcing an in-memory representation or a third-party dependency. - */ +/** + * A visitor-like construct that allows for capturing contextual values of several types, without + * enforcing an in-memory representation or a third-party dependency. + */ trait JsonLike { type J @@ -40,4 +41,4 @@ object JsonLike { type Aux[Json] = JsonLike { type J = Json } -} \ No newline at end of file +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index c062a4a7..02370c57 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -20,11 +20,12 @@ import scala.collection.{Map => MapLike} import scala.collection.mutable.{Map => MMap} import scala.concurrent.duration.FiniteDuration -/** Low-level interface exposing methods to enrich a log record with relevant - * information. The methods are designed to capture elements that cannot be - * easily captured from a monadic context (or by running an effect). Elements - * such as timestamps should be provided by means of middlewares. - */ +/** + * Low-level interface exposing methods to enrich a log record with relevant information. The + * methods are designed to capture elements that cannot be easily captured from a monadic context + * (or by running an effect). Elements such as timestamps should be provided by means of + * middlewares. + */ trait Log { def timestamp: Option[FiniteDuration] def level: LogLevel @@ -147,4 +148,4 @@ object Log { this } } -} \ No newline at end of file +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala index 7043741c..2c862c99 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala @@ -56,4 +56,4 @@ object LogLevel { def apply(name: String): LogLevel = get(name).getOrElse( throw new RuntimeException(s"Level not found by name: $name") ) -} \ No newline at end of file +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala index 6445ed60..7b71a499 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala @@ -16,27 +16,23 @@ package org.typelevel.log4cats -/** This is the fundamental abstraction: a single-abstract-method interface - * that has the following properties: - * - * - Doesn't enforce a specific memory layout. More specific interfaces can - * back `LogRecord` with mutables/immutables values, and avoid storing - * things that are not important. - * - * - LogRecord allows to precisely capture a lot of information. In - * particular, it does not enforce a `Map[String, String]` representation - * of context values that is not sufficient to leverage all the power from - * logging backends query engines, and without pulling a third-party JSON - * library. - * - * - The SAM-like nature of the construct makes it inherently middleware - * friendly, as a single method call needs to be intercepted/proxied in - * order to amend the behaviour of the logger. - * - * This also means that different libraries can use wrappers on top of this - * kernel interface to use whatever UX is preferred without necessarily - * imposing constraints on the underlying implementation. - */ +/** + * This is the fundamental abstraction: a single-abstract-method interface that has the following + * properties: + * + * - Doesn't enforce a specific memory layout. More specific interfaces can back `LogRecord` with + * mutables/immutables values, and avoid storing things that are not important. + * - LogRecord allows to precisely capture a lot of information. In particular, it does not + * enforce a `Map[String, String]` representation of context values that is not sufficient to + * leverage all the power from logging backends query engines, and without pulling a third-party + * JSON library. + * - The SAM-like nature of the construct makes it inherently middleware friendly, as a single + * method call needs to be intercepted/proxied in order to amend the behaviour of the logger. + * + * This also means that different libraries can use wrappers on top of this kernel interface to use + * whatever UX is preferred without necessarily imposing constraints on the underlying + * implementation. + */ trait LoggerKernel[F[_]] { def log(level: LogLevel, record: Log.Builder => Log.Builder): F[Unit] @@ -50,4 +46,4 @@ trait LoggerKernel[F[_]] { log(LogLevel.Warn, record) final def logError(record: Log.Builder => Log.Builder): F[Unit] = log(LogLevel.Error, record) -} \ No newline at end of file +} From f6e9d555389211bb48585619a9333ee5c7ddc7e6 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 16:46:50 +0000 Subject: [PATCH 03/62] added withMethodName method --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 02370c57..5fae0cf9 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -147,5 +147,10 @@ object Log { this._line = line this } + + def withMethodName(name: String): this.type = { + this._methodName = name + this + } } } From e7f0be4c42b57775265a84194d39d9f4cc29dfe3 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:13:55 +0000 Subject: [PATCH 04/62] added more SAM type specific files --- .../org/typelevel/log4cats/LogRecord.scala | 43 +++++++++++++++++ .../org/typelevel/log4cats/Recordable.scala | 46 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala new file mode 100644 index 00000000..4db71bf5 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala @@ -0,0 +1,43 @@ +/* + * 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 + +/** + * This allows to capture several elements in vararg-based interface methods, enriching a single log + * with various pieces of information. + * + * This allows for an interesting UX, where the details of the encoding of some data into a log can + * be separate from the actual log statements. + */ +trait LogRecord extends (Log.Builder => Log.Builder) + +object LogRecord { + def combine(all: Seq[LogRecord]): LogRecord = Combined(all) + + implicit def toLogRecord[A: Recordable](value: => A): LogRecord = + Recordable[A].record(value) + + private case class Combined(all: Seq[LogRecord]) extends LogRecord { + def apply(record: Log.Builder): Log.Builder = { + var current = record + all.foreach { logBit => + current = logBit(current) + } + current + } + } +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala new file mode 100644 index 00000000..52286be1 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala @@ -0,0 +1,46 @@ +/* + * 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 + +/** + * Typeclass representing the notion that a value can contribute to a log, by transforming it in + * some way. + */ +trait Recordable[A] { + def record(value: => A): LogRecord +} + +object Recordable { + def apply[A](implicit ev: Recordable[A]): ev.type = ev + + implicit val stringLoggable: Recordable[String] = new Recordable[String] { + def record(value: => String) = _.withMessage(value) + } + + implicit def tupleLoggable[T: Context.Encoder]: Recordable[(String, T)] = + new Recordable[(String, T)] { + override def record(value: => (String, T)): LogRecord = { + val (k, v) = value + (_: Log.Builder).withContext(k)(v) + } + } + + implicit def throwableLoggable[T <: Throwable]: Recordable[T] = + new Recordable[T] { + def record(value: => T): LogRecord = _.withThrowable(value) + } +} From 7f6dc0e3f070f34840e4078ae335c0b2b85a1605 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:17:23 +0000 Subject: [PATCH 05/62] changed LogLevel to remove conflict --- .../log4cats/extras/DeferredLogMessage.scala | 36 +++++------ .../log4cats/extras/DefferedLogLevel.scala | 63 +++++++++++++++++++ .../log4cats/extras/LogMessage.scala | 22 +++---- .../extras/StructuredLogMessage.scala | 22 +++---- .../log4cats/extras/WriterTLogger.scala | 23 +++---- .../extras/WriterTStructuredLogger.scala | 42 ++++++------- .../log4cats/console/ConsoleLogger.scala | 16 ++--- .../testing/TestingLoggerFactory.scala | 14 ++--- 8 files changed, 151 insertions(+), 87 deletions(-) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala index cca7322a..bc74cb4a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala @@ -35,34 +35,34 @@ import org.typelevel.log4cats.extras.DeferredLogMessage.{ * At some point, this should be renamed to `StructuredLogMessage` and replace the old class. */ sealed trait DeferredLogMessage { - def level: LogLevel + def level: DefferedLogLevel def context: Map[String, String] def throwableOpt: Option[Throwable] def message: () => String def log[F[_]](logger: Logger[F]): F[Unit] = { level match { - case LogLevel.Error => + case DefferedLogLevel.Error => throwableOpt match { case Some(e) => logger.error(e)(message()) case None => logger.error(message()) } - case LogLevel.Warn => + case DefferedLogLevel.Warn => throwableOpt match { case Some(e) => logger.warn(e)(message()) case None => logger.warn(message()) } - case LogLevel.Info => + case DefferedLogLevel.Info => throwableOpt match { case Some(e) => logger.info(e)(message()) case None => logger.info(message()) } - case LogLevel.Debug => + case DefferedLogLevel.Debug => throwableOpt match { case Some(e) => logger.debug(e)(message()) case None => logger.debug(message()) } - case LogLevel.Trace => + case DefferedLogLevel.Trace => throwableOpt match { case Some(e) => logger.trace(e)(message()) case None => logger.trace(message()) @@ -72,27 +72,27 @@ sealed trait DeferredLogMessage { def logStructured[F[_]](logger: StructuredLogger[F]): F[Unit] = { level match { - case LogLevel.Error => + case DefferedLogLevel.Error => throwableOpt match { case Some(e) => logger.error(context, e)(message()) case None => logger.error(context)(message()) } - case LogLevel.Warn => + case DefferedLogLevel.Warn => throwableOpt match { case Some(e) => logger.warn(context, e)(message()) case None => logger.warn(context)(message()) } - case LogLevel.Info => + case DefferedLogLevel.Info => throwableOpt match { case Some(e) => logger.info(context, e)(message()) case None => logger.info(context)(message()) } - case LogLevel.Debug => + case DefferedLogLevel.Debug => throwableOpt match { case Some(e) => logger.debug(context, e)(message()) case None => logger.debug(context)(message()) } - case LogLevel.Trace => + case DefferedLogLevel.Trace => throwableOpt match { case Some(e) => logger.trace(context, e)(message()) case None => logger.trace(context)(message()) @@ -111,32 +111,32 @@ sealed trait DeferredLogMessage { } object DeferredLogMessage { def apply( - l: LogLevel, + l: DefferedLogLevel, c: Map[String, String], t: Option[Throwable], m: () => String ): DeferredLogMessage = new DeferredLogMessage { - override val level: LogLevel = l + override val level: DefferedLogLevel = l override val context: Map[String, String] = c override val throwableOpt: Option[Throwable] = t override val message: () => String = m } def trace(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(LogLevel.Trace, c, t, m) + apply(DefferedLogLevel.Trace, c, t, m) def debug(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(LogLevel.Debug, c, t, m) + apply(DefferedLogLevel.Debug, c, t, m) def info(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(LogLevel.Info, c, t, m) + apply(DefferedLogLevel.Info, c, t, m) def warn(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(LogLevel.Warn, c, t, m) + apply(DefferedLogLevel.Warn, c, t, m) def error(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(LogLevel.Error, c, t, m) + apply(DefferedLogLevel.Error, c, t, m) implicit val deferredStructuredLogMessageHash: Hash[DeferredLogMessage] = Hash.by { l => (l.level, l.context, l.throwableOpt.map(_.getMessage), l.message()) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala new file mode 100644 index 00000000..ee55ab4b --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala @@ -0,0 +1,63 @@ +/* + * 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.* + +sealed trait DefferedLogLevel +object DefferedLogLevel { + case object Error extends DefferedLogLevel + case object Warn extends DefferedLogLevel + case object Info extends DefferedLogLevel + case object Debug extends DefferedLogLevel + case object Trace extends DefferedLogLevel + + def fromString(s: String): Option[DefferedLogLevel] = s.toLowerCase match { + case "error" => Some(DefferedLogLevel.Error) + case "warn" => Some(DefferedLogLevel.Warn) + case "info" => Some(DefferedLogLevel.Info) + case "debug" => Some(DefferedLogLevel.Debug) + case "trace" => Some(DefferedLogLevel.Trace) + case "loglevel.error" => Some(DefferedLogLevel.Error) + case "loglevel.warn" => Some(DefferedLogLevel.Warn) + case "loglevel.info" => Some(DefferedLogLevel.Info) + case "loglevel.debug" => Some(DefferedLogLevel.Debug) + case "loglevel.trace" => Some(DefferedLogLevel.Trace) + case _ => None + } + + implicit val logLevelShow: Show[DefferedLogLevel] = Show.show[DefferedLogLevel] { + case Error => "DefferedLogLevel.Error" + case Warn => "DefferedLogLevel.Warn" + case Info => "DefferedLogLevel.Info" + case Debug => "DefferedLogLevel.Debug" + case Trace => "DefferedLogLevel.Trace" + } + + private def toIndex(l: DefferedLogLevel): Int = l match { + case Error => 5 + case Warn => 4 + case Info => 3 + case Debug => 2 + case Trace => 1 + } + + implicit final val logLevelOrder: Order[DefferedLogLevel] = + Order.by[DefferedLogLevel, Int](toIndex) + + implicit final val logLevelHash: Hash[DefferedLogLevel] = Hash.by(toIndex) +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala index 176c0848..698e5a2c 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala @@ -20,25 +20,25 @@ import cats.* import cats.syntax.all.* import org.typelevel.log4cats.Logger -final case class LogMessage(level: LogLevel, t: Option[Throwable], message: String) +final case class LogMessage(level: DefferedLogLevel, t: Option[Throwable], message: String) object LogMessage { implicit val logMessageShow: Show[LogMessage] = Show.show[LogMessage](l => show"LogMessage(${l.level},${l.t.map(_.getMessage)},${l.message})") def log[F[_]](sm: LogMessage, l: Logger[F]): F[Unit] = sm match { - case LogMessage(LogLevel.Trace, Some(t), m) => l.trace(t)(m) - case LogMessage(LogLevel.Trace, None, m) => l.trace(m) + case LogMessage(DefferedLogLevel.Trace, Some(t), m) => l.trace(t)(m) + case LogMessage(DefferedLogLevel.Trace, None, m) => l.trace(m) - case LogMessage(LogLevel.Debug, Some(t), m) => l.debug(t)(m) - case LogMessage(LogLevel.Debug, None, m) => l.debug(m) + case LogMessage(DefferedLogLevel.Debug, Some(t), m) => l.debug(t)(m) + case LogMessage(DefferedLogLevel.Debug, None, m) => l.debug(m) - case LogMessage(LogLevel.Info, Some(t), m) => l.info(t)(m) - case LogMessage(LogLevel.Info, None, m) => l.info(m) + case LogMessage(DefferedLogLevel.Info, Some(t), m) => l.info(t)(m) + case LogMessage(DefferedLogLevel.Info, None, m) => l.info(m) - case LogMessage(LogLevel.Warn, Some(t), m) => l.warn(t)(m) - case LogMessage(LogLevel.Warn, None, m) => l.warn(m) + case LogMessage(DefferedLogLevel.Warn, Some(t), m) => l.warn(t)(m) + case LogMessage(DefferedLogLevel.Warn, None, m) => l.warn(m) - case LogMessage(LogLevel.Error, Some(t), m) => l.error(t)(m) - case LogMessage(LogLevel.Error, None, m) => l.error(m) + case LogMessage(DefferedLogLevel.Error, Some(t), m) => l.error(t)(m) + case LogMessage(DefferedLogLevel.Error, None, m) => l.error(m) } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala index ca6a869a..a8b46b04 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala @@ -21,27 +21,27 @@ import cats.syntax.all.* import org.typelevel.log4cats.StructuredLogger final case class StructuredLogMessage( - level: LogLevel, + level: DefferedLogLevel, context: Map[String, String], throwableOpt: Option[Throwable], message: String ) object StructuredLogMessage { def log[F[_]](sm: StructuredLogMessage, l: StructuredLogger[F]): F[Unit] = sm match { - case StructuredLogMessage(LogLevel.Trace, ctx, Some(t), m) => l.trace(ctx, t)(m) - case StructuredLogMessage(LogLevel.Trace, ctx, None, m) => l.trace(ctx)(m) + case StructuredLogMessage(DefferedLogLevel.Trace, ctx, Some(t), m) => l.trace(ctx, t)(m) + case StructuredLogMessage(DefferedLogLevel.Trace, ctx, None, m) => l.trace(ctx)(m) - case StructuredLogMessage(LogLevel.Debug, ctx, Some(t), m) => l.debug(ctx, t)(m) - case StructuredLogMessage(LogLevel.Debug, ctx, None, m) => l.debug(ctx)(m) + case StructuredLogMessage(DefferedLogLevel.Debug, ctx, Some(t), m) => l.debug(ctx, t)(m) + case StructuredLogMessage(DefferedLogLevel.Debug, ctx, None, m) => l.debug(ctx)(m) - case StructuredLogMessage(LogLevel.Info, ctx, Some(t), m) => l.info(ctx, t)(m) - case StructuredLogMessage(LogLevel.Info, ctx, None, m) => l.info(ctx)(m) + case StructuredLogMessage(DefferedLogLevel.Info, ctx, Some(t), m) => l.info(ctx, t)(m) + case StructuredLogMessage(DefferedLogLevel.Info, ctx, None, m) => l.info(ctx)(m) - case StructuredLogMessage(LogLevel.Warn, ctx, Some(t), m) => l.warn(ctx, t)(m) - case StructuredLogMessage(LogLevel.Warn, ctx, None, m) => l.warn(ctx)(m) + case StructuredLogMessage(DefferedLogLevel.Warn, ctx, Some(t), m) => l.warn(ctx, t)(m) + case StructuredLogMessage(DefferedLogLevel.Warn, ctx, None, m) => l.warn(ctx)(m) - case StructuredLogMessage(LogLevel.Error, ctx, Some(t), m) => l.error(ctx, t)(m) - case StructuredLogMessage(LogLevel.Error, ctx, None, m) => l.error(ctx)(m) + case StructuredLogMessage(DefferedLogLevel.Error, ctx, Some(t), m) => l.error(ctx, t)(m) + case StructuredLogMessage(DefferedLogLevel.Error, ctx, None, m) => l.error(ctx)(m) } implicit val structuredLogMessageShow: Show[StructuredLogMessage] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 70ecd01a..89b8ad65 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -20,6 +20,7 @@ import cats.* import cats.data.* import cats.syntax.all.* import org.typelevel.log4cats.* +import org.typelevel.log4cats.extras.DefferedLogLevel /** * A `SelfAwareLogger` implemented using `cats.data.WriterT`. @@ -47,36 +48,36 @@ object WriterTLogger { override def isErrorEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(errorEnabled) override def trace(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(traceEnabled, LogLevel.Trace, t.some, message) + build(traceEnabled, DefferedLogLevel.Trace, t.some, message) override def trace(message: => String): WriterT[F, G[LogMessage], Unit] = - build(traceEnabled, LogLevel.Trace, None, message) + build(traceEnabled, DefferedLogLevel.Trace, None, message) override def debug(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(debugEnabled, LogLevel.Debug, t.some, message) + build(debugEnabled, DefferedLogLevel.Debug, t.some, message) override def debug(message: => String): WriterT[F, G[LogMessage], Unit] = - build(debugEnabled, LogLevel.Debug, None, message) + build(debugEnabled, DefferedLogLevel.Debug, None, message) override def info(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(infoEnabled, LogLevel.Info, t.some, message) + build(infoEnabled, DefferedLogLevel.Info, t.some, message) override def info(message: => String): WriterT[F, G[LogMessage], Unit] = - build(infoEnabled, LogLevel.Info, None, message) + build(infoEnabled, DefferedLogLevel.Info, None, message) override def warn(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(warnEnabled, LogLevel.Warn, t.some, message) + build(warnEnabled, DefferedLogLevel.Warn, t.some, message) override def warn(message: => String): WriterT[F, G[LogMessage], Unit] = - build(warnEnabled, LogLevel.Warn, None, message) + build(warnEnabled, DefferedLogLevel.Warn, None, message) override def error(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(errorEnabled, LogLevel.Error, t.some, message) + build(errorEnabled, DefferedLogLevel.Error, t.some, message) override def error(message: => String): WriterT[F, G[LogMessage], Unit] = - build(errorEnabled, LogLevel.Error, None, message) + build(errorEnabled, DefferedLogLevel.Error, None, message) private def isEnabled(enabled: Boolean): WriterT[F, G[LogMessage], Boolean] = WriterT.liftF[F, G[LogMessage], Boolean](Applicative[F].pure(enabled)) private def build( enabled: Boolean, - level: LogLevel, + level: DefferedLogLevel, t: Option[Throwable], message: => String ): WriterT[F, G[LogMessage], Unit] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala index b8b218de..83b10468 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala @@ -54,34 +54,34 @@ object WriterTStructuredLogger { override def isErrorEnabled: LoggerF[Boolean] = isEnabled(errorEnabled) override def trace(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, traceEnabled, LogLevel.Trace, t.some, message) + build(Map.empty, traceEnabled, DefferedLogLevel.Trace, t.some, message) override def trace(message: => String): LoggerF[Unit] = - build(Map.empty, traceEnabled, LogLevel.Trace, None, message) + build(Map.empty, traceEnabled, DefferedLogLevel.Trace, None, message) override def debug(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, debugEnabled, LogLevel.Debug, t.some, message) + build(Map.empty, debugEnabled, DefferedLogLevel.Debug, t.some, message) override def debug(message: => String): LoggerF[Unit] = - build(Map.empty, debugEnabled, LogLevel.Debug, None, message) + build(Map.empty, debugEnabled, DefferedLogLevel.Debug, None, message) override def info(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, infoEnabled, LogLevel.Info, t.some, message) + build(Map.empty, infoEnabled, DefferedLogLevel.Info, t.some, message) override def info(message: => String): LoggerF[Unit] = - build(Map.empty, infoEnabled, LogLevel.Info, None, message) + build(Map.empty, infoEnabled, DefferedLogLevel.Info, None, message) override def warn(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, warnEnabled, LogLevel.Warn, t.some, message) + build(Map.empty, warnEnabled, DefferedLogLevel.Warn, t.some, message) override def warn(message: => String): LoggerF[Unit] = - build(Map.empty, warnEnabled, LogLevel.Warn, None, message) + build(Map.empty, warnEnabled, DefferedLogLevel.Warn, None, message) override def error(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, errorEnabled, LogLevel.Error, t.some, message) + build(Map.empty, errorEnabled, DefferedLogLevel.Error, t.some, message) override def error(message: => String): LoggerF[Unit] = - build(Map.empty, errorEnabled, LogLevel.Error, None, message) + build(Map.empty, errorEnabled, DefferedLogLevel.Error, None, message) private def isEnabled(enabled: Boolean): LoggerF[Boolean] = WriterT.liftF[F, G[StructuredLogMessage], Boolean](Applicative[F].pure(enabled)) @@ -89,7 +89,7 @@ object WriterTStructuredLogger { private def build( ctx: Map[String, String], enabled: Boolean, - level: LogLevel, + level: DefferedLogLevel, t: Option[Throwable], message: => String ): LoggerF[Unit] = @@ -103,40 +103,40 @@ object WriterTStructuredLogger { Alternative[G].algebra[StructuredLogMessage] override def trace(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, traceEnabled, LogLevel.Trace, None, message) + build(ctx, traceEnabled, DefferedLogLevel.Trace, None, message) override def trace(ctx: Map[String, String], t: Throwable)( message: => String ): LoggerF[Unit] = - build(ctx, traceEnabled, LogLevel.Trace, t.some, message) + build(ctx, traceEnabled, DefferedLogLevel.Trace, t.some, message) override def debug(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, debugEnabled, LogLevel.Debug, None, message) + build(ctx, debugEnabled, DefferedLogLevel.Debug, None, message) override def debug(ctx: Map[String, String], t: Throwable)( message: => String ): LoggerF[Unit] = - build(ctx, debugEnabled, LogLevel.Debug, t.some, message) + build(ctx, debugEnabled, DefferedLogLevel.Debug, t.some, message) override def info(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, infoEnabled, LogLevel.Info, None, message) + build(ctx, infoEnabled, DefferedLogLevel.Info, None, message) override def info(ctx: Map[String, String], t: Throwable)(message: => String): LoggerF[Unit] = - build(ctx, infoEnabled, LogLevel.Info, t.some, message) + build(ctx, infoEnabled, DefferedLogLevel.Info, t.some, message) override def warn(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, warnEnabled, LogLevel.Warn, None, message) + build(ctx, warnEnabled, DefferedLogLevel.Warn, None, message) override def warn(ctx: Map[String, String], t: Throwable)(message: => String): LoggerF[Unit] = - build(ctx, warnEnabled, LogLevel.Warn, t.some, message) + build(ctx, warnEnabled, DefferedLogLevel.Warn, t.some, message) override def error(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, errorEnabled, LogLevel.Error, None, message) + build(ctx, errorEnabled, DefferedLogLevel.Error, None, message) override def error(ctx: Map[String, String], t: Throwable)( message: => String ): LoggerF[Unit] = - build(ctx, errorEnabled, LogLevel.Error, t.some, message) + build(ctx, errorEnabled, DefferedLogLevel.Error, t.some, message) } def run[F[_]: Monad, G[_]: Foldable]( diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index 6f79e754..dbb7d48c 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -19,31 +19,31 @@ package console import cats.effect.kernel.Sync import cats.syntax.all.* -import org.typelevel.log4cats.extras.LogLevel -import org.typelevel.log4cats.extras.LogLevel.* +import org.typelevel.log4cats.extras.DefferedLogLevel +import org.typelevel.log4cats.extras.DefferedLogLevel.* -class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) +class ConsoleLogger[F[_]: Sync](DefferedLogLevel: Option[DefferedLogLevel] = Option(Trace)) extends SelfAwareStructuredLogger[F] { private val ConsoleF: ConsoleF[F] = implicitly override def trace(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) override def trace(message: => String): F[Unit] = ConsoleF.debug(message) - override def isTraceEnabled: F[Boolean] = logLevel.exists(_ <= Trace).pure[F] + override def isTraceEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Trace).pure[F] override def debug(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) override def debug(message: => String): F[Unit] = ConsoleF.debug(message) - override def isDebugEnabled: F[Boolean] = logLevel.exists(_ <= Debug).pure[F] + override def isDebugEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Debug).pure[F] override def info(t: Throwable)(message: => String): F[Unit] = ConsoleF.info(message, t) override def info(message: => String): F[Unit] = ConsoleF.info(message) - override def isInfoEnabled: F[Boolean] = logLevel.exists(_ <= Info).pure[F] + override def isInfoEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Info).pure[F] override def warn(t: Throwable)(message: => String): F[Unit] = ConsoleF.warn(message, t) override def warn(message: => String): F[Unit] = ConsoleF.warn(message) - override def isWarnEnabled: F[Boolean] = logLevel.exists(_ <= Warn).pure[F] + override def isWarnEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Warn).pure[F] override def error(t: Throwable)(message: => String): F[Unit] = ConsoleF.error(message, t) override def error(message: => String): F[Unit] = ConsoleF.error(message) - override def isErrorEnabled: F[Boolean] = logLevel.exists(_ <= Error).pure[F] + override def isErrorEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Error).pure[F] /* * ConsoleLogger should probably not extend from StructuredLogger, because there's not diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala index c05c95e9..6db1948f 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala @@ -20,7 +20,7 @@ import cats.Show import cats.data.Chain import cats.effect.{Ref, Sync} import cats.syntax.all.* -import org.typelevel.log4cats.extras.LogLevel +import org.typelevel.log4cats.extras.DefferedLogLevel import org.typelevel.log4cats.testing.TestingLoggerFactory.LogMessage import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} @@ -38,12 +38,12 @@ object TestingLoggerFactory { def ctx: Map[String, String] def message: String def throwOpt: Option[Throwable] - def level: LogLevel = this match { - case _: Trace => LogLevel.Trace - case _: Debug => LogLevel.Debug - case _: Info => LogLevel.Info - case _: Warn => LogLevel.Warn - case _: Error => LogLevel.Error + def level: DefferedLogLevel = this match { + case _: Trace => DefferedLogLevel.Trace + case _: Debug => DefferedLogLevel.Debug + case _: Info => DefferedLogLevel.Info + case _: Warn => DefferedLogLevel.Warn + case _: Error => DefferedLogLevel.Error } } From 9c3c243f2b5c5873accac31f15a6a34a1b925fdf Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:27:45 +0000 Subject: [PATCH 06/62] going back to LogLevel name --- .../log4cats/extras/DeferredLogMessage.scala | 36 ++++++++-------- .../typelevel/log4cats/extras/LogLevel.scala | 3 +- .../log4cats/extras/LogMessage.scala | 22 +++++----- .../extras/StructuredLogMessage.scala | 22 +++++----- .../log4cats/extras/WriterTLogger.scala | 24 +++++------ .../extras/WriterTStructuredLogger.scala | 42 +++++++++---------- .../log4cats/console/ConsoleLogger.scala | 16 +++---- .../testing/TestingLoggerFactory.scala | 14 +++---- 8 files changed, 90 insertions(+), 89 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala index bc74cb4a..cca7322a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogMessage.scala @@ -35,34 +35,34 @@ import org.typelevel.log4cats.extras.DeferredLogMessage.{ * At some point, this should be renamed to `StructuredLogMessage` and replace the old class. */ sealed trait DeferredLogMessage { - def level: DefferedLogLevel + def level: LogLevel def context: Map[String, String] def throwableOpt: Option[Throwable] def message: () => String def log[F[_]](logger: Logger[F]): F[Unit] = { level match { - case DefferedLogLevel.Error => + case LogLevel.Error => throwableOpt match { case Some(e) => logger.error(e)(message()) case None => logger.error(message()) } - case DefferedLogLevel.Warn => + case LogLevel.Warn => throwableOpt match { case Some(e) => logger.warn(e)(message()) case None => logger.warn(message()) } - case DefferedLogLevel.Info => + case LogLevel.Info => throwableOpt match { case Some(e) => logger.info(e)(message()) case None => logger.info(message()) } - case DefferedLogLevel.Debug => + case LogLevel.Debug => throwableOpt match { case Some(e) => logger.debug(e)(message()) case None => logger.debug(message()) } - case DefferedLogLevel.Trace => + case LogLevel.Trace => throwableOpt match { case Some(e) => logger.trace(e)(message()) case None => logger.trace(message()) @@ -72,27 +72,27 @@ sealed trait DeferredLogMessage { def logStructured[F[_]](logger: StructuredLogger[F]): F[Unit] = { level match { - case DefferedLogLevel.Error => + case LogLevel.Error => throwableOpt match { case Some(e) => logger.error(context, e)(message()) case None => logger.error(context)(message()) } - case DefferedLogLevel.Warn => + case LogLevel.Warn => throwableOpt match { case Some(e) => logger.warn(context, e)(message()) case None => logger.warn(context)(message()) } - case DefferedLogLevel.Info => + case LogLevel.Info => throwableOpt match { case Some(e) => logger.info(context, e)(message()) case None => logger.info(context)(message()) } - case DefferedLogLevel.Debug => + case LogLevel.Debug => throwableOpt match { case Some(e) => logger.debug(context, e)(message()) case None => logger.debug(context)(message()) } - case DefferedLogLevel.Trace => + case LogLevel.Trace => throwableOpt match { case Some(e) => logger.trace(context, e)(message()) case None => logger.trace(context)(message()) @@ -111,32 +111,32 @@ sealed trait DeferredLogMessage { } object DeferredLogMessage { def apply( - l: DefferedLogLevel, + l: LogLevel, c: Map[String, String], t: Option[Throwable], m: () => String ): DeferredLogMessage = new DeferredLogMessage { - override val level: DefferedLogLevel = l + override val level: LogLevel = l override val context: Map[String, String] = c override val throwableOpt: Option[Throwable] = t override val message: () => String = m } def trace(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(DefferedLogLevel.Trace, c, t, m) + apply(LogLevel.Trace, c, t, m) def debug(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(DefferedLogLevel.Debug, c, t, m) + apply(LogLevel.Debug, c, t, m) def info(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(DefferedLogLevel.Info, c, t, m) + apply(LogLevel.Info, c, t, m) def warn(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(DefferedLogLevel.Warn, c, t, m) + apply(LogLevel.Warn, c, t, m) def error(c: Map[String, String], t: Option[Throwable], m: () => String): DeferredLogMessage = - apply(DefferedLogLevel.Error, c, t, m) + apply(LogLevel.Error, c, t, m) implicit val deferredStructuredLogMessageHash: Hash[DeferredLogMessage] = Hash.by { l => (l.level, l.context, l.throwableOpt.map(_.getMessage), l.message()) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala index 2a4c6754..7c9d7001 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogLevel.scala @@ -56,7 +56,8 @@ object LogLevel { case Trace => 1 } - implicit final val logLevelOrder: Order[LogLevel] = Order.by[LogLevel, Int](toIndex) + implicit final val logLevelOrder: Order[LogLevel] = + Order.by[LogLevel, Int](toIndex) implicit final val logLevelHash: Hash[LogLevel] = Hash.by(toIndex) } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala index 698e5a2c..176c0848 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/LogMessage.scala @@ -20,25 +20,25 @@ import cats.* import cats.syntax.all.* import org.typelevel.log4cats.Logger -final case class LogMessage(level: DefferedLogLevel, t: Option[Throwable], message: String) +final case class LogMessage(level: LogLevel, t: Option[Throwable], message: String) object LogMessage { implicit val logMessageShow: Show[LogMessage] = Show.show[LogMessage](l => show"LogMessage(${l.level},${l.t.map(_.getMessage)},${l.message})") def log[F[_]](sm: LogMessage, l: Logger[F]): F[Unit] = sm match { - case LogMessage(DefferedLogLevel.Trace, Some(t), m) => l.trace(t)(m) - case LogMessage(DefferedLogLevel.Trace, None, m) => l.trace(m) + case LogMessage(LogLevel.Trace, Some(t), m) => l.trace(t)(m) + case LogMessage(LogLevel.Trace, None, m) => l.trace(m) - case LogMessage(DefferedLogLevel.Debug, Some(t), m) => l.debug(t)(m) - case LogMessage(DefferedLogLevel.Debug, None, m) => l.debug(m) + case LogMessage(LogLevel.Debug, Some(t), m) => l.debug(t)(m) + case LogMessage(LogLevel.Debug, None, m) => l.debug(m) - case LogMessage(DefferedLogLevel.Info, Some(t), m) => l.info(t)(m) - case LogMessage(DefferedLogLevel.Info, None, m) => l.info(m) + case LogMessage(LogLevel.Info, Some(t), m) => l.info(t)(m) + case LogMessage(LogLevel.Info, None, m) => l.info(m) - case LogMessage(DefferedLogLevel.Warn, Some(t), m) => l.warn(t)(m) - case LogMessage(DefferedLogLevel.Warn, None, m) => l.warn(m) + case LogMessage(LogLevel.Warn, Some(t), m) => l.warn(t)(m) + case LogMessage(LogLevel.Warn, None, m) => l.warn(m) - case LogMessage(DefferedLogLevel.Error, Some(t), m) => l.error(t)(m) - case LogMessage(DefferedLogLevel.Error, None, m) => l.error(m) + case LogMessage(LogLevel.Error, Some(t), m) => l.error(t)(m) + case LogMessage(LogLevel.Error, None, m) => l.error(m) } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala index a8b46b04..ca6a869a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/StructuredLogMessage.scala @@ -21,27 +21,27 @@ import cats.syntax.all.* import org.typelevel.log4cats.StructuredLogger final case class StructuredLogMessage( - level: DefferedLogLevel, + level: LogLevel, context: Map[String, String], throwableOpt: Option[Throwable], message: String ) object StructuredLogMessage { def log[F[_]](sm: StructuredLogMessage, l: StructuredLogger[F]): F[Unit] = sm match { - case StructuredLogMessage(DefferedLogLevel.Trace, ctx, Some(t), m) => l.trace(ctx, t)(m) - case StructuredLogMessage(DefferedLogLevel.Trace, ctx, None, m) => l.trace(ctx)(m) + case StructuredLogMessage(LogLevel.Trace, ctx, Some(t), m) => l.trace(ctx, t)(m) + case StructuredLogMessage(LogLevel.Trace, ctx, None, m) => l.trace(ctx)(m) - case StructuredLogMessage(DefferedLogLevel.Debug, ctx, Some(t), m) => l.debug(ctx, t)(m) - case StructuredLogMessage(DefferedLogLevel.Debug, ctx, None, m) => l.debug(ctx)(m) + case StructuredLogMessage(LogLevel.Debug, ctx, Some(t), m) => l.debug(ctx, t)(m) + case StructuredLogMessage(LogLevel.Debug, ctx, None, m) => l.debug(ctx)(m) - case StructuredLogMessage(DefferedLogLevel.Info, ctx, Some(t), m) => l.info(ctx, t)(m) - case StructuredLogMessage(DefferedLogLevel.Info, ctx, None, m) => l.info(ctx)(m) + case StructuredLogMessage(LogLevel.Info, ctx, Some(t), m) => l.info(ctx, t)(m) + case StructuredLogMessage(LogLevel.Info, ctx, None, m) => l.info(ctx)(m) - case StructuredLogMessage(DefferedLogLevel.Warn, ctx, Some(t), m) => l.warn(ctx, t)(m) - case StructuredLogMessage(DefferedLogLevel.Warn, ctx, None, m) => l.warn(ctx)(m) + case StructuredLogMessage(LogLevel.Warn, ctx, Some(t), m) => l.warn(ctx, t)(m) + case StructuredLogMessage(LogLevel.Warn, ctx, None, m) => l.warn(ctx)(m) - case StructuredLogMessage(DefferedLogLevel.Error, ctx, Some(t), m) => l.error(ctx, t)(m) - case StructuredLogMessage(DefferedLogLevel.Error, ctx, None, m) => l.error(ctx)(m) + case StructuredLogMessage(LogLevel.Error, ctx, Some(t), m) => l.error(ctx, t)(m) + case StructuredLogMessage(LogLevel.Error, ctx, None, m) => l.error(ctx)(m) } implicit val structuredLogMessageShow: Show[StructuredLogMessage] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 89b8ad65..90ed5645 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -20,7 +20,7 @@ import cats.* import cats.data.* import cats.syntax.all.* import org.typelevel.log4cats.* -import org.typelevel.log4cats.extras.DefferedLogLevel +import org.typelevel.log4cats.extras.LogLevel /** * A `SelfAwareLogger` implemented using `cats.data.WriterT`. @@ -48,36 +48,36 @@ object WriterTLogger { override def isErrorEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(errorEnabled) override def trace(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(traceEnabled, DefferedLogLevel.Trace, t.some, message) + build(traceEnabled, LogLevel.Trace, t.some, message) override def trace(message: => String): WriterT[F, G[LogMessage], Unit] = - build(traceEnabled, DefferedLogLevel.Trace, None, message) + build(traceEnabled, LogLevel.Trace, None, message) override def debug(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(debugEnabled, DefferedLogLevel.Debug, t.some, message) + build(debugEnabled, LogLevel.Debug, t.some, message) override def debug(message: => String): WriterT[F, G[LogMessage], Unit] = - build(debugEnabled, DefferedLogLevel.Debug, None, message) + build(debugEnabled, LogLevel.Debug, None, message) override def info(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(infoEnabled, DefferedLogLevel.Info, t.some, message) + build(infoEnabled, LogLevel.Info, t.some, message) override def info(message: => String): WriterT[F, G[LogMessage], Unit] = - build(infoEnabled, DefferedLogLevel.Info, None, message) + build(infoEnabled, LogLevel.Info, None, message) override def warn(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(warnEnabled, DefferedLogLevel.Warn, t.some, message) + build(warnEnabled, LogLevel.Warn, t.some, message) override def warn(message: => String): WriterT[F, G[LogMessage], Unit] = - build(warnEnabled, DefferedLogLevel.Warn, None, message) + build(warnEnabled, LogLevel.Warn, None, message) override def error(t: Throwable)(message: => String): WriterT[F, G[LogMessage], Unit] = - build(errorEnabled, DefferedLogLevel.Error, t.some, message) + build(errorEnabled, LogLevel.Error, t.some, message) override def error(message: => String): WriterT[F, G[LogMessage], Unit] = - build(errorEnabled, DefferedLogLevel.Error, None, message) + build(errorEnabled, LogLevel.Error, None, message) private def isEnabled(enabled: Boolean): WriterT[F, G[LogMessage], Boolean] = WriterT.liftF[F, G[LogMessage], Boolean](Applicative[F].pure(enabled)) private def build( enabled: Boolean, - level: DefferedLogLevel, + level: LogLevel, t: Option[Throwable], message: => String ): WriterT[F, G[LogMessage], Unit] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala index 83b10468..b8b218de 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala @@ -54,34 +54,34 @@ object WriterTStructuredLogger { override def isErrorEnabled: LoggerF[Boolean] = isEnabled(errorEnabled) override def trace(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, traceEnabled, DefferedLogLevel.Trace, t.some, message) + build(Map.empty, traceEnabled, LogLevel.Trace, t.some, message) override def trace(message: => String): LoggerF[Unit] = - build(Map.empty, traceEnabled, DefferedLogLevel.Trace, None, message) + build(Map.empty, traceEnabled, LogLevel.Trace, None, message) override def debug(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, debugEnabled, DefferedLogLevel.Debug, t.some, message) + build(Map.empty, debugEnabled, LogLevel.Debug, t.some, message) override def debug(message: => String): LoggerF[Unit] = - build(Map.empty, debugEnabled, DefferedLogLevel.Debug, None, message) + build(Map.empty, debugEnabled, LogLevel.Debug, None, message) override def info(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, infoEnabled, DefferedLogLevel.Info, t.some, message) + build(Map.empty, infoEnabled, LogLevel.Info, t.some, message) override def info(message: => String): LoggerF[Unit] = - build(Map.empty, infoEnabled, DefferedLogLevel.Info, None, message) + build(Map.empty, infoEnabled, LogLevel.Info, None, message) override def warn(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, warnEnabled, DefferedLogLevel.Warn, t.some, message) + build(Map.empty, warnEnabled, LogLevel.Warn, t.some, message) override def warn(message: => String): LoggerF[Unit] = - build(Map.empty, warnEnabled, DefferedLogLevel.Warn, None, message) + build(Map.empty, warnEnabled, LogLevel.Warn, None, message) override def error(t: Throwable)(message: => String): LoggerF[Unit] = - build(Map.empty, errorEnabled, DefferedLogLevel.Error, t.some, message) + build(Map.empty, errorEnabled, LogLevel.Error, t.some, message) override def error(message: => String): LoggerF[Unit] = - build(Map.empty, errorEnabled, DefferedLogLevel.Error, None, message) + build(Map.empty, errorEnabled, LogLevel.Error, None, message) private def isEnabled(enabled: Boolean): LoggerF[Boolean] = WriterT.liftF[F, G[StructuredLogMessage], Boolean](Applicative[F].pure(enabled)) @@ -89,7 +89,7 @@ object WriterTStructuredLogger { private def build( ctx: Map[String, String], enabled: Boolean, - level: DefferedLogLevel, + level: LogLevel, t: Option[Throwable], message: => String ): LoggerF[Unit] = @@ -103,40 +103,40 @@ object WriterTStructuredLogger { Alternative[G].algebra[StructuredLogMessage] override def trace(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, traceEnabled, DefferedLogLevel.Trace, None, message) + build(ctx, traceEnabled, LogLevel.Trace, None, message) override def trace(ctx: Map[String, String], t: Throwable)( message: => String ): LoggerF[Unit] = - build(ctx, traceEnabled, DefferedLogLevel.Trace, t.some, message) + build(ctx, traceEnabled, LogLevel.Trace, t.some, message) override def debug(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, debugEnabled, DefferedLogLevel.Debug, None, message) + build(ctx, debugEnabled, LogLevel.Debug, None, message) override def debug(ctx: Map[String, String], t: Throwable)( message: => String ): LoggerF[Unit] = - build(ctx, debugEnabled, DefferedLogLevel.Debug, t.some, message) + build(ctx, debugEnabled, LogLevel.Debug, t.some, message) override def info(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, infoEnabled, DefferedLogLevel.Info, None, message) + build(ctx, infoEnabled, LogLevel.Info, None, message) override def info(ctx: Map[String, String], t: Throwable)(message: => String): LoggerF[Unit] = - build(ctx, infoEnabled, DefferedLogLevel.Info, t.some, message) + build(ctx, infoEnabled, LogLevel.Info, t.some, message) override def warn(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, warnEnabled, DefferedLogLevel.Warn, None, message) + build(ctx, warnEnabled, LogLevel.Warn, None, message) override def warn(ctx: Map[String, String], t: Throwable)(message: => String): LoggerF[Unit] = - build(ctx, warnEnabled, DefferedLogLevel.Warn, t.some, message) + build(ctx, warnEnabled, LogLevel.Warn, t.some, message) override def error(ctx: Map[String, String])(message: => String): LoggerF[Unit] = - build(ctx, errorEnabled, DefferedLogLevel.Error, None, message) + build(ctx, errorEnabled, LogLevel.Error, None, message) override def error(ctx: Map[String, String], t: Throwable)( message: => String ): LoggerF[Unit] = - build(ctx, errorEnabled, DefferedLogLevel.Error, t.some, message) + build(ctx, errorEnabled, LogLevel.Error, t.some, message) } def run[F[_]: Monad, G[_]: Foldable]( diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index dbb7d48c..0834599c 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -19,31 +19,31 @@ package console import cats.effect.kernel.Sync import cats.syntax.all.* -import org.typelevel.log4cats.extras.DefferedLogLevel -import org.typelevel.log4cats.extras.DefferedLogLevel.* +import org.typelevel.log4cats.extras.LogLevel +import org.typelevel.log4cats.extras.LogLevel.* -class ConsoleLogger[F[_]: Sync](DefferedLogLevel: Option[DefferedLogLevel] = Option(Trace)) +class ConsoleLogger[F[_]: Sync](LogLevel: Option[LogLevel] = Option(Trace)) extends SelfAwareStructuredLogger[F] { private val ConsoleF: ConsoleF[F] = implicitly override def trace(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) override def trace(message: => String): F[Unit] = ConsoleF.debug(message) - override def isTraceEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Trace).pure[F] + override def isTraceEnabled: F[Boolean] = LogLevel.exists(_ <= Trace).pure[F] override def debug(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) override def debug(message: => String): F[Unit] = ConsoleF.debug(message) - override def isDebugEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Debug).pure[F] + override def isDebugEnabled: F[Boolean] = LogLevel.exists(_ <= Debug).pure[F] override def info(t: Throwable)(message: => String): F[Unit] = ConsoleF.info(message, t) override def info(message: => String): F[Unit] = ConsoleF.info(message) - override def isInfoEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Info).pure[F] + override def isInfoEnabled: F[Boolean] = LogLevel.exists(_ <= Info).pure[F] override def warn(t: Throwable)(message: => String): F[Unit] = ConsoleF.warn(message, t) override def warn(message: => String): F[Unit] = ConsoleF.warn(message) - override def isWarnEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Warn).pure[F] + override def isWarnEnabled: F[Boolean] = LogLevel.exists(_ <= Warn).pure[F] override def error(t: Throwable)(message: => String): F[Unit] = ConsoleF.error(message, t) override def error(message: => String): F[Unit] = ConsoleF.error(message) - override def isErrorEnabled: F[Boolean] = DefferedLogLevel.exists(_ <= Error).pure[F] + override def isErrorEnabled: F[Boolean] = LogLevel.exists(_ <= Error).pure[F] /* * ConsoleLogger should probably not extend from StructuredLogger, because there's not diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala index 6db1948f..c05c95e9 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala @@ -20,7 +20,7 @@ import cats.Show import cats.data.Chain import cats.effect.{Ref, Sync} import cats.syntax.all.* -import org.typelevel.log4cats.extras.DefferedLogLevel +import org.typelevel.log4cats.extras.LogLevel import org.typelevel.log4cats.testing.TestingLoggerFactory.LogMessage import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} @@ -38,12 +38,12 @@ object TestingLoggerFactory { def ctx: Map[String, String] def message: String def throwOpt: Option[Throwable] - def level: DefferedLogLevel = this match { - case _: Trace => DefferedLogLevel.Trace - case _: Debug => DefferedLogLevel.Debug - case _: Info => DefferedLogLevel.Info - case _: Warn => DefferedLogLevel.Warn - case _: Error => DefferedLogLevel.Error + def level: LogLevel = this match { + case _: Trace => LogLevel.Trace + case _: Debug => LogLevel.Debug + case _: Info => LogLevel.Info + case _: Warn => LogLevel.Warn + case _: Error => LogLevel.Error } } From 9908c0099140bb13c615fa9301ecc6765544a8e4 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:33:47 +0000 Subject: [PATCH 07/62] original WriterTLogger --- .../scala/org/typelevel/log4cats/extras/WriterTLogger.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 90ed5645..05950d2d 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -20,7 +20,6 @@ import cats.* import cats.data.* import cats.syntax.all.* import org.typelevel.log4cats.* -import org.typelevel.log4cats.extras.LogLevel /** * A `SelfAwareLogger` implemented using `cats.data.WriterT`. @@ -96,4 +95,4 @@ object WriterTLogger { toLog.traverse_(LogMessage.log(_, l)).as(out) } } -} +} \ No newline at end of file From 4fb2850bfd78ad11d5b2e981c41722486bb74ec0 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:38:10 +0000 Subject: [PATCH 08/62] deleted DefferedLogLevel --- .../log4cats/extras/DefferedLogLevel.scala | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala deleted file mode 100644 index ee55ab4b..00000000 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DefferedLogLevel.scala +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.* - -sealed trait DefferedLogLevel -object DefferedLogLevel { - case object Error extends DefferedLogLevel - case object Warn extends DefferedLogLevel - case object Info extends DefferedLogLevel - case object Debug extends DefferedLogLevel - case object Trace extends DefferedLogLevel - - def fromString(s: String): Option[DefferedLogLevel] = s.toLowerCase match { - case "error" => Some(DefferedLogLevel.Error) - case "warn" => Some(DefferedLogLevel.Warn) - case "info" => Some(DefferedLogLevel.Info) - case "debug" => Some(DefferedLogLevel.Debug) - case "trace" => Some(DefferedLogLevel.Trace) - case "loglevel.error" => Some(DefferedLogLevel.Error) - case "loglevel.warn" => Some(DefferedLogLevel.Warn) - case "loglevel.info" => Some(DefferedLogLevel.Info) - case "loglevel.debug" => Some(DefferedLogLevel.Debug) - case "loglevel.trace" => Some(DefferedLogLevel.Trace) - case _ => None - } - - implicit val logLevelShow: Show[DefferedLogLevel] = Show.show[DefferedLogLevel] { - case Error => "DefferedLogLevel.Error" - case Warn => "DefferedLogLevel.Warn" - case Info => "DefferedLogLevel.Info" - case Debug => "DefferedLogLevel.Debug" - case Trace => "DefferedLogLevel.Trace" - } - - private def toIndex(l: DefferedLogLevel): Int = l match { - case Error => 5 - case Warn => 4 - case Info => 3 - case Debug => 2 - case Trace => 1 - } - - implicit final val logLevelOrder: Order[DefferedLogLevel] = - Order.by[DefferedLogLevel, Int](toIndex) - - implicit final val logLevelHash: Hash[DefferedLogLevel] = Hash.by(toIndex) -} From fc5867088eeeabbf20f4f983153c91e806c40388 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:38:27 +0000 Subject: [PATCH 09/62] build.sbt --- build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 03d46d0c..cb70a5be 100644 --- a/build.sbt +++ b/build.sbt @@ -31,6 +31,7 @@ val catsEffectV = "3.6.1" val slf4jV = "1.7.36" val munitCatsEffectV = "2.1.0" val logbackClassicV = "1.2.13" +val sourcecodeV = "0.3.1" Global / onChangedBuildSource := ReloadOnSourceChanges @@ -47,7 +48,8 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) name := "log4cats-core", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, - "org.typelevel" %%% "cats-effect-std" % catsEffectV + "org.typelevel" %%% "cats-effect-std" % catsEffectV, + "com.lihaoyi" %%% "sourcecode" % sourcecodeV ), libraryDependencies ++= { if (tlIsScala3.value) Seq.empty From 23b2094b4bafc89778c2d70a0022339972ec077b Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:42:18 +0000 Subject: [PATCH 10/62] scalafmt --- .../scala/org/typelevel/log4cats/extras/WriterTLogger.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 05950d2d..70ecd01a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -95,4 +95,4 @@ object WriterTLogger { toLog.traverse_(LogMessage.log(_, l)).as(out) } } -} \ No newline at end of file +} From 3f1f34ca768299d4e514f24265b69132a235937a Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 17:49:41 +0000 Subject: [PATCH 11/62] sbt scalafmtSbt --- build.sbt | 2 +- .../scala/org/typelevel/log4cats/extras/WriterTLogger.scala | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index cb70a5be..d223627a 100644 --- a/build.sbt +++ b/build.sbt @@ -49,7 +49,7 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-std" % catsEffectV, - "com.lihaoyi" %%% "sourcecode" % sourcecodeV + "com.lihaoyi" %%% "sourcecode" % sourcecodeV ), libraryDependencies ++= { if (tlIsScala3.value) Seq.empty diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 70ecd01a..0b2efaf5 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -20,6 +20,7 @@ import cats.* import cats.data.* import cats.syntax.all.* import org.typelevel.log4cats.* +import org.typelevel.log4cats.extras.LogLevel /** * A `SelfAwareLogger` implemented using `cats.data.WriterT`. @@ -81,7 +82,9 @@ object WriterTLogger { message: => String ): WriterT[F, G[LogMessage], Unit] = if (enabled) - WriterT.tell[F, G[LogMessage]](Applicative[G].pure(LogMessage(level, t, message))) + WriterT.tell[F, G[LogMessage]]( + Applicative[G].pure(org.typelevel.log4cats.extras.LogMessage(level, t, message)) + ) else WriterT.value[F, G[LogMessage], Unit](()) private implicit val monoidGLogMessage: Monoid[G[LogMessage]] = From 7961c15d0b00eb50ab8e56573ef719ab445211d3 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 18 Jun 2025 18:05:10 +0000 Subject: [PATCH 12/62] added KernelLogLevel --- .../{LogLevel.scala => KernelLogLevel.scala} | 34 +++++++++---------- .../scala/org/typelevel/log4cats/Log.scala | 10 +++--- .../org/typelevel/log4cats/LoggerKernel.scala | 12 +++---- .../log4cats/extras/WriterTLogger.scala | 5 +-- 4 files changed, 29 insertions(+), 32 deletions(-) rename core/shared/src/main/scala/org/typelevel/log4cats/{LogLevel.scala => KernelLogLevel.scala} (52%) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala similarity index 52% rename from core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala rename to core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala index 2c862c99..e8f4c166 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LogLevel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala @@ -16,29 +16,29 @@ package org.typelevel.log4cats -final case class LogLevel(name: String, value: Double) { - def namePadded: String = LogLevel.padded(this) +final case class KernelLogLevel(name: String, value: Double) { + def namePadded: String = KernelLogLevel.padded(this) - LogLevel.add(this) + KernelLogLevel.add(this) } -object LogLevel { +object KernelLogLevel { private var maxLength = 0 - private var map = Map.empty[String, LogLevel] - private var padded = Map.empty[LogLevel, String] + private var map = Map.empty[String, KernelLogLevel] + private var padded = Map.empty[KernelLogLevel, String] - implicit final val LevelOrdering: Ordering[LogLevel] = - Ordering.by[LogLevel, Double](_.value).reverse + implicit final val LevelOrdering: Ordering[KernelLogLevel] = + Ordering.by[KernelLogLevel, Double](_.value).reverse - val Trace: LogLevel = LogLevel("TRACE", 100.0) - val Debug: LogLevel = LogLevel("DEBUG", 200.0) - val Info: LogLevel = LogLevel("INFO", 300.0) - val Warn: LogLevel = LogLevel("WARN", 400.0) - val Error: LogLevel = LogLevel("ERROR", 500.0) - val Fatal: LogLevel = LogLevel("FATAL", 600.0) + val Trace: KernelLogLevel = KernelLogLevel("TRACE", 100.0) + val Debug: KernelLogLevel = KernelLogLevel("DEBUG", 200.0) + val Info: KernelLogLevel = KernelLogLevel("INFO", 300.0) + val Warn: KernelLogLevel = KernelLogLevel("WARN", 400.0) + val Error: KernelLogLevel = KernelLogLevel("ERROR", 500.0) + val Fatal: KernelLogLevel = KernelLogLevel("FATAL", 600.0) - def add(level: LogLevel): Unit = synchronized { + def add(level: KernelLogLevel): Unit = synchronized { val length = level.name.length map += level.name.toLowerCase -> level if (length > maxLength) { @@ -51,9 +51,9 @@ object LogLevel { } } - def get(name: String): Option[LogLevel] = map.get(name.toLowerCase) + def get(name: String): Option[KernelLogLevel] = map.get(name.toLowerCase) - def apply(name: String): LogLevel = get(name).getOrElse( + def apply(name: String): KernelLogLevel = get(name).getOrElse( throw new RuntimeException(s"Level not found by name: $name") ) } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 5fae0cf9..abb85cf4 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -28,7 +28,7 @@ import scala.concurrent.duration.FiniteDuration */ trait Log { def timestamp: Option[FiniteDuration] - def level: LogLevel + def level: KernelLogLevel def levelValue: Double def message: String def throwable: Option[Throwable] @@ -45,7 +45,7 @@ trait Log { object Log { trait Builder { def withTimestamp(value: FiniteDuration): Builder - def withLevel(level: LogLevel): Builder + def withLevel(level: KernelLogLevel): Builder def withLevelValue(levelValue: Double): Builder def withMessage(message: => String): Builder def withThrowable(throwable: Throwable): Builder @@ -73,7 +73,7 @@ object Log { def build(): Log = this def timestamp: Option[FiniteDuration] = Option(_timestamp) - def level: LogLevel = if (_level == null) LogLevel.Debug else _level + def level: KernelLogLevel = if (_level == null) KernelLogLevel.Debug else _level def levelValue: Double = if (_levelValue < 0) level.value else _levelValue def message: String = if (_message == null) "" else _message @@ -90,7 +90,7 @@ object Log { def unsafeContext: MapLike[String, Context] = _context private var _timestamp: FiniteDuration = null - private var _level: LogLevel = null + private var _level: KernelLogLevel = null private var _levelValue: Double = -1 private var _message: String = null private var _throwable: Throwable = null @@ -105,7 +105,7 @@ object Log { this } - def withLevel(level: LogLevel): this.type = { + def withLevel(level: KernelLogLevel): this.type = { this._level = level this } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala index 7b71a499..b454ab32 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala @@ -34,16 +34,16 @@ package org.typelevel.log4cats * implementation. */ trait LoggerKernel[F[_]] { - def log(level: LogLevel, record: Log.Builder => Log.Builder): F[Unit] + def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] final def logTrace(record: Log.Builder => Log.Builder): F[Unit] = - log(LogLevel.Trace, record) + log(KernelLogLevel.Trace, record) final def logDebug(record: Log.Builder => Log.Builder): F[Unit] = - log(LogLevel.Debug, record) + log(KernelLogLevel.Debug, record) final def logInfo(record: Log.Builder => Log.Builder): F[Unit] = - log(LogLevel.Info, record) + log(KernelLogLevel.Info, record) final def logWarn(record: Log.Builder => Log.Builder): F[Unit] = - log(LogLevel.Warn, record) + log(KernelLogLevel.Warn, record) final def logError(record: Log.Builder => Log.Builder): F[Unit] = - log(LogLevel.Error, record) + log(KernelLogLevel.Error, record) } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 0b2efaf5..70ecd01a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -20,7 +20,6 @@ import cats.* import cats.data.* import cats.syntax.all.* import org.typelevel.log4cats.* -import org.typelevel.log4cats.extras.LogLevel /** * A `SelfAwareLogger` implemented using `cats.data.WriterT`. @@ -82,9 +81,7 @@ object WriterTLogger { message: => String ): WriterT[F, G[LogMessage], Unit] = if (enabled) - WriterT.tell[F, G[LogMessage]]( - Applicative[G].pure(org.typelevel.log4cats.extras.LogMessage(level, t, message)) - ) + WriterT.tell[F, G[LogMessage]](Applicative[G].pure(LogMessage(level, t, message))) else WriterT.value[F, G[LogMessage], Unit](()) private implicit val monoidGLogMessage: Monoid[G[LogMessage]] = From a5708c38bbbaada80101fc3100ef81ab57584d31 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Fri, 20 Jun 2025 13:11:54 +0000 Subject: [PATCH 13/62] add new samlogger interface based on old and poc logger --- .../org/typelevel/log4cats/SamLogger.scala | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala new file mode 100644 index 00000000..9dc55fac --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -0,0 +1,145 @@ +/* + * 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 + +import cats.* +import cats.data.{EitherT, Kleisli, OptionT} + +/** + * A SAM-based Logger that extends LoggerKernel and provides a user-friendly interface. This is the + * new design that will eventually replace the current Logger trait. + */ +abstract class SamLogger[F[_]] extends LoggerKernel[F] { + + final def info(logBit: LogRecord, others: LogRecord*)(implicit + pkg: sourcecode.Pkg, + filename: sourcecode.FileName, + name: sourcecode.Name, + line: sourcecode.Line + ): F[Unit] = log_(KernelLogLevel.Info, logBit, others: _*) + + final def warn(logBit: LogRecord, others: LogRecord*)(implicit + pkg: sourcecode.Pkg, + filename: sourcecode.FileName, + name: sourcecode.Name, + line: sourcecode.Line + ): F[Unit] = log_(KernelLogLevel.Warn, logBit, others: _*) + + final def error(logBit: LogRecord, others: LogRecord*)(implicit + pkg: sourcecode.Pkg, + filename: sourcecode.FileName, + name: sourcecode.Name, + line: sourcecode.Line + ): F[Unit] = log_(KernelLogLevel.Error, logBit, others: _*) + + final def trace(logBit: LogRecord, others: LogRecord*)(implicit + pkg: sourcecode.Pkg, + filename: sourcecode.FileName, + name: sourcecode.Name, + line: sourcecode.Line + ): F[Unit] = log_(KernelLogLevel.Trace, logBit, others: _*) + + final def debug(logBit: LogRecord, others: LogRecord*)(implicit + pkg: sourcecode.Pkg, + filename: sourcecode.FileName, + name: sourcecode.Name, + line: sourcecode.Line + ): F[Unit] = log_(KernelLogLevel.Debug, logBit, others: _*) + + private final def log_( + level: KernelLogLevel, + bit: LogRecord, + others: LogRecord* + )(implicit + pkg: sourcecode.Pkg, + filename: sourcecode.FileName, + name: sourcecode.Name, + line: sourcecode.Line + ): F[Unit] = { + log( + level, + (record: Log.Builder) => + LogRecord.combine(others)( + bit( + record + .withLevel(level) + .withClassName(pkg.value + "." + name.value) + .withFileName(filename.value) + .withLine(line.value) + ) + ) + ) + } + + def withModifiedString(f: String => String): SamLogger[F] = + SamLogger.withModifiedString[F](this, f) + def mapK[G[_]](fk: F ~> G): SamLogger[G] = SamLogger.mapK(fk)(this) +} + +object SamLogger { + def apply[F[_]](implicit ev: SamLogger[F]) = ev + + def wrap[F[_]](kernel: LoggerKernel[F]): SamLogger[F] = new SamLogger[F] { + def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = + kernel.log(level, record) + } + + implicit def optionTSamLogger[F[_]: SamLogger: Functor]: SamLogger[OptionT[F, *]] = + SamLogger[F].mapK(OptionT.liftK[F]) + + implicit def eitherTSamLogger[F[_]: SamLogger: Functor, E]: SamLogger[EitherT[F, E, *]] = + SamLogger[F].mapK(EitherT.liftK[F, E]) + + implicit def kleisliSamLogger[F[_]: SamLogger, A]: SamLogger[Kleisli[F, A, *]] = + SamLogger[F].mapK(Kleisli.liftK[F, A]) + + private def withModifiedString[F[_]](l: SamLogger[F], f: String => String): SamLogger[F] = + new SamLogger[F] { + def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = { + val modifiedRecord = (builder: Log.Builder) => { + val originalLog = record(builder).build() + val modifiedMessage = f(originalLog.message) + + val newBuilder = Log + .mutableBuilder() + .withLevel(level) + .withMessage(modifiedMessage) + + // we can copy the fields from the original log like this + originalLog.timestamp.foreach(newBuilder.withTimestamp) + originalLog.throwable.foreach(newBuilder.withThrowable) + + originalLog.fileName.foreach(newBuilder.withFileName) + originalLog.className.foreach(newBuilder.withClassName) + originalLog.line.foreach(newBuilder.withLine) + + originalLog.context.foreach { case (k, v) => + newBuilder.withContext(k)(v) + } + + newBuilder + } + l.log(level, modifiedRecord) + } + } + + private def mapK[G[_], F[_]](f: G ~> F)(logger: SamLogger[G]): SamLogger[F] = + new SamLogger[F] { + def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = + f(logger.log(level, record)) + } +} From 649468dc075c8bfa215b37f9b97f6c0559f17734 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Mon, 23 Jun 2025 07:15:08 +0000 Subject: [PATCH 14/62] add SamStructuredLogger that delegates to LoggerKernel, with original Logger --- .../log4cats/SamStructuredLogger.scala | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala new file mode 100644 index 00000000..a52dc5f9 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -0,0 +1,247 @@ +/* + * 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 + +import cats.* +import cats.Show.Shown + +/** + * SAM-based implementation of StructuredLogger that delegates to LoggerKernel. This provides the + * same interface as StructuredLogger but uses the SAM architecture underneath for better + * performance and middleware compatibility. + */ +trait SamStructuredLogger[F[_]] extends Logger[F] { + protected def kernel: LoggerKernel[F] + + def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.logTrace(_.withMessage(msg).withContextMap(ctx)) + + def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.logTrace(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.logDebug(_.withMessage(msg).withContextMap(ctx)) + + def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.logDebug(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def info(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.logInfo(_.withMessage(msg).withContextMap(ctx)) + + def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.logInfo(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.logWarn(_.withMessage(msg).withContextMap(ctx)) + + def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.logWarn(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def error(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.logError(_.withMessage(msg).withContextMap(ctx)) + + def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.logError(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + override def trace(message: => String): F[Unit] = + kernel.logTrace(_.withMessage(message)) + + override def trace(t: Throwable)(message: => String): F[Unit] = + kernel.logTrace(_.withMessage(message).withThrowable(t)) + + override def debug(message: => String): F[Unit] = + kernel.logDebug(_.withMessage(message)) + + override def debug(t: Throwable)(message: => String): F[Unit] = + kernel.logDebug(_.withMessage(message).withThrowable(t)) + + override def info(message: => String): F[Unit] = + kernel.logInfo(_.withMessage(message)) + + override def info(t: Throwable)(message: => String): F[Unit] = + kernel.logInfo(_.withMessage(message).withThrowable(t)) + + override def warn(message: => String): F[Unit] = + kernel.logWarn(_.withMessage(message)) + + override def warn(t: Throwable)(message: => String): F[Unit] = + kernel.logWarn(_.withMessage(message).withThrowable(t)) + + override def error(message: => String): F[Unit] = + kernel.logError(_.withMessage(message)) + + override def error(t: Throwable)(message: => String): F[Unit] = + kernel.logError(_.withMessage(message).withThrowable(t)) + + def addContext(ctx: Map[String, String]): SamStructuredLogger[F] = + SamStructuredLogger.withContext(this)(ctx) + + def addContext(pairs: (String, Shown)*): SamStructuredLogger[F] = + SamStructuredLogger.withContext(this)( + pairs.map { case (k, v) => (k, v.toString) }.toMap + ) + + override def withModifiedString(f: String => String): SamStructuredLogger[F] = + SamStructuredLogger.withModifiedString[F](this, f) + + override def mapK[G[_]](fk: F ~> G): SamStructuredLogger[G] = + SamStructuredLogger.mapK(fk)(this) +} + +object SamStructuredLogger { + def apply[F[_]](implicit ev: SamStructuredLogger[F]): SamStructuredLogger[F] = ev + + def fromKernel[F[_]](kernelImpl: LoggerKernel[F]): SamStructuredLogger[F] = + new SamStructuredLogger[F] { + protected def kernel: LoggerKernel[F] = kernelImpl + } + + def withContext[F[_]](sl: SamStructuredLogger[F])( + ctx: Map[String, String] + ): SamStructuredLogger[F] = + new ModifiedContextSamStructuredLogger[F](sl)(ctx ++ _) + + def withModifiedContext[F[_]]( + sl: SamStructuredLogger[F] + )(modifyCtx: Map[String, String] => Map[String, String]): SamStructuredLogger[F] = + new ModifiedContextSamStructuredLogger[F](sl)(modifyCtx) + + private class ModifiedContextSamStructuredLogger[F[_]](sl: SamStructuredLogger[F])( + modify: Map[String, String] => Map[String, String] + ) extends SamStructuredLogger[F] { + protected def kernel: LoggerKernel[F] = sl.kernel + + private lazy val defaultCtx: Map[String, String] = modify(Map.empty) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + sl.trace(modify(ctx))(msg) + + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + sl.debug(modify(ctx))(msg) + + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = + sl.info(modify(ctx))(msg) + + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + sl.warn(modify(ctx))(msg) + + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = + sl.error(modify(ctx))(msg) + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + sl.trace(modify(ctx), t)(msg) + + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + sl.debug(modify(ctx), t)(msg) + + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + sl.info(modify(ctx), t)(msg) + + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + sl.warn(modify(ctx), t)(msg) + + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + sl.error(modify(ctx), t)(msg) + + override def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) + override def trace(t: Throwable)(message: => String): F[Unit] = sl.trace(defaultCtx, t)(message) + override def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) + override def debug(t: Throwable)(message: => String): F[Unit] = sl.debug(defaultCtx, t)(message) + override def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) + override def info(t: Throwable)(message: => String): F[Unit] = sl.info(defaultCtx, t)(message) + override def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) + override def warn(t: Throwable)(message: => String): F[Unit] = sl.warn(defaultCtx, t)(message) + override def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) + override def error(t: Throwable)(message: => String): F[Unit] = sl.error(defaultCtx, t)(message) + } + + private def withModifiedString[F[_]]( + l: SamStructuredLogger[F], + f: String => String + ): SamStructuredLogger[F] = + new SamStructuredLogger[F] { + protected def kernel: LoggerKernel[F] = l.kernel + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = l.trace(ctx)(f(msg)) + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + l.trace(ctx, t)(f(msg)) + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = l.debug(ctx)(f(msg)) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + l.debug(ctx, t)(f(msg)) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = l.info(ctx)(f(msg)) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + l.info(ctx, t)(f(msg)) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = l.warn(ctx)(f(msg)) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + l.warn(ctx, t)(f(msg)) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = l.error(ctx)(f(msg)) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + l.error(ctx, t)(f(msg)) + + override def trace(message: => String): F[Unit] = l.trace(f(message)) + override def trace(t: Throwable)(message: => String): F[Unit] = l.trace(t)(f(message)) + override def debug(message: => String): F[Unit] = l.debug(f(message)) + override def debug(t: Throwable)(message: => String): F[Unit] = l.debug(t)(f(message)) + override def info(message: => String): F[Unit] = l.info(f(message)) + override def info(t: Throwable)(message: => String): F[Unit] = l.info(t)(f(message)) + override def warn(message: => String): F[Unit] = l.warn(f(message)) + override def warn(t: Throwable)(message: => String): F[Unit] = l.warn(t)(f(message)) + override def error(message: => String): F[Unit] = l.error(f(message)) + override def error(t: Throwable)(message: => String): F[Unit] = l.error(t)(f(message)) + } + + private def mapK[G[_], F[_]](f: G ~> F)(logger: SamStructuredLogger[G]): SamStructuredLogger[F] = + new SamStructuredLogger[F] { + protected def kernel: LoggerKernel[F] = new LoggerKernel[F] { + def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = + f(logger.kernel.log(level, record)) + } + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + f(logger.trace(ctx)(msg)) + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + f(logger.debug(ctx)(msg)) + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = + f(logger.info(ctx)(msg)) + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + f(logger.warn(ctx)(msg)) + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = + f(logger.error(ctx)(msg)) + + override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + f(logger.trace(ctx, t)(msg)) + override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + f(logger.debug(ctx, t)(msg)) + override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + f(logger.info(ctx, t)(msg)) + override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + f(logger.warn(ctx, t)(msg)) + override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + f(logger.error(ctx, t)(msg)) + + override def trace(message: => String): F[Unit] = f(logger.trace(message)) + override def trace(t: Throwable)(message: => String): F[Unit] = f(logger.trace(t)(message)) + override def debug(message: => String): F[Unit] = f(logger.debug(message)) + override def debug(t: Throwable)(message: => String): F[Unit] = f(logger.debug(t)(message)) + override def info(message: => String): F[Unit] = f(logger.info(message)) + override def info(t: Throwable)(message: => String): F[Unit] = f(logger.info(t)(message)) + override def warn(message: => String): F[Unit] = f(logger.warn(message)) + override def warn(t: Throwable)(message: => String): F[Unit] = f(logger.warn(t)(message)) + override def error(message: => String): F[Unit] = f(logger.error(message)) + override def error(t: Throwable)(message: => String): F[Unit] = f(logger.error(t)(message)) + } +} From ac5aaee33d58307605ff7750ce9f523416387ea2 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sun, 29 Jun 2025 10:51:10 +0000 Subject: [PATCH 15/62] modify log with parameterized with the context type --- .../scala/org/typelevel/log4cats/Log.scala | 135 +++++++++--------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index abb85cf4..110319ec 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -16,8 +16,7 @@ package org.typelevel.log4cats -import scala.collection.{Map => MapLike} -import scala.collection.mutable.{Map => MMap} +import scala.collection.mutable import scala.concurrent.duration.FiniteDuration /** @@ -26,130 +25,132 @@ import scala.concurrent.duration.FiniteDuration * (or by running an effect). Elements such as timestamps should be provided by means of * middlewares. */ -trait Log { +trait Log[Ctx] { def timestamp: Option[FiniteDuration] def level: KernelLogLevel - def levelValue: Double + def levelValue: Int def message: String def throwable: Option[Throwable] - def context: Map[String, Context] + def context: Map[String, Ctx] def fileName: Option[String] def className: Option[String] def methodName: Option[String] def line: Option[Int] def unsafeThrowable: Throwable - def unsafeContext: MapLike[String, Context] + def unsafeContext: Map[String, Ctx] } object Log { - trait Builder { - def withTimestamp(value: FiniteDuration): Builder - def withLevel(level: KernelLogLevel): Builder - def withLevelValue(levelValue: Double): Builder - def withMessage(message: => String): Builder - def withThrowable(throwable: Throwable): Builder - def withContext(name: String)(f: Context): Builder - def withFileName(name: String): Builder - def withClassName(name: String): Builder - def withLine(line: Int): Builder + trait Builder[Ctx] { + def withTimestamp(value: FiniteDuration): Builder[Ctx] + def withLevel(level: KernelLogLevel): Builder[Ctx] + def withLevelValue(levelValue: Int): Builder[Ctx] + def withMessage(message: => String): Builder[Ctx] + def withThrowable(throwable: Throwable): Builder[Ctx] + def withContext(name: String)(f: Ctx): Builder[Ctx] + def withFileName(name: String): Builder[Ctx] + def withClassName(name: String): Builder[Ctx] + def withLine(line: Int): Builder[Ctx] final def withContextMap[A: Context.Encoder]( - mdc: Map[String, A] - ): Builder = { + contextMap: Map[String, A] + )(implicit ev: A =:= Ctx): Builder[Ctx] = { var builder = this - mdc.foreach { case (k, v) => - builder = withContext(k)(v) + contextMap.foreach { case (k, v) => + builder = withContext(k)(ev(v)) } builder } - def build(): Log + def build(): Log[Ctx] } - def mutableBuilder(): Builder = new MutableBuilder() - - private class MutableBuilder private[Log] () extends Builder with Log { - def build(): Log = this - - def timestamp: Option[FiniteDuration] = Option(_timestamp) - def level: KernelLogLevel = if (_level == null) KernelLogLevel.Debug else _level - def levelValue: Double = - if (_levelValue < 0) level.value else _levelValue - def message: String = if (_message == null) "" else _message - def throwable: Option[Throwable] = Option(_throwable) - def context: Map[String, Context] = - if (_context == null) Map.empty else _context.toMap - - def className: Option[String] = Option(_className) - def fileName: Option[String] = Option(_fileName) - def methodName: Option[String] = Option(_methodName) - def line: Option[Int] = Some(_line).filter(_ > 0) - - def unsafeThrowable: Throwable = _throwable - def unsafeContext: MapLike[String, Context] = _context - - private var _timestamp: FiniteDuration = null - private var _level: KernelLogLevel = null - private var _levelValue: Double = -1 - private var _message: String = null - private var _throwable: Throwable = null - private var _context: MMap[String, Context] = null - private var _fileName: String = null - private var _className: String = null - private var _methodName: String = null - private var _line: Int = -1 + def mutableBuilder[Ctx](): Builder[Ctx] = new MutableBuilder[Ctx]() + + private class MutableBuilder[Ctx] private[Log] () extends Builder[Ctx] with Log[Ctx] { + private var _timestamp: Option[FiniteDuration] = None + private var _level: Option[KernelLogLevel] = None + private var _levelValue: Option[Int] = None + private var _message: Option[String] = None + private var _throwable: Option[Throwable] = None + private var _context: Option[mutable.Map[String, Ctx]] = None + private var _fileName: Option[String] = None + private var _className: Option[String] = None + private var _methodName: Option[String] = None + private var _line: Option[Int] = None + + def build(): Log[Ctx] = this + + def timestamp: Option[FiniteDuration] = _timestamp + def level: KernelLogLevel = _level.getOrElse(KernelLogLevel.Debug) + def levelValue: Int = + _levelValue.getOrElse(level.value) + def message: String = _message.getOrElse("") + def throwable: Option[Throwable] = _throwable + def context: Map[String, Ctx] = + _context.map(_.toMap).getOrElse(Map.empty) + + def className: Option[String] = _className + def fileName: Option[String] = _fileName + def methodName: Option[String] = _methodName + def line: Option[Int] = _line.filter(_ > 0) + + def unsafeThrowable: Throwable = _throwable.get + def unsafeContext: Map[String, Ctx] = _context.get.toMap def withTimestamp(value: FiniteDuration): this.type = { - this._timestamp = value + this._timestamp = Some(value) this } def withLevel(level: KernelLogLevel): this.type = { - this._level = level + this._level = Some(level) this } - def withLevelValue(levelValue: Double): this.type = { - this._levelValue = levelValue + def withLevelValue(levelValue: Int): this.type = { + this._levelValue = Some(levelValue) this } def withMessage(message: => String): this.type = { - this._message = message + this._message = Some(message) this } def withThrowable(throwable: Throwable): this.type = { - this._throwable = throwable + this._throwable = Some(throwable) this } - def withContext(name: String)(value: Context): this.type = { - if (this._context == null) { - this._context = MMap.empty[String, Context] + def withContext(name: String)(value: Ctx): this.type = { + val map = _context.getOrElse { + val newMap = mutable.Map.empty[String, Ctx] + this._context = Some(newMap) + newMap } - this._context += name -> value + map += name -> value this } def withFileName(name: String): this.type = { - this._fileName = name + this._fileName = Some(name) this } def withClassName(name: String): this.type = { - this._className = name + this._className = Some(name) this } def withLine(line: Int): this.type = { - this._line = line + this._line = Some(line) this } def withMethodName(name: String): this.type = { - this._methodName = name + this._methodName = Some(name) this } } From 53dda4f2945a29dd0821561a91f3afcbe7a9f26a Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sun, 29 Jun 2025 10:55:46 +0000 Subject: [PATCH 16/62] change datatype double to int and use cats Order --- .../typelevel/log4cats/KernelLogLevel.scala | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala index e8f4c166..63542936 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala @@ -16,7 +16,9 @@ package org.typelevel.log4cats -final case class KernelLogLevel(name: String, value: Double) { +import cats.Order + +final case class KernelLogLevel(name: String, value: Int) { def namePadded: String = KernelLogLevel.padded(this) KernelLogLevel.add(this) @@ -28,15 +30,19 @@ object KernelLogLevel { private var map = Map.empty[String, KernelLogLevel] private var padded = Map.empty[KernelLogLevel, String] - implicit final val LevelOrdering: Ordering[KernelLogLevel] = - Ordering.by[KernelLogLevel, Double](_.value).reverse + implicit final val orderKernelLogLevel: Order[KernelLogLevel] = + Order.by[KernelLogLevel, Int](-_.value) + + // For Java/legacy interop, if needed (not implicit) + val LevelOrdering: Ordering[KernelLogLevel] = + Ordering.by[KernelLogLevel, Int](_.value).reverse - val Trace: KernelLogLevel = KernelLogLevel("TRACE", 100.0) - val Debug: KernelLogLevel = KernelLogLevel("DEBUG", 200.0) - val Info: KernelLogLevel = KernelLogLevel("INFO", 300.0) - val Warn: KernelLogLevel = KernelLogLevel("WARN", 400.0) - val Error: KernelLogLevel = KernelLogLevel("ERROR", 500.0) - val Fatal: KernelLogLevel = KernelLogLevel("FATAL", 600.0) + val Trace: KernelLogLevel = KernelLogLevel("TRACE", 100) + val Debug: KernelLogLevel = KernelLogLevel("DEBUG", 200) + val Info: KernelLogLevel = KernelLogLevel("INFO", 300) + val Warn: KernelLogLevel = KernelLogLevel("WARN", 400) + val Error: KernelLogLevel = KernelLogLevel("ERROR", 500) + val Fatal: KernelLogLevel = KernelLogLevel("FATAL", 600) def add(level: KernelLogLevel): Unit = synchronized { val length = level.name.length From 634de7dd3a6c2372774c34e2f9e8353907a58aa4 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sun, 29 Jun 2025 10:59:26 +0000 Subject: [PATCH 17/62] update LoggerKernel to use new Log.scala --- .../org/typelevel/log4cats/LoggerKernel.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala index b454ab32..34e97475 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala @@ -33,17 +33,19 @@ package org.typelevel.log4cats * whatever UX is preferred without necessarily imposing constraints on the underlying * implementation. */ -trait LoggerKernel[F[_]] { - def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] +trait LoggerKernel[F[_], Ctx] { + type Builder = Log.Builder[Ctx] - final def logTrace(record: Log.Builder => Log.Builder): F[Unit] = + def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] + + final def logTrace(record: Builder => Builder): F[Unit] = log(KernelLogLevel.Trace, record) - final def logDebug(record: Log.Builder => Log.Builder): F[Unit] = + final def logDebug(record: Builder => Builder): F[Unit] = log(KernelLogLevel.Debug, record) - final def logInfo(record: Log.Builder => Log.Builder): F[Unit] = + final def logInfo(record: Builder => Builder): F[Unit] = log(KernelLogLevel.Info, record) - final def logWarn(record: Log.Builder => Log.Builder): F[Unit] = + final def logWarn(record: Builder => Builder): F[Unit] = log(KernelLogLevel.Warn, record) - final def logError(record: Log.Builder => Log.Builder): F[Unit] = + final def logError(record: Builder => Builder): F[Unit] = log(KernelLogLevel.Error, record) } From b7f0d102f095afc9fb79a87d4cb29b8fe21bd0cf Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sun, 29 Jun 2025 11:00:38 +0000 Subject: [PATCH 18/62] update LogRecord and Recordable to use paramerterized Log --- .../org/typelevel/log4cats/LogRecord.scala | 12 +++--- .../org/typelevel/log4cats/Recordable.scala | 42 ++++++++++++++----- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala index 4db71bf5..a284917e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala @@ -23,16 +23,16 @@ package org.typelevel.log4cats * This allows for an interesting UX, where the details of the encoding of some data into a log can * be separate from the actual log statements. */ -trait LogRecord extends (Log.Builder => Log.Builder) +trait LogRecord[Ctx] extends (Log.Builder[Ctx] => Log.Builder[Ctx]) object LogRecord { - def combine(all: Seq[LogRecord]): LogRecord = Combined(all) + def combine[Ctx](all: Seq[LogRecord[Ctx]]): LogRecord[Ctx] = Combined(all) - implicit def toLogRecord[A: Recordable](value: => A): LogRecord = - Recordable[A].record(value) + implicit def toLogRecord[Ctx, A: Recordable[Ctx, *]](value: => A): LogRecord[Ctx] = + Recordable[Ctx, A].record(value) - private case class Combined(all: Seq[LogRecord]) extends LogRecord { - def apply(record: Log.Builder): Log.Builder = { + private case class Combined[Ctx](all: Seq[LogRecord[Ctx]]) extends LogRecord[Ctx] { + def apply(record: Log.Builder[Ctx]): Log.Builder[Ctx] = { var current = record all.foreach { logBit => current = logBit(current) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala index 52286be1..88096130 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala @@ -20,27 +20,47 @@ package org.typelevel.log4cats * Typeclass representing the notion that a value can contribute to a log, by transforming it in * some way. */ -trait Recordable[A] { - def record(value: => A): LogRecord +trait Recordable[Ctx, A] { + def record(value: => A): LogRecord[Ctx] } object Recordable { - def apply[A](implicit ev: Recordable[A]): ev.type = ev + def apply[Ctx, A](implicit ev: Recordable[Ctx, A]): ev.type = ev - implicit val stringLoggable: Recordable[String] = new Recordable[String] { + implicit def stringLoggable[Ctx]: Recordable[Ctx, String] = new Recordable[Ctx, String] { def record(value: => String) = _.withMessage(value) } - implicit def tupleLoggable[T: Context.Encoder]: Recordable[(String, T)] = - new Recordable[(String, T)] { - override def record(value: => (String, T)): LogRecord = { + implicit def tupleLoggable[Ctx, T: Context.Encoder](implicit + ev: T =:= Ctx + ): Recordable[Ctx, (String, T)] = + new Recordable[Ctx, (String, T)] { + override def record(value: => (String, T)): LogRecord[Ctx] = { val (k, v) = value - (_: Log.Builder).withContext(k)(v) + (_: Log.Builder[Ctx]).withContext(k)(ev(v)) } } - implicit def throwableLoggable[T <: Throwable]: Recordable[T] = - new Recordable[T] { - def record(value: => T): LogRecord = _.withThrowable(value) + // Special case for (String, String) when Ctx is Context + implicit def stringTupleLoggable: Recordable[Context, (String, String)] = + new Recordable[Context, (String, String)] { + override def record(value: => (String, String)): LogRecord[Context] = { + val (k, v) = value + (_: Log.Builder[Context]).withContext(k)(v: Context) + } + } + + // Special case for (String, Int) when Ctx is Context + implicit def intTupleLoggable: Recordable[Context, (String, Int)] = + new Recordable[Context, (String, Int)] { + override def record(value: => (String, Int)): LogRecord[Context] = { + val (k, v) = value + (_: Log.Builder[Context]).withContext(k)(v: Context) + } + } + + implicit def throwableLoggable[Ctx, T <: Throwable]: Recordable[Ctx, T] = + new Recordable[Ctx, T] { + def record(value: => T): LogRecord[Ctx] = _.withThrowable(value) } } From 859753b2780f49e9e53251bcbe96339e259c2ac0 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sun, 29 Jun 2025 11:09:08 +0000 Subject: [PATCH 19/62] update ConsoleLogger to original, JsonLike --- .../org/typelevel/log4cats/JsonLike.scala | 4 +- .../org/typelevel/log4cats/SamLogger.scala | 75 +++++----- .../log4cats/SamStructuredLogger.scala | 139 ++++++++++++++---- .../log4cats/console/ConsoleLogger.scala | 12 +- 4 files changed, 159 insertions(+), 71 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala index 2d1e27bf..9c46748c 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala @@ -33,8 +33,8 @@ trait JsonLike { def double(value: Double): J def timestamp(ts: FiniteDuration): J def string(value: String): J - def obj(bindings: (String, J)*): J - def arr(elems: J*): J + def obj(bindings: Iterable[(String, J)]): J + def arr(elems: Iterable[J]): J } object JsonLike { diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index 9dc55fac..e95626aa 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -23,37 +23,37 @@ import cats.data.{EitherT, Kleisli, OptionT} * A SAM-based Logger that extends LoggerKernel and provides a user-friendly interface. This is the * new design that will eventually replace the current Logger trait. */ -abstract class SamLogger[F[_]] extends LoggerKernel[F] { +abstract class SamLogger[F[_], Ctx] extends LoggerKernel[F, Ctx] { - final def info(logBit: LogRecord, others: LogRecord*)(implicit + final def info(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line ): F[Unit] = log_(KernelLogLevel.Info, logBit, others: _*) - final def warn(logBit: LogRecord, others: LogRecord*)(implicit + final def warn(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line ): F[Unit] = log_(KernelLogLevel.Warn, logBit, others: _*) - final def error(logBit: LogRecord, others: LogRecord*)(implicit + final def error(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line ): F[Unit] = log_(KernelLogLevel.Error, logBit, others: _*) - final def trace(logBit: LogRecord, others: LogRecord*)(implicit + final def trace(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line ): F[Unit] = log_(KernelLogLevel.Trace, logBit, others: _*) - final def debug(logBit: LogRecord, others: LogRecord*)(implicit + final def debug(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, @@ -62,8 +62,8 @@ abstract class SamLogger[F[_]] extends LoggerKernel[F] { private final def log_( level: KernelLogLevel, - bit: LogRecord, - others: LogRecord* + bit: LogRecord[Ctx], + others: LogRecord[Ctx]* )(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, @@ -72,8 +72,8 @@ abstract class SamLogger[F[_]] extends LoggerKernel[F] { ): F[Unit] = { log( level, - (record: Log.Builder) => - LogRecord.combine(others)( + (record: Builder) => + LogRecord.combine[Ctx](others)( bit( record .withLevel(level) @@ -85,37 +85,46 @@ abstract class SamLogger[F[_]] extends LoggerKernel[F] { ) } - def withModifiedString(f: String => String): SamLogger[F] = - SamLogger.withModifiedString[F](this, f) - def mapK[G[_]](fk: F ~> G): SamLogger[G] = SamLogger.mapK(fk)(this) + def withModifiedString(f: String => String): SamLogger[F, Ctx] = + SamLogger.withModifiedString[F, Ctx](this, f) + def mapK[G[_]](fk: F ~> G): SamLogger[G, Ctx] = SamLogger.mapK(fk)(this) } object SamLogger { - def apply[F[_]](implicit ev: SamLogger[F]) = ev + def apply[F[_], Ctx](implicit ev: SamLogger[F, Ctx]) = ev - def wrap[F[_]](kernel: LoggerKernel[F]): SamLogger[F] = new SamLogger[F] { - def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = + def wrap[F[_], Ctx](kernel: LoggerKernel[F, Ctx]): SamLogger[F, Ctx] = new SamLogger[F, Ctx] { + def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = kernel.log(level, record) } - implicit def optionTSamLogger[F[_]: SamLogger: Functor]: SamLogger[OptionT[F, *]] = - SamLogger[F].mapK(OptionT.liftK[F]) - - implicit def eitherTSamLogger[F[_]: SamLogger: Functor, E]: SamLogger[EitherT[F, E, *]] = - SamLogger[F].mapK(EitherT.liftK[F, E]) - - implicit def kleisliSamLogger[F[_]: SamLogger, A]: SamLogger[Kleisli[F, A, *]] = - SamLogger[F].mapK(Kleisli.liftK[F, A]) - - private def withModifiedString[F[_]](l: SamLogger[F], f: String => String): SamLogger[F] = - new SamLogger[F] { - def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = { - val modifiedRecord = (builder: Log.Builder) => { + implicit def optionTSamLogger[F[_]: Functor, Ctx](implicit + ev: SamLogger[F, Ctx] + ): SamLogger[OptionT[F, *], Ctx] = + ev.mapK(OptionT.liftK[F]) + + implicit def eitherTSamLogger[F[_]: Functor, E, Ctx](implicit + ev: SamLogger[F, Ctx] + ): SamLogger[EitherT[F, E, *], Ctx] = + ev.mapK(EitherT.liftK[F, E]) + + implicit def kleisliSamLogger[F[_], A, Ctx](implicit + ev: SamLogger[F, Ctx] + ): SamLogger[Kleisli[F, A, *], Ctx] = + ev.mapK(Kleisli.liftK[F, A]) + + private def withModifiedString[F[_], Ctx]( + l: SamLogger[F, Ctx], + f: String => String + ): SamLogger[F, Ctx] = + new SamLogger[F, Ctx] { + def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = { + val modifiedRecord = (builder: Builder) => { val originalLog = record(builder).build() val modifiedMessage = f(originalLog.message) val newBuilder = Log - .mutableBuilder() + .mutableBuilder[Ctx]() .withLevel(level) .withMessage(modifiedMessage) @@ -137,9 +146,9 @@ object SamLogger { } } - private def mapK[G[_], F[_]](f: G ~> F)(logger: SamLogger[G]): SamLogger[F] = - new SamLogger[F] { - def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = + private def mapK[G[_], F[_], Ctx](f: G ~> F)(logger: SamLogger[G, Ctx]): SamLogger[F, Ctx] = + new SamLogger[F, Ctx] { + def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = f(logger.log(level, record)) } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala index a52dc5f9..b42d159c 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -17,45 +17,106 @@ package org.typelevel.log4cats import cats.* +import cats.data.{EitherT, Kleisli, OptionT} import cats.Show.Shown /** - * SAM-based implementation of StructuredLogger that delegates to LoggerKernel. This provides the - * same interface as StructuredLogger but uses the SAM architecture underneath for better - * performance and middleware compatibility. + * A SAM-based StructuredLogger that extends Logger and provides structured logging capabilities. + * This implementation uses the new SAM LoggerKernel design for better performance and middleware + * compatibility. */ trait SamStructuredLogger[F[_]] extends Logger[F] { - protected def kernel: LoggerKernel[F] + protected def kernel: LoggerKernel[F, Context] - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - kernel.logTrace(_.withMessage(msg).withContextMap(ctx)) + def trace(ctx: Map[String, String])(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logTrace(builder) + } - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - kernel.logTrace(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg).withThrowable(t) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logTrace(builder) + } - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - kernel.logDebug(_.withMessage(msg).withContextMap(ctx)) + def debug(ctx: Map[String, String])(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logDebug(builder) + } - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - kernel.logDebug(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg).withThrowable(t) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logDebug(builder) + } - def info(ctx: Map[String, String])(msg: => String): F[Unit] = - kernel.logInfo(_.withMessage(msg).withContextMap(ctx)) + def info(ctx: Map[String, String])(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logInfo(builder) + } - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - kernel.logInfo(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg).withThrowable(t) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logInfo(builder) + } - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - kernel.logWarn(_.withMessage(msg).withContextMap(ctx)) + def warn(ctx: Map[String, String])(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logWarn(builder) + } - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - kernel.logWarn(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg).withThrowable(t) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logWarn(builder) + } - def error(ctx: Map[String, String])(msg: => String): F[Unit] = - kernel.logError(_.withMessage(msg).withContextMap(ctx)) + def error(ctx: Map[String, String])(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logError(builder) + } - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - kernel.logError(_.withMessage(msg).withContextMap(ctx).withThrowable(t)) + def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + val builder = (b: Log.Builder[Context]) => { + var current = b.withMessage(msg).withThrowable(t) + ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + current + } + kernel.logError(builder) + } override def trace(message: => String): F[Unit] = kernel.logTrace(_.withMessage(message)) @@ -105,9 +166,9 @@ trait SamStructuredLogger[F[_]] extends Logger[F] { object SamStructuredLogger { def apply[F[_]](implicit ev: SamStructuredLogger[F]): SamStructuredLogger[F] = ev - def fromKernel[F[_]](kernelImpl: LoggerKernel[F]): SamStructuredLogger[F] = + def fromKernel[F[_]](kernelImpl: LoggerKernel[F, Context]): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F] = kernelImpl + protected def kernel: LoggerKernel[F, Context] = kernelImpl } def withContext[F[_]](sl: SamStructuredLogger[F])( @@ -123,7 +184,7 @@ object SamStructuredLogger { private class ModifiedContextSamStructuredLogger[F[_]](sl: SamStructuredLogger[F])( modify: Map[String, String] => Map[String, String] ) extends SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F] = sl.kernel + protected def kernel: LoggerKernel[F, Context] = sl.kernel private lazy val defaultCtx: Map[String, String] = modify(Map.empty) @@ -174,7 +235,7 @@ object SamStructuredLogger { f: String => String ): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F] = l.kernel + protected def kernel: LoggerKernel[F, Context] = l.kernel override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = l.trace(ctx)(f(msg)) override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = @@ -206,8 +267,11 @@ object SamStructuredLogger { private def mapK[G[_], F[_]](f: G ~> F)(logger: SamStructuredLogger[G]): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F] = new LoggerKernel[F] { - def log(level: KernelLogLevel, record: Log.Builder => Log.Builder): F[Unit] = + protected def kernel: LoggerKernel[F, Context] = new LoggerKernel[F, Context] { + def log( + level: KernelLogLevel, + record: Log.Builder[Context] => Log.Builder[Context] + ): F[Unit] = f(logger.kernel.log(level, record)) } @@ -244,4 +308,19 @@ object SamStructuredLogger { override def error(message: => String): F[Unit] = f(logger.error(message)) override def error(t: Throwable)(message: => String): F[Unit] = f(logger.error(t)(message)) } + + implicit def optionTSamStructuredLogger[F[_]: Functor](implicit + ev: SamStructuredLogger[F] + ): SamStructuredLogger[OptionT[F, *]] = + ev.mapK(OptionT.liftK[F]) + + implicit def eitherTSamStructuredLogger[F[_]: Functor, E](implicit + ev: SamStructuredLogger[F] + ): SamStructuredLogger[EitherT[F, E, *]] = + ev.mapK(EitherT.liftK[F, E]) + + implicit def kleisliSamStructuredLogger[F[_], A](implicit + ev: SamStructuredLogger[F] + ): SamStructuredLogger[Kleisli[F, A, *]] = + ev.mapK(Kleisli.liftK[F, A]) } diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index 0834599c..6f79e754 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -22,28 +22,28 @@ import cats.syntax.all.* import org.typelevel.log4cats.extras.LogLevel import org.typelevel.log4cats.extras.LogLevel.* -class ConsoleLogger[F[_]: Sync](LogLevel: Option[LogLevel] = Option(Trace)) +class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) extends SelfAwareStructuredLogger[F] { private val ConsoleF: ConsoleF[F] = implicitly override def trace(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) override def trace(message: => String): F[Unit] = ConsoleF.debug(message) - override def isTraceEnabled: F[Boolean] = LogLevel.exists(_ <= Trace).pure[F] + override def isTraceEnabled: F[Boolean] = logLevel.exists(_ <= Trace).pure[F] override def debug(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) override def debug(message: => String): F[Unit] = ConsoleF.debug(message) - override def isDebugEnabled: F[Boolean] = LogLevel.exists(_ <= Debug).pure[F] + override def isDebugEnabled: F[Boolean] = logLevel.exists(_ <= Debug).pure[F] override def info(t: Throwable)(message: => String): F[Unit] = ConsoleF.info(message, t) override def info(message: => String): F[Unit] = ConsoleF.info(message) - override def isInfoEnabled: F[Boolean] = LogLevel.exists(_ <= Info).pure[F] + override def isInfoEnabled: F[Boolean] = logLevel.exists(_ <= Info).pure[F] override def warn(t: Throwable)(message: => String): F[Unit] = ConsoleF.warn(message, t) override def warn(message: => String): F[Unit] = ConsoleF.warn(message) - override def isWarnEnabled: F[Boolean] = LogLevel.exists(_ <= Warn).pure[F] + override def isWarnEnabled: F[Boolean] = logLevel.exists(_ <= Warn).pure[F] override def error(t: Throwable)(message: => String): F[Unit] = ConsoleF.error(message, t) override def error(message: => String): F[Unit] = ConsoleF.error(message) - override def isErrorEnabled: F[Boolean] = LogLevel.exists(_ <= Error).pure[F] + override def isErrorEnabled: F[Boolean] = logLevel.exists(_ <= Error).pure[F] /* * ConsoleLogger should probably not extend from StructuredLogger, because there's not From ac1a39b62cc898393545528401f871fefec7dedf Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sun, 29 Jun 2025 11:15:02 +0000 Subject: [PATCH 20/62] remove unused Context.Encoder --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 2 +- .../src/main/scala/org/typelevel/log4cats/Recordable.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 110319ec..ad7f57fc 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -53,7 +53,7 @@ object Log { def withClassName(name: String): Builder[Ctx] def withLine(line: Int): Builder[Ctx] - final def withContextMap[A: Context.Encoder]( + final def withContextMap[A]( contextMap: Map[String, A] )(implicit ev: A =:= Ctx): Builder[Ctx] = { var builder = this diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala index 88096130..01f60ce7 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala @@ -31,7 +31,7 @@ object Recordable { def record(value: => String) = _.withMessage(value) } - implicit def tupleLoggable[Ctx, T: Context.Encoder](implicit + implicit def tupleLoggable[Ctx, T](implicit ev: T =:= Ctx ): Recordable[Ctx, (String, T)] = new Recordable[Ctx, (String, T)] { From 44ab02bebeb94e38d7387fbb5c509568c7d316ab Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Mon, 11 Aug 2025 07:08:14 +0000 Subject: [PATCH 21/62] remove JsonLike Structure instead bypass it direct --- .../org/typelevel/log4cats/Context.scala | 45 ++++----- .../org/typelevel/log4cats/JsonLike.scala | 44 --------- .../scala/org/typelevel/log4cats/Log.scala | 98 ++++++++----------- .../org/typelevel/log4cats/SamLogger.scala | 4 +- 4 files changed, 62 insertions(+), 129 deletions(-) delete mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala index 11fbb878..554d1cdc 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala @@ -16,47 +16,42 @@ package org.typelevel.log4cats +import java.time.Instant +import java.time.format.DateTimeFormatter import scala.concurrent.duration.FiniteDuration -import org.typelevel.log4cats.JsonLike.Aux + +import org.typelevel.log4cats.Context.Encoder /** * A value that can be written into a json-like construct, provided a visitor. */ -trait Context { - def capture[J](jsonLike: JsonLike.Aux[J]): J +trait Context[C] { + def capture[A](a: A)(implicit E: Encoder[A, C]): C } object Context { - trait Encoder[A] { - def encode[J](json: JsonLike.Aux[J], a: A): J + trait Encoder[A, B] { + def encode(a: A): B } object Encoder { - def apply[A](implicit ev: Encoder[A]): ev.type = ev + def apply[A, B](implicit ev: Encoder[A, B]): ev.type = ev - implicit val stringEncoder: Encoder[String] = new Encoder[String] { - def encode[J](json: JsonLike.Aux[J], a: String) = json.string(a) - } + // Identity encoder for when input and output types are the same + implicit def identityEncoder[A]: Encoder[A, A] = a => a - implicit val intEncoder: Encoder[Int] = new Encoder[Int] { - def encode[J](json: JsonLike.Aux[J], a: Int) = json.int(a) - } + implicit val stringToStringEncoder: Encoder[String, String] = a => a - implicit val booleanEncoder: Encoder[Boolean] = new Encoder[Boolean] { - def encode[J](json: Aux[J], a: Boolean): J = json.bool(a) - } + implicit val intToStringEncoder: Encoder[Int, String] = _.toString - implicit val timestampEncoder: Encoder[FiniteDuration] = - new Encoder[FiniteDuration] { - def encode[J](json: JsonLike.Aux[J], a: FiniteDuration) = - json.timestamp(a) - } - } + implicit val longToStringEncoder: Encoder[Long, String] = _.toString + + implicit val doubleToStringEncoder: Encoder[Double, String] = _.toString + + implicit val booleanToStringEncoder: Encoder[Boolean, String] = if (_) "true" else "false" - implicit def toContext[A: Encoder](a: A): Context = - DeferredRecord(a, Encoder[A]) + implicit val instantToStringEncoder: Encoder[Instant, String] = DateTimeFormatter.ISO_INSTANT.format(_) - private case class DeferredRecord[A](a: A, encoder: Encoder[A]) extends Context { - def capture[J](jsonLike: JsonLike.Aux[J]): J = encoder.encode(jsonLike, a) + implicit val finiteDurationToStringEncoder: Encoder[FiniteDuration, String] = _.toString } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala b/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala deleted file mode 100644 index 9c46748c..00000000 --- a/core/shared/src/main/scala/org/typelevel/log4cats/JsonLike.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 - -import scala.concurrent.duration.FiniteDuration - -/** - * A visitor-like construct that allows for capturing contextual values of several types, without - * enforcing an in-memory representation or a third-party dependency. - */ -trait JsonLike { - type J - - def nul: J - def bool(value: Boolean): J - def int(value: Int): J - def short(value: Int): J - def long(value: Int): J - def double(value: Double): J - def timestamp(ts: FiniteDuration): J - def string(value: String): J - def obj(bindings: Iterable[(String, J)]): J - def arr(elems: Iterable[J]): J -} - -object JsonLike { - type Aux[Json] = JsonLike { - type J = Json - } -} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index ad7f57fc..6c458030 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -45,112 +45,94 @@ object Log { trait Builder[Ctx] { def withTimestamp(value: FiniteDuration): Builder[Ctx] def withLevel(level: KernelLogLevel): Builder[Ctx] - def withLevelValue(levelValue: Int): Builder[Ctx] def withMessage(message: => String): Builder[Ctx] def withThrowable(throwable: Throwable): Builder[Ctx] - def withContext(name: String)(f: Ctx): Builder[Ctx] + def withContext[A](name: String)(ctx: A)(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] def withFileName(name: String): Builder[Ctx] def withClassName(name: String): Builder[Ctx] + def withMethodName(name: String): Builder[Ctx] def withLine(line: Int): Builder[Ctx] final def withContextMap[A]( contextMap: Map[String, A] - )(implicit ev: A =:= Ctx): Builder[Ctx] = { - var builder = this - contextMap.foreach { case (k, v) => - builder = withContext(k)(ev(v)) - } - builder - } + )(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] = + contextMap.foldLeft(this) { case (ctx, (k, v)) => ctx.withContext(k)(v) } def build(): Log[Ctx] } def mutableBuilder[Ctx](): Builder[Ctx] = new MutableBuilder[Ctx]() - private class MutableBuilder[Ctx] private[Log] () extends Builder[Ctx] with Log[Ctx] { + private val noopMessage: () => String = () => "" + + private class MutableBuilder[Ctx] extends Builder[Ctx] { private var _timestamp: Option[FiniteDuration] = None private var _level: Option[KernelLogLevel] = None - private var _levelValue: Option[Int] = None - private var _message: Option[String] = None + private var _message: () => String = noopMessage private var _throwable: Option[Throwable] = None - private var _context: Option[mutable.Map[String, Ctx]] = None + private var _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] private var _fileName: Option[String] = None private var _className: Option[String] = None private var _methodName: Option[String] = None private var _line: Option[Int] = None - def build(): Log[Ctx] = this - - def timestamp: Option[FiniteDuration] = _timestamp - def level: KernelLogLevel = _level.getOrElse(KernelLogLevel.Debug) - def levelValue: Int = - _levelValue.getOrElse(level.value) - def message: String = _message.getOrElse("") - def throwable: Option[Throwable] = _throwable - def context: Map[String, Ctx] = - _context.map(_.toMap).getOrElse(Map.empty) - - def className: Option[String] = _className - def fileName: Option[String] = _fileName - def methodName: Option[String] = _methodName - def line: Option[Int] = _line.filter(_ > 0) - - def unsafeThrowable: Throwable = _throwable.get - def unsafeContext: Map[String, Ctx] = _context.get.toMap - - def withTimestamp(value: FiniteDuration): this.type = { - this._timestamp = Some(value) - this + def build(): Log[Ctx] = new Log[Ctx] { + override def timestamp: Option[FiniteDuration] = _timestamp + override def level: KernelLogLevel = _level.getOrElse(KernelLogLevel.Info) + override def levelValue: Int = level.value + override def message: String = _message() + override def throwable: Option[Throwable] = _throwable + override def context: Map[String, Ctx] = _context.toMap + override def className: Option[String] = _className + override def fileName: Option[String] = _fileName + override def methodName: Option[String] = _methodName + override def line: Option[Int] = _line.filter(_ > 0) + override def unsafeThrowable: Throwable = _throwable.get + override def unsafeContext: Map[String, Ctx] = _context.toMap } - def withLevel(level: KernelLogLevel): this.type = { - this._level = Some(level) + override def withTimestamp(value: FiniteDuration): this.type = { + _timestamp = Some(value) this } - def withLevelValue(levelValue: Int): this.type = { - this._levelValue = Some(levelValue) + override def withLevel(level: KernelLogLevel): this.type = { + _level = Some(level) this } - def withMessage(message: => String): this.type = { - this._message = Some(message) + override def withMessage(message: => String): this.type = { + _message = () => message this } - def withThrowable(throwable: Throwable): this.type = { - this._throwable = Some(throwable) + override def withThrowable(throwable: Throwable): this.type = { + _throwable = Some(throwable) this } - def withContext(name: String)(value: Ctx): this.type = { - val map = _context.getOrElse { - val newMap = mutable.Map.empty[String, Ctx] - this._context = Some(newMap) - newMap - } - map += name -> value + override def withContext[A](name: String)(ctx: A)(implicit E: Context.Encoder[A, Ctx]): this.type = { + _context += (name -> E.encode(ctx)) this } - def withFileName(name: String): this.type = { - this._fileName = Some(name) + override def withFileName(name: String): this.type = { + _fileName = Some(name) this } - def withClassName(name: String): this.type = { - this._className = Some(name) + override def withClassName(name: String): this.type = { + _className = Some(name) this } - def withLine(line: Int): this.type = { - this._line = Some(line) + override def withMethodName(name: String): this.type = { + _methodName = Some(name) this } - def withMethodName(name: String): this.type = { - this._methodName = Some(name) + override def withLine(line: Int): this.type = { + _line = if (line > 0) Some(line) else None this } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index e95626aa..1b97cb9b 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -128,14 +128,14 @@ object SamLogger { .withLevel(level) .withMessage(modifiedMessage) - // we can copy the fields from the original log like this + // Copy the fields from the original log originalLog.timestamp.foreach(newBuilder.withTimestamp) originalLog.throwable.foreach(newBuilder.withThrowable) - originalLog.fileName.foreach(newBuilder.withFileName) originalLog.className.foreach(newBuilder.withClassName) originalLog.line.foreach(newBuilder.withLine) + // Copy context - since Ctx is the same type, we can use identity encoder originalLog.context.foreach { case (k, v) => newBuilder.withContext(k)(v) } From 10b0c8123b5706572bed13962194b26425576993 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Mon, 11 Aug 2025 14:17:15 +0000 Subject: [PATCH 22/62] modify files to adapt new change --- .../log4cats/SamStructuredLogger.scala | 54 +++++++++---------- .../src/main/scala/logger/Recordable.scala | 33 ++++++++++++ 2 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala index b42d159c..3681e403 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -26,93 +26,93 @@ import cats.Show.Shown * compatibility. */ trait SamStructuredLogger[F[_]] extends Logger[F] { - protected def kernel: LoggerKernel[F, Context] + protected def kernel: LoggerKernel[F, String] def trace(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logTrace(builder) } def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logTrace(builder) } def debug(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logDebug(builder) } def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logDebug(builder) } def info(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logInfo(builder) } def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logInfo(builder) } def warn(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logWarn(builder) } def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logWarn(builder) } def error(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logError(builder) } def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[Context]) => { + val builder = (b: Log.Builder[String]) => { var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext(k)(v: Context) } + ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } current } kernel.logError(builder) @@ -166,9 +166,9 @@ trait SamStructuredLogger[F[_]] extends Logger[F] { object SamStructuredLogger { def apply[F[_]](implicit ev: SamStructuredLogger[F]): SamStructuredLogger[F] = ev - def fromKernel[F[_]](kernelImpl: LoggerKernel[F, Context]): SamStructuredLogger[F] = + def fromKernel[F[_]](kernelImpl: LoggerKernel[F, String]): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, Context] = kernelImpl + protected def kernel: LoggerKernel[F, String] = kernelImpl } def withContext[F[_]](sl: SamStructuredLogger[F])( @@ -184,7 +184,7 @@ object SamStructuredLogger { private class ModifiedContextSamStructuredLogger[F[_]](sl: SamStructuredLogger[F])( modify: Map[String, String] => Map[String, String] ) extends SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, Context] = sl.kernel + protected def kernel: LoggerKernel[F, String] = sl.kernel private lazy val defaultCtx: Map[String, String] = modify(Map.empty) @@ -235,7 +235,7 @@ object SamStructuredLogger { f: String => String ): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, Context] = l.kernel + protected def kernel: LoggerKernel[F, String] = l.kernel override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = l.trace(ctx)(f(msg)) override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = @@ -267,10 +267,10 @@ object SamStructuredLogger { private def mapK[G[_], F[_]](f: G ~> F)(logger: SamStructuredLogger[G]): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, Context] = new LoggerKernel[F, Context] { + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { def log( level: KernelLogLevel, - record: Log.Builder[Context] => Log.Builder[Context] + record: Log.Builder[String] => Log.Builder[String] ): F[Unit] = f(logger.kernel.log(level, record)) } diff --git a/logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala b/logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala new file mode 100644 index 00000000..e55850bd --- /dev/null +++ b/logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala @@ -0,0 +1,33 @@ +package logger + +/** Typeclass representing the notion that a value can contribute to a log, by + * transforming it in some way. + */ +trait Recordable[A] { + def record(value: => A): LogRecord +} + +object Recordable { + + def apply[A](implicit ev: Recordable[A]): ev.type = ev + + implicit val stringLoggable: Recordable[String] = new Recordable[String] { + def record(value: => String) = _.withMessage(value) + } + + implicit def tupleLoggable[T: Context.Encoder]: Recordable[(String, T)] = + new Recordable[(String, T)] { + + override def record(value: => (String, T)): LogRecord = { + val (k, v) = value + (_: Log.Builder).withContext(k)(v) + } + + } + + implicit def throwableLoggable[T <: Throwable]: Recordable[T] = + new Recordable[T] { + def record(value: => T): LogRecord = _.withThrowable(value) + } + +} From 4b746e26851d49219ef2307fde50745d70fb718d Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 12 Aug 2025 05:33:39 +0000 Subject: [PATCH 23/62] update log.scala --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 6c458030..a2028e0e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -56,7 +56,7 @@ object Log { final def withContextMap[A]( contextMap: Map[String, A] )(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] = - contextMap.foldLeft(this) { case (ctx, (k, v)) => ctx.withContext(k)(v) } + contextMap.foldLeft(this) { case (builder, (k, v)) => builder.withContext(k)(v) } def build(): Log[Ctx] } From 2ad479c3d7aea32e4ca6dfa4a26d545626e978f7 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 13 Aug 2025 09:58:19 +0000 Subject: [PATCH 24/62] update recordable --- .../org/typelevel/log4cats/Context.scala | 3 +- .../scala/org/typelevel/log4cats/Log.scala | 6 +- .../org/typelevel/log4cats/Recordable.scala | 83 ++++++++++++++----- 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala index 554d1cdc..e558890f 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala @@ -50,7 +50,8 @@ object Context { implicit val booleanToStringEncoder: Encoder[Boolean, String] = if (_) "true" else "false" - implicit val instantToStringEncoder: Encoder[Instant, String] = DateTimeFormatter.ISO_INSTANT.format(_) + implicit val instantToStringEncoder: Encoder[Instant, String] = + DateTimeFormatter.ISO_INSTANT.format(_) implicit val finiteDurationToStringEncoder: Encoder[FiniteDuration, String] = _.toString } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index a2028e0e..b60d8964 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -55,7 +55,7 @@ object Log { final def withContextMap[A]( contextMap: Map[String, A] - )(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] = + )(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] = contextMap.foldLeft(this) { case (builder, (k, v)) => builder.withContext(k)(v) } def build(): Log[Ctx] @@ -111,7 +111,9 @@ object Log { this } - override def withContext[A](name: String)(ctx: A)(implicit E: Context.Encoder[A, Ctx]): this.type = { + override def withContext[A]( + name: String + )(ctx: A)(implicit E: Context.Encoder[A, Ctx]): this.type = { _context += (name -> E.encode(ctx)) this } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala index 01f60ce7..5612895e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala @@ -27,40 +27,81 @@ trait Recordable[Ctx, A] { object Recordable { def apply[Ctx, A](implicit ev: Recordable[Ctx, A]): ev.type = ev + // Basic string message recording implicit def stringLoggable[Ctx]: Recordable[Ctx, String] = new Recordable[Ctx, String] { def record(value: => String) = _.withMessage(value) } - implicit def tupleLoggable[Ctx, T](implicit - ev: T =:= Ctx - ): Recordable[Ctx, (String, T)] = - new Recordable[Ctx, (String, T)] { - override def record(value: => (String, T)): LogRecord[Ctx] = { + // Context key-value pair recording with proper encoding + implicit def contextPairLoggable[Ctx, A](implicit + encoder: Context.Encoder[A, Ctx] + ): Recordable[Ctx, (String, A)] = + new Recordable[Ctx, (String, A)] { + override def record(value: => (String, A)): LogRecord[Ctx] = { val (k, v) = value - (_: Log.Builder[Ctx]).withContext(k)(ev(v)) + (_: Log.Builder[Ctx]).withContext(k)(v) } } - // Special case for (String, String) when Ctx is Context - implicit def stringTupleLoggable: Recordable[Context, (String, String)] = - new Recordable[Context, (String, String)] { - override def record(value: => (String, String)): LogRecord[Context] = { - val (k, v) = value - (_: Log.Builder[Context]).withContext(k)(v: Context) + // Throwable recording + implicit def throwableLoggable[Ctx, T <: Throwable]: Recordable[Ctx, T] = + new Recordable[Ctx, T] { + def record(value: => T): LogRecord[Ctx] = _.withThrowable(value) + } + + // Numeric value recording with automatic string conversion + implicit def intLoggable[Ctx](implicit + encoder: Context.Encoder[Int, Ctx] + ): Recordable[Ctx, Int] = + new Recordable[Ctx, Int] { + def record(value: => Int): LogRecord[Ctx] = { + val v = value + (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) } } - // Special case for (String, Int) when Ctx is Context - implicit def intTupleLoggable: Recordable[Context, (String, Int)] = - new Recordable[Context, (String, Int)] { - override def record(value: => (String, Int)): LogRecord[Context] = { - val (k, v) = value - (_: Log.Builder[Context]).withContext(k)(v: Context) + implicit def longLoggable[Ctx](implicit + encoder: Context.Encoder[Long, Ctx] + ): Recordable[Ctx, Long] = + new Recordable[Ctx, Long] { + def record(value: => Long): LogRecord[Ctx] = { + val v = value + (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) } } - implicit def throwableLoggable[Ctx, T <: Throwable]: Recordable[Ctx, T] = - new Recordable[Ctx, T] { - def record(value: => T): LogRecord[Ctx] = _.withThrowable(value) + implicit def doubleLoggable[Ctx](implicit + encoder: Context.Encoder[Double, Ctx] + ): Recordable[Ctx, Double] = + new Recordable[Ctx, Double] { + def record(value: => Double): LogRecord[Ctx] = { + val v = value + (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) + } + } + + implicit def booleanLoggable[Ctx](implicit + encoder: Context.Encoder[Boolean, Ctx] + ): Recordable[Ctx, Boolean] = + new Recordable[Ctx, Boolean] { + def record(value: => Boolean): LogRecord[Ctx] = { + val v = value + (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) + } + } + + // Map recording for structured data + implicit def mapLoggable[Ctx, A](implicit + encoder: Context.Encoder[A, Ctx] + ): Recordable[Ctx, Map[String, A]] = + new Recordable[Ctx, Map[String, A]] { + def record(value: => Map[String, A]): LogRecord[Ctx] = { + val map = value + (builder: Log.Builder[Ctx]) => { + var current = builder + map.foreach { case (k, v) => current = current.withContext(k)(v) } + current + } + } } } From 842bb7e2c65d6f06b310d50db6835196dfd4062d Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 13 Aug 2025 10:02:58 +0000 Subject: [PATCH 25/62] update Log.scala --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index b60d8964..27d04591 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -70,7 +70,7 @@ object Log { private var _level: Option[KernelLogLevel] = None private var _message: () => String = noopMessage private var _throwable: Option[Throwable] = None - private var _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] + private val _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] private var _fileName: Option[String] = None private var _className: Option[String] = None private var _methodName: Option[String] = None From fd6bab173617c83fde08d0b6d02ba768b0593712 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 26 Aug 2025 12:53:49 +0000 Subject: [PATCH 26/62] update log.scala --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 27d04591..2ac7d0c5 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -28,7 +28,6 @@ import scala.concurrent.duration.FiniteDuration trait Log[Ctx] { def timestamp: Option[FiniteDuration] def level: KernelLogLevel - def levelValue: Int def message: String def throwable: Option[Throwable] def context: Map[String, Ctx] @@ -79,7 +78,6 @@ object Log { def build(): Log[Ctx] = new Log[Ctx] { override def timestamp: Option[FiniteDuration] = _timestamp override def level: KernelLogLevel = _level.getOrElse(KernelLogLevel.Info) - override def levelValue: Int = level.value override def message: String = _message() override def throwable: Option[Throwable] = _throwable override def context: Map[String, Ctx] = _context.toMap From 63ba4c45652fd52dd6c23a28d1adbc02842eb329 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 26 Aug 2025 13:20:57 +0000 Subject: [PATCH 27/62] update Log.scala --- .../src/main/scala/org/typelevel/log4cats/Log.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 2ac7d0c5..e515cac1 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -62,12 +62,10 @@ object Log { def mutableBuilder[Ctx](): Builder[Ctx] = new MutableBuilder[Ctx]() - private val noopMessage: () => String = () => "" - private class MutableBuilder[Ctx] extends Builder[Ctx] { private var _timestamp: Option[FiniteDuration] = None private var _level: Option[KernelLogLevel] = None - private var _message: () => String = noopMessage + private var _message: Option[String] = None private var _throwable: Option[Throwable] = None private val _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] private var _fileName: Option[String] = None @@ -78,7 +76,7 @@ object Log { def build(): Log[Ctx] = new Log[Ctx] { override def timestamp: Option[FiniteDuration] = _timestamp override def level: KernelLogLevel = _level.getOrElse(KernelLogLevel.Info) - override def message: String = _message() + override def message: String = _message.getOrElse("") override def throwable: Option[Throwable] = _throwable override def context: Map[String, Ctx] = _context.toMap override def className: Option[String] = _className @@ -100,7 +98,7 @@ object Log { } override def withMessage(message: => String): this.type = { - _message = () => message + _message = Some(message) this } From 5b85513781c18601ebf25f1407a16c8431f12a6a Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 16:46:39 +0000 Subject: [PATCH 28/62] update with the suggested changes --- .../scala/org/typelevel/log4cats/Log.scala | 79 +++++++-- .../org/typelevel/log4cats/Recordable.scala | 11 +- .../org/typelevel/log4cats/SamLogger.scala | 2 +- .../typelevel/log4cats/SamLoggerAdapter.scala | 159 ++++++++++++++++++ .../log4cats/SamStructuredLogger.scala | 70 ++------ 5 files changed, 238 insertions(+), 83 deletions(-) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index e515cac1..44890d96 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -28,16 +28,14 @@ import scala.concurrent.duration.FiniteDuration trait Log[Ctx] { def timestamp: Option[FiniteDuration] def level: KernelLogLevel - def message: String + def message: () => String def throwable: Option[Throwable] def context: Map[String, Ctx] def fileName: Option[String] def className: Option[String] def methodName: Option[String] def line: Option[Int] - - def unsafeThrowable: Throwable - def unsafeContext: Map[String, Ctx] + def levelValue: Int } object Log { @@ -57,6 +55,16 @@ object Log { )(implicit E: Context.Encoder[A, Ctx]): Builder[Ctx] = contextMap.foldLeft(this) { case (builder, (k, v)) => builder.withContext(k)(v) } + def adaptTimestamp(f: FiniteDuration => FiniteDuration): Builder[Ctx] + def adaptLevel(f: KernelLogLevel => KernelLogLevel): Builder[Ctx] + def adaptMessage(f: String => String): Builder[Ctx] + def adaptThrowable(f: Throwable => Throwable): Builder[Ctx] + def adaptContext(f: Map[String, Ctx] => Map[String, Ctx]): Builder[Ctx] + def adaptFileName(f: String => String): Builder[Ctx] + def adaptClassName(f: String => String): Builder[Ctx] + def adaptMethodName(f: String => String): Builder[Ctx] + def adaptLine(f: Int => Int): Builder[Ctx] + def build(): Log[Ctx] } @@ -64,8 +72,8 @@ object Log { private class MutableBuilder[Ctx] extends Builder[Ctx] { private var _timestamp: Option[FiniteDuration] = None - private var _level: Option[KernelLogLevel] = None - private var _message: Option[String] = None + private var _level: KernelLogLevel = KernelLogLevel.Info + private var _message: () => String = () => "" private var _throwable: Option[Throwable] = None private val _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] private var _fileName: Option[String] = None @@ -75,16 +83,15 @@ object Log { def build(): Log[Ctx] = new Log[Ctx] { override def timestamp: Option[FiniteDuration] = _timestamp - override def level: KernelLogLevel = _level.getOrElse(KernelLogLevel.Info) - override def message: String = _message.getOrElse("") + override def level: KernelLogLevel = _level + override def message: () => String = _message override def throwable: Option[Throwable] = _throwable override def context: Map[String, Ctx] = _context.toMap override def className: Option[String] = _className override def fileName: Option[String] = _fileName override def methodName: Option[String] = _methodName override def line: Option[Int] = _line.filter(_ > 0) - override def unsafeThrowable: Throwable = _throwable.get - override def unsafeContext: Map[String, Ctx] = _context.toMap + override def levelValue: Int = _level.value } override def withTimestamp(value: FiniteDuration): this.type = { @@ -93,12 +100,60 @@ object Log { } override def withLevel(level: KernelLogLevel): this.type = { - _level = Some(level) + _level = level this } override def withMessage(message: => String): this.type = { - _message = Some(message) + _message = () => message + this + } + + override def adaptMessage(f: String => String): this.type = { + _message = () => f(_message()) + this + } + + override def adaptTimestamp(f: FiniteDuration => FiniteDuration): this.type = { + _timestamp = _timestamp.map(f) + this + } + + override def adaptLevel(f: KernelLogLevel => KernelLogLevel): this.type = { + _level = f(_level) + this + } + + override def adaptThrowable(f: Throwable => Throwable): this.type = { + _throwable = _throwable.map(f) + this + } + + override def adaptContext(f: Map[String, Ctx] => Map[String, Ctx]): this.type = { + val newContext = Map.newBuilder[String, Ctx] + newContext.addAll(f(_context.toMap)) + _context.clear() + _context.addAll(newContext.result()) + this + } + + override def adaptFileName(f: String => String): this.type = { + _fileName = _fileName.map(f) + this + } + + override def adaptClassName(f: String => String): this.type = { + _className = _className.map(f) + this + } + + override def adaptMethodName(f: String => String): this.type = { + _methodName = _methodName.map(f) + this + } + + override def adaptLine(f: Int => Int): this.type = { + _line = _line.map(f) this } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala index 5612895e..cb60ff5c 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala @@ -27,12 +27,10 @@ trait Recordable[Ctx, A] { object Recordable { def apply[Ctx, A](implicit ev: Recordable[Ctx, A]): ev.type = ev - // Basic string message recording implicit def stringLoggable[Ctx]: Recordable[Ctx, String] = new Recordable[Ctx, String] { def record(value: => String) = _.withMessage(value) } - // Context key-value pair recording with proper encoding implicit def contextPairLoggable[Ctx, A](implicit encoder: Context.Encoder[A, Ctx] ): Recordable[Ctx, (String, A)] = @@ -43,13 +41,11 @@ object Recordable { } } - // Throwable recording implicit def throwableLoggable[Ctx, T <: Throwable]: Recordable[Ctx, T] = new Recordable[Ctx, T] { def record(value: => T): LogRecord[Ctx] = _.withThrowable(value) } - // Numeric value recording with automatic string conversion implicit def intLoggable[Ctx](implicit encoder: Context.Encoder[Int, Ctx] ): Recordable[Ctx, Int] = @@ -90,18 +86,13 @@ object Recordable { } } - // Map recording for structured data implicit def mapLoggable[Ctx, A](implicit encoder: Context.Encoder[A, Ctx] ): Recordable[Ctx, Map[String, A]] = new Recordable[Ctx, Map[String, A]] { def record(value: => Map[String, A]): LogRecord[Ctx] = { val map = value - (builder: Log.Builder[Ctx]) => { - var current = builder - map.foreach { case (k, v) => current = current.withContext(k)(v) } - current - } + (builder: Log.Builder[Ctx]) => builder.withContextMap(map) } } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index 1b97cb9b..ac8b0f49 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -121,7 +121,7 @@ object SamLogger { def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = { val modifiedRecord = (builder: Builder) => { val originalLog = record(builder).build() - val modifiedMessage = f(originalLog.message) + val modifiedMessage = f(originalLog.message()) val newBuilder = Log .mutableBuilder[Ctx]() diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala new file mode 100644 index 00000000..ab5be515 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala @@ -0,0 +1,159 @@ +/* + * 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 + +/** + * Adapter layer to bridge the new SAM LoggerKernel with the existing Logger interface. This allows + * gradual migration from the old multi-method design to the new SAM design. + * + * ## Usage + * + * ### Converting SAM LoggerKernel to old Logger interface + * ```scala + * val kernel = Slf4jLoggerKernel.fromName[IO]("MyApp") + * val oldLogger = SamLoggerAdapter.toLogger(kernel) + * + * // Use old API + * oldLogger.info("Works with old API") + * oldLogger.error(exception)("Works with old API") + * ``` + * + * ### Converting old Logger to SAM LoggerKernel + * ```scala + * val oldLogger = Slf4jLogger.getLogger[IO] + * val samLogger = SamLoggerAdapter.loggerToSamLogger(oldLogger) + * + * // Use new SAM API + * samLogger.info("Works with new API", ("key", "value")) + * ``` + * + * ## Limitations + * + * - **Context Loss**: When converting from LoggerKernel → Logger → LoggerKernel, structured + * context information (key-value pairs) is lost because the old Logger interface doesn't + * support structured logging. Only message and throwable information is preserved. + * - **Performance**: The `toLoggerKernel` method creates a full log record to extract message and + * throwable, which has some overhead. + * + * ## Best Practices + * + * - Use `toLogger` for backward compatibility when migrating to new backends + * - Use `loggerToSamLogger` when you need structured logging capabilities + * - Avoid round-trip conversions (LoggerKernel → Logger → LoggerKernel) as they lose context + */ +object SamLoggerAdapter { + + /** + * Convert a LoggerKernel to the existing Logger interface. + * + * This is the primary use case for the adapter - allowing new SAM-based backends to work with + * existing code that uses the old Logger interface. + * + * **Note**: Structured context information is not accessible through the old Logger interface, so + * any context added via the SAM API will not be visible when using the returned Logger. + */ + def toLogger[F[_], Ctx](kernel: LoggerKernel[F, Ctx]): Logger[F] = new Logger[F] { + def error(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Error, _.withMessage(message)) + + def error(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Error, _.withMessage(message).withThrowable(t)) + + def warn(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Warn, _.withMessage(message)) + + def warn(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Warn, _.withMessage(message).withThrowable(t)) + + def info(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Info, _.withMessage(message)) + + def info(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Info, _.withMessage(message).withThrowable(t)) + + def debug(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Debug, _.withMessage(message)) + + def debug(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Debug, _.withMessage(message).withThrowable(t)) + + def trace(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Trace, _.withMessage(message)) + + def trace(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Trace, _.withMessage(message).withThrowable(t)) + } + + /** + * Convert the existing Logger interface to a LoggerKernel. + * + * **Important**: This conversion has limitations: + * - Only message and throwable information is preserved + * - All structured context (key-value pairs) is lost because the old Logger interface doesn't + * support structured logging + * - This method is primarily intended for testing and round-trip compatibility + * + * For production use, prefer using the SAM LoggerKernel directly or use `toLogger` to convert + * from LoggerKernel to Logger. + */ + def toLoggerKernel[F[_], Ctx](logger: Logger[F]): LoggerKernel[F, Ctx] = + new LoggerKernel[F, Ctx] { + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = { + // Build the log record to extract message and throwable + // Note: Context information is intentionally not preserved as the old Logger + // interface doesn't support structured logging + val logRecord = record(Log.mutableBuilder[Ctx]()).build() + val message = logRecord.message() + val throwable = logRecord.throwable + + // Route to appropriate logger method based on level and throwable presence + (level, throwable) match { + case (KernelLogLevel.Error, Some(t)) => logger.error(t)(message) + case (KernelLogLevel.Error, None) => logger.error(message) + case (KernelLogLevel.Warn, Some(t)) => logger.warn(t)(message) + case (KernelLogLevel.Warn, None) => logger.warn(message) + case (KernelLogLevel.Info, Some(t)) => logger.info(t)(message) + case (KernelLogLevel.Info, None) => logger.info(message) + case (KernelLogLevel.Debug, Some(t)) => logger.debug(t)(message) + case (KernelLogLevel.Debug, None) => logger.debug(message) + case (KernelLogLevel.Trace, Some(t)) => logger.trace(t)(message) + case (KernelLogLevel.Trace, None) => logger.trace(message) + case _ => logger.info(message) // fallback for unknown levels + } + } + } + + /** + * Convert a SamLogger to the existing Logger interface. + * + * This is a convenience method that delegates to `toLogger`. + */ + def samLoggerToLogger[F[_], Ctx](samLogger: SamLogger[F, Ctx]): Logger[F] = toLogger(samLogger) + + /** + * Convert the existing Logger interface to a SamLogger. + * + * This allows old Logger implementations to be used with the new SAM API, enabling structured + * logging capabilities. + * + * **Note**: The underlying Logger implementation must support the old interface. Any structured + * context added via the SAM API will be lost when the underlying Logger processes the log (since + * it only receives message and throwable). + */ + def loggerToSamLogger[F[_], Ctx](logger: Logger[F]): SamLogger[F, Ctx] = + SamLogger.wrap(toLoggerKernel(logger)) +} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala index 3681e403..41a03467 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -29,93 +29,43 @@ trait SamStructuredLogger[F[_]] extends Logger[F] { protected def kernel: LoggerKernel[F, String] def trace(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logTrace(builder) + kernel.logTrace(_.withMessage(msg).withContextMap(ctx)) } def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logTrace(builder) + kernel.logTrace(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) } def debug(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logDebug(builder) + kernel.logDebug(_.withMessage(msg).withContextMap(ctx)) } def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logDebug(builder) + kernel.logDebug(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) } def info(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logInfo(builder) + kernel.logInfo(_.withMessage(msg).withContextMap(ctx)) } def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logInfo(builder) + kernel.logInfo(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) } def warn(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logWarn(builder) + kernel.logWarn(_.withMessage(msg).withContextMap(ctx)) } def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logWarn(builder) + kernel.logWarn(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) } def error(ctx: Map[String, String])(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logError(builder) + kernel.logError(_.withMessage(msg).withContextMap(ctx)) } def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { - val builder = (b: Log.Builder[String]) => { - var current = b.withMessage(msg).withThrowable(t) - ctx.foreach { case (k, v) => current = current.withContext[String](k)(v) } - current - } - kernel.logError(builder) + kernel.logError(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) } override def trace(message: => String): F[Unit] = From 022f96b6141391c79262b13e5e616331ae82ac73 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 16:52:55 +0000 Subject: [PATCH 29/62] use builder for the context --- .../src/main/scala/org/typelevel/log4cats/Log.scala | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 44890d96..d659ce73 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -75,7 +75,7 @@ object Log { private var _level: KernelLogLevel = KernelLogLevel.Info private var _message: () => String = () => "" private var _throwable: Option[Throwable] = None - private val _context: mutable.Map[String, Ctx] = mutable.Map.empty[String, Ctx] + private var _context: mutable.Builder[(String, Ctx), Map[String, Ctx]] = Map.newBuilder[String, Ctx] private var _fileName: Option[String] = None private var _className: Option[String] = None private var _methodName: Option[String] = None @@ -86,7 +86,7 @@ object Log { override def level: KernelLogLevel = _level override def message: () => String = _message override def throwable: Option[Throwable] = _throwable - override def context: Map[String, Ctx] = _context.toMap + override def context: Map[String, Ctx] = _context.result() override def className: Option[String] = _className override def fileName: Option[String] = _fileName override def methodName: Option[String] = _methodName @@ -130,10 +130,9 @@ object Log { } override def adaptContext(f: Map[String, Ctx] => Map[String, Ctx]): this.type = { - val newContext = Map.newBuilder[String, Ctx] - newContext.addAll(f(_context.toMap)) - _context.clear() - _context.addAll(newContext.result()) + val currentContext = _context.result() + _context = Map.newBuilder[String, Ctx] + _context.addAll(f(currentContext)) this } From 3d00cf9af53eb9ea62aa990eb5269201df390606 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 17:08:04 +0000 Subject: [PATCH 30/62] add sourcecode stable version --- build.sbt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 685a3dfc..fccce5d1 100644 --- a/build.sbt +++ b/build.sbt @@ -31,7 +31,7 @@ val catsEffectV = "3.6.3" val slf4jV = "1.7.36" val munitCatsEffectV = "2.1.0" val logbackClassicV = "1.2.13" -val sourcecodeV = "0.3.1" +val sourcecodeV = "0.4.2" Global / onChangedBuildSource := ReloadOnSourceChanges @@ -48,14 +48,19 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) name := "log4cats-core", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, - "org.typelevel" %%% "cats-effect-std" % catsEffectV, - "com.lihaoyi" %%% "sourcecode" % sourcecodeV + "org.typelevel" %%% "cats-effect-std" % catsEffectV ), libraryDependencies ++= { if (tlIsScala3.value) Seq.empty else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided) } ) + .jvmSettings( + libraryDependencies += "com.lihaoyi" %%% "sourcecode" % sourcecodeV + ) + .jsSettings( + libraryDependencies += "com.lihaoyi" %%% "sourcecode" % sourcecodeV + ) .nativeSettings(commonNativeSettings) lazy val testing = crossProject(JSPlatform, JVMPlatform, NativePlatform) From 17cb9025fe8b14e98efdb939a701cc9c1c955e8e Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 17:11:03 +0000 Subject: [PATCH 31/62] sbt scalafmt --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index d659ce73..4b559f15 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -75,7 +75,8 @@ object Log { private var _level: KernelLogLevel = KernelLogLevel.Info private var _message: () => String = () => "" private var _throwable: Option[Throwable] = None - private var _context: mutable.Builder[(String, Ctx), Map[String, Ctx]] = Map.newBuilder[String, Ctx] + private var _context: mutable.Builder[(String, Ctx), Map[String, Ctx]] = + Map.newBuilder[String, Ctx] private var _fileName: Option[String] = None private var _className: Option[String] = None private var _methodName: Option[String] = None From 3df49637f37cf1290664383c0b27e7e7082dec0e Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 17:17:29 +0000 Subject: [PATCH 32/62] sbt scalafmt --- .../log4cats/SamStructuredLogger.scala | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala index 41a03467..190c6960 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -28,45 +28,35 @@ import cats.Show.Shown trait SamStructuredLogger[F[_]] extends Logger[F] { protected def kernel: LoggerKernel[F, String] - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = { + def trace(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logTrace(_.withMessage(msg).withContextMap(ctx)) - } - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logTrace(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - } - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = { + def debug(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logDebug(_.withMessage(msg).withContextMap(ctx)) - } - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logDebug(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - } - def info(ctx: Map[String, String])(msg: => String): F[Unit] = { + def info(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logInfo(_.withMessage(msg).withContextMap(ctx)) - } - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logInfo(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - } - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = { + def warn(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logWarn(_.withMessage(msg).withContextMap(ctx)) - } - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logWarn(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - } - def error(ctx: Map[String, String])(msg: => String): F[Unit] = { + def error(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logError(_.withMessage(msg).withContextMap(ctx)) - } - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = { + def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logError(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - } override def trace(message: => String): F[Unit] = kernel.logTrace(_.withMessage(message)) From 4602d5661d8fb405b5e65641d33f720c4a530290 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 17:23:23 +0000 Subject: [PATCH 33/62] add foreach loop instead forALL --- core/shared/src/main/scala/org/typelevel/log4cats/Log.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 4b559f15..908916dd 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -133,7 +133,7 @@ object Log { override def adaptContext(f: Map[String, Ctx] => Map[String, Ctx]): this.type = { val currentContext = _context.result() _context = Map.newBuilder[String, Ctx] - _context.addAll(f(currentContext)) + f(currentContext).foreach { case (k, v) => _context += (k -> v) } this } From f139d0c9e50186c67f2b2922c4615f1f4ffc50fa Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Thu, 4 Sep 2025 17:36:00 +0000 Subject: [PATCH 34/62] resolve sourcecode scalanavative error --- .../org/typelevel/log4cats/SamLogger.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index ac8b0f49..e6d13342 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -19,6 +19,25 @@ package org.typelevel.log4cats import cats.* import cats.data.{EitherT, Kleisli, OptionT} +// Conditional import for sourcecode - only available on JVM and JS +//#if !scalaNative +import sourcecode._ +//#else +// Stub types for Native platform +object sourcecode { + case class Pkg(value: String) + case class FileName(value: String) + case class Name(value: String) + case class Line(value: Int) + + // Implicit values for Native + implicit val pkg: Pkg = Pkg("") + implicit val filename: FileName = FileName("") + implicit val name: Name = Name("") + implicit val line: Line = Line(0) +} +//#endif + /** * A SAM-based Logger that extends LoggerKernel and provides a user-friendly interface. This is the * new design that will eventually replace the current Logger trait. From 401c5ad5a4ed30dcced4436471dd1ae68e6e1b5a Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 16 Sep 2025 10:41:16 +0000 Subject: [PATCH 35/62] fix sourcecode library issue --- build.sbt | 9 +--- .../org/typelevel/log4cats/SamLogger.scala | 47 +------------------ 2 files changed, 4 insertions(+), 52 deletions(-) diff --git a/build.sbt b/build.sbt index fccce5d1..0ec2a74d 100644 --- a/build.sbt +++ b/build.sbt @@ -48,19 +48,14 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) name := "log4cats-core", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, - "org.typelevel" %%% "cats-effect-std" % catsEffectV + "org.typelevel" %%% "cats-effect-std" % catsEffectV, + "com.lihaoyi" %%% "sourcecode" % sourcecodeV ), libraryDependencies ++= { if (tlIsScala3.value) Seq.empty else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided) } ) - .jvmSettings( - libraryDependencies += "com.lihaoyi" %%% "sourcecode" % sourcecodeV - ) - .jsSettings( - libraryDependencies += "com.lihaoyi" %%% "sourcecode" % sourcecodeV - ) .nativeSettings(commonNativeSettings) lazy val testing = crossProject(JSPlatform, JVMPlatform, NativePlatform) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index e6d13342..63cb634e 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -19,25 +19,6 @@ package org.typelevel.log4cats import cats.* import cats.data.{EitherT, Kleisli, OptionT} -// Conditional import for sourcecode - only available on JVM and JS -//#if !scalaNative -import sourcecode._ -//#else -// Stub types for Native platform -object sourcecode { - case class Pkg(value: String) - case class FileName(value: String) - case class Name(value: String) - case class Line(value: Int) - - // Implicit values for Native - implicit val pkg: Pkg = Pkg("") - implicit val filename: FileName = FileName("") - implicit val name: Name = Name("") - implicit val line: Line = Line(0) -} -//#endif - /** * A SAM-based Logger that extends LoggerKernel and provides a user-friendly interface. This is the * new design that will eventually replace the current Logger trait. @@ -137,32 +118,8 @@ object SamLogger { f: String => String ): SamLogger[F, Ctx] = new SamLogger[F, Ctx] { - def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = { - val modifiedRecord = (builder: Builder) => { - val originalLog = record(builder).build() - val modifiedMessage = f(originalLog.message()) - - val newBuilder = Log - .mutableBuilder[Ctx]() - .withLevel(level) - .withMessage(modifiedMessage) - - // Copy the fields from the original log - originalLog.timestamp.foreach(newBuilder.withTimestamp) - originalLog.throwable.foreach(newBuilder.withThrowable) - originalLog.fileName.foreach(newBuilder.withFileName) - originalLog.className.foreach(newBuilder.withClassName) - originalLog.line.foreach(newBuilder.withLine) - - // Copy context - since Ctx is the same type, we can use identity encoder - originalLog.context.foreach { case (k, v) => - newBuilder.withContext(k)(v) - } - - newBuilder - } - l.log(level, modifiedRecord) - } + def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = + l.log(level, record(_).adaptMessage(f)) } private def mapK[G[_], F[_], Ctx](f: G ~> F)(logger: SamLogger[G, Ctx]): SamLogger[F, Ctx] = From f88c446f288ccfd070d04bdc7ecfb95efb3759ff Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 16 Sep 2025 10:47:48 +0000 Subject: [PATCH 36/62] sbt scalafmtSbt --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0ec2a74d..aa2b8f3e 100644 --- a/build.sbt +++ b/build.sbt @@ -49,7 +49,7 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, "org.typelevel" %%% "cats-effect-std" % catsEffectV, - "com.lihaoyi" %%% "sourcecode" % sourcecodeV + "com.lihaoyi" %%% "sourcecode" % sourcecodeV ), libraryDependencies ++= { if (tlIsScala3.value) Seq.empty From 10cd36cc29e90953776e1301c15261938a0dd399 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 16 Sep 2025 11:09:36 +0000 Subject: [PATCH 37/62] add test suites --- .../log4cats/ConsoleLoggerKernel.scala | 58 ++++++++ .../log4cats/SamLoggerAdapterTest.scala | 89 ++++++++++++ .../typelevel/log4cats/SamLoggerTest.scala | 129 ++++++++++++++++++ .../log4cats/SamStructuredLoggerTest.scala | 50 +++++++ 4 files changed, 326 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala create mode 100644 core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala create mode 100644 core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala create mode 100644 core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala new file mode 100644 index 00000000..511d0a27 --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala @@ -0,0 +1,58 @@ +/* + * 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 + +import cats.effect.kernel.Sync +import java.time.Instant + +/** + * A simple console implementation of LoggerKernel for testing the SAM design. + */ +class ConsoleLoggerKernel[F[_], Ctx](implicit F: Sync[F]) extends LoggerKernel[F, Ctx] { + + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = { + F.delay { + val logRecord = record(Log.mutableBuilder[Ctx]()).build() + + val timestamp = logRecord.timestamp.map(_.toMillis).getOrElse(System.currentTimeMillis()) + val instant = Instant.ofEpochMilli(timestamp) + val timeStr = instant.toString + + val levelStr = logRecord.level.namePadded + val message = logRecord.message + val className = logRecord.className.map(c => s"[$c]").getOrElse("") + val fileName = + logRecord.fileName.map(f => s"($f:${logRecord.line.getOrElse(0)})").getOrElse("") + + val contextStr = if (logRecord.context.nonEmpty) { + val contextPairs = logRecord.context.map { case (k, v) => s"$k=$v" }.mkString(", ") + s" {$contextPairs}" + } else "" + + val throwableStr = logRecord.throwable.map(t => s"\n${t.toString}").getOrElse("") + + val logLine = s"$timeStr $levelStr $className$fileName$contextStr $message$throwableStr" + + println(logLine) + } + } +} + +object ConsoleLoggerKernel { + def apply[F[_], Ctx](implicit F: Sync[F]): ConsoleLoggerKernel[F, Ctx] = + new ConsoleLoggerKernel[F, Ctx] +} diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala new file mode 100644 index 00000000..107c9502 --- /dev/null +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala @@ -0,0 +1,89 @@ +/* + * 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 + +import cats.effect.IO +import munit.CatsEffectSuite + +class SamLoggerAdapterTest extends CatsEffectSuite { + + test("Adapter should convert LoggerKernel to Logger") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLoggerAdapter.toLogger(kernel) + + // Test basic logging + logger.info("Test message").void + logger.error("Test error").void + logger.warn("Test warning").void + logger.debug("Test debug").void + logger.trace("Test trace").void + } + + test("Adapter should convert Logger to LoggerKernel") { + val originalKernel = ConsoleLoggerKernel[IO, String] + val logger = SamLoggerAdapter.toLogger(originalKernel) + val kernel = SamLoggerAdapter.toLoggerKernel(logger) + + // Test that the kernel works + kernel.log(KernelLogLevel.Info, _.withMessage("Test via adapter")).void + } + + test("Adapter should handle throwables correctly") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLoggerAdapter.toLogger(kernel) + val exception = new RuntimeException("Test exception") + + logger.error(exception)("Error with throwable").void + logger.warn(exception)("Warning with throwable").void + logger.info(exception)("Info with throwable").void + logger.debug(exception)("Debug with throwable").void + logger.trace(exception)("Trace with throwable").void + } + + test("Adapter should convert SamLogger to Logger") { + val kernel = ConsoleLoggerKernel[IO, String] + val samLogger = SamLogger.wrap(kernel) + val logger = SamLoggerAdapter.samLoggerToLogger(samLogger) + + logger.info("Test from SamLogger").void + logger.error("Test error from SamLogger").void + } + + test("Adapter should convert Logger to SamLogger") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLoggerAdapter.toLogger(kernel) + val samLogger = SamLoggerAdapter.loggerToSamLogger[IO, String](logger) + + // Test that the SamLogger works with structured logging + samLogger + .info( + "Test structured logging", + ("key1", "value1"), + ("key2", "value2") + ) + .void + } + + test("Round-trip conversion should preserve functionality") { + val originalKernel = ConsoleLoggerKernel[IO, String] + val logger = SamLoggerAdapter.toLogger(originalKernel) + val roundTripKernel = SamLoggerAdapter.toLoggerKernel(logger) + + // Test that round-trip conversion works + roundTripKernel.log(KernelLogLevel.Info, _.withMessage("Round-trip test")).void + } +} diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala new file mode 100644 index 00000000..84c14289 --- /dev/null +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala @@ -0,0 +1,129 @@ +/* + * 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 + +import cats.effect.IO +import munit.CatsEffectSuite + +class SamLoggerTest extends CatsEffectSuite { + + test("LoggerKernel should work with a simple console implementation") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + + logger.info("Hello, SAM Logger!").void + } + + test("LoggerKernel should support structured logging with context") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + + logger + .info( + "User action", + ("user_id", "123"), + ("action", "login") + ) + .void + } + + test("LoggerKernel should support error logging with throwables") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + + val exception = new RuntimeException("Test exception") + + logger + .error( + "Something went wrong", + exception + ) + .void + } + + test("LoggerKernel should support multiple log records") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + + logger + .info( + "Complex log entry", + ("request_id", "abc-123"), + ("duration_ms", 150), + new RuntimeException("Nested error") + ) + .void + } + + test("SamLogger withModifiedString should modify messages correctly") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + val modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") + + // Test that the message is modified + modifiedLogger.info("Test message").void + } + + test("SamLogger withModifiedString should preserve context and other fields") { + val kernel = ConsoleLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + val modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") + + // Test that context and other fields are preserved + modifiedLogger + .info( + "Test message", + ("key", "value"), + new RuntimeException("Test exception") + ) + .void + } + + test("LogRecord should combine multiple records correctly") { + val record1: LogRecord[String] = _.withMessage("Hello") + val record2: LogRecord[String] = _.withContext("key")("value") + + val combined = LogRecord.combine(Seq(record1, record2)) + val builder = Log.mutableBuilder[String]() + val result = combined(builder).build() + + assertEquals(result.message(), "Hello") + assert(result.context.contains("key")) + } + + test("Recordable should convert strings to log records") { + val record = implicitly[Recordable[String, String]].record("test message") + val result = record(Log.mutableBuilder[String]()).build() + + assertEquals(result.message(), "test message") + } + + test("Recordable should convert tuples to context records") { + val record = implicitly[Recordable[String, (String, String)]].record(("key", "value")) + val result = record(Log.mutableBuilder[String]()).build() + + assert(result.context.contains("key")) + } + + test("Recordable should convert throwables to log records") { + val exception = new RuntimeException("test") + val record = implicitly[Recordable[String, Throwable]].record(exception) + val result = record(Log.mutableBuilder[String]()).build() + + assertEquals(result.throwable, Some(exception)) + } +} diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala new file mode 100644 index 00000000..e04c626b --- /dev/null +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -0,0 +1,50 @@ +/* + * 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 + +import cats.effect.IO +import munit.CatsEffectSuite + +class SamStructuredLoggerTest extends CatsEffectSuite { + + test("SamStructuredLogger should handle context with multiple entries") { + var capturedLogs: List[(KernelLogLevel, Log.Builder[String] => Log.Builder[String])] = Nil + + val testKernel = new LoggerKernel[IO, String] { + def log( + level: KernelLogLevel, + record: Log.Builder[String] => Log.Builder[String] + ): IO[Unit] = { + capturedLogs = capturedLogs :+ (level, record) + IO.unit + } + } + + val logger = SamStructuredLogger.fromKernel(testKernel) + logger.info(Map("base" -> "value", "extra" -> "data"))("Test message").void + + assertEquals(capturedLogs.length, 1) + val (level, record) = capturedLogs.head + assertEquals(level, KernelLogLevel.Info) + + val logRecord = record(Log.mutableBuilder[String]()).build() + assertEquals(logRecord.message(), "Test message") + assertEquals(logRecord.context.size, 2) + assertEquals(logRecord.context("base"), "value") + assertEquals(logRecord.context("extra"), "data") + } +} From e8dd1350594e2221bc482264a067108b725ad5ff Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 16 Sep 2025 11:21:16 +0000 Subject: [PATCH 38/62] fix test error --- .../scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala index e04c626b..35067261 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -29,7 +29,8 @@ class SamStructuredLoggerTest extends CatsEffectSuite { level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String] ): IO[Unit] = { - capturedLogs = capturedLogs :+ (level, record) + + capturedLogs = capturedLogs :+ ((level, record)) IO.unit } } From f9364f1f2ee7ee7417e8761d186d55de58d9e8bc Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 16 Sep 2025 11:39:13 +0000 Subject: [PATCH 39/62] fix dateTimeFormatter error --- .../scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala | 5 ++--- .../src/main/scala/org/typelevel/log4cats/Context.scala | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala index 511d0a27..95f9b637 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala @@ -17,7 +17,6 @@ package org.typelevel.log4cats import cats.effect.kernel.Sync -import java.time.Instant /** * A simple console implementation of LoggerKernel for testing the SAM design. @@ -29,8 +28,8 @@ class ConsoleLoggerKernel[F[_], Ctx](implicit F: Sync[F]) extends LoggerKernel[F val logRecord = record(Log.mutableBuilder[Ctx]()).build() val timestamp = logRecord.timestamp.map(_.toMillis).getOrElse(System.currentTimeMillis()) - val instant = Instant.ofEpochMilli(timestamp) - val timeStr = instant.toString + // Use simple timestamp formatting instead of java.time.Instant for Scala Native compatibility + val timeStr = s"${new java.util.Date(timestamp).toString}" val levelStr = logRecord.level.namePadded val message = logRecord.message diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala index e558890f..e92839a0 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Context.scala @@ -16,8 +16,6 @@ package org.typelevel.log4cats -import java.time.Instant -import java.time.format.DateTimeFormatter import scala.concurrent.duration.FiniteDuration import org.typelevel.log4cats.Context.Encoder @@ -50,8 +48,9 @@ object Context { implicit val booleanToStringEncoder: Encoder[Boolean, String] = if (_) "true" else "false" - implicit val instantToStringEncoder: Encoder[Instant, String] = - DateTimeFormatter.ISO_INSTANT.format(_) + // Removed Instant encoder for Scala Native compatibility + // implicit val instantToStringEncoder: Encoder[Instant, String] = + // DateTimeFormatter.ISO_INSTANT.format(_) implicit val finiteDurationToStringEncoder: Encoder[FiniteDuration, String] = _.toString } From aafe9bacffb4ec183433d8bed1783e41b2f34253 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Tue, 16 Sep 2025 11:54:14 +0000 Subject: [PATCH 40/62] add Noop Logger with tests --- .../log4cats/noop/NoOpLoggerKernel.scala | 29 +++++++++++++ .../log4cats/NoOpLoggerKernelTest.scala | 41 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala create mode 100644 core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala new file mode 100644 index 00000000..01be751a --- /dev/null +++ b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala @@ -0,0 +1,29 @@ +/* + * 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.noop + +import cats.Applicative +import org.typelevel.log4cats.{KernelLogLevel, Log, LoggerKernel} + +class NoOpLoggerKernel[F[_]: Applicative, Ctx] extends LoggerKernel[F, Ctx] { + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = + Applicative[F].unit +} + +object NoOpLoggerKernel { + def apply[F[_]: Applicative, Ctx]: NoOpLoggerKernel[F, Ctx] = new NoOpLoggerKernel[F, Ctx] +} diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala new file mode 100644 index 00000000..6496c43d --- /dev/null +++ b/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala @@ -0,0 +1,41 @@ +/* + * 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.noop + +import cats.effect.IO +import munit.CatsEffectSuite +import org.typelevel.log4cats._ + +class NoOpLoggerKernelTest extends CatsEffectSuite { + test("NoOpLoggerKernel should do nothing and not fail") { + val kernel = NoOpLoggerKernel[IO, String] + val logger = SamLogger.wrap(kernel) + // All of these should be no-ops and not throw + logger.info("This should not appear").void *> + logger.error("This should not appear").void *> + logger.warn("This should not appear").void *> + logger.debug("This should not appear").void *> + logger.trace("This should not appear").void + } + + test("NoOpLoggerKernel should work with the adapter") { + val kernel = NoOpLoggerKernel[IO, String] + val logger = SamLoggerAdapter.toLogger(kernel) + logger.info("Adapter test").void *> + logger.error("Adapter test").void + } +} From ad0143eb0ae31c2444db0e6880b00b1d32c2572d Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Wed, 17 Sep 2025 05:01:39 +0000 Subject: [PATCH 41/62] remove unwanted file --- .../src/main/scala/logger/Recordable.scala | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala diff --git a/logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala b/logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala deleted file mode 100644 index e55850bd..00000000 --- a/logger-poc/modules/frontend/src/main/scala/logger/Recordable.scala +++ /dev/null @@ -1,33 +0,0 @@ -package logger - -/** Typeclass representing the notion that a value can contribute to a log, by - * transforming it in some way. - */ -trait Recordable[A] { - def record(value: => A): LogRecord -} - -object Recordable { - - def apply[A](implicit ev: Recordable[A]): ev.type = ev - - implicit val stringLoggable: Recordable[String] = new Recordable[String] { - def record(value: => String) = _.withMessage(value) - } - - implicit def tupleLoggable[T: Context.Encoder]: Recordable[(String, T)] = - new Recordable[(String, T)] { - - override def record(value: => (String, T)): LogRecord = { - val (k, v) = value - (_: Log.Builder).withContext(k)(v) - } - - } - - implicit def throwableLoggable[T <: Throwable]: Recordable[T] = - new Recordable[T] { - def record(value: => T): LogRecord = _.withThrowable(value) - } - -} From bd68364481ed832973e7c9c8da1b55b75b42bf25 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 06:57:53 +0000 Subject: [PATCH 42/62] remove LogRecord Recordable and SamLoggerAdapter --- .../org/typelevel/log4cats/LogRecord.scala | 43 ----- .../org/typelevel/log4cats/Recordable.scala | 98 ----------- .../typelevel/log4cats/SamLoggerAdapter.scala | 159 ------------------ 3 files changed, 300 deletions(-) delete mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala delete mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala delete mode 100644 core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala deleted file mode 100644 index a284917e..00000000 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LogRecord.scala +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 - -/** - * This allows to capture several elements in vararg-based interface methods, enriching a single log - * with various pieces of information. - * - * This allows for an interesting UX, where the details of the encoding of some data into a log can - * be separate from the actual log statements. - */ -trait LogRecord[Ctx] extends (Log.Builder[Ctx] => Log.Builder[Ctx]) - -object LogRecord { - def combine[Ctx](all: Seq[LogRecord[Ctx]]): LogRecord[Ctx] = Combined(all) - - implicit def toLogRecord[Ctx, A: Recordable[Ctx, *]](value: => A): LogRecord[Ctx] = - Recordable[Ctx, A].record(value) - - private case class Combined[Ctx](all: Seq[LogRecord[Ctx]]) extends LogRecord[Ctx] { - def apply(record: Log.Builder[Ctx]): Log.Builder[Ctx] = { - var current = record - all.foreach { logBit => - current = logBit(current) - } - current - } - } -} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala deleted file mode 100644 index cb60ff5c..00000000 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Recordable.scala +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 - -/** - * Typeclass representing the notion that a value can contribute to a log, by transforming it in - * some way. - */ -trait Recordable[Ctx, A] { - def record(value: => A): LogRecord[Ctx] -} - -object Recordable { - def apply[Ctx, A](implicit ev: Recordable[Ctx, A]): ev.type = ev - - implicit def stringLoggable[Ctx]: Recordable[Ctx, String] = new Recordable[Ctx, String] { - def record(value: => String) = _.withMessage(value) - } - - implicit def contextPairLoggable[Ctx, A](implicit - encoder: Context.Encoder[A, Ctx] - ): Recordable[Ctx, (String, A)] = - new Recordable[Ctx, (String, A)] { - override def record(value: => (String, A)): LogRecord[Ctx] = { - val (k, v) = value - (_: Log.Builder[Ctx]).withContext(k)(v) - } - } - - implicit def throwableLoggable[Ctx, T <: Throwable]: Recordable[Ctx, T] = - new Recordable[Ctx, T] { - def record(value: => T): LogRecord[Ctx] = _.withThrowable(value) - } - - implicit def intLoggable[Ctx](implicit - encoder: Context.Encoder[Int, Ctx] - ): Recordable[Ctx, Int] = - new Recordable[Ctx, Int] { - def record(value: => Int): LogRecord[Ctx] = { - val v = value - (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) - } - } - - implicit def longLoggable[Ctx](implicit - encoder: Context.Encoder[Long, Ctx] - ): Recordable[Ctx, Long] = - new Recordable[Ctx, Long] { - def record(value: => Long): LogRecord[Ctx] = { - val v = value - (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) - } - } - - implicit def doubleLoggable[Ctx](implicit - encoder: Context.Encoder[Double, Ctx] - ): Recordable[Ctx, Double] = - new Recordable[Ctx, Double] { - def record(value: => Double): LogRecord[Ctx] = { - val v = value - (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) - } - } - - implicit def booleanLoggable[Ctx](implicit - encoder: Context.Encoder[Boolean, Ctx] - ): Recordable[Ctx, Boolean] = - new Recordable[Ctx, Boolean] { - def record(value: => Boolean): LogRecord[Ctx] = { - val v = value - (builder: Log.Builder[Ctx]) => builder.withContext("value")(v) - } - } - - implicit def mapLoggable[Ctx, A](implicit - encoder: Context.Encoder[A, Ctx] - ): Recordable[Ctx, Map[String, A]] = - new Recordable[Ctx, Map[String, A]] { - def record(value: => Map[String, A]): LogRecord[Ctx] = { - val map = value - (builder: Log.Builder[Ctx]) => builder.withContextMap(map) - } - } -} diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala deleted file mode 100644 index ab5be515..00000000 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLoggerAdapter.scala +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 - -/** - * Adapter layer to bridge the new SAM LoggerKernel with the existing Logger interface. This allows - * gradual migration from the old multi-method design to the new SAM design. - * - * ## Usage - * - * ### Converting SAM LoggerKernel to old Logger interface - * ```scala - * val kernel = Slf4jLoggerKernel.fromName[IO]("MyApp") - * val oldLogger = SamLoggerAdapter.toLogger(kernel) - * - * // Use old API - * oldLogger.info("Works with old API") - * oldLogger.error(exception)("Works with old API") - * ``` - * - * ### Converting old Logger to SAM LoggerKernel - * ```scala - * val oldLogger = Slf4jLogger.getLogger[IO] - * val samLogger = SamLoggerAdapter.loggerToSamLogger(oldLogger) - * - * // Use new SAM API - * samLogger.info("Works with new API", ("key", "value")) - * ``` - * - * ## Limitations - * - * - **Context Loss**: When converting from LoggerKernel → Logger → LoggerKernel, structured - * context information (key-value pairs) is lost because the old Logger interface doesn't - * support structured logging. Only message and throwable information is preserved. - * - **Performance**: The `toLoggerKernel` method creates a full log record to extract message and - * throwable, which has some overhead. - * - * ## Best Practices - * - * - Use `toLogger` for backward compatibility when migrating to new backends - * - Use `loggerToSamLogger` when you need structured logging capabilities - * - Avoid round-trip conversions (LoggerKernel → Logger → LoggerKernel) as they lose context - */ -object SamLoggerAdapter { - - /** - * Convert a LoggerKernel to the existing Logger interface. - * - * This is the primary use case for the adapter - allowing new SAM-based backends to work with - * existing code that uses the old Logger interface. - * - * **Note**: Structured context information is not accessible through the old Logger interface, so - * any context added via the SAM API will not be visible when using the returned Logger. - */ - def toLogger[F[_], Ctx](kernel: LoggerKernel[F, Ctx]): Logger[F] = new Logger[F] { - def error(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Error, _.withMessage(message)) - - def error(t: Throwable)(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Error, _.withMessage(message).withThrowable(t)) - - def warn(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Warn, _.withMessage(message)) - - def warn(t: Throwable)(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Warn, _.withMessage(message).withThrowable(t)) - - def info(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Info, _.withMessage(message)) - - def info(t: Throwable)(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Info, _.withMessage(message).withThrowable(t)) - - def debug(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Debug, _.withMessage(message)) - - def debug(t: Throwable)(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Debug, _.withMessage(message).withThrowable(t)) - - def trace(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Trace, _.withMessage(message)) - - def trace(t: Throwable)(message: => String): F[Unit] = - kernel.log(KernelLogLevel.Trace, _.withMessage(message).withThrowable(t)) - } - - /** - * Convert the existing Logger interface to a LoggerKernel. - * - * **Important**: This conversion has limitations: - * - Only message and throwable information is preserved - * - All structured context (key-value pairs) is lost because the old Logger interface doesn't - * support structured logging - * - This method is primarily intended for testing and round-trip compatibility - * - * For production use, prefer using the SAM LoggerKernel directly or use `toLogger` to convert - * from LoggerKernel to Logger. - */ - def toLoggerKernel[F[_], Ctx](logger: Logger[F]): LoggerKernel[F, Ctx] = - new LoggerKernel[F, Ctx] { - def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = { - // Build the log record to extract message and throwable - // Note: Context information is intentionally not preserved as the old Logger - // interface doesn't support structured logging - val logRecord = record(Log.mutableBuilder[Ctx]()).build() - val message = logRecord.message() - val throwable = logRecord.throwable - - // Route to appropriate logger method based on level and throwable presence - (level, throwable) match { - case (KernelLogLevel.Error, Some(t)) => logger.error(t)(message) - case (KernelLogLevel.Error, None) => logger.error(message) - case (KernelLogLevel.Warn, Some(t)) => logger.warn(t)(message) - case (KernelLogLevel.Warn, None) => logger.warn(message) - case (KernelLogLevel.Info, Some(t)) => logger.info(t)(message) - case (KernelLogLevel.Info, None) => logger.info(message) - case (KernelLogLevel.Debug, Some(t)) => logger.debug(t)(message) - case (KernelLogLevel.Debug, None) => logger.debug(message) - case (KernelLogLevel.Trace, Some(t)) => logger.trace(t)(message) - case (KernelLogLevel.Trace, None) => logger.trace(message) - case _ => logger.info(message) // fallback for unknown levels - } - } - } - - /** - * Convert a SamLogger to the existing Logger interface. - * - * This is a convenience method that delegates to `toLogger`. - */ - def samLoggerToLogger[F[_], Ctx](samLogger: SamLogger[F, Ctx]): Logger[F] = toLogger(samLogger) - - /** - * Convert the existing Logger interface to a SamLogger. - * - * This allows old Logger implementations to be used with the new SAM API, enabling structured - * logging capabilities. - * - * **Note**: The underlying Logger implementation must support the old interface. Any structured - * context added via the SAM API will be lost when the underlying Logger processes the log (since - * it only receives message and throwable). - */ - def loggerToSamLogger[F[_], Ctx](logger: Logger[F]): SamLogger[F, Ctx] = - SamLogger.wrap(toLoggerKernel(logger)) -} From d19ffaee3e77fcf19d587769288be96a1a250c9c Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:02:50 +0000 Subject: [PATCH 43/62] update core's extras --- .../log4cats/extras/DeferredLogger.scala | 29 ++++++++++++++++- .../DeferredSelfAwareStructuredLogger.scala | 31 ++++++++++++++++++- .../extras/DeferredStructuredLogger.scala | 31 ++++++++++++++++++- .../log4cats/extras/WriterTLogger.scala | 14 +++++++++ .../extras/WriterTStructuredLogger.scala | 17 +++++++++- 5 files changed, 118 insertions(+), 4 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala index d8ac9c60..cd795ad2 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala @@ -21,7 +21,8 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.~> -import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.{Logger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log /** * `Logger` that does not immediately log. @@ -49,6 +50,8 @@ import org.typelevel.log4cats.Logger * >>> WARNING: READ BEFORE USAGE! <<< */ trait DeferredLogger[F[_]] extends Logger[F] with DeferredLogging[F] { + protected def kernel: LoggerKernel[F, String] + override def withModifiedString(f: String => String): DeferredLogger[F] = DeferredLogger.withModifiedString(this, f) override def mapK[G[_]](fk: F ~> G): DeferredLogger[G] = DeferredLogger.mapK(this, fk) @@ -66,6 +69,26 @@ object DeferredLogger { new DeferredLogger[F] { private def save(lm: DeferredLogMessage): F[Unit] = ref.update(_.append(lm)) + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + } + val deferredMsg = DeferredLogMessage( + logLevel, + log.context, + log.throwable, + log.message + ) + save(deferredMsg) + } + } + override def trace(t: Throwable)(msg: => String): F[Unit] = save(DeferredLogMessage.trace(Map.empty, t.some, () => msg)) override def debug(t: Throwable)(msg: => String): F[Unit] = @@ -99,6 +122,8 @@ object DeferredLogger { fk: F ~> G ): DeferredLogger[G] = new DeferredLogger[G] { + protected def kernel: LoggerKernel[G, String] = logger.kernel.mapK(fk) + override def inspect: G[Chain[DeferredLogMessage]] = fk(logger.inspect) override def log: G[Unit] = fk(logger.log) @@ -124,6 +149,8 @@ object DeferredLogger { f: String => String ): DeferredLogger[F] = new DeferredLogger[F] { + protected def kernel: LoggerKernel[F, String] = logger.kernel + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala index 778305db..7e592b7b 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala @@ -21,7 +21,8 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.{~>, Show} -import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats.{SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log /** * Similar to `DeferredStructuredLogger`, for `SelfAwareStructuredLogger` @@ -33,6 +34,8 @@ import org.typelevel.log4cats.SelfAwareStructuredLogger trait DeferredSelfAwareStructuredLogger[F[_]] extends SelfAwareStructuredLogger[F] with DeferredLogging[F] { + protected def kernel: LoggerKernel[F, String] + override def mapK[G[_]](fk: F ~> G): DeferredSelfAwareStructuredLogger[G] = DeferredSelfAwareStructuredLogger.mapK(this, fk) @@ -61,6 +64,26 @@ object DeferredSelfAwareStructuredLogger { private def save(lm: DeferredLogMessage): F[Unit] = stash.update(_.append(lm -> logger)) + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + } + val deferredMsg = DeferredLogMessage( + logLevel, + log.context, + log.throwable, + log.message + ) + save(deferredMsg) + } + } + override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled override def isDebugEnabled: F[Boolean] = logger.isDebugEnabled override def isInfoEnabled: F[Boolean] = logger.isInfoEnabled @@ -156,6 +179,8 @@ object DeferredSelfAwareStructuredLogger { fk: F ~> G ): DeferredSelfAwareStructuredLogger[G] = new DeferredSelfAwareStructuredLogger[G] { + protected def kernel: LoggerKernel[G, String] = logger.kernel.mapK(fk) + override def inspect: G[Chain[DeferredLogMessage]] = fk( logger.inspect ) @@ -217,6 +242,8 @@ object DeferredSelfAwareStructuredLogger { new DeferredSelfAwareStructuredLogger[F] { private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx + protected def kernel: LoggerKernel[F, String] = logger.kernel + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled @@ -270,6 +297,8 @@ object DeferredSelfAwareStructuredLogger { f: String => String ): DeferredSelfAwareStructuredLogger[F] = new DeferredSelfAwareStructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = logger.kernel + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala index 1c0855bc..00ae8e82 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala @@ -22,7 +22,8 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.~> -import org.typelevel.log4cats.StructuredLogger +import org.typelevel.log4cats.{StructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log /** * `StructuredLogger` that does not immediately log. @@ -50,6 +51,8 @@ import org.typelevel.log4cats.StructuredLogger * >>> WARNING: READ BEFORE USAGE! <<< */ trait DeferredStructuredLogger[F[_]] extends StructuredLogger[F] with DeferredLogging[F] { + protected def kernel: LoggerKernel[F, String] + override def mapK[G[_]](fk: F ~> G): DeferredStructuredLogger[G] = DeferredStructuredLogger.mapK(this, fk) @@ -79,6 +82,26 @@ object DeferredStructuredLogger { new DeferredStructuredLogger[F] { private def save(lm: DeferredLogMessage): F[Unit] = ref.update(_.append(lm)) + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + } + val deferredMsg = DeferredLogMessage( + logLevel, + log.context, + log.throwable, + log.message + ) + save(deferredMsg) + } + } + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = save(DeferredLogMessage.trace(ctx, none, () => msg)) override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = @@ -135,6 +158,8 @@ object DeferredStructuredLogger { fk: F ~> G ): DeferredStructuredLogger[G] = new DeferredStructuredLogger[G] { + protected def kernel: LoggerKernel[G, String] = logger.kernel.mapK(fk) + override def inspect: G[Chain[DeferredLogMessage]] = fk(logger.inspect) override def log: G[Unit] = fk(logger.log) @@ -178,6 +203,8 @@ object DeferredStructuredLogger { baseCtx: Map[String, String] ): DeferredStructuredLogger[F] = new DeferredStructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = logger.kernel + private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect @@ -228,6 +255,8 @@ object DeferredStructuredLogger { f: String => String ): DeferredStructuredLogger[F] = new DeferredStructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = logger.kernel + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 70ecd01a..699d4a8c 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -40,6 +40,20 @@ object WriterTLogger { errorEnabled: Boolean = true ): SelfAwareLogger[WriterT[F, G[LogMessage], *]] = new SelfAwareLogger[WriterT[F, G[LogMessage], *]] { + protected def kernel: LoggerKernel[WriterT[F, G[LogMessage], *], String] = new LoggerKernel[WriterT[F, G[LogMessage], *], String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): WriterT[F, G[LogMessage], Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + } + WriterT.tell[F, G[LogMessage]](Applicative[G].pure(LogMessage(logLevel, log.throwable, log.message()))) + } + } + override def isTraceEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(traceEnabled) override def isDebugEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(debugEnabled) override def isInfoEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(infoEnabled) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala index b8b218de..52777792 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala @@ -20,7 +20,8 @@ import cats.data.WriterT import cats.kernel.Monoid import cats.syntax.all.* import cats.{~>, Alternative, Applicative, Foldable, Monad} -import org.typelevel.log4cats.{SelfAwareStructuredLogger, StructuredLogger} +import org.typelevel.log4cats.{SelfAwareStructuredLogger, StructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log /** * A `SelfAwareStructuredLogger` implemented using `cats.data.WriterT`. @@ -43,6 +44,20 @@ object WriterTStructuredLogger { new SelfAwareStructuredLogger[WriterT[F, G[StructuredLogMessage], *]] { type LoggerF[A] = WriterT[F, G[StructuredLogMessage], A] + protected def kernel: LoggerKernel[WriterT[F, G[StructuredLogMessage], *], String] = new LoggerKernel[WriterT[F, G[StructuredLogMessage], *], String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): WriterT[F, G[StructuredLogMessage], Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + } + WriterT.tell[F, G[StructuredLogMessage]](Applicative[G].pure(StructuredLogMessage(logLevel, log.context, log.throwable, log.message()))) + } + } + override def isTraceEnabled: LoggerF[Boolean] = isEnabled(traceEnabled) override def isDebugEnabled: LoggerF[Boolean] = isEnabled(debugEnabled) From 749fd3d251c3d29880e60bb26a030e75aedec658 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:08:35 +0000 Subject: [PATCH 44/62] update old interfaces with logger kernel functionality --- .../scala/org/typelevel/log4cats/Log.scala | 3 +- .../scala/org/typelevel/log4cats/Logger.scala | 88 ++++++------ .../typelevel/log4cats/LoggerFactory.scala | 11 -- .../PagingSelfAwareStructuredLogger.scala | 2 + .../typelevel/log4cats/SelfAwareLogger.scala | 40 ++---- .../log4cats/SelfAwareStructuredLogger.scala | 115 +++++----------- .../typelevel/log4cats/StructuredLogger.scala | 128 ++++++++---------- 7 files changed, 155 insertions(+), 232 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala index 908916dd..68e755ae 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Log.scala @@ -111,7 +111,8 @@ object Log { } override def adaptMessage(f: String => String): this.type = { - _message = () => f(_message()) + val originalMessage = _message + _message = () => f(originalMessage()) this } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala index 78a84258..7f10b80a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala @@ -17,9 +17,45 @@ package org.typelevel.log4cats import cats.* -import cats.data.{EitherT, Kleisli, OptionT} trait Logger[F[_]] extends MessageLogger[F] with ErrorLogger[F] { + protected def kernel: LoggerKernel[F, String] + + /** Access to the underlying kernel for advanced use cases */ + def underlying: LoggerKernel[F, String] = kernel + + // MessageLogger methods + def error(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Error, _.withMessage(message)) + + def warn(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Warn, _.withMessage(message)) + + def info(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Info, _.withMessage(message)) + + def debug(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Debug, _.withMessage(message)) + + def trace(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Trace, _.withMessage(message)) + + // ErrorLogger methods + def error(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Error, _.withMessage(message).withThrowable(t)) + + def warn(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Warn, _.withMessage(message).withThrowable(t)) + + def info(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Info, _.withMessage(message).withThrowable(t)) + + def debug(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Debug, _.withMessage(message).withThrowable(t)) + + def trace(t: Throwable)(message: => String): F[Unit] = + kernel.log(KernelLogLevel.Trace, _.withMessage(message).withThrowable(t)) + def withModifiedString(f: String => String): Logger[F] = Logger.withModifiedString[F](this, f) override def mapK[G[_]](fk: F ~> G): Logger[G] = Logger.mapK(fk)(this) } @@ -27,51 +63,25 @@ trait Logger[F[_]] extends MessageLogger[F] with ErrorLogger[F] { object Logger { def apply[F[_]](implicit ev: Logger[F]) = ev - implicit def optionTLogger[F[_]: Logger: Functor]: Logger[OptionT[F, *]] = - Logger[F].mapK(OptionT.liftK[F]) - implicit def eitherTLogger[F[_]: Logger: Functor, E]: Logger[EitherT[F, E, *]] = - Logger[F].mapK(EitherT.liftK[F, E]) - - implicit def kleisliLogger[F[_]: Logger, A]: Logger[Kleisli[F, A, *]] = - Logger[F].mapK(Kleisli.liftK[F, A]) private def withModifiedString[F[_]](l: Logger[F], f: String => String): Logger[F] = new Logger[F] { - def error(message: => String): F[Unit] = l.error(f(message)) - def error(t: Throwable)(message: => String): F[Unit] = l.error(t)(f(message)) - def warn(message: => String): F[Unit] = l.warn(f(message)) - def warn(t: Throwable)(message: => String): F[Unit] = l.warn(t)(f(message)) - def info(message: => String): F[Unit] = l.info(f(message)) - def info(t: Throwable)(message: => String): F[Unit] = l.info(t)(f(message)) - def debug(message: => String): F[Unit] = l.debug(f(message)) - def debug(t: Throwable)(message: => String): F[Unit] = l.debug(t)(f(message)) - def trace(message: => String): F[Unit] = l.trace(f(message)) - def trace(t: Throwable)(message: => String): F[Unit] = l.trace(t)(f(message)) + protected def kernel: LoggerKernel[F, String] = l.underlying + override def error(message: => String): F[Unit] = l.error(f(message)) + override def error(t: Throwable)(message: => String): F[Unit] = l.error(t)(f(message)) + override def warn(message: => String): F[Unit] = l.warn(f(message)) + override def warn(t: Throwable)(message: => String): F[Unit] = l.warn(t)(f(message)) + override def info(message: => String): F[Unit] = l.info(f(message)) + override def info(t: Throwable)(message: => String): F[Unit] = l.info(t)(f(message)) + override def debug(message: => String): F[Unit] = l.debug(f(message)) + override def debug(t: Throwable)(message: => String): F[Unit] = l.debug(t)(f(message)) + override def trace(message: => String): F[Unit] = l.trace(f(message)) + override def trace(t: Throwable)(message: => String): F[Unit] = l.trace(t)(f(message)) } private def mapK[G[_], F[_]](f: G ~> F)(logger: Logger[G]): Logger[F] = new Logger[F] { - def error(t: Throwable)(message: => String): F[Unit] = - f(logger.error(t)(message)) - def warn(t: Throwable)(message: => String): F[Unit] = - f(logger.warn(t)(message)) - def info(t: Throwable)(message: => String): F[Unit] = - f(logger.info(t)(message)) - def debug(t: Throwable)(message: => String): F[Unit] = - f(logger.debug(t)(message)) - def trace(t: Throwable)(message: => String): F[Unit] = - f(logger.trace(t)(message)) - def error(message: => String): F[Unit] = - f(logger.error(message)) - def warn(message: => String): F[Unit] = - f(logger.warn(message)) - def info(message: => String): F[Unit] = - f(logger.info(message)) - def debug(message: => String): F[Unit] = - f(logger.debug(message)) - def trace(message: => String): F[Unit] = - f(logger.trace(message)) + protected def kernel: LoggerKernel[F, String] = logger.underlying.mapK(f) } - } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala index f8d3ad94..881e7a50 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala @@ -18,11 +18,8 @@ package org.typelevel.log4cats import cats.Functor import cats.Show.Shown -import cats.data.Kleisli import cats.syntax.functor.* import cats.~> -import cats.data.OptionT -import cats.data.EitherT import scala.annotation.implicitNotFound @@ -49,14 +46,6 @@ trait LoggerFactory[F[_]] extends LoggerFactoryGen[F] { object LoggerFactory extends LoggerFactoryGenCompanion { def apply[F[_]: LoggerFactory]: LoggerFactory[F] = implicitly - implicit def optionTFactory[F[_]: LoggerFactory: Functor]: LoggerFactory[OptionT[F, *]] = - LoggerFactory[F].mapK(OptionT.liftK[F]) - - implicit def eitherTFactory[F[_]: LoggerFactory: Functor, E]: LoggerFactory[EitherT[F, E, *]] = - LoggerFactory[F].mapK(EitherT.liftK[F, E]) - - implicit def kleisliFactory[F[_]: LoggerFactory: Functor, A]: LoggerFactory[Kleisli[F, A, *]] = - LoggerFactory[F].mapK(Kleisli.liftK[F, A]) private def mapK[F[_]: Functor, G[_]](fk: F ~> G)(lf: LoggerFactory[F]): LoggerFactory[G] = new LoggerFactory[G] { diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/PagingSelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/PagingSelfAwareStructuredLogger.scala index 432514d2..0464cb1b 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/PagingSelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/PagingSelfAwareStructuredLogger.scala @@ -76,6 +76,8 @@ object PagingSelfAwareStructuredLogger { s"pageSizeK(=$pageSizeK) and maxPageNeeded(=$maxPageNeeded) must be positive numbers." ) + protected def kernel: LoggerKernel[F, String] = sl.underlying + @deprecated("Use constructor with randomUUID", "2.5.0") def this(pageSizeK: Int, maxPageNeeded: Int)( sl: SelfAwareStructuredLogger[F] diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala index e609e1eb..8e8ce740 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala @@ -34,37 +34,13 @@ object SelfAwareLogger { private def mapK[G[_], F[_]](f: G ~> F)(logger: SelfAwareLogger[G]): SelfAwareLogger[F] = new SelfAwareLogger[F] { - def isTraceEnabled: F[Boolean] = - f(logger.isTraceEnabled) - def isDebugEnabled: F[Boolean] = - f(logger.isDebugEnabled) - def isInfoEnabled: F[Boolean] = - f(logger.isInfoEnabled) - def isWarnEnabled: F[Boolean] = - f(logger.isWarnEnabled) - def isErrorEnabled: F[Boolean] = - f(logger.isErrorEnabled) - - def error(t: Throwable)(message: => String): F[Unit] = - f(logger.error(t)(message)) - def warn(t: Throwable)(message: => String): F[Unit] = - f(logger.warn(t)(message)) - def info(t: Throwable)(message: => String): F[Unit] = - f(logger.info(t)(message)) - def debug(t: Throwable)(message: => String): F[Unit] = - f(logger.debug(t)(message)) - def trace(t: Throwable)(message: => String): F[Unit] = - f(logger.trace(t)(message)) - def error(message: => String): F[Unit] = - f(logger.error(message)) - def warn(message: => String): F[Unit] = - f(logger.warn(message)) - def info(message: => String): F[Unit] = - f(logger.info(message)) - def debug(message: => String): F[Unit] = - f(logger.debug(message)) - def trace(message: => String): F[Unit] = - f(logger.trace(message)) + protected def kernel: LoggerKernel[F, String] = logger.underlying.mapK(f) + + def isTraceEnabled: F[Boolean] = f(logger.isTraceEnabled) + def isDebugEnabled: F[Boolean] = f(logger.isDebugEnabled) + def isInfoEnabled: F[Boolean] = f(logger.isInfoEnabled) + def isWarnEnabled: F[Boolean] = f(logger.isWarnEnabled) + def isErrorEnabled: F[Boolean] = f(logger.isErrorEnabled) } private def withModifiedString[F[_]]( @@ -72,6 +48,8 @@ object SelfAwareLogger { f: String => String ): SelfAwareLogger[F] = new SelfAwareLogger[F] { + protected def kernel: LoggerKernel[F, String] = l.underlying + override def isTraceEnabled: F[Boolean] = l.isTraceEnabled override def isDebugEnabled: F[Boolean] = l.isDebugEnabled override def isInfoEnabled: F[Boolean] = l.isInfoEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala index 40907bcf..89193351 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala @@ -48,49 +48,52 @@ object SelfAwareStructuredLogger { private class ModifiedContextSelfAwareStructuredLogger[F[_]](sl: SelfAwareStructuredLogger[F])( modify: Map[String, String] => Map[String, String] ) extends SelfAwareStructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = sl.underlying private lazy val defaultCtx: Map[String, String] = modify(Map.empty) - def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) - def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) - def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) - def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) - def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + + override def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) + override def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) + override def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) + override def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) + override def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = sl.trace(modify(ctx))(msg) - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = sl.debug(modify(ctx))(msg) - def info(ctx: Map[String, String])(msg: => String): F[Unit] = + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = sl.info(modify(ctx))(msg) - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = sl.warn(modify(ctx))(msg) - def error(ctx: Map[String, String])(msg: => String): F[Unit] = + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = sl.error(modify(ctx))(msg) - def isTraceEnabled: F[Boolean] = sl.isTraceEnabled - def isDebugEnabled: F[Boolean] = sl.isDebugEnabled - def isInfoEnabled: F[Boolean] = sl.isInfoEnabled - def isWarnEnabled: F[Boolean] = sl.isWarnEnabled - def isErrorEnabled: F[Boolean] = sl.isErrorEnabled + override def isTraceEnabled: F[Boolean] = sl.isTraceEnabled + override def isDebugEnabled: F[Boolean] = sl.isDebugEnabled + override def isInfoEnabled: F[Boolean] = sl.isInfoEnabled + override def isWarnEnabled: F[Boolean] = sl.isWarnEnabled + override def isErrorEnabled: F[Boolean] = sl.isErrorEnabled - def error(t: Throwable)(message: => String): F[Unit] = + override def error(t: Throwable)(message: => String): F[Unit] = sl.error(defaultCtx, t)(message) - def warn(t: Throwable)(message: => String): F[Unit] = + override def warn(t: Throwable)(message: => String): F[Unit] = sl.warn(defaultCtx, t)(message) - def info(t: Throwable)(message: => String): F[Unit] = + override def info(t: Throwable)(message: => String): F[Unit] = sl.info(defaultCtx, t)(message) - def debug(t: Throwable)(message: => String): F[Unit] = + override def debug(t: Throwable)(message: => String): F[Unit] = sl.debug(defaultCtx, t)(message) - def trace(t: Throwable)(message: => String): F[Unit] = + override def trace(t: Throwable)(message: => String): F[Unit] = sl.trace(defaultCtx, t)(message) - def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.error(modify(ctx), t)(message) - def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.warn(modify(ctx), t)(message) - def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.info(modify(ctx), t)(message) - def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.debug(modify(ctx), t)(message) - def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.trace(modify(ctx), t)(message) } @@ -99,6 +102,8 @@ object SelfAwareStructuredLogger { f: String => String ): SelfAwareStructuredLogger[F] = new SelfAwareStructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = l.underlying + def isTraceEnabled: F[Boolean] = l.isTraceEnabled def isDebugEnabled: F[Boolean] = l.isDebugEnabled def isInfoEnabled: F[Boolean] = l.isInfoEnabled @@ -136,58 +141,12 @@ object SelfAwareStructuredLogger { f: G ~> F )(logger: SelfAwareStructuredLogger[G]): SelfAwareStructuredLogger[F] = new SelfAwareStructuredLogger[F] { - def isTraceEnabled: F[Boolean] = - f(logger.isTraceEnabled) - def isDebugEnabled: F[Boolean] = - f(logger.isDebugEnabled) - def isInfoEnabled: F[Boolean] = - f(logger.isInfoEnabled) - def isWarnEnabled: F[Boolean] = - f(logger.isWarnEnabled) - def isErrorEnabled: F[Boolean] = - f(logger.isErrorEnabled) - - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.trace(ctx)(msg)) - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.debug(ctx)(msg)) - def info(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.info(ctx)(msg)) - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.warn(ctx)(msg)) - def error(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.error(ctx)(msg)) - - def error(t: Throwable)(message: => String): F[Unit] = - f(logger.error(t)(message)) - def warn(t: Throwable)(message: => String): F[Unit] = - f(logger.warn(t)(message)) - def info(t: Throwable)(message: => String): F[Unit] = - f(logger.info(t)(message)) - def debug(t: Throwable)(message: => String): F[Unit] = - f(logger.debug(t)(message)) - def trace(t: Throwable)(message: => String): F[Unit] = - f(logger.trace(t)(message)) - def error(message: => String): F[Unit] = - f(logger.error(message)) - def warn(message: => String): F[Unit] = - f(logger.warn(message)) - def info(message: => String): F[Unit] = - f(logger.info(message)) - def debug(message: => String): F[Unit] = - f(logger.debug(message)) - def trace(message: => String): F[Unit] = - f(logger.trace(message)) - - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.trace(ctx, t)(msg)) - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.debug(ctx, t)(msg)) - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.info(ctx, t)(msg)) - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.warn(ctx, t)(msg)) - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.error(ctx, t)(msg)) + protected def kernel: LoggerKernel[F, String] = logger.underlying.mapK(f) + + def isTraceEnabled: F[Boolean] = f(logger.isTraceEnabled) + def isDebugEnabled: F[Boolean] = f(logger.isDebugEnabled) + def isInfoEnabled: F[Boolean] = f(logger.isInfoEnabled) + def isWarnEnabled: F[Boolean] = f(logger.isWarnEnabled) + def isErrorEnabled: F[Boolean] = f(logger.isErrorEnabled) } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala index ee50e011..7f16333b 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala @@ -20,16 +20,37 @@ import cats.* import cats.Show.Shown trait StructuredLogger[F[_]] extends Logger[F] { - def trace(ctx: Map[String, String])(msg: => String): F[Unit] - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] - def debug(ctx: Map[String, String])(msg: => String): F[Unit] - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] - def info(ctx: Map[String, String])(msg: => String): F[Unit] - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] - def warn(ctx: Map[String, String])(msg: => String): F[Unit] - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] - def error(ctx: Map[String, String])(msg: => String): F[Unit] - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] + // Structured logging methods + def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Trace, _.withMessage(msg).withContextMap(ctx)) + + def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Trace, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Debug, _.withMessage(msg).withContextMap(ctx)) + + def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Debug, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def info(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Info, _.withMessage(msg).withContextMap(ctx)) + + def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Info, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Warn, _.withMessage(msg).withContextMap(ctx)) + + def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Warn, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) + + def error(ctx: Map[String, String])(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Error, _.withMessage(msg).withContextMap(ctx)) + + def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + kernel.log(KernelLogLevel.Error, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) + override def mapK[G[_]](fk: F ~> G): StructuredLogger[G] = StructuredLogger.mapK(fk)(this) @@ -61,43 +82,46 @@ object StructuredLogger { private class ModifiedContextStructuredLogger[F[_]](sl: StructuredLogger[F])( modify: Map[String, String] => Map[String, String] ) extends StructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = sl.underlying private lazy val defaultCtx: Map[String, String] = modify(Map.empty) - def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) - def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) - def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) - def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) - def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + + override def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) + override def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) + override def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) + override def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) + override def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) + + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = sl.trace(modify(ctx))(msg) - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = sl.debug(modify(ctx))(msg) - def info(ctx: Map[String, String])(msg: => String): F[Unit] = + override def info(ctx: Map[String, String])(msg: => String): F[Unit] = sl.info(modify(ctx))(msg) - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = sl.warn(modify(ctx))(msg) - def error(ctx: Map[String, String])(msg: => String): F[Unit] = + override def error(ctx: Map[String, String])(msg: => String): F[Unit] = sl.error(modify(ctx))(msg) - def error(t: Throwable)(message: => String): F[Unit] = + override def error(t: Throwable)(message: => String): F[Unit] = sl.error(defaultCtx, t)(message) - def warn(t: Throwable)(message: => String): F[Unit] = + override def warn(t: Throwable)(message: => String): F[Unit] = sl.warn(defaultCtx, t)(message) - def info(t: Throwable)(message: => String): F[Unit] = + override def info(t: Throwable)(message: => String): F[Unit] = sl.info(defaultCtx, t)(message) - def debug(t: Throwable)(message: => String): F[Unit] = + override def debug(t: Throwable)(message: => String): F[Unit] = sl.debug(defaultCtx, t)(message) - def trace(t: Throwable)(message: => String): F[Unit] = + override def trace(t: Throwable)(message: => String): F[Unit] = sl.trace(defaultCtx, t)(message) - def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.error(modify(ctx), t)(message) - def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.warn(modify(ctx), t)(message) - def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.info(modify(ctx), t)(message) - def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.debug(modify(ctx), t)(message) - def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = sl.trace(modify(ctx), t)(message) } @@ -106,6 +130,7 @@ object StructuredLogger { f: String => String ): StructuredLogger[F] = new StructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = l.underlying override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = l.trace(ctx)(f(msg)) override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = l.trace(ctx, t)(f(msg)) @@ -135,47 +160,6 @@ object StructuredLogger { private def mapK[G[_], F[_]](f: G ~> F)(logger: StructuredLogger[G]): StructuredLogger[F] = new StructuredLogger[F] { - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.trace(ctx)(msg)) - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.debug(ctx)(msg)) - def info(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.info(ctx)(msg)) - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.warn(ctx)(msg)) - def error(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.error(ctx)(msg)) - - def error(t: Throwable)(message: => String): F[Unit] = - f(logger.error(t)(message)) - def warn(t: Throwable)(message: => String): F[Unit] = - f(logger.warn(t)(message)) - def info(t: Throwable)(message: => String): F[Unit] = - f(logger.info(t)(message)) - def debug(t: Throwable)(message: => String): F[Unit] = - f(logger.debug(t)(message)) - def trace(t: Throwable)(message: => String): F[Unit] = - f(logger.trace(t)(message)) - def error(message: => String): F[Unit] = - f(logger.error(message)) - def warn(message: => String): F[Unit] = - f(logger.warn(message)) - def info(message: => String): F[Unit] = - f(logger.info(message)) - def debug(message: => String): F[Unit] = - f(logger.debug(message)) - def trace(message: => String): F[Unit] = - f(logger.trace(message)) - - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.trace(ctx, t)(msg)) - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.debug(ctx, t)(msg)) - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.info(ctx, t)(msg)) - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.warn(ctx, t)(msg)) - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.error(ctx, t)(msg)) + protected def kernel: LoggerKernel[F, String] = logger.underlying.mapK(f) } } From 72376e8204b795219ead42dfaa29ce7048376405 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:09:05 +0000 Subject: [PATCH 45/62] remove SamLoggerAdaptorTest --- .../log4cats/SamLoggerAdapterTest.scala | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala deleted file mode 100644 index 107c9502..00000000 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerAdapterTest.scala +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 - -import cats.effect.IO -import munit.CatsEffectSuite - -class SamLoggerAdapterTest extends CatsEffectSuite { - - test("Adapter should convert LoggerKernel to Logger") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLoggerAdapter.toLogger(kernel) - - // Test basic logging - logger.info("Test message").void - logger.error("Test error").void - logger.warn("Test warning").void - logger.debug("Test debug").void - logger.trace("Test trace").void - } - - test("Adapter should convert Logger to LoggerKernel") { - val originalKernel = ConsoleLoggerKernel[IO, String] - val logger = SamLoggerAdapter.toLogger(originalKernel) - val kernel = SamLoggerAdapter.toLoggerKernel(logger) - - // Test that the kernel works - kernel.log(KernelLogLevel.Info, _.withMessage("Test via adapter")).void - } - - test("Adapter should handle throwables correctly") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLoggerAdapter.toLogger(kernel) - val exception = new RuntimeException("Test exception") - - logger.error(exception)("Error with throwable").void - logger.warn(exception)("Warning with throwable").void - logger.info(exception)("Info with throwable").void - logger.debug(exception)("Debug with throwable").void - logger.trace(exception)("Trace with throwable").void - } - - test("Adapter should convert SamLogger to Logger") { - val kernel = ConsoleLoggerKernel[IO, String] - val samLogger = SamLogger.wrap(kernel) - val logger = SamLoggerAdapter.samLoggerToLogger(samLogger) - - logger.info("Test from SamLogger").void - logger.error("Test error from SamLogger").void - } - - test("Adapter should convert Logger to SamLogger") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLoggerAdapter.toLogger(kernel) - val samLogger = SamLoggerAdapter.loggerToSamLogger[IO, String](logger) - - // Test that the SamLogger works with structured logging - samLogger - .info( - "Test structured logging", - ("key1", "value1"), - ("key2", "value2") - ) - .void - } - - test("Round-trip conversion should preserve functionality") { - val originalKernel = ConsoleLoggerKernel[IO, String] - val logger = SamLoggerAdapter.toLogger(originalKernel) - val roundTripKernel = SamLoggerAdapter.toLoggerKernel(logger) - - // Test that round-trip conversion works - roundTripKernel.log(KernelLogLevel.Info, _.withMessage("Round-trip test")).void - } -} From b0ddff1e475bd048cc9e94e8bcfe6ecafe985260 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:12:19 +0000 Subject: [PATCH 46/62] update sam implementation after removing LogRecor and Recordable --- .../typelevel/log4cats/KernelLogLevel.scala | 32 +-- .../org/typelevel/log4cats/LoggerKernel.scala | 25 ++- .../org/typelevel/log4cats/SamLogger.scala | 82 ++++---- .../log4cats/SamStructuredLogger.scala | 182 +++--------------- 4 files changed, 81 insertions(+), 240 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala index 63542936..cd715669 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/KernelLogLevel.scala @@ -19,47 +19,17 @@ package org.typelevel.log4cats import cats.Order final case class KernelLogLevel(name: String, value: Int) { - def namePadded: String = KernelLogLevel.padded(this) - - KernelLogLevel.add(this) + def namePadded: String = name.padTo(5, ' ').mkString } object KernelLogLevel { - private var maxLength = 0 - - private var map = Map.empty[String, KernelLogLevel] - private var padded = Map.empty[KernelLogLevel, String] - implicit final val orderKernelLogLevel: Order[KernelLogLevel] = Order.by[KernelLogLevel, Int](-_.value) - // For Java/legacy interop, if needed (not implicit) - val LevelOrdering: Ordering[KernelLogLevel] = - Ordering.by[KernelLogLevel, Int](_.value).reverse - val Trace: KernelLogLevel = KernelLogLevel("TRACE", 100) val Debug: KernelLogLevel = KernelLogLevel("DEBUG", 200) val Info: KernelLogLevel = KernelLogLevel("INFO", 300) val Warn: KernelLogLevel = KernelLogLevel("WARN", 400) val Error: KernelLogLevel = KernelLogLevel("ERROR", 500) val Fatal: KernelLogLevel = KernelLogLevel("FATAL", 600) - - def add(level: KernelLogLevel): Unit = synchronized { - val length = level.name.length - map += level.name.toLowerCase -> level - if (length > maxLength) { - maxLength = length - padded = map.map { case (_, level) => - level -> level.name.padTo(maxLength, ' ').mkString - } - } else { - padded += level -> level.name.padTo(maxLength, ' ').mkString - } - } - - def get(name: String): Option[KernelLogLevel] = map.get(name.toLowerCase) - - def apply(name: String): KernelLogLevel = get(name).getOrElse( - throw new RuntimeException(s"Level not found by name: $name") - ) } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala index 34e97475..b8ce46c0 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala @@ -16,6 +16,8 @@ package org.typelevel.log4cats +import cats.~> + /** * This is the fundamental abstraction: a single-abstract-method interface that has the following * properties: @@ -34,18 +36,25 @@ package org.typelevel.log4cats * implementation. */ trait LoggerKernel[F[_], Ctx] { - type Builder = Log.Builder[Ctx] - - def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] - final def logTrace(record: Builder => Builder): F[Unit] = + final def logTrace(record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = log(KernelLogLevel.Trace, record) - final def logDebug(record: Builder => Builder): F[Unit] = + final def logDebug(record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = log(KernelLogLevel.Debug, record) - final def logInfo(record: Builder => Builder): F[Unit] = + final def logInfo(record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = log(KernelLogLevel.Info, record) - final def logWarn(record: Builder => Builder): F[Unit] = + final def logWarn(record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = log(KernelLogLevel.Warn, record) - final def logError(record: Builder => Builder): F[Unit] = + final def logError(record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = log(KernelLogLevel.Error, record) + + /** + * Transform the effect type using a natural transformation. + * This allows converting between different effect types (e.g., IO to Task, Task to IO). + */ + def mapK[G[_]](f: F ~> G): LoggerKernel[G, Ctx] = new LoggerKernel[G, Ctx] { + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): G[Unit] = + f(LoggerKernel.this.log(level, record)) + } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index 63cb634e..b33eeae6 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -17,71 +17,70 @@ package org.typelevel.log4cats import cats.* -import cats.data.{EitherT, Kleisli, OptionT} /** - * A SAM-based Logger that extends LoggerKernel and provides a user-friendly interface. This is the + * A SAM-based Logger that provides a user-friendly interface. This is the * new design that will eventually replace the current Logger trait. */ -abstract class SamLogger[F[_], Ctx] extends LoggerKernel[F, Ctx] { +trait SamLogger[F[_], Ctx] { + protected def kernel: LoggerKernel[F, Ctx] + + /** Access to the underlying kernel for advanced use cases */ + def underlying: LoggerKernel[F, Ctx] = kernel - final def info(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit + final def info(message: => String)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Info, logBit, others: _*) + ): F[Unit] = log_(KernelLogLevel.Info, message) - final def warn(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit + final def warn(message: => String)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Warn, logBit, others: _*) + ): F[Unit] = log_(KernelLogLevel.Warn, message) - final def error(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit + final def error(message: => String)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Error, logBit, others: _*) + ): F[Unit] = log_(KernelLogLevel.Error, message) - final def trace(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit + final def trace(message: => String)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Trace, logBit, others: _*) + ): F[Unit] = log_(KernelLogLevel.Trace, message) - final def debug(logBit: LogRecord[Ctx], others: LogRecord[Ctx]*)(implicit + final def debug(message: => String)(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Debug, logBit, others: _*) + ): F[Unit] = log_(KernelLogLevel.Debug, message) private final def log_( level: KernelLogLevel, - bit: LogRecord[Ctx], - others: LogRecord[Ctx]* + message: => String )(implicit pkg: sourcecode.Pkg, filename: sourcecode.FileName, name: sourcecode.Name, line: sourcecode.Line ): F[Unit] = { - log( + kernel.log( level, - (record: Builder) => - LogRecord.combine[Ctx](others)( - bit( - record - .withLevel(level) - .withClassName(pkg.value + "." + name.value) - .withFileName(filename.value) - .withLine(line.value) - ) - ) + (record: Log.Builder[Ctx]) => + record + .withLevel(level) + .withMessage(message) + .withClassName(pkg.value + "." + name.value) + .withFileName(filename.value) + .withLine(line.value) ) } @@ -93,38 +92,27 @@ abstract class SamLogger[F[_], Ctx] extends LoggerKernel[F, Ctx] { object SamLogger { def apply[F[_], Ctx](implicit ev: SamLogger[F, Ctx]) = ev - def wrap[F[_], Ctx](kernel: LoggerKernel[F, Ctx]): SamLogger[F, Ctx] = new SamLogger[F, Ctx] { - def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = - kernel.log(level, record) + def wrap[F[_], Ctx](k: LoggerKernel[F, Ctx]): SamLogger[F, Ctx] = new SamLogger[F, Ctx] { + protected def kernel: LoggerKernel[F, Ctx] = k } - implicit def optionTSamLogger[F[_]: Functor, Ctx](implicit - ev: SamLogger[F, Ctx] - ): SamLogger[OptionT[F, *], Ctx] = - ev.mapK(OptionT.liftK[F]) - - implicit def eitherTSamLogger[F[_]: Functor, E, Ctx](implicit - ev: SamLogger[F, Ctx] - ): SamLogger[EitherT[F, E, *], Ctx] = - ev.mapK(EitherT.liftK[F, E]) - - implicit def kleisliSamLogger[F[_], A, Ctx](implicit - ev: SamLogger[F, Ctx] - ): SamLogger[Kleisli[F, A, *], Ctx] = - ev.mapK(Kleisli.liftK[F, A]) private def withModifiedString[F[_], Ctx]( l: SamLogger[F, Ctx], f: String => String ): SamLogger[F, Ctx] = new SamLogger[F, Ctx] { - def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = - l.log(level, record(_).adaptMessage(f)) + protected def kernel: LoggerKernel[F, Ctx] = new LoggerKernel[F, Ctx] { + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = + l.underlying.log(level, record(_).adaptMessage(f)) + } } private def mapK[G[_], F[_], Ctx](f: G ~> F)(logger: SamLogger[G, Ctx]): SamLogger[F, Ctx] = new SamLogger[F, Ctx] { - def log(level: KernelLogLevel, record: Builder => Builder): F[Unit] = - f(logger.log(level, record)) + protected def kernel: LoggerKernel[F, Ctx] = new LoggerKernel[F, Ctx] { + def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = + f(logger.underlying.log(level, record)) + } } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala index 190c6960..b0531b06 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -17,7 +17,6 @@ package org.typelevel.log4cats import cats.* -import cats.data.{EitherT, Kleisli, OptionT} import cats.Show.Shown /** @@ -28,64 +27,64 @@ import cats.Show.Shown trait SamStructuredLogger[F[_]] extends Logger[F] { protected def kernel: LoggerKernel[F, String] - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + final def trace(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logTrace(_.withMessage(msg).withContextMap(ctx)) - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + final def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logTrace(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + final def debug(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logDebug(_.withMessage(msg).withContextMap(ctx)) - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + final def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logDebug(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - def info(ctx: Map[String, String])(msg: => String): F[Unit] = + final def info(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logInfo(_.withMessage(msg).withContextMap(ctx)) - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + final def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logInfo(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + final def warn(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logWarn(_.withMessage(msg).withContextMap(ctx)) - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + final def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logWarn(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - def error(ctx: Map[String, String])(msg: => String): F[Unit] = + final def error(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.logError(_.withMessage(msg).withContextMap(ctx)) - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + final def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.logError(_.withMessage(msg).withThrowable(t).withContextMap(ctx)) - override def trace(message: => String): F[Unit] = + final override def trace(message: => String): F[Unit] = kernel.logTrace(_.withMessage(message)) - override def trace(t: Throwable)(message: => String): F[Unit] = + final override def trace(t: Throwable)(message: => String): F[Unit] = kernel.logTrace(_.withMessage(message).withThrowable(t)) - override def debug(message: => String): F[Unit] = + final override def debug(message: => String): F[Unit] = kernel.logDebug(_.withMessage(message)) - override def debug(t: Throwable)(message: => String): F[Unit] = + final override def debug(t: Throwable)(message: => String): F[Unit] = kernel.logDebug(_.withMessage(message).withThrowable(t)) - override def info(message: => String): F[Unit] = + final override def info(message: => String): F[Unit] = kernel.logInfo(_.withMessage(message)) - override def info(t: Throwable)(message: => String): F[Unit] = + final override def info(t: Throwable)(message: => String): F[Unit] = kernel.logInfo(_.withMessage(message).withThrowable(t)) - override def warn(message: => String): F[Unit] = + final override def warn(message: => String): F[Unit] = kernel.logWarn(_.withMessage(message)) - override def warn(t: Throwable)(message: => String): F[Unit] = + final override def warn(t: Throwable)(message: => String): F[Unit] = kernel.logWarn(_.withMessage(message).withThrowable(t)) - override def error(message: => String): F[Unit] = + final override def error(message: => String): F[Unit] = kernel.logError(_.withMessage(message)) - override def error(t: Throwable)(message: => String): F[Unit] = + final override def error(t: Throwable)(message: => String): F[Unit] = kernel.logError(_.withMessage(message).withThrowable(t)) def addContext(ctx: Map[String, String]): SamStructuredLogger[F] = @@ -113,154 +112,29 @@ object SamStructuredLogger { def withContext[F[_]](sl: SamStructuredLogger[F])( ctx: Map[String, String] - ): SamStructuredLogger[F] = - new ModifiedContextSamStructuredLogger[F](sl)(ctx ++ _) + ): SamStructuredLogger[F] = withModifiedContext[F](sl)(ctx ++ _) def withModifiedContext[F[_]]( sl: SamStructuredLogger[F] )(modifyCtx: Map[String, String] => Map[String, String]): SamStructuredLogger[F] = - new ModifiedContextSamStructuredLogger[F](sl)(modifyCtx) - - private class ModifiedContextSamStructuredLogger[F[_]](sl: SamStructuredLogger[F])( - modify: Map[String, String] => Map[String, String] - ) extends SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, String] = sl.kernel - - private lazy val defaultCtx: Map[String, String] = modify(Map.empty) - - override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - sl.trace(modify(ctx))(msg) - - override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - sl.debug(modify(ctx))(msg) - - override def info(ctx: Map[String, String])(msg: => String): F[Unit] = - sl.info(modify(ctx))(msg) - - override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - sl.warn(modify(ctx))(msg) - - override def error(ctx: Map[String, String])(msg: => String): F[Unit] = - sl.error(modify(ctx))(msg) - - override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - sl.trace(modify(ctx), t)(msg) - - override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - sl.debug(modify(ctx), t)(msg) - - override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - sl.info(modify(ctx), t)(msg) - - override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - sl.warn(modify(ctx), t)(msg) - - override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - sl.error(modify(ctx), t)(msg) + new SamStructuredLogger[F] { + protected val kernel: LoggerKernel[F, String] = + (level, record) => sl.kernel.log(level, record(_).adaptContext(modifyCtx)) + } - override def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) - override def trace(t: Throwable)(message: => String): F[Unit] = sl.trace(defaultCtx, t)(message) - override def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) - override def debug(t: Throwable)(message: => String): F[Unit] = sl.debug(defaultCtx, t)(message) - override def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) - override def info(t: Throwable)(message: => String): F[Unit] = sl.info(defaultCtx, t)(message) - override def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) - override def warn(t: Throwable)(message: => String): F[Unit] = sl.warn(defaultCtx, t)(message) - override def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) - override def error(t: Throwable)(message: => String): F[Unit] = sl.error(defaultCtx, t)(message) - } private def withModifiedString[F[_]]( l: SamStructuredLogger[F], f: String => String ): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, String] = l.kernel - - override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = l.trace(ctx)(f(msg)) - override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - l.trace(ctx, t)(f(msg)) - override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = l.debug(ctx)(f(msg)) - override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - l.debug(ctx, t)(f(msg)) - override def info(ctx: Map[String, String])(msg: => String): F[Unit] = l.info(ctx)(f(msg)) - override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - l.info(ctx, t)(f(msg)) - override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = l.warn(ctx)(f(msg)) - override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - l.warn(ctx, t)(f(msg)) - override def error(ctx: Map[String, String])(msg: => String): F[Unit] = l.error(ctx)(f(msg)) - override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - l.error(ctx, t)(f(msg)) - - override def trace(message: => String): F[Unit] = l.trace(f(message)) - override def trace(t: Throwable)(message: => String): F[Unit] = l.trace(t)(f(message)) - override def debug(message: => String): F[Unit] = l.debug(f(message)) - override def debug(t: Throwable)(message: => String): F[Unit] = l.debug(t)(f(message)) - override def info(message: => String): F[Unit] = l.info(f(message)) - override def info(t: Throwable)(message: => String): F[Unit] = l.info(t)(f(message)) - override def warn(message: => String): F[Unit] = l.warn(f(message)) - override def warn(t: Throwable)(message: => String): F[Unit] = l.warn(t)(f(message)) - override def error(message: => String): F[Unit] = l.error(f(message)) - override def error(t: Throwable)(message: => String): F[Unit] = l.error(t)(f(message)) + protected val kernel: LoggerKernel[F, String] = + (level, record) => l.kernel.log(level, record(_).adaptMessage(f)) } private def mapK[G[_], F[_]](f: G ~> F)(logger: SamStructuredLogger[G]): SamStructuredLogger[F] = new SamStructuredLogger[F] { - protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log( - level: KernelLogLevel, - record: Log.Builder[String] => Log.Builder[String] - ): F[Unit] = - f(logger.kernel.log(level, record)) - } - - override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.trace(ctx)(msg)) - override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.debug(ctx)(msg)) - override def info(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.info(ctx)(msg)) - override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.warn(ctx)(msg)) - override def error(ctx: Map[String, String])(msg: => String): F[Unit] = - f(logger.error(ctx)(msg)) - - override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.trace(ctx, t)(msg)) - override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.debug(ctx, t)(msg)) - override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.info(ctx, t)(msg)) - override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.warn(ctx, t)(msg)) - override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - f(logger.error(ctx, t)(msg)) - - override def trace(message: => String): F[Unit] = f(logger.trace(message)) - override def trace(t: Throwable)(message: => String): F[Unit] = f(logger.trace(t)(message)) - override def debug(message: => String): F[Unit] = f(logger.debug(message)) - override def debug(t: Throwable)(message: => String): F[Unit] = f(logger.debug(t)(message)) - override def info(message: => String): F[Unit] = f(logger.info(message)) - override def info(t: Throwable)(message: => String): F[Unit] = f(logger.info(t)(message)) - override def warn(message: => String): F[Unit] = f(logger.warn(message)) - override def warn(t: Throwable)(message: => String): F[Unit] = f(logger.warn(t)(message)) - override def error(message: => String): F[Unit] = f(logger.error(message)) - override def error(t: Throwable)(message: => String): F[Unit] = f(logger.error(t)(message)) + protected val kernel: LoggerKernel[F, String] = logger.kernel.mapK(f) } - implicit def optionTSamStructuredLogger[F[_]: Functor](implicit - ev: SamStructuredLogger[F] - ): SamStructuredLogger[OptionT[F, *]] = - ev.mapK(OptionT.liftK[F]) - - implicit def eitherTSamStructuredLogger[F[_]: Functor, E](implicit - ev: SamStructuredLogger[F] - ): SamStructuredLogger[EitherT[F, E, *]] = - ev.mapK(EitherT.liftK[F, E]) - - implicit def kleisliSamStructuredLogger[F[_], A](implicit - ev: SamStructuredLogger[F] - ): SamStructuredLogger[Kleisli[F, A, *]] = - ev.mapK(Kleisli.liftK[F, A]) } From 58f2b51a6acd330013e5c2446af1a425c0b4fea6 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:15:53 +0000 Subject: [PATCH 47/62] update test suite --- .../typelevel/log4cats/SamLoggerTest.scala | 179 +++++++++--------- .../log4cats/SamStructuredLoggerTest.scala | 117 ++++++++++-- .../LoggerFactorySyntaxCompilation.scala | 6 +- .../syntax/LoggerSyntaxCompilation.scala | 6 +- 4 files changed, 191 insertions(+), 117 deletions(-) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala index 84c14289..fad1885a 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala @@ -17,113 +17,110 @@ package org.typelevel.log4cats import cats.effect.IO +import cats.effect.Ref import munit.CatsEffectSuite class SamLoggerTest extends CatsEffectSuite { - test("LoggerKernel should work with a simple console implementation") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLogger.wrap(kernel) - - logger.info("Hello, SAM Logger!").void + // Test kernel that captures log calls for verification + def testKernel[F[_]: cats.effect.Sync](ref: Ref[F, List[Log[String]]]): LoggerKernel[F, String] = { + new LoggerKernel[F, String] { + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val logRecord = record(Log.mutableBuilder[String]()).build() + ref.update(_ :+ logRecord) + } + } } - test("LoggerKernel should support structured logging with context") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLogger.wrap(kernel) - - logger - .info( - "User action", - ("user_id", "123"), - ("action", "login") - ) - .void - } - - test("LoggerKernel should support error logging with throwables") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLogger.wrap(kernel) - - val exception = new RuntimeException("Test exception") - - logger - .error( - "Something went wrong", - exception - ) - .void + test("SamLogger should execute logging operations and capture them") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamLogger.wrap(kernel) + + _ <- logger.info("Hello, SAM Logger!") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + assertEquals(logs.head.message(), "Hello, SAM Logger!") + } } - test("LoggerKernel should support multiple log records") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLogger.wrap(kernel) - - logger - .info( - "Complex log entry", - ("request_id", "abc-123"), - ("duration_ms", 150), - new RuntimeException("Nested error") - ) - .void + test("SamLogger should support simple message logging") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamLogger.wrap(kernel) + + _ <- logger.info("User action") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "User action") + } } - test("SamLogger withModifiedString should modify messages correctly") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLogger.wrap(kernel) - val modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") - - // Test that the message is modified - modifiedLogger.info("Test message").void + test("SamLogger should support error logging") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamLogger.wrap(kernel) + + _ <- logger.error("Something went wrong") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "Something went wrong") + } } - test("SamLogger withModifiedString should preserve context and other fields") { - val kernel = ConsoleLoggerKernel[IO, String] - val logger = SamLogger.wrap(kernel) - val modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") - - // Test that context and other fields are preserved - modifiedLogger - .info( - "Test message", - ("key", "value"), - new RuntimeException("Test exception") - ) - .void + test("SamLogger should support multiple log calls") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamLogger.wrap(kernel) + + _ <- logger.info("First log entry") + _ <- logger.info("Second log entry") + logs <- ref.get + } yield { + assertEquals(logs.length, 2) + assertEquals(logs(0).message(), "First log entry") + assertEquals(logs(1).message(), "Second log entry") + } } - test("LogRecord should combine multiple records correctly") { - val record1: LogRecord[String] = _.withMessage("Hello") - val record2: LogRecord[String] = _.withContext("key")("value") - - val combined = LogRecord.combine(Seq(record1, record2)) - val builder = Log.mutableBuilder[String]() - val result = combined(builder).build() - - assertEquals(result.message(), "Hello") - assert(result.context.contains("key")) - } - - test("Recordable should convert strings to log records") { - val record = implicitly[Recordable[String, String]].record("test message") - val result = record(Log.mutableBuilder[String]()).build() - - assertEquals(result.message(), "test message") + test("SamLogger withModifiedString should modify messages correctly") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamLogger.wrap(kernel) + modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") + + _ <- modifiedLogger.info("Test message") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + assertEquals(logs.head.message(), "[MODIFIED] Test message") + } } - test("Recordable should convert tuples to context records") { - val record = implicitly[Recordable[String, (String, String)]].record(("key", "value")) - val result = record(Log.mutableBuilder[String]()).build() - - assert(result.context.contains("key")) + test("SamLogger withModifiedString should preserve other fields") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamLogger.wrap(kernel) + modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") + + _ <- modifiedLogger.info("Test message") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "[MODIFIED] Test message") + } } - test("Recordable should convert throwables to log records") { - val exception = new RuntimeException("test") - val record = implicitly[Recordable[String, Throwable]].record(exception) - val result = record(Log.mutableBuilder[String]()).build() - - assertEquals(result.throwable, Some(exception)) - } } diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala index 35067261..d61095c2 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -17,35 +17,112 @@ package org.typelevel.log4cats import cats.effect.IO +import cats.effect.Ref import munit.CatsEffectSuite class SamStructuredLoggerTest extends CatsEffectSuite { - test("SamStructuredLogger should handle context with multiple entries") { - var capturedLogs: List[(KernelLogLevel, Log.Builder[String] => Log.Builder[String])] = Nil + // Test kernel that captures log calls for verification + def testKernel[F[_]: cats.effect.Sync](ref: Ref[F, List[Log[String]]]): LoggerKernel[F, String] = { + new LoggerKernel[F, String] { + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val logRecord = record(Log.mutableBuilder[String]()).build() + ref.update(_ :+ logRecord) + } + } + } - val testKernel = new LoggerKernel[IO, String] { - def log( - level: KernelLogLevel, - record: Log.Builder[String] => Log.Builder[String] - ): IO[Unit] = { + test("SamStructuredLogger should handle context with multiple entries") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamStructuredLogger.fromKernel(kernel) + + _ <- logger.info(Map("base" -> "value", "extra" -> "data"))("Test message") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "Test message") + assertEquals(log.context.size, 2) + assertEquals(log.context("base"), "value") + assertEquals(log.context("extra"), "data") + } + } - capturedLogs = capturedLogs :+ ((level, record)) - IO.unit - } + test("SamStructuredLogger should support all log levels") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamStructuredLogger.fromKernel(kernel) + + _ <- logger.trace(Map("level" -> "trace"))("Trace message") + _ <- logger.debug(Map("level" -> "debug"))("Debug message") + _ <- logger.info(Map("level" -> "info"))("Info message") + _ <- logger.warn(Map("level" -> "warn"))("Warn message") + _ <- logger.error(Map("level" -> "error"))("Error message") + logs <- ref.get + } yield { + assertEquals(logs.length, 5) + assertEquals(logs(0).context("level"), "trace") + assertEquals(logs(1).context("level"), "debug") + assertEquals(logs(2).context("level"), "info") + assertEquals(logs(3).context("level"), "warn") + assertEquals(logs(4).context("level"), "error") } + } - val logger = SamStructuredLogger.fromKernel(testKernel) - logger.info(Map("base" -> "value", "extra" -> "data"))("Test message").void + test("SamStructuredLogger should support error logging with throwables") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamStructuredLogger.fromKernel(kernel) + + val exception = new RuntimeException("Test exception") + _ <- logger.error(Map("error_type" -> "runtime"), exception)("Error with context") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "Error with context") + assertEquals(log.context("error_type"), "runtime") + assertEquals(log.throwable, Some(exception)) + } + } - assertEquals(capturedLogs.length, 1) - val (level, record) = capturedLogs.head - assertEquals(level, KernelLogLevel.Info) + test("SamStructuredLogger should support addContext functionality") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamStructuredLogger.fromKernel(kernel) + enrichedLogger = logger.addContext(Map("service" -> "test-service")) + + _ <- enrichedLogger.info(Map("request_id" -> "123"))("Request processed") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "Request processed") + assertEquals(log.context("service"), "test-service") + assertEquals(log.context("request_id"), "123") + } + } - val logRecord = record(Log.mutableBuilder[String]()).build() - assertEquals(logRecord.message(), "Test message") - assertEquals(logRecord.context.size, 2) - assertEquals(logRecord.context("base"), "value") - assertEquals(logRecord.context("extra"), "data") + test("SamStructuredLogger should support addContext with key-value pairs") { + for { + ref <- Ref.of[IO, List[Log[String]]](List.empty) + kernel = testKernel[IO](ref) + logger = SamStructuredLogger.fromKernel(kernel) + enrichedLogger = logger.addContext("service" -> "test-service", "version" -> "1.0.0") + + _ <- enrichedLogger.info("Service started") + logs <- ref.get + } yield { + assertEquals(logs.length, 1) + val log = logs.head + assertEquals(log.message(), "Service started") + assertEquals(log.context("service"), "test-service") + assertEquals(log.context("version"), "1.0.0") + } } } diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerFactorySyntaxCompilation.scala b/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerFactorySyntaxCompilation.scala index e440320c..0f87b598 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerFactorySyntaxCompilation.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerFactorySyntaxCompilation.scala @@ -34,12 +34,12 @@ object LoggerFactorySyntaxCompilation { lf.mapK(Kleisli.liftK[F, A]) def eitherTLoggerFactory[F[_]: Functor: LoggerFactory, E]: LoggerFactory[EitherT[F, E, *]] = - LoggerFactory[EitherT[F, E, *]] + LoggerFactory[F].mapK(EitherT.liftK[F, E]) def optionTLoggerFactory[F[_]: Functor: LoggerFactory]: LoggerFactory[OptionT[F, *]] = - LoggerFactory[OptionT[F, *]] + LoggerFactory[F].mapK(OptionT.liftK[F]) def kleisliLoggerFactory[F[_]: Functor: LoggerFactory, A]: LoggerFactory[Kleisli[F, A, *]] = - LoggerFactory[Kleisli[F, A, *]] + LoggerFactory[F].mapK(Kleisli.liftK[F, A]) } diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerSyntaxCompilation.scala b/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerSyntaxCompilation.scala index 27ec4558..cf6d9815 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerSyntaxCompilation.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/extras/syntax/LoggerSyntaxCompilation.scala @@ -54,12 +54,12 @@ object LoggerSyntaxCompilation { l.withModifiedString(identity) def eitherTLogger[F[_]: Functor: Logger, A]: EitherT[F, A, Unit] = - Logger[EitherT[F, A, *]].info("foo") + Logger[F].mapK(EitherT.liftK[F, A]).info("foo") def kleisliLogger[F[_]: Logger, A]: Kleisli[F, A, Unit] = - Logger[Kleisli[F, A, *]].info("bar") + Logger[F].mapK(Kleisli.liftK[F, A]).info("bar") def optionTLogger[F[_]: Functor: Logger]: OptionT[F, Unit] = - Logger[OptionT[F, *]].info("baz") + Logger[F].mapK(OptionT.liftK[F]).info("baz") } From b606c3b8a7ad78addc96faf500d3e22f83fe95be Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:18:34 +0000 Subject: [PATCH 48/62] update js-console ConsoleLogger --- .../log4cats/console/ConsoleLogger.scala | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index 6f79e754..3c754444 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -25,25 +25,30 @@ import org.typelevel.log4cats.extras.LogLevel.* class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) extends SelfAwareStructuredLogger[F] { private val ConsoleF: ConsoleF[F] = implicitly - override def trace(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) - override def trace(message: => String): F[Unit] = ConsoleF.debug(message) - override def isTraceEnabled: F[Boolean] = logLevel.exists(_ <= Trace).pure[F] - - override def debug(t: Throwable)(message: => String): F[Unit] = ConsoleF.debug(message, t) - override def debug(message: => String): F[Unit] = ConsoleF.debug(message) - override def isDebugEnabled: F[Boolean] = logLevel.exists(_ <= Debug).pure[F] - - override def info(t: Throwable)(message: => String): F[Unit] = ConsoleF.info(message, t) - override def info(message: => String): F[Unit] = ConsoleF.info(message) - override def isInfoEnabled: F[Boolean] = logLevel.exists(_ <= Info).pure[F] - - override def warn(t: Throwable)(message: => String): F[Unit] = ConsoleF.warn(message, t) - override def warn(message: => String): F[Unit] = ConsoleF.warn(message) - override def isWarnEnabled: F[Boolean] = logLevel.exists(_ <= Warn).pure[F] - - override def error(t: Throwable)(message: => String): F[Unit] = ConsoleF.error(message, t) - override def error(message: => String): F[Unit] = ConsoleF.error(message) - override def isErrorEnabled: F[Boolean] = logLevel.exists(_ <= Error).pure[F] + + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val logRecord = record(Log.mutableBuilder[String]()) + val log = logRecord.build() + val message = log.message() + val throwable = log.throwable + + level match { + case KernelLogLevel.Trace => ConsoleF.debug(message, throwable.orNull) + case KernelLogLevel.Debug => ConsoleF.debug(message, throwable.orNull) + case KernelLogLevel.Info => ConsoleF.info(message, throwable.orNull) + case KernelLogLevel.Warn => ConsoleF.warn(message, throwable.orNull) + case KernelLogLevel.Error => ConsoleF.error(message, throwable.orNull) + case KernelLogLevel.Fatal => ConsoleF.error(message, throwable.orNull) + } + } + } + + def isTraceEnabled: F[Boolean] = logLevel.exists(_ <= Trace).pure[F] + def isDebugEnabled: F[Boolean] = logLevel.exists(_ <= Debug).pure[F] + def isInfoEnabled: F[Boolean] = logLevel.exists(_ <= Info).pure[F] + def isWarnEnabled: F[Boolean] = logLevel.exists(_ <= Warn).pure[F] + def isErrorEnabled: F[Boolean] = logLevel.exists(_ <= Error).pure[F] /* * ConsoleLogger should probably not extend from StructuredLogger, because there's not From 6962ff85a0ccc15e8a6e336139679e3c230d7ad2 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:19:49 +0000 Subject: [PATCH 49/62] update slf4j Logger Internel --- .../slf4j/internal/Slf4jLoggerInternal.scala | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala index fc0f771c..ab35b90b 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala @@ -49,6 +49,74 @@ private[slf4j] object Slf4jLoggerInternal { ) = this(logger, Sync.Type.Delay)(F) + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + // Check if the log level is enabled before building the Log object + val isEnabled = level match { + case KernelLogLevel.Trace => F.delay(logger.isTraceEnabled) + case KernelLogLevel.Debug => F.delay(logger.isDebugEnabled) + case KernelLogLevel.Info => F.delay(logger.isInfoEnabled) + case KernelLogLevel.Warn => F.delay(logger.isWarnEnabled) + case KernelLogLevel.Error => F.delay(logger.isErrorEnabled) + } + + Sync[F].flatMap(isEnabled) { enabled => + if (enabled) { + // Only build the Log object if the level is enabled + val log = logBuilder(Log.mutableBuilder[String]()).build() + val context = log.context + + // Set MDC context + val setMdc = F.delay { + context.foreach { case (k, v) => MDC.put(k, v) } + } + + val clearMdc = F.delay { + context.keys.foreach(MDC.remove) + } + + val logMessage = level match { + case KernelLogLevel.Trace => + F.delay { + val message = log.message() + val throwable = log.throwable + if (throwable.isDefined) logger.trace(message, throwable.get) else logger.trace(message) + } + case KernelLogLevel.Debug => + F.delay { + val message = log.message() + val throwable = log.throwable + if (throwable.isDefined) logger.debug(message, throwable.get) else logger.debug(message) + } + case KernelLogLevel.Info => + F.delay { + val message = log.message() + val throwable = log.throwable + if (throwable.isDefined) logger.info(message, throwable.get) else logger.info(message) + } + case KernelLogLevel.Warn => + F.delay { + val message = log.message() + val throwable = log.throwable + if (throwable.isDefined) logger.warn(message, throwable.get) else logger.warn(message) + } + case KernelLogLevel.Error => + F.delay { + val message = log.message() + val throwable = log.throwable + if (throwable.isDefined) logger.error(message, throwable.get) else logger.error(message) + } + } + + Sync[F].flatMap(setMdc)(_ => Sync[F].flatMap(logMessage)(_ => clearMdc)) + } else { + // If level is disabled, do nothing + F.unit + } + } + } + } + private val isTraceEnabledUnsafe = () => logger.isTraceEnabled private val isDebugEnabledUnsafe = () => logger.isDebugEnabled private val isInfoEnabledUnsafe = () => logger.isInfoEnabled From 041375c46b7342ef310a6d7fff659ae3f9432135 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:21:34 +0000 Subject: [PATCH 50/62] update testing module with LoggerKernel --- .../testing/StructuredTestingLogger.scala | 65 +++++++++++++------ .../log4cats/testing/TestingLogger.scala | 44 +++++++++---- .../testing/TestingLoggerFactory.scala | 25 ++++++- 3 files changed, 101 insertions(+), 33 deletions(-) diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala index 640a67f5..578a8a77 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala @@ -17,7 +17,8 @@ package org.typelevel.log4cats.testing import cats.data.Chain -import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats.{SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log import cats.effect.{Ref, Sync} import cats.syntax.all.* @@ -145,51 +146,73 @@ object StructuredTestingLogger { def isWarnEnabled: F[Boolean] = Sync[F].pure(warnEnabled) def isErrorEnabled: F[Boolean] = Sync[F].pure(errorEnabled) + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val message = log.message() + val throwable = log.throwable + val context = log.context + + level match { + case KernelLogLevel.Trace => + if (traceEnabled) appendLogMessage(TRACE(message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Debug => + if (debugEnabled) appendLogMessage(DEBUG(message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Info => + if (infoEnabled) appendLogMessage(INFO(message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Warn => + if (warnEnabled) appendLogMessage(WARN(message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Error => + if (errorEnabled) appendLogMessage(ERROR(message, throwable, context)) else Sync[F].unit + } + } + } + private val noop = Sync[F].unit - def error(message: => String): F[Unit] = + override def error(message: => String): F[Unit] = if (errorEnabled) appendLogMessage(ERROR(message, None)) else noop - def error(t: Throwable)(message: => String): F[Unit] = + override def error(t: Throwable)(message: => String): F[Unit] = if (errorEnabled) appendLogMessage(ERROR(message, t.some)) else noop - def error(ctx: Map[String, String])(message: => String): F[Unit] = + override def error(ctx: Map[String, String])(message: => String): F[Unit] = if (errorEnabled) appendLogMessage(ERROR(message, None, ctx)) else noop - def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = if (errorEnabled) appendLogMessage(ERROR(message, t.some, ctx)) else noop - def warn(message: => String): F[Unit] = + override def warn(message: => String): F[Unit] = if (warnEnabled) appendLogMessage(WARN(message, None)) else noop - def warn(t: Throwable)(message: => String): F[Unit] = + override def warn(t: Throwable)(message: => String): F[Unit] = if (warnEnabled) appendLogMessage(WARN(message, t.some)) else noop - def warn(ctx: Map[String, String])(message: => String): F[Unit] = + override def warn(ctx: Map[String, String])(message: => String): F[Unit] = if (warnEnabled) appendLogMessage(WARN(message, None, ctx)) else noop - def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = if (warnEnabled) appendLogMessage(WARN(message, t.some, ctx)) else noop - def info(message: => String): F[Unit] = + override def info(message: => String): F[Unit] = if (infoEnabled) appendLogMessage(INFO(message, None)) else noop - def info(t: Throwable)(message: => String): F[Unit] = + override def info(t: Throwable)(message: => String): F[Unit] = if (infoEnabled) appendLogMessage(INFO(message, t.some)) else noop - def info(ctx: Map[String, String])(message: => String): F[Unit] = + override def info(ctx: Map[String, String])(message: => String): F[Unit] = if (infoEnabled) appendLogMessage(INFO(message, None, ctx)) else noop - def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = if (infoEnabled) appendLogMessage(INFO(message, t.some, ctx)) else noop - def debug(message: => String): F[Unit] = + override def debug(message: => String): F[Unit] = if (debugEnabled) appendLogMessage(DEBUG(message, None)) else noop - def debug(t: Throwable)(message: => String): F[Unit] = + override def debug(t: Throwable)(message: => String): F[Unit] = if (debugEnabled) appendLogMessage(DEBUG(message, t.some)) else noop - def debug(ctx: Map[String, String])(message: => String): F[Unit] = + override def debug(ctx: Map[String, String])(message: => String): F[Unit] = if (debugEnabled) appendLogMessage(DEBUG(message, None, ctx)) else noop - def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = if (debugEnabled) appendLogMessage(DEBUG(message, t.some, ctx)) else noop - def trace(message: => String): F[Unit] = + override def trace(message: => String): F[Unit] = if (traceEnabled) appendLogMessage(TRACE(message, None)) else noop - def trace(t: Throwable)(message: => String): F[Unit] = + override def trace(t: Throwable)(message: => String): F[Unit] = if (traceEnabled) appendLogMessage(TRACE(message, t.some)) else noop - def trace(ctx: Map[String, String])(message: => String): F[Unit] = + override def trace(ctx: Map[String, String])(message: => String): F[Unit] = if (traceEnabled) appendLogMessage(TRACE(message, None, ctx)) else noop - def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = + override def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] = if (traceEnabled) appendLogMessage(TRACE(message, t.some, ctx)) else noop } } diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala index 80bc2700..edd05ede 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala @@ -17,7 +17,8 @@ package org.typelevel.log4cats.testing import cats.data.Chain -import org.typelevel.log4cats.SelfAwareLogger +import org.typelevel.log4cats.{SelfAwareLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log import cats.effect.{Ref, Sync} import cats.syntax.all.* @@ -124,31 +125,52 @@ object TestingLogger { def isWarnEnabled: F[Boolean] = Sync[F].pure(warnEnabled) def isErrorEnabled: F[Boolean] = Sync[F].pure(errorEnabled) + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val message = log.message() + val throwable = log.throwable + + level match { + case KernelLogLevel.Trace => + if (traceEnabled) appendLogMessage(TRACE(message, throwable)) else Sync[F].unit + case KernelLogLevel.Debug => + if (debugEnabled) appendLogMessage(DEBUG(message, throwable)) else Sync[F].unit + case KernelLogLevel.Info => + if (infoEnabled) appendLogMessage(INFO(message, throwable)) else Sync[F].unit + case KernelLogLevel.Warn => + if (warnEnabled) appendLogMessage(WARN(message, throwable)) else Sync[F].unit + case KernelLogLevel.Error => + if (errorEnabled) appendLogMessage(ERROR(message, throwable)) else Sync[F].unit + } + } + } + private val noop = Sync[F].unit - def error(message: => String): F[Unit] = + override def error(message: => String): F[Unit] = if (errorEnabled) appendLogMessage(ERROR(message, None)) else noop - def error(t: Throwable)(message: => String): F[Unit] = + override def error(t: Throwable)(message: => String): F[Unit] = if (errorEnabled) appendLogMessage(ERROR(message, t.some)) else noop - def warn(message: => String): F[Unit] = + override def warn(message: => String): F[Unit] = if (warnEnabled) appendLogMessage(WARN(message, None)) else noop - def warn(t: Throwable)(message: => String): F[Unit] = + override def warn(t: Throwable)(message: => String): F[Unit] = if (warnEnabled) appendLogMessage(WARN(message, t.some)) else noop - def info(message: => String): F[Unit] = + override def info(message: => String): F[Unit] = if (infoEnabled) appendLogMessage(INFO(message, None)) else noop - def info(t: Throwable)(message: => String): F[Unit] = + override def info(t: Throwable)(message: => String): F[Unit] = if (infoEnabled) appendLogMessage(INFO(message, t.some)) else noop - def debug(message: => String): F[Unit] = + override def debug(message: => String): F[Unit] = if (debugEnabled) appendLogMessage(DEBUG(message, None)) else noop - def debug(t: Throwable)(message: => String): F[Unit] = + override def debug(t: Throwable)(message: => String): F[Unit] = if (debugEnabled) appendLogMessage(DEBUG(message, t.some)) else noop - def trace(message: => String): F[Unit] = + override def trace(message: => String): F[Unit] = if (traceEnabled) appendLogMessage(TRACE(message, None)) else noop - def trace(t: Throwable)(message: => String): F[Unit] = + override def trace(t: Throwable)(message: => String): F[Unit] = if (traceEnabled) appendLogMessage(TRACE(message, t.some)) else noop } diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala index c05c95e9..b3969087 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala @@ -22,7 +22,8 @@ import cats.effect.{Ref, Sync} import cats.syntax.all.* import org.typelevel.log4cats.extras.LogLevel import org.typelevel.log4cats.testing.TestingLoggerFactory.LogMessage -import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} +import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.Log import java.io.{PrintWriter, StringWriter} import java.util.concurrent.atomic.AtomicReference @@ -179,6 +180,28 @@ object TestingLoggerFactory { override val isWarnEnabled: F[Boolean] = warnEnabled.pure[F] override val isErrorEnabled: F[Boolean] = errorEnabled.pure[F] + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val message = log.message() + val throwable = log.throwable + val context = log.context + + level match { + case KernelLogLevel.Trace => + if (traceEnabled) save(Trace(name, message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Debug => + if (debugEnabled) save(Debug(name, message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Info => + if (infoEnabled) save(Info(name, message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Warn => + if (warnEnabled) save(Warn(name, message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Error => + if (errorEnabled) save(Error(name, message, throwable, context)) else Sync[F].unit + } + } + } + private val noop = Sync[F].unit override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = From 468114fa02678ca7263663156f76da4df7915640 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 07:29:31 +0000 Subject: [PATCH 51/62] update NoOp Logger --- .../typelevel/log4cats/noop/NoOpLogger.scala | 87 ++++--------------- .../log4cats/noop/NoOpLoggerKernel.scala | 10 +-- .../log4cats/NoOpLoggerKernelTest.scala | 20 +++-- 3 files changed, 33 insertions(+), 84 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala index 95762949..091441e6 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala @@ -17,7 +17,7 @@ package org.typelevel.log4cats.noop import cats.Applicative -import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats.{SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel, Log} object NoOpLogger { def apply[F[_]: Applicative]: SelfAwareStructuredLogger[F] = impl[F] @@ -31,80 +31,29 @@ object NoOpLogger { if (evaluateArgs) strictImpl else lazyImpl private def lazyImpl[F[_]: Applicative] = new SelfAwareStructuredLogger[F] { + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = + Applicative[F].pure(()) + } - val no: F[Boolean] = Applicative[F].pure(false) - val unit: F[Unit] = Applicative[F].pure(()) - - @inline override def isTraceEnabled: F[Boolean] = no - @inline override def isDebugEnabled: F[Boolean] = no - @inline override def isInfoEnabled: F[Boolean] = no - @inline override def isWarnEnabled: F[Boolean] = no - @inline override def isErrorEnabled: F[Boolean] = no - @inline override def trace(t: Throwable)(msg: => String): F[Unit] = unit - @inline override def trace(msg: => String): F[Unit] = unit - @inline override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = unit - @inline override def debug(t: Throwable)(msg: => String): F[Unit] = unit - @inline override def debug(msg: => String): F[Unit] = unit - @inline override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = unit - @inline override def info(t: Throwable)(msg: => String): F[Unit] = unit - @inline override def info(msg: => String): F[Unit] = unit - @inline override def info(ctx: Map[String, String])(msg: => String): F[Unit] = unit - @inline override def warn(t: Throwable)(msg: => String): F[Unit] = unit - @inline override def warn(msg: => String): F[Unit] = unit - @inline override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = unit - @inline override def error(t: Throwable)(msg: => String): F[Unit] = unit - @inline override def error(msg: => String): F[Unit] = unit - @inline override def error(ctx: Map[String, String])(msg: => String): F[Unit] = unit - @inline override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - unit - @inline override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - unit - @inline override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - unit - @inline override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - unit - @inline override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - unit + @inline override def isTraceEnabled: F[Boolean] = Applicative[F].pure(false) + @inline override def isDebugEnabled: F[Boolean] = Applicative[F].pure(false) + @inline override def isInfoEnabled: F[Boolean] = Applicative[F].pure(false) + @inline override def isWarnEnabled: F[Boolean] = Applicative[F].pure(false) + @inline override def isErrorEnabled: F[Boolean] = Applicative[F].pure(false) } private def strictImpl[F[_]: Applicative] = new SelfAwareStructuredLogger[F] { - - val yes: F[Boolean] = Applicative[F].pure(true) - def void(arg: => Any): F[Unit] = Applicative[F].pure { - val _ = arg - () + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = + Applicative[F].pure(()) } - @inline override def isTraceEnabled: F[Boolean] = yes - @inline override def isDebugEnabled: F[Boolean] = yes - @inline override def isInfoEnabled: F[Boolean] = yes - @inline override def isWarnEnabled: F[Boolean] = yes - @inline override def isErrorEnabled: F[Boolean] = yes - @inline override def trace(t: Throwable)(msg: => String): F[Unit] = void(t) - @inline override def trace(msg: => String): F[Unit] = void(msg) - @inline override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = void(msg) - @inline override def debug(t: Throwable)(msg: => String): F[Unit] = void(msg) - @inline override def debug(msg: => String): F[Unit] = void(msg) - @inline override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = void(msg) - @inline override def info(t: Throwable)(msg: => String): F[Unit] = void(msg) - @inline override def info(msg: => String): F[Unit] = void(msg) - @inline override def info(ctx: Map[String, String])(msg: => String): F[Unit] = void(msg) - @inline override def warn(t: Throwable)(msg: => String): F[Unit] = void(msg) - @inline override def warn(msg: => String): F[Unit] = void(msg) - @inline override def warn(ctx: Map[String, String])(msg: => String): F[Unit] = void(msg) - @inline override def error(t: Throwable)(msg: => String): F[Unit] = void(msg) - @inline override def error(msg: => String): F[Unit] = void(msg) - @inline override def error(ctx: Map[String, String])(msg: => String): F[Unit] = void(msg) - @inline override def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - void(msg) - @inline override def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - void(msg) - @inline override def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - void(msg) - @inline override def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - void(msg) - @inline override def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = - void(msg) + @inline override def isTraceEnabled: F[Boolean] = Applicative[F].pure(true) + @inline override def isDebugEnabled: F[Boolean] = Applicative[F].pure(true) + @inline override def isInfoEnabled: F[Boolean] = Applicative[F].pure(true) + @inline override def isWarnEnabled: F[Boolean] = Applicative[F].pure(true) + @inline override def isErrorEnabled: F[Boolean] = Applicative[F].pure(true) } } diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala index 01be751a..b56ea115 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLoggerKernel.scala @@ -19,11 +19,9 @@ package org.typelevel.log4cats.noop import cats.Applicative import org.typelevel.log4cats.{KernelLogLevel, Log, LoggerKernel} -class NoOpLoggerKernel[F[_]: Applicative, Ctx] extends LoggerKernel[F, Ctx] { - def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = - Applicative[F].unit -} - object NoOpLoggerKernel { - def apply[F[_]: Applicative, Ctx]: NoOpLoggerKernel[F, Ctx] = new NoOpLoggerKernel[F, Ctx] + def apply[F[_]: Applicative, Ctx]: LoggerKernel[F, Ctx] = new LoggerKernel[F, Ctx] { + override def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): F[Unit] = + Applicative[F].unit + } } diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala index 6496c43d..00fa0474 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala @@ -21,21 +21,23 @@ import munit.CatsEffectSuite import org.typelevel.log4cats._ class NoOpLoggerKernelTest extends CatsEffectSuite { + private def boom()(implicit loc: munit.Location): String = fail("This code should not have executed") + test("NoOpLoggerKernel should do nothing and not fail") { val kernel = NoOpLoggerKernel[IO, String] val logger = SamLogger.wrap(kernel) // All of these should be no-ops and not throw - logger.info("This should not appear").void *> - logger.error("This should not appear").void *> - logger.warn("This should not appear").void *> - logger.debug("This should not appear").void *> - logger.trace("This should not appear").void + logger.info("This should not appear") *> + logger.error(boom()) *> + logger.warn(boom()) *> + logger.debug(boom()) *> + logger.trace(boom()) } - test("NoOpLoggerKernel should work with the adapter") { + test("NoOpLoggerKernel should work with SamLogger") { val kernel = NoOpLoggerKernel[IO, String] - val logger = SamLoggerAdapter.toLogger(kernel) - logger.info("Adapter test").void *> - logger.error("Adapter test").void + val logger = SamLogger.wrap(kernel) + logger.info("SamLogger test") *> + logger.error("SamLogger test") } } From f6bf9a340903750498c59e78e22bb92ec7a3c750 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 08:03:37 +0000 Subject: [PATCH 52/62] fix match level error --- .../scala/org/typelevel/log4cats/Logger.scala | 40 ++++++++-------- .../typelevel/log4cats/LoggerFactory.scala | 1 - .../org/typelevel/log4cats/LoggerKernel.scala | 4 +- .../org/typelevel/log4cats/SamLogger.scala | 7 ++- .../log4cats/SamStructuredLogger.scala | 1 - .../typelevel/log4cats/SelfAwareLogger.scala | 4 +- .../log4cats/SelfAwareStructuredLogger.scala | 8 ++-- .../typelevel/log4cats/StructuredLogger.scala | 42 ++++++++--------- .../log4cats/extras/DeferredLogger.scala | 14 ++++-- .../DeferredSelfAwareStructuredLogger.scala | 46 ++++++++++--------- .../extras/DeferredStructuredLogger.scala | 16 ++++--- .../log4cats/extras/WriterTLogger.scala | 31 ++++++++----- .../extras/WriterTStructuredLogger.scala | 38 ++++++++++----- .../typelevel/log4cats/noop/NoOpLogger.scala | 6 +-- .../log4cats/NoOpLoggerKernelTest.scala | 6 ++- .../typelevel/log4cats/SamLoggerTest.scala | 21 +++++---- .../log4cats/SamStructuredLoggerTest.scala | 19 +++++--- .../log4cats/console/ConsoleLogger.scala | 6 +-- .../slf4j/internal/Slf4jLoggerInternal.scala | 40 +++++++++------- .../testing/StructuredTestingLogger.scala | 28 ++++++----- .../log4cats/testing/TestingLogger.scala | 19 ++++---- .../testing/TestingLoggerFactory.scala | 24 ++++++---- 22 files changed, 243 insertions(+), 178 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala index 7f10b80a..fbe8ded9 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/Logger.scala @@ -20,40 +20,40 @@ import cats.* trait Logger[F[_]] extends MessageLogger[F] with ErrorLogger[F] { protected def kernel: LoggerKernel[F, String] - + /** Access to the underlying kernel for advanced use cases */ def underlying: LoggerKernel[F, String] = kernel // MessageLogger methods - def error(message: => String): F[Unit] = + def error(message: => String): F[Unit] = kernel.log(KernelLogLevel.Error, _.withMessage(message)) - - def warn(message: => String): F[Unit] = + + def warn(message: => String): F[Unit] = kernel.log(KernelLogLevel.Warn, _.withMessage(message)) - - def info(message: => String): F[Unit] = + + def info(message: => String): F[Unit] = kernel.log(KernelLogLevel.Info, _.withMessage(message)) - - def debug(message: => String): F[Unit] = + + def debug(message: => String): F[Unit] = kernel.log(KernelLogLevel.Debug, _.withMessage(message)) - - def trace(message: => String): F[Unit] = + + def trace(message: => String): F[Unit] = kernel.log(KernelLogLevel.Trace, _.withMessage(message)) // ErrorLogger methods - def error(t: Throwable)(message: => String): F[Unit] = + def error(t: Throwable)(message: => String): F[Unit] = kernel.log(KernelLogLevel.Error, _.withMessage(message).withThrowable(t)) - - def warn(t: Throwable)(message: => String): F[Unit] = + + def warn(t: Throwable)(message: => String): F[Unit] = kernel.log(KernelLogLevel.Warn, _.withMessage(message).withThrowable(t)) - - def info(t: Throwable)(message: => String): F[Unit] = + + def info(t: Throwable)(message: => String): F[Unit] = kernel.log(KernelLogLevel.Info, _.withMessage(message).withThrowable(t)) - - def debug(t: Throwable)(message: => String): F[Unit] = + + def debug(t: Throwable)(message: => String): F[Unit] = kernel.log(KernelLogLevel.Debug, _.withMessage(message).withThrowable(t)) - - def trace(t: Throwable)(message: => String): F[Unit] = + + def trace(t: Throwable)(message: => String): F[Unit] = kernel.log(KernelLogLevel.Trace, _.withMessage(message).withThrowable(t)) def withModifiedString(f: String => String): Logger[F] = Logger.withModifiedString[F](this, f) @@ -63,8 +63,6 @@ trait Logger[F[_]] extends MessageLogger[F] with ErrorLogger[F] { object Logger { def apply[F[_]](implicit ev: Logger[F]) = ev - - private def withModifiedString[F[_]](l: Logger[F], f: String => String): Logger[F] = new Logger[F] { protected def kernel: LoggerKernel[F, String] = l.underlying diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala index 881e7a50..afe8390a 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactory.scala @@ -46,7 +46,6 @@ trait LoggerFactory[F[_]] extends LoggerFactoryGen[F] { object LoggerFactory extends LoggerFactoryGenCompanion { def apply[F[_]: LoggerFactory]: LoggerFactory[F] = implicitly - private def mapK[F[_]: Functor, G[_]](fk: F ~> G)(lf: LoggerFactory[F]): LoggerFactory[G] = new LoggerFactory[G] { diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala index b8ce46c0..286758ed 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerKernel.scala @@ -50,8 +50,8 @@ trait LoggerKernel[F[_], Ctx] { log(KernelLogLevel.Error, record) /** - * Transform the effect type using a natural transformation. - * This allows converting between different effect types (e.g., IO to Task, Task to IO). + * Transform the effect type using a natural transformation. This allows converting between + * different effect types (e.g., IO to Task, Task to IO). */ def mapK[G[_]](f: F ~> G): LoggerKernel[G, Ctx] = new LoggerKernel[G, Ctx] { def log(level: KernelLogLevel, record: Log.Builder[Ctx] => Log.Builder[Ctx]): G[Unit] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index b33eeae6..6f84c085 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -19,12 +19,12 @@ package org.typelevel.log4cats import cats.* /** - * A SAM-based Logger that provides a user-friendly interface. This is the - * new design that will eventually replace the current Logger trait. + * A SAM-based Logger that provides a user-friendly interface. This is the new design that will + * eventually replace the current Logger trait. */ trait SamLogger[F[_], Ctx] { protected def kernel: LoggerKernel[F, Ctx] - + /** Access to the underlying kernel for advanced use cases */ def underlying: LoggerKernel[F, Ctx] = kernel @@ -96,7 +96,6 @@ object SamLogger { protected def kernel: LoggerKernel[F, Ctx] = k } - private def withModifiedString[F[_], Ctx]( l: SamLogger[F, Ctx], f: String => String diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala index b0531b06..804e2071 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamStructuredLogger.scala @@ -122,7 +122,6 @@ object SamStructuredLogger { (level, record) => sl.kernel.log(level, record(_).adaptContext(modifyCtx)) } - private def withModifiedString[F[_]]( l: SamStructuredLogger[F], f: String => String diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala index 8e8ce740..5462b666 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareLogger.scala @@ -35,7 +35,7 @@ object SelfAwareLogger { private def mapK[G[_], F[_]](f: G ~> F)(logger: SelfAwareLogger[G]): SelfAwareLogger[F] = new SelfAwareLogger[F] { protected def kernel: LoggerKernel[F, String] = logger.underlying.mapK(f) - + def isTraceEnabled: F[Boolean] = f(logger.isTraceEnabled) def isDebugEnabled: F[Boolean] = f(logger.isDebugEnabled) def isInfoEnabled: F[Boolean] = f(logger.isInfoEnabled) @@ -49,7 +49,7 @@ object SelfAwareLogger { ): SelfAwareLogger[F] = new SelfAwareLogger[F] { protected def kernel: LoggerKernel[F, String] = l.underlying - + override def isTraceEnabled: F[Boolean] = l.isTraceEnabled override def isDebugEnabled: F[Boolean] = l.isDebugEnabled override def isInfoEnabled: F[Boolean] = l.isInfoEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala index 89193351..f4e4b4d3 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala @@ -50,13 +50,13 @@ object SelfAwareStructuredLogger { ) extends SelfAwareStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = sl.underlying private lazy val defaultCtx: Map[String, String] = modify(Map.empty) - + override def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) override def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) override def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) override def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) override def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) - + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = sl.trace(modify(ctx))(msg) override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = @@ -103,7 +103,7 @@ object SelfAwareStructuredLogger { ): SelfAwareStructuredLogger[F] = new SelfAwareStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = l.underlying - + def isTraceEnabled: F[Boolean] = l.isTraceEnabled def isDebugEnabled: F[Boolean] = l.isDebugEnabled def isInfoEnabled: F[Boolean] = l.isInfoEnabled @@ -142,7 +142,7 @@ object SelfAwareStructuredLogger { )(logger: SelfAwareStructuredLogger[G]): SelfAwareStructuredLogger[F] = new SelfAwareStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = logger.underlying.mapK(f) - + def isTraceEnabled: F[Boolean] = f(logger.isTraceEnabled) def isDebugEnabled: F[Boolean] = f(logger.isDebugEnabled) def isInfoEnabled: F[Boolean] = f(logger.isInfoEnabled) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala index 7f16333b..8e233a6f 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala @@ -21,34 +21,34 @@ import cats.Show.Shown trait StructuredLogger[F[_]] extends Logger[F] { // Structured logging methods - def trace(ctx: Map[String, String])(msg: => String): F[Unit] = + def trace(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Trace, _.withMessage(msg).withContextMap(ctx)) - - def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + + def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Trace, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) - - def debug(ctx: Map[String, String])(msg: => String): F[Unit] = + + def debug(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Debug, _.withMessage(msg).withContextMap(ctx)) - - def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + + def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Debug, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) - - def info(ctx: Map[String, String])(msg: => String): F[Unit] = + + def info(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Info, _.withMessage(msg).withContextMap(ctx)) - - def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + + def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Info, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) - - def warn(ctx: Map[String, String])(msg: => String): F[Unit] = + + def warn(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Warn, _.withMessage(msg).withContextMap(ctx)) - - def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + + def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Warn, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) - - def error(ctx: Map[String, String])(msg: => String): F[Unit] = + + def error(ctx: Map[String, String])(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Error, _.withMessage(msg).withContextMap(ctx)) - - def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = + + def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = kernel.log(KernelLogLevel.Error, _.withMessage(msg).withContextMap(ctx).withThrowable(t)) override def mapK[G[_]](fk: F ~> G): StructuredLogger[G] = @@ -84,13 +84,13 @@ object StructuredLogger { ) extends StructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = sl.underlying private lazy val defaultCtx: Map[String, String] = modify(Map.empty) - + override def error(message: => String): F[Unit] = sl.error(defaultCtx)(message) override def warn(message: => String): F[Unit] = sl.warn(defaultCtx)(message) override def info(message: => String): F[Unit] = sl.info(defaultCtx)(message) override def debug(message: => String): F[Unit] = sl.debug(defaultCtx)(message) override def trace(message: => String): F[Unit] = sl.trace(defaultCtx)(message) - + override def trace(ctx: Map[String, String])(msg: => String): F[Unit] = sl.trace(modify(ctx))(msg) override def debug(ctx: Map[String, String])(msg: => String): F[Unit] = diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala index cd795ad2..c00484b8 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala @@ -21,7 +21,7 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.~> -import org.typelevel.log4cats.{Logger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{KernelLogLevel, Logger, LoggerKernel} import org.typelevel.log4cats.Log /** @@ -51,7 +51,7 @@ import org.typelevel.log4cats.Log */ trait DeferredLogger[F[_]] extends Logger[F] with DeferredLogging[F] { protected def kernel: LoggerKernel[F, String] - + override def withModifiedString(f: String => String): DeferredLogger[F] = DeferredLogger.withModifiedString(this, f) override def mapK[G[_]](fk: F ~> G): DeferredLogger[G] = DeferredLogger.mapK(this, fk) @@ -70,7 +70,10 @@ object DeferredLogger { private def save(lm: DeferredLogMessage): F[Unit] = ref.update(_.append(lm)) protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val log = logBuilder(Log.mutableBuilder[String]()).build() val logLevel = level match { case KernelLogLevel.Trace => LogLevel.Trace @@ -78,6 +81,7 @@ object DeferredLogger { case KernelLogLevel.Info => LogLevel.Info case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error + case KernelLogLevel.Fatal => LogLevel.Error } val deferredMsg = DeferredLogMessage( logLevel, @@ -123,7 +127,7 @@ object DeferredLogger { ): DeferredLogger[G] = new DeferredLogger[G] { protected def kernel: LoggerKernel[G, String] = logger.kernel.mapK(fk) - + override def inspect: G[Chain[DeferredLogMessage]] = fk(logger.inspect) override def log: G[Unit] = fk(logger.log) @@ -150,7 +154,7 @@ object DeferredLogger { ): DeferredLogger[F] = new DeferredLogger[F] { protected def kernel: LoggerKernel[F, String] = logger.kernel - + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala index 7e592b7b..c3f9fadd 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala @@ -21,7 +21,7 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.{~>, Show} -import org.typelevel.log4cats.{SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{KernelLogLevel, LoggerKernel, SelfAwareStructuredLogger} import org.typelevel.log4cats.Log /** @@ -35,7 +35,7 @@ trait DeferredSelfAwareStructuredLogger[F[_]] extends SelfAwareStructuredLogger[F] with DeferredLogging[F] { protected def kernel: LoggerKernel[F, String] - + override def mapK[G[_]](fk: F ~> G): DeferredSelfAwareStructuredLogger[G] = DeferredSelfAwareStructuredLogger.mapK(this, fk) @@ -65,23 +65,27 @@ object DeferredSelfAwareStructuredLogger { stash.update(_.append(lm -> logger)) protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { - val log = logBuilder(Log.mutableBuilder[String]()).build() - val logLevel = level match { - case KernelLogLevel.Trace => LogLevel.Trace - case KernelLogLevel.Debug => LogLevel.Debug - case KernelLogLevel.Info => LogLevel.Info - case KernelLogLevel.Warn => LogLevel.Warn - case KernelLogLevel.Error => LogLevel.Error - } - val deferredMsg = DeferredLogMessage( - logLevel, - log.context, - log.throwable, - log.message - ) - save(deferredMsg) - } + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + case KernelLogLevel.Fatal => LogLevel.Error + } + val deferredMsg = DeferredLogMessage( + logLevel, + log.context, + log.throwable, + log.message + ) + save(deferredMsg) + } } override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled @@ -180,7 +184,7 @@ object DeferredSelfAwareStructuredLogger { ): DeferredSelfAwareStructuredLogger[G] = new DeferredSelfAwareStructuredLogger[G] { protected def kernel: LoggerKernel[G, String] = logger.kernel.mapK(fk) - + override def inspect: G[Chain[DeferredLogMessage]] = fk( logger.inspect ) @@ -298,7 +302,7 @@ object DeferredSelfAwareStructuredLogger { ): DeferredSelfAwareStructuredLogger[F] = new DeferredSelfAwareStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = logger.kernel - + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log override def isTraceEnabled: F[Boolean] = logger.isTraceEnabled diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala index 00ae8e82..95f7ad8b 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala @@ -22,7 +22,7 @@ import cats.effect.kernel.Resource.ExitCase import cats.effect.kernel.{Concurrent, Ref, Resource} import cats.syntax.all.* import cats.~> -import org.typelevel.log4cats.{StructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{KernelLogLevel, LoggerKernel, StructuredLogger} import org.typelevel.log4cats.Log /** @@ -52,7 +52,7 @@ import org.typelevel.log4cats.Log */ trait DeferredStructuredLogger[F[_]] extends StructuredLogger[F] with DeferredLogging[F] { protected def kernel: LoggerKernel[F, String] - + override def mapK[G[_]](fk: F ~> G): DeferredStructuredLogger[G] = DeferredStructuredLogger.mapK(this, fk) @@ -83,7 +83,10 @@ object DeferredStructuredLogger { private def save(lm: DeferredLogMessage): F[Unit] = ref.update(_.append(lm)) protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val log = logBuilder(Log.mutableBuilder[String]()).build() val logLevel = level match { case KernelLogLevel.Trace => LogLevel.Trace @@ -91,6 +94,7 @@ object DeferredStructuredLogger { case KernelLogLevel.Info => LogLevel.Info case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error + case KernelLogLevel.Fatal => LogLevel.Error } val deferredMsg = DeferredLogMessage( logLevel, @@ -159,7 +163,7 @@ object DeferredStructuredLogger { ): DeferredStructuredLogger[G] = new DeferredStructuredLogger[G] { protected def kernel: LoggerKernel[G, String] = logger.kernel.mapK(fk) - + override def inspect: G[Chain[DeferredLogMessage]] = fk(logger.inspect) override def log: G[Unit] = fk(logger.log) @@ -204,7 +208,7 @@ object DeferredStructuredLogger { ): DeferredStructuredLogger[F] = new DeferredStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = logger.kernel - + private def addCtx(ctx: Map[String, String]): Map[String, String] = baseCtx ++ ctx override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect @@ -256,7 +260,7 @@ object DeferredStructuredLogger { ): DeferredStructuredLogger[F] = new DeferredStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = logger.kernel - + override def inspect: F[Chain[DeferredLogMessage]] = logger.inspect override def log: F[Unit] = logger.log diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 699d4a8c..8f5d5e16 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -40,20 +40,27 @@ object WriterTLogger { errorEnabled: Boolean = true ): SelfAwareLogger[WriterT[F, G[LogMessage], *]] = new SelfAwareLogger[WriterT[F, G[LogMessage], *]] { - protected def kernel: LoggerKernel[WriterT[F, G[LogMessage], *], String] = new LoggerKernel[WriterT[F, G[LogMessage], *], String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): WriterT[F, G[LogMessage], Unit] = { - val log = logBuilder(Log.mutableBuilder[String]()).build() - val logLevel = level match { - case KernelLogLevel.Trace => LogLevel.Trace - case KernelLogLevel.Debug => LogLevel.Debug - case KernelLogLevel.Info => LogLevel.Info - case KernelLogLevel.Warn => LogLevel.Warn - case KernelLogLevel.Error => LogLevel.Error + protected def kernel: LoggerKernel[WriterT[F, G[LogMessage], *], String] = + new LoggerKernel[WriterT[F, G[LogMessage], *], String] { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): WriterT[F, G[LogMessage], Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + case KernelLogLevel.Fatal => LogLevel.Error + } + WriterT.tell[F, G[LogMessage]]( + Applicative[G].pure(LogMessage(logLevel, log.throwable, log.message())) + ) } - WriterT.tell[F, G[LogMessage]](Applicative[G].pure(LogMessage(logLevel, log.throwable, log.message()))) } - } - + override def isTraceEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(traceEnabled) override def isDebugEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(debugEnabled) override def isInfoEnabled: WriterT[F, G[LogMessage], Boolean] = isEnabled(infoEnabled) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala index 52777792..40f17285 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala @@ -20,7 +20,12 @@ import cats.data.WriterT import cats.kernel.Monoid import cats.syntax.all.* import cats.{~>, Alternative, Applicative, Foldable, Monad} -import org.typelevel.log4cats.{SelfAwareStructuredLogger, StructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{ + KernelLogLevel, + LoggerKernel, + SelfAwareStructuredLogger, + StructuredLogger +} import org.typelevel.log4cats.Log /** @@ -44,19 +49,28 @@ object WriterTStructuredLogger { new SelfAwareStructuredLogger[WriterT[F, G[StructuredLogMessage], *]] { type LoggerF[A] = WriterT[F, G[StructuredLogMessage], A] - protected def kernel: LoggerKernel[WriterT[F, G[StructuredLogMessage], *], String] = new LoggerKernel[WriterT[F, G[StructuredLogMessage], *], String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): WriterT[F, G[StructuredLogMessage], Unit] = { - val log = logBuilder(Log.mutableBuilder[String]()).build() - val logLevel = level match { - case KernelLogLevel.Trace => LogLevel.Trace - case KernelLogLevel.Debug => LogLevel.Debug - case KernelLogLevel.Info => LogLevel.Info - case KernelLogLevel.Warn => LogLevel.Warn - case KernelLogLevel.Error => LogLevel.Error + protected def kernel: LoggerKernel[WriterT[F, G[StructuredLogMessage], *], String] = + new LoggerKernel[WriterT[F, G[StructuredLogMessage], *], String] { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): WriterT[F, G[StructuredLogMessage], Unit] = { + val log = logBuilder(Log.mutableBuilder[String]()).build() + val logLevel = level match { + case KernelLogLevel.Trace => LogLevel.Trace + case KernelLogLevel.Debug => LogLevel.Debug + case KernelLogLevel.Info => LogLevel.Info + case KernelLogLevel.Warn => LogLevel.Warn + case KernelLogLevel.Error => LogLevel.Error + case KernelLogLevel.Fatal => LogLevel.Error + } + WriterT.tell[F, G[StructuredLogMessage]]( + Applicative[G].pure( + StructuredLogMessage(logLevel, log.context, log.throwable, log.message()) + ) + ) } - WriterT.tell[F, G[StructuredLogMessage]](Applicative[G].pure(StructuredLogMessage(logLevel, log.context, log.throwable, log.message()))) } - } override def isTraceEnabled: LoggerF[Boolean] = isEnabled(traceEnabled) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala index 091441e6..7813f994 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogger.scala @@ -17,7 +17,7 @@ package org.typelevel.log4cats.noop import cats.Applicative -import org.typelevel.log4cats.{SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel, Log} +import org.typelevel.log4cats.{KernelLogLevel, Log, LoggerKernel, SelfAwareStructuredLogger} object NoOpLogger { def apply[F[_]: Applicative]: SelfAwareStructuredLogger[F] = impl[F] @@ -32,7 +32,7 @@ object NoOpLogger { private def lazyImpl[F[_]: Applicative] = new SelfAwareStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = Applicative[F].pure(()) } @@ -45,7 +45,7 @@ object NoOpLogger { private def strictImpl[F[_]: Applicative] = new SelfAwareStructuredLogger[F] { protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = + def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = Applicative[F].pure(()) } diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala index 00fa0474..e0327e1c 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/NoOpLoggerKernelTest.scala @@ -21,8 +21,10 @@ import munit.CatsEffectSuite import org.typelevel.log4cats._ class NoOpLoggerKernelTest extends CatsEffectSuite { - private def boom()(implicit loc: munit.Location): String = fail("This code should not have executed") - + private def boom()(implicit loc: munit.Location): String = fail( + "This code should not have executed" + ) + test("NoOpLoggerKernel should do nothing and not fail") { val kernel = NoOpLoggerKernel[IO, String] val logger = SamLogger.wrap(kernel) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala index fad1885a..5bdff2bc 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala @@ -23,9 +23,14 @@ import munit.CatsEffectSuite class SamLoggerTest extends CatsEffectSuite { // Test kernel that captures log calls for verification - def testKernel[F[_]: cats.effect.Sync](ref: Ref[F, List[Log[String]]]): LoggerKernel[F, String] = { + def testKernel[F[_]: cats.effect.Sync]( + ref: Ref[F, List[Log[String]]] + ): LoggerKernel[F, String] = { new LoggerKernel[F, String] { - def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + record: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val logRecord = record(Log.mutableBuilder[String]()).build() ref.update(_ :+ logRecord) } @@ -37,7 +42,7 @@ class SamLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamLogger.wrap(kernel) - + _ <- logger.info("Hello, SAM Logger!") logs <- ref.get } yield { @@ -51,7 +56,7 @@ class SamLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamLogger.wrap(kernel) - + _ <- logger.info("User action") logs <- ref.get } yield { @@ -66,7 +71,7 @@ class SamLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamLogger.wrap(kernel) - + _ <- logger.error("Something went wrong") logs <- ref.get } yield { @@ -81,7 +86,7 @@ class SamLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamLogger.wrap(kernel) - + _ <- logger.info("First log entry") _ <- logger.info("Second log entry") logs <- ref.get @@ -98,7 +103,7 @@ class SamLoggerTest extends CatsEffectSuite { kernel = testKernel[IO](ref) logger = SamLogger.wrap(kernel) modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") - + _ <- modifiedLogger.info("Test message") logs <- ref.get } yield { @@ -113,7 +118,7 @@ class SamLoggerTest extends CatsEffectSuite { kernel = testKernel[IO](ref) logger = SamLogger.wrap(kernel) modifiedLogger = logger.withModifiedString(msg => s"[MODIFIED] $msg") - + _ <- modifiedLogger.info("Test message") logs <- ref.get } yield { diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala index d61095c2..957090e0 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -23,9 +23,14 @@ import munit.CatsEffectSuite class SamStructuredLoggerTest extends CatsEffectSuite { // Test kernel that captures log calls for verification - def testKernel[F[_]: cats.effect.Sync](ref: Ref[F, List[Log[String]]]): LoggerKernel[F, String] = { + def testKernel[F[_]: cats.effect.Sync]( + ref: Ref[F, List[Log[String]]] + ): LoggerKernel[F, String] = { new LoggerKernel[F, String] { - def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + record: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val logRecord = record(Log.mutableBuilder[String]()).build() ref.update(_ :+ logRecord) } @@ -37,7 +42,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamStructuredLogger.fromKernel(kernel) - + _ <- logger.info(Map("base" -> "value", "extra" -> "data"))("Test message") logs <- ref.get } yield { @@ -55,7 +60,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamStructuredLogger.fromKernel(kernel) - + _ <- logger.trace(Map("level" -> "trace"))("Trace message") _ <- logger.debug(Map("level" -> "debug"))("Debug message") _ <- logger.info(Map("level" -> "info"))("Info message") @@ -77,7 +82,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { ref <- Ref.of[IO, List[Log[String]]](List.empty) kernel = testKernel[IO](ref) logger = SamStructuredLogger.fromKernel(kernel) - + val exception = new RuntimeException("Test exception") _ <- logger.error(Map("error_type" -> "runtime"), exception)("Error with context") logs <- ref.get @@ -96,7 +101,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { kernel = testKernel[IO](ref) logger = SamStructuredLogger.fromKernel(kernel) enrichedLogger = logger.addContext(Map("service" -> "test-service")) - + _ <- enrichedLogger.info(Map("request_id" -> "123"))("Request processed") logs <- ref.get } yield { @@ -114,7 +119,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { kernel = testKernel[IO](ref) logger = SamStructuredLogger.fromKernel(kernel) enrichedLogger = logger.addContext("service" -> "test-service", "version" -> "1.0.0") - + _ <- enrichedLogger.info("Service started") logs <- ref.get } yield { diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index 3c754444..f96b2d5f 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -25,14 +25,14 @@ import org.typelevel.log4cats.extras.LogLevel.* class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) extends SelfAwareStructuredLogger[F] { private val ConsoleF: ConsoleF[F] = implicitly - + protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { def log(level: KernelLogLevel, record: Log.Builder[String] => Log.Builder[String]): F[Unit] = { val logRecord = record(Log.mutableBuilder[String]()) val log = logRecord.build() val message = log.message() val throwable = log.throwable - + level match { case KernelLogLevel.Trace => ConsoleF.debug(message, throwable.orNull) case KernelLogLevel.Debug => ConsoleF.debug(message, throwable.orNull) @@ -43,7 +43,7 @@ class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) } } } - + def isTraceEnabled: F[Boolean] = logLevel.exists(_ <= Trace).pure[F] def isDebugEnabled: F[Boolean] = logLevel.exists(_ <= Debug).pure[F] def isInfoEnabled: F[Boolean] = logLevel.exists(_ <= Info).pure[F] diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala index ab35b90b..afd5b0e9 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala @@ -50,7 +50,10 @@ private[slf4j] object Slf4jLoggerInternal { this(logger, Sync.Type.Delay)(F) protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { // Check if the log level is enabled before building the Log object val isEnabled = level match { case KernelLogLevel.Trace => F.delay(logger.isTraceEnabled) @@ -59,55 +62,60 @@ private[slf4j] object Slf4jLoggerInternal { case KernelLogLevel.Warn => F.delay(logger.isWarnEnabled) case KernelLogLevel.Error => F.delay(logger.isErrorEnabled) } - + Sync[F].flatMap(isEnabled) { enabled => if (enabled) { // Only build the Log object if the level is enabled val log = logBuilder(Log.mutableBuilder[String]()).build() val context = log.context - + // Set MDC context val setMdc = F.delay { context.foreach { case (k, v) => MDC.put(k, v) } } - + val clearMdc = F.delay { context.keys.foreach(MDC.remove) } - + val logMessage = level match { - case KernelLogLevel.Trace => + case KernelLogLevel.Trace => F.delay { val message = log.message() val throwable = log.throwable - if (throwable.isDefined) logger.trace(message, throwable.get) else logger.trace(message) + if (throwable.isDefined) logger.trace(message, throwable.get) + else logger.trace(message) } - case KernelLogLevel.Debug => + case KernelLogLevel.Debug => F.delay { val message = log.message() val throwable = log.throwable - if (throwable.isDefined) logger.debug(message, throwable.get) else logger.debug(message) + if (throwable.isDefined) logger.debug(message, throwable.get) + else logger.debug(message) } - case KernelLogLevel.Info => + case KernelLogLevel.Info => F.delay { val message = log.message() val throwable = log.throwable - if (throwable.isDefined) logger.info(message, throwable.get) else logger.info(message) + if (throwable.isDefined) logger.info(message, throwable.get) + else logger.info(message) } - case KernelLogLevel.Warn => + case KernelLogLevel.Warn => F.delay { val message = log.message() val throwable = log.throwable - if (throwable.isDefined) logger.warn(message, throwable.get) else logger.warn(message) + if (throwable.isDefined) logger.warn(message, throwable.get) + else logger.warn(message) } - case KernelLogLevel.Error => + case KernelLogLevel.Error => F.delay { val message = log.message() val throwable = log.throwable - if (throwable.isDefined) logger.error(message, throwable.get) else logger.error(message) + if (throwable.isDefined) logger.error(message, throwable.get) + else logger.error(message) } } - + Sync[F].flatMap(setMdc)(_ => Sync[F].flatMap(logMessage)(_ => clearMdc)) } else { // If level is disabled, do nothing diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala index 578a8a77..c75d94b5 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala @@ -17,7 +17,7 @@ package org.typelevel.log4cats.testing import cats.data.Chain -import org.typelevel.log4cats.{SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{KernelLogLevel, LoggerKernel, SelfAwareStructuredLogger} import org.typelevel.log4cats.Log import cats.effect.{Ref, Sync} import cats.syntax.all.* @@ -147,23 +147,29 @@ object StructuredTestingLogger { def isErrorEnabled: F[Boolean] = Sync[F].pure(errorEnabled) protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val log = logBuilder(Log.mutableBuilder[String]()).build() val message = log.message() val throwable = log.throwable val context = log.context - + level match { - case KernelLogLevel.Trace => - if (traceEnabled) appendLogMessage(TRACE(message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Debug => - if (debugEnabled) appendLogMessage(DEBUG(message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Info => + case KernelLogLevel.Trace => + if (traceEnabled) appendLogMessage(TRACE(message, throwable, context)) + else Sync[F].unit + case KernelLogLevel.Debug => + if (debugEnabled) appendLogMessage(DEBUG(message, throwable, context)) + else Sync[F].unit + case KernelLogLevel.Info => if (infoEnabled) appendLogMessage(INFO(message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Warn => + case KernelLogLevel.Warn => if (warnEnabled) appendLogMessage(WARN(message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Error => - if (errorEnabled) appendLogMessage(ERROR(message, throwable, context)) else Sync[F].unit + case KernelLogLevel.Error => + if (errorEnabled) appendLogMessage(ERROR(message, throwable, context)) + else Sync[F].unit } } } diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala index edd05ede..8daa54ac 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala @@ -17,7 +17,7 @@ package org.typelevel.log4cats.testing import cats.data.Chain -import org.typelevel.log4cats.{SelfAwareLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{KernelLogLevel, LoggerKernel, SelfAwareLogger} import org.typelevel.log4cats.Log import cats.effect.{Ref, Sync} import cats.syntax.all.* @@ -126,21 +126,24 @@ object TestingLogger { def isErrorEnabled: F[Boolean] = Sync[F].pure(errorEnabled) protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val log = logBuilder(Log.mutableBuilder[String]()).build() val message = log.message() val throwable = log.throwable - + level match { - case KernelLogLevel.Trace => + case KernelLogLevel.Trace => if (traceEnabled) appendLogMessage(TRACE(message, throwable)) else Sync[F].unit - case KernelLogLevel.Debug => + case KernelLogLevel.Debug => if (debugEnabled) appendLogMessage(DEBUG(message, throwable)) else Sync[F].unit - case KernelLogLevel.Info => + case KernelLogLevel.Info => if (infoEnabled) appendLogMessage(INFO(message, throwable)) else Sync[F].unit - case KernelLogLevel.Warn => + case KernelLogLevel.Warn => if (warnEnabled) appendLogMessage(WARN(message, throwable)) else Sync[F].unit - case KernelLogLevel.Error => + case KernelLogLevel.Error => if (errorEnabled) appendLogMessage(ERROR(message, throwable)) else Sync[F].unit } } diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala index b3969087..9b9c33f7 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala @@ -22,7 +22,12 @@ import cats.effect.{Ref, Sync} import cats.syntax.all.* import org.typelevel.log4cats.extras.LogLevel import org.typelevel.log4cats.testing.TestingLoggerFactory.LogMessage -import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger, LoggerKernel, KernelLogLevel} +import org.typelevel.log4cats.{ + KernelLogLevel, + LoggerFactory, + LoggerKernel, + SelfAwareStructuredLogger +} import org.typelevel.log4cats.Log import java.io.{PrintWriter, StringWriter} @@ -181,22 +186,25 @@ object TestingLoggerFactory { override val isErrorEnabled: F[Boolean] = errorEnabled.pure[F] protected def kernel: LoggerKernel[F, String] = new LoggerKernel[F, String] { - def log(level: KernelLogLevel, logBuilder: Log.Builder[String] => Log.Builder[String]): F[Unit] = { + def log( + level: KernelLogLevel, + logBuilder: Log.Builder[String] => Log.Builder[String] + ): F[Unit] = { val log = logBuilder(Log.mutableBuilder[String]()).build() val message = log.message() val throwable = log.throwable val context = log.context - + level match { - case KernelLogLevel.Trace => + case KernelLogLevel.Trace => if (traceEnabled) save(Trace(name, message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Debug => + case KernelLogLevel.Debug => if (debugEnabled) save(Debug(name, message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Info => + case KernelLogLevel.Info => if (infoEnabled) save(Info(name, message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Warn => + case KernelLogLevel.Warn => if (warnEnabled) save(Warn(name, message, throwable, context)) else Sync[F].unit - case KernelLogLevel.Error => + case KernelLogLevel.Error => if (errorEnabled) save(Error(name, message, throwable, context)) else Sync[F].unit } } From 329bc40d5abdf20eae2963809a72726e661b5da0 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 08:09:23 +0000 Subject: [PATCH 53/62] fix match error --- .../scala/org/typelevel/log4cats/extras/DeferredLogger.scala | 1 + .../log4cats/extras/DeferredSelfAwareStructuredLogger.scala | 1 + .../org/typelevel/log4cats/extras/DeferredStructuredLogger.scala | 1 + .../main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala | 1 + .../org/typelevel/log4cats/extras/WriterTStructuredLogger.scala | 1 + 5 files changed, 5 insertions(+) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala index c00484b8..2d6773aa 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredLogger.scala @@ -82,6 +82,7 @@ object DeferredLogger { case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error case KernelLogLevel.Fatal => LogLevel.Error + case _ => LogLevel.Error // Handle any other KernelLogLevel values } val deferredMsg = DeferredLogMessage( logLevel, diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala index c3f9fadd..06d3d056 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala @@ -77,6 +77,7 @@ object DeferredSelfAwareStructuredLogger { case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error case KernelLogLevel.Fatal => LogLevel.Error + case _ => LogLevel.Error // Handle any other KernelLogLevel values } val deferredMsg = DeferredLogMessage( logLevel, diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala index 95f7ad8b..87f5f4de 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala @@ -95,6 +95,7 @@ object DeferredStructuredLogger { case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error case KernelLogLevel.Fatal => LogLevel.Error + case _ => LogLevel.Error // Handle any other KernelLogLevel values } val deferredMsg = DeferredLogMessage( logLevel, diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala index 8f5d5e16..2375e864 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTLogger.scala @@ -54,6 +54,7 @@ object WriterTLogger { case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error case KernelLogLevel.Fatal => LogLevel.Error + case _ => LogLevel.Error // Handle any other KernelLogLevel values } WriterT.tell[F, G[LogMessage]]( Applicative[G].pure(LogMessage(logLevel, log.throwable, log.message())) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala index 40f17285..6f77c6a6 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/extras/WriterTStructuredLogger.scala @@ -63,6 +63,7 @@ object WriterTStructuredLogger { case KernelLogLevel.Warn => LogLevel.Warn case KernelLogLevel.Error => LogLevel.Error case KernelLogLevel.Fatal => LogLevel.Error + case _ => LogLevel.Error // Handle any other KernelLogLevel values } WriterT.tell[F, G[StructuredLogMessage]]( Applicative[G].pure( From a32d9087e824490c31b08d12c2c65cf500d4870d Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 08:20:11 +0000 Subject: [PATCH 54/62] fix match error --- .../log4cats/slf4j/internal/Slf4jLoggerInternal.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala index afd5b0e9..34b5c223 100644 --- a/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala +++ b/slf4j/src/main/scala/org/typelevel/log4cats/slf4j/internal/Slf4jLoggerInternal.scala @@ -61,6 +61,7 @@ private[slf4j] object Slf4jLoggerInternal { case KernelLogLevel.Info => F.delay(logger.isInfoEnabled) case KernelLogLevel.Warn => F.delay(logger.isWarnEnabled) case KernelLogLevel.Error => F.delay(logger.isErrorEnabled) + case _ => F.pure(false) // Handle any other KernelLogLevel values } Sync[F].flatMap(isEnabled) { enabled => @@ -114,6 +115,13 @@ private[slf4j] object Slf4jLoggerInternal { if (throwable.isDefined) logger.error(message, throwable.get) else logger.error(message) } + case _ => + F.delay { + val message = log.message() + val throwable = log.throwable + if (throwable.isDefined) logger.error(message, throwable.get) + else logger.error(message) + } } Sync[F].flatMap(setMdc)(_ => Sync[F].flatMap(logMessage)(_ => clearMdc)) From 809e56072d7515c1f6983ddaad2d9e5ba81aee93 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 08:56:07 +0000 Subject: [PATCH 55/62] fir test errors --- .../src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala | 4 ++-- .../org/typelevel/log4cats/SamStructuredLoggerTest.scala | 4 ++-- .../typelevel/log4cats/testing/StructuredTestingLogger.scala | 3 +++ .../scala/org/typelevel/log4cats/testing/TestingLogger.scala | 2 ++ .../org/typelevel/log4cats/testing/TestingLoggerFactory.scala | 2 ++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala index 5bdff2bc..4872ffdb 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala @@ -23,9 +23,9 @@ import munit.CatsEffectSuite class SamLoggerTest extends CatsEffectSuite { // Test kernel that captures log calls for verification - def testKernel[F[_]: cats.effect.Sync]( + def testKernel[F[_]]( ref: Ref[F, List[Log[String]]] - ): LoggerKernel[F, String] = { + )(implicit F: cats.effect.Sync[F]): LoggerKernel[F, String] = { new LoggerKernel[F, String] { def log( level: KernelLogLevel, diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala index 957090e0..c637edab 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -23,9 +23,9 @@ import munit.CatsEffectSuite class SamStructuredLoggerTest extends CatsEffectSuite { // Test kernel that captures log calls for verification - def testKernel[F[_]: cats.effect.Sync]( + def testKernel[F[_]]( ref: Ref[F, List[Log[String]]] - ): LoggerKernel[F, String] = { + )(implicit F: cats.effect.Sync[F]): LoggerKernel[F, String] = { new LoggerKernel[F, String] { def log( level: KernelLogLevel, diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala index c75d94b5..d9fe88f7 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/StructuredTestingLogger.scala @@ -170,6 +170,9 @@ object StructuredTestingLogger { case KernelLogLevel.Error => if (errorEnabled) appendLogMessage(ERROR(message, throwable, context)) else Sync[F].unit + case _ => + if (errorEnabled) appendLogMessage(ERROR(message, throwable, context)) + else Sync[F].unit } } } diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala index 8daa54ac..9309c600 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLogger.scala @@ -145,6 +145,8 @@ object TestingLogger { if (warnEnabled) appendLogMessage(WARN(message, throwable)) else Sync[F].unit case KernelLogLevel.Error => if (errorEnabled) appendLogMessage(ERROR(message, throwable)) else Sync[F].unit + case _ => + if (errorEnabled) appendLogMessage(ERROR(message, throwable)) else Sync[F].unit } } } diff --git a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala index 9b9c33f7..96079218 100644 --- a/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala +++ b/testing/shared/src/main/scala/org/typelevel/log4cats/testing/TestingLoggerFactory.scala @@ -206,6 +206,8 @@ object TestingLoggerFactory { if (warnEnabled) save(Warn(name, message, throwable, context)) else Sync[F].unit case KernelLogLevel.Error => if (errorEnabled) save(Error(name, message, throwable, context)) else Sync[F].unit + case _ => + if (errorEnabled) save(Error(name, message, throwable, context)) else Sync[F].unit } } } From 1b7aeb93b6bf2dd89221ab284269cd2b2e26b66e Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 08:59:28 +0000 Subject: [PATCH 56/62] try differentt fix --- .../src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala | 2 +- .../scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala index 4872ffdb..3db1da9a 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamLoggerTest.scala @@ -32,7 +32,7 @@ class SamLoggerTest extends CatsEffectSuite { record: Log.Builder[String] => Log.Builder[String] ): F[Unit] = { val logRecord = record(Log.mutableBuilder[String]()).build() - ref.update(_ :+ logRecord) + F.flatMap(ref.update(_ :+ logRecord))(_ => F.unit) } } } diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala index c637edab..a75e40b6 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -32,7 +32,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { record: Log.Builder[String] => Log.Builder[String] ): F[Unit] = { val logRecord = record(Log.mutableBuilder[String]()).build() - ref.update(_ :+ logRecord) + F.flatMap(ref.update(_ :+ logRecord))(_ => F.unit) } } } From 2a9fe26681911e8216495a42eb85984500ac9fac Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 09:06:14 +0000 Subject: [PATCH 57/62] fix tests --- .../scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala | 2 +- .../scala/org/typelevel/log4cats/console/ConsoleLogger.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala index a75e40b6..9d825f82 100644 --- a/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala +++ b/core/shared/src/test/scala/org/typelevel/log4cats/SamStructuredLoggerTest.scala @@ -83,7 +83,7 @@ class SamStructuredLoggerTest extends CatsEffectSuite { kernel = testKernel[IO](ref) logger = SamStructuredLogger.fromKernel(kernel) - val exception = new RuntimeException("Test exception") + exception = new RuntimeException("Test exception") _ <- logger.error(Map("error_type" -> "runtime"), exception)("Error with context") logs <- ref.get } yield { diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index f96b2d5f..6c4d618b 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -40,6 +40,7 @@ class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) case KernelLogLevel.Warn => ConsoleF.warn(message, throwable.orNull) case KernelLogLevel.Error => ConsoleF.error(message, throwable.orNull) case KernelLogLevel.Fatal => ConsoleF.error(message, throwable.orNull) + case _ => ConsoleF.error(message, throwable.orNull) // Handle any other KernelLogLevel values } } } From 3a139669de1d257486c25ddd476f8e93df796ad9 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 09:08:37 +0000 Subject: [PATCH 58/62] sbt scala format --- .../scala/org/typelevel/log4cats/console/ConsoleLogger.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala index 6c4d618b..dac951da 100644 --- a/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala +++ b/js-console/src/main/scala/org/typelevel/log4cats/console/ConsoleLogger.scala @@ -40,7 +40,8 @@ class ConsoleLogger[F[_]: Sync](logLevel: Option[LogLevel] = Option(Trace)) case KernelLogLevel.Warn => ConsoleF.warn(message, throwable.orNull) case KernelLogLevel.Error => ConsoleF.error(message, throwable.orNull) case KernelLogLevel.Fatal => ConsoleF.error(message, throwable.orNull) - case _ => ConsoleF.error(message, throwable.orNull) // Handle any other KernelLogLevel values + case _ => + ConsoleF.error(message, throwable.orNull) // Handle any other KernelLogLevel values } } } From 0f8c15244b4a7e6e31f8fb5379f84218fe942581 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 09:20:38 +0000 Subject: [PATCH 59/62] update mima Binary issues --- build.sbt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a2456b5a..8df10a95 100644 --- a/build.sbt +++ b/build.sbt @@ -54,7 +54,18 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) libraryDependencies ++= { if (tlIsScala3.value) Seq.empty else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided) - } + }, + mimaBinaryIssueFilters ++= Seq( + // Removed implicit conversions for monad transformers - these were intentionally removed + ProblemFilters + .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.Logger.kleisliLogger"), + ProblemFilters + .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.Logger.eitherTLogger"), + ProblemFilters + .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.Logger.optionTLogger"), + // Added new kernel method - this is a new API addition + ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.log4cats.Logger.kernel") + ) ) .nativeSettings(commonNativeSettings) From 21b3e6c028fc915c1216d552941725599a855682 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 09:32:32 +0000 Subject: [PATCH 60/62] add MiMa Exclusions --- build.sbt | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 8df10a95..f9ba009e 100644 --- a/build.sbt +++ b/build.sbt @@ -56,15 +56,31 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided) }, mimaBinaryIssueFilters ++= Seq( - // Removed implicit conversions for monad transformers - these were intentionally removed + // Removed implicit conversions for monad transformers from Logger - these were intentionally removed ProblemFilters .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.Logger.kleisliLogger"), ProblemFilters .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.Logger.eitherTLogger"), ProblemFilters .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.Logger.optionTLogger"), - // Added new kernel method - this is a new API addition - ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.log4cats.Logger.kernel") + // Removed implicit conversions for monad transformers from LoggerFactory - these were intentionally removed + ProblemFilters + .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.LoggerFactory.kleisliFactory"), + ProblemFilters + .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.LoggerFactory.eitherTFactory"), + ProblemFilters + .exclude[DirectMissingMethodProblem]("org.typelevel.log4cats.LoggerFactory.optionTFactory"), + // Added new kernel methods - these are new API additions + ProblemFilters.exclude[ReversedMissingMethodProblem]("org.typelevel.log4cats.Logger.kernel"), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "org.typelevel.log4cats.extras.DeferredLogger.kernel" + ), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "org.typelevel.log4cats.extras.DeferredSelfAwareStructuredLogger.kernel" + ), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "org.typelevel.log4cats.extras.DeferredStructuredLogger.kernel" + ) ) ) .nativeSettings(commonNativeSettings) From e69b27d55b96e3985f5ab2bd54cc37a5fbe33100 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 09:54:06 +0000 Subject: [PATCH 61/62] remove sourcecode --- build.sbt | 4 +- .../org/typelevel/log4cats/SamLogger.scala | 43 +++---------------- 2 files changed, 6 insertions(+), 41 deletions(-) diff --git a/build.sbt b/build.sbt index f9ba009e..14d537fe 100644 --- a/build.sbt +++ b/build.sbt @@ -31,7 +31,6 @@ val catsEffectV = "3.7.0-RC1" val slf4jV = "1.7.36" val munitCatsEffectV = "2.2.0-RC1" val logbackClassicV = "1.2.13" -val sourcecodeV = "0.4.2" Global / onChangedBuildSource := ReloadOnSourceChanges @@ -48,8 +47,7 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) name := "log4cats-core", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-core" % catsV, - "org.typelevel" %%% "cats-effect-std" % catsEffectV, - "com.lihaoyi" %%% "sourcecode" % sourcecodeV + "org.typelevel" %%% "cats-effect-std" % catsEffectV ), libraryDependencies ++= { if (tlIsScala3.value) Seq.empty diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala index 6f84c085..5c4d6024 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/SamLogger.scala @@ -28,49 +28,19 @@ trait SamLogger[F[_], Ctx] { /** Access to the underlying kernel for advanced use cases */ def underlying: LoggerKernel[F, Ctx] = kernel - final def info(message: => String)(implicit - pkg: sourcecode.Pkg, - filename: sourcecode.FileName, - name: sourcecode.Name, - line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Info, message) + final def info(message: => String): F[Unit] = log_(KernelLogLevel.Info, message) - final def warn(message: => String)(implicit - pkg: sourcecode.Pkg, - filename: sourcecode.FileName, - name: sourcecode.Name, - line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Warn, message) + final def warn(message: => String): F[Unit] = log_(KernelLogLevel.Warn, message) - final def error(message: => String)(implicit - pkg: sourcecode.Pkg, - filename: sourcecode.FileName, - name: sourcecode.Name, - line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Error, message) + final def error(message: => String): F[Unit] = log_(KernelLogLevel.Error, message) - final def trace(message: => String)(implicit - pkg: sourcecode.Pkg, - filename: sourcecode.FileName, - name: sourcecode.Name, - line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Trace, message) + final def trace(message: => String): F[Unit] = log_(KernelLogLevel.Trace, message) - final def debug(message: => String)(implicit - pkg: sourcecode.Pkg, - filename: sourcecode.FileName, - name: sourcecode.Name, - line: sourcecode.Line - ): F[Unit] = log_(KernelLogLevel.Debug, message) + final def debug(message: => String): F[Unit] = log_(KernelLogLevel.Debug, message) private final def log_( level: KernelLogLevel, message: => String - )(implicit - pkg: sourcecode.Pkg, - filename: sourcecode.FileName, - name: sourcecode.Name, - line: sourcecode.Line ): F[Unit] = { kernel.log( level, @@ -78,9 +48,6 @@ trait SamLogger[F[_], Ctx] { record .withLevel(level) .withMessage(message) - .withClassName(pkg.value + "." + name.value) - .withFileName(filename.value) - .withLine(line.value) ) } From 8bbf7949c7f02058b9999bfe62944070f12a7cf8 Mon Sep 17 00:00:00 2001 From: Jay-Lokhande Date: Sat, 4 Oct 2025 11:12:50 +0000 Subject: [PATCH 62/62] remove java.util time class as suggested --- .../scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala b/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala index 95f9b637..43f3fdc9 100644 --- a/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala +++ b/core/shared/src/main/scala/org/typelevel/log4cats/ConsoleLoggerKernel.scala @@ -27,9 +27,8 @@ class ConsoleLoggerKernel[F[_], Ctx](implicit F: Sync[F]) extends LoggerKernel[F F.delay { val logRecord = record(Log.mutableBuilder[Ctx]()).build() - val timestamp = logRecord.timestamp.map(_.toMillis).getOrElse(System.currentTimeMillis()) - // Use simple timestamp formatting instead of java.time.Instant for Scala Native compatibility - val timeStr = s"${new java.util.Date(timestamp).toString}" + val timestamp = logRecord.timestamp.getOrElse(java.time.Instant.now()) + val timeStr = timestamp.toString val levelStr = logRecord.level.namePadded val message = logRecord.message