Skip to content

Commit 4b8f336

Browse files
committed
Close #441 - Add MdcAdapter for Cats Effect 3 to properly share context through MDC with IO and IOLocal from Cats Effect 3
1 parent 0b8292e commit 4b8f336

File tree

10 files changed

+1154
-4
lines changed

10 files changed

+1154
-4
lines changed

build.sbt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ lazy val loggerF = (project in file("."))
8484
slf4jMdcNative,
8585
logbackMdcMonix3Jvm,
8686
testLogbackMdcMonix3Jvm,
87+
logbackMdcCatsEffect3Jvm,
8788
doobie1Jvm,
8889
testKitJvm,
8990
testKitJs,
@@ -342,6 +343,29 @@ lazy val testLogbackMdcMonix3 = testProject(ProjectName("logback-mdc-monix3")
342343
)
343344
lazy val testLogbackMdcMonix3Jvm = testLogbackMdcMonix3.jvm
344345

346+
lazy val logbackMdcCatsEffect3 = module(ProjectName("logback-mdc-cats-effect3"), crossProject(JVMPlatform))
347+
.settings(
348+
description := "Logger for F[_] - logback MDC context map support for Cats Effect 3",
349+
libraryDependencies ++= Seq(
350+
libs.logbackClassic,
351+
libs.logbackScalaInterop,
352+
libs.catsEffect3Eap,
353+
libs.tests.effectieCatsEffect3,
354+
libs.tests.extrasHedgehogCatsEffect3,
355+
) ++ libs.tests.hedgehogLibs,
356+
libraryDependencies := libraryDependenciesRemoveScala3Incompatible(
357+
scalaVersion.value,
358+
libraryDependencies.value,
359+
),
360+
javaOptions += "-Dcats.effect.ioLocalPropagation=true",
361+
)
362+
.dependsOn(
363+
core,
364+
monix % Test,
365+
slf4jLogger % Test,
366+
)
367+
lazy val logbackMdcCatsEffect3Jvm = logbackMdcCatsEffect3.jvm
368+
345369
lazy val doobie1 = module(ProjectName("doobie1"), crossProject(JVMPlatform))
346370
.settings(
347371
description := "Logger for F[_] - for Doobie v1",
@@ -890,6 +914,7 @@ def projectCommonSettings(projectName: String, crossProject: CrossProject.Builde
890914
// , Compile / compile / wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value)
891915
// , Test / compile / wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value)
892916
wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value),
917+
fork := true,
893918
Compile / console / wartremoverErrors := List.empty,
894919
Compile / console / wartremoverWarnings := List.empty,
895920
Compile / console / scalacOptions :=

changelogs/2.2.0-beta1.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## [2.2.0-beta1](https://github.com/Kevin-Lee/logger-f/issues?q=is%3Aissue+milestone%3Av2-m3) - 2025-01-03
2+
3+
* Add `MdcAdapter` for Cats Effect 3 to properly share context through `MDC` with `IO` and `IOLocal` from Cats Effect 3 (#441)
4+
* This is a beta released for testing the new feature with cats-effect `3.6-02a43a6`.

modules/logger-f-cats-effect3/shared/src/test/scala/loggerf/cats/testing/ConcurrentSupport.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ trait ConcurrentSupport {
2525
val stringWriter = new StringWriter()
2626
val printWriter = new PrintWriter(stringWriter)
2727
th.printStackTrace(printWriter)
28-
logger(s"⚠️ Error in Executor: ${stringWriter.toString}")
28+
29+
@SuppressWarnings(Array("org.wartremover.warts.ToString"))
30+
val message = stringWriter.toString
31+
logger(s"⚠️ Error in Executor: $message")
2932
},
3033
)
3134

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package loggerf.logger.logback
2+
3+
import cats.effect.unsafe.IOLocals
4+
import cats.effect.{IOLocal, SyncIO}
5+
import cats.syntax.all._
6+
import ch.qos.logback.classic.LoggerContext
7+
import logback_scala_interop.JLoggerFMdcAdapter
8+
import org.slf4j.{LoggerFactory, MDC}
9+
10+
import java.util.{Map => JMap, Set => JSet}
11+
import scala.jdk.CollectionConverters._
12+
import scala.util.control.NonFatal
13+
14+
/** @author Kevin Lee
15+
* @since 2023-07-07
16+
*/
17+
class Ce3MdcAdapter extends JLoggerFMdcAdapter {
18+
19+
private[this] val localContext: IOLocal[Map[String, String]] =
20+
IOLocal[Map[String, String]](Map.empty[String, String])
21+
.syncStep(1)
22+
.flatMap(
23+
_.leftMap(_ =>
24+
new Error(
25+
"Failed to initialize the local context of the Ce3MdcAdapter."
26+
)
27+
).liftTo[SyncIO]
28+
)
29+
.unsafeRunSync()
30+
31+
override def put(key: String, `val`: String): Unit =
32+
IOLocals.update(localContext)(_ + (key -> `val`))
33+
34+
@SuppressWarnings(Array("org.wartremover.warts.Null"))
35+
override def get(key: String): String =
36+
IOLocals.get(localContext).getOrElse(key, null) // scalafix:ok DisableSyntax.null
37+
38+
override def remove(key: String): Unit = IOLocals.update(localContext)(_ - key)
39+
40+
override def clear(): Unit = IOLocals.reset(localContext)
41+
42+
override def getCopyOfContextMap: JMap[String, String] = getPropertyMap0
43+
44+
override def setContextMap0(contextMap: JMap[String, String]): Unit =
45+
IOLocals.set(localContext, contextMap.asScala.toMap)
46+
47+
private def getPropertyMap0: JMap[String, String] = IOLocals.get(localContext).asJava
48+
49+
override def getPropertyMap: JMap[String, String] = getPropertyMap0
50+
51+
override def getKeys: JSet[String] = IOLocals.get(localContext).keySet.asJava
52+
53+
}
54+
object Ce3MdcAdapter {
55+
56+
@SuppressWarnings(Array("org.wartremover.warts.Null"))
57+
private def initialize0(): Ce3MdcAdapter = {
58+
val field = classOf[MDC].getDeclaredField("mdcAdapter")
59+
field.setAccessible(true)
60+
val adapter = new Ce3MdcAdapter
61+
field.set(null, adapter) // scalafix:ok DisableSyntax.null
62+
field.setAccessible(false)
63+
adapter
64+
}
65+
66+
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", "scalafix:DisableSyntax.asInstanceOf"))
67+
def initialize(): Ce3MdcAdapter = {
68+
val loggerContext =
69+
LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
70+
initializeWithLoggerContext(loggerContext)
71+
}
72+
73+
def initializeWithLoggerContext(loggerContext: LoggerContext): Ce3MdcAdapter = {
74+
val adapter = initialize0()
75+
try {
76+
val field = classOf[LoggerContext].getDeclaredField("mdcAdapter")
77+
field.setAccessible(true)
78+
field.set(loggerContext, adapter)
79+
field.setAccessible(false)
80+
adapter
81+
} catch {
82+
case NonFatal(_) => adapter
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)