Skip to content

Commit 45232f2

Browse files
committed
feat!: add ContextPair to StructuredLogger
Motivation ========== `addContext` has one sharp edge on it that seems to come up a lot during onboarding: if you want to add a single context element, the syntax is unintuitive. Say we want to add the context `"foo" -> "bar"` to a logger. Naturally, one might reach for `addContext(pairs: (String, Shown)*)`, but alas, after erasure we have ```scala logger.addContext( "foo" -> "bar" // Tuple2 ) ``` which doesn't conform to either of `Map` or `Seq`. This leaves us with these options: ```scala logger.addContext( Map( "foo" -> bar.show ) ) logger.addContext( "foo" -> (bar: Shown) ) ``` Neither feels _great_, and it would be really pleasant to have consistent syntax for single-element and multi-element context data without having to use `Map` literals and explicit `.show`/`.toString`. Result ====== This patch hijacks `StructuredLogger`'s implicit scope to do a bit of juggling and resolve the erasure issue, and in the process cleans up a chunk of duplicative code in its descendants. I've tested this in the work code, and it appears to work as expected for any subtype of `StructuredLogger`. The signature ain't exactly beautiful, but the syntactic consistency is gratifying.
1 parent ee304de commit 45232f2

File tree

4 files changed

+16
-24
lines changed

4 files changed

+16
-24
lines changed

core/shared/src/main/scala/org/typelevel/log4cats/SelfAwareStructuredLogger.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.typelevel.log4cats
1818

1919
import cats.*
20-
import cats.Show.Shown
2120

2221
trait SelfAwareStructuredLogger[F[_]] extends SelfAwareLogger[F] with StructuredLogger[F] {
2322
override def mapK[G[_]](fk: F ~> G): SelfAwareStructuredLogger[G] =
@@ -26,13 +25,6 @@ trait SelfAwareStructuredLogger[F[_]] extends SelfAwareLogger[F] with Structured
2625
override def addContext(ctx: Map[String, String]): SelfAwareStructuredLogger[F] =
2726
SelfAwareStructuredLogger.withContext(this)(ctx)
2827

29-
override def addContext(
30-
pairs: (String, Shown)*
31-
): SelfAwareStructuredLogger[F] =
32-
SelfAwareStructuredLogger.withContext(this)(
33-
pairs.map { case (k, v) => (k, v.toString) }.toMap
34-
)
35-
3628
override def withModifiedString(f: String => String): SelfAwareStructuredLogger[F] =
3729
SelfAwareStructuredLogger.withModifiedString[F](this, f)
3830
}

core/shared/src/main/scala/org/typelevel/log4cats/StructuredLogger.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.typelevel.log4cats
1818

1919
import cats.*
20+
import cats.syntax.foldable.*
2021
import cats.Show.Shown
22+
import cats.Show.ContravariantShow
2123

2224
trait StructuredLogger[F[_]] extends Logger[F] {
2325
def trace(ctx: Map[String, String])(msg: => String): F[Unit]
@@ -36,18 +38,27 @@ trait StructuredLogger[F[_]] extends Logger[F] {
3638
def addContext(ctx: Map[String, String]): StructuredLogger[F] =
3739
StructuredLogger.withContext(this)(ctx)
3840

41+
def addContext(pair: StructuredLogger.ContextPair): StructuredLogger[F] =
42+
addContext(pair.toMap)
43+
3944
def addContext(
40-
pairs: (String, Shown)*
45+
pairs: StructuredLogger.ContextPair*
4146
): StructuredLogger[F] =
42-
StructuredLogger.withContext(this)(
43-
pairs.map { case (k, v) => (k, v.toString) }.toMap
44-
)
47+
addContext(pairs.toList.foldMap(_.toMap))
4548

4649
override def withModifiedString(f: String => String): StructuredLogger[F] =
4750
StructuredLogger.withModifiedString[F](this, f)
4851
}
4952

5053
object StructuredLogger {
54+
55+
final case class ContextPair(value: (String, Shown)) extends AnyVal {
56+
def toMap: Map[String, String] = Map(value._1 -> value._2.toString)
57+
}
58+
59+
implicit def log4catsTupleToContextPair[A: ContravariantShow](pair: (String, A)): ContextPair =
60+
ContextPair((pair._1, Shown.mat(pair._2)))
61+
5162
def apply[F[_]](implicit ev: StructuredLogger[F]): StructuredLogger[F] = ev
5263

5364
def withContext[F[_]](sl: StructuredLogger[F])(ctx: Map[String, String]): StructuredLogger[F] =

core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredSelfAwareStructuredLogger.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import cats.data.Chain
2020
import cats.effect.kernel.Resource.ExitCase
2121
import cats.effect.kernel.{Concurrent, Ref, Resource}
2222
import cats.syntax.all.*
23-
import cats.{~>, Show}
23+
import cats.~>
2424
import org.typelevel.log4cats.SelfAwareStructuredLogger
2525

2626
/**
@@ -39,11 +39,6 @@ trait DeferredSelfAwareStructuredLogger[F[_]]
3939
override def addContext(ctx: Map[String, String]): DeferredSelfAwareStructuredLogger[F] =
4040
DeferredSelfAwareStructuredLogger.withContext(this)(ctx)
4141

42-
override def addContext(pairs: (String, Show.Shown)*): DeferredSelfAwareStructuredLogger[F] =
43-
DeferredSelfAwareStructuredLogger.withContext(this)(
44-
pairs.map { case (k, v) => (k, v.toString) }.toMap
45-
)
46-
4742
override def withModifiedString(f: String => String): DeferredSelfAwareStructuredLogger[F] =
4843
DeferredSelfAwareStructuredLogger.withModifiedString(this, f)
4944
}

core/shared/src/main/scala/org/typelevel/log4cats/extras/DeferredStructuredLogger.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.typelevel.log4cats.extras
1818

19-
import cats.Show.Shown
2019
import cats.data.Chain
2120
import cats.effect.kernel.Resource.ExitCase
2221
import cats.effect.kernel.{Concurrent, Ref, Resource}
@@ -56,11 +55,6 @@ trait DeferredStructuredLogger[F[_]] extends StructuredLogger[F] with DeferredLo
5655
override def addContext(ctx: Map[String, String]): DeferredStructuredLogger[F] =
5756
DeferredStructuredLogger.withContext(this, ctx)
5857

59-
override def addContext(
60-
pairs: (String, Shown)*
61-
): DeferredStructuredLogger[F] =
62-
DeferredStructuredLogger.withContext(this, pairs.map { case (k, v) => (k, v.toString) }.toMap)
63-
6458
override def withModifiedString(f: String => String): DeferredStructuredLogger[F] =
6559
DeferredStructuredLogger.withModifiedString[F](this, f)
6660
}

0 commit comments

Comments
 (0)