Skip to content

Commit 2c886b8

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 dc5ee2c commit 2c886b8

File tree

14 files changed

+1205
-45
lines changed

14 files changed

+1205
-45
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ jobs:
1919
strategy:
2020
matrix:
2121
scala:
22-
- { name: "Scala 2", version: "2.12.13", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
23-
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
24-
- { name: "Scala 3", version: "3.0.2", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
22+
- { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
23+
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
24+
- { name: "Scala 3", version: "3.3.0", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
2525

2626
steps:
2727
- uses: actions/checkout@v3

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
matrix:
2020
scala:
21-
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
21+
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
2222

2323
steps:
2424
- uses: actions/checkout@v3

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ jobs:
2222
strategy:
2323
matrix:
2424
scala:
25-
- { name: "Scala 2", version: "2.12.13", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
26-
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "" }
27-
- { name: "Scala 3", version: "3.0.2", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
25+
- { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
26+
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "" }
27+
- { name: "Scala 3", version: "3.3.0", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
2828

2929
steps:
3030
- uses: actions/checkout@v3

build.sbt

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ lazy val loggerF = (project in file("."))
8181
catsJs,
8282
logbackMdcMonix3Jvm,
8383
logbackMdcMonix3Js,
84+
logbackMdcCatsEffect3Jvm,
85+
logbackMdcCatsEffect3Js,
8486
testKitJvm,
8587
testKitJs,
8688
catsEffectJvm,
@@ -107,7 +109,7 @@ lazy val core =
107109
),
108110
)
109111
lazy val coreJvm = core.jvm
110-
lazy val coreJs = core.js
112+
lazy val coreJs = core.js.settings(commonJsSettings)
111113

112114
lazy val slf4jLogger = module(ProjectName("slf4j"), crossProject(JVMPlatform, JSPlatform))
113115
.settings(
@@ -122,7 +124,7 @@ lazy val slf4jLogger = module(ProjectName("slf4j"), crossProject(JVMPlatform,
122124
)
123125
.dependsOn(core)
124126
lazy val slf4jLoggerJvm = slf4jLogger.jvm
125-
lazy val slf4jLoggerJs = slf4jLogger.js
127+
lazy val slf4jLoggerJs = slf4jLogger.js.settings(commonJsSettings)
126128

127129
lazy val log4sLogger =
128130
module(ProjectName("log4s"), crossProject(JVMPlatform, JSPlatform))
@@ -138,7 +140,7 @@ lazy val log4sLogger =
138140
)
139141
.dependsOn(core)
140142
lazy val log4sLoggerJvm = log4sLogger.jvm
141-
lazy val log4sLoggerJs = log4sLogger.js
143+
lazy val log4sLoggerJs = log4sLogger.js.settings(commonJsSettings)
142144

143145
lazy val log4jLogger =
144146
module(ProjectName("log4j"), crossProject(JVMPlatform, JSPlatform))
@@ -198,7 +200,7 @@ lazy val log4jLogger =
198200
)
199201
.dependsOn(core)
200202
lazy val log4jLoggerJvm = log4jLogger.jvm
201-
lazy val log4jLoggerJs = log4jLogger.js
203+
lazy val log4jLoggerJs = log4jLogger.js.settings(commonJsSettings)
202204

203205
lazy val sbtLogging =
204206
module(ProjectName("sbt-logging"), crossProject(JVMPlatform, JSPlatform))
@@ -230,7 +232,7 @@ lazy val sbtLogging =
230232
)
231233
.dependsOn(core)
232234
lazy val sbtLoggingJvm = sbtLogging.jvm
233-
lazy val sbtLoggingJs = sbtLogging.js
235+
lazy val sbtLoggingJs = sbtLogging.js.settings(commonJsSettings)
234236

235237
lazy val cats =
236238
module(ProjectName("cats"), crossProject(JVMPlatform, JSPlatform))
@@ -248,7 +250,7 @@ lazy val cats =
248250
)
249251
.dependsOn(core % props.IncludeTest)
250252
lazy val catsJvm = cats.jvm
251-
lazy val catsJs = cats.js
253+
lazy val catsJs = cats.js.settings(commonJsSettings)
252254

253255
lazy val logbackMdcMonix3 = module(ProjectName("logback-mdc-monix3"), crossProject(JVMPlatform, JSPlatform))
254256
.settings(
@@ -271,7 +273,31 @@ lazy val logbackMdcMonix3 = module(ProjectName("logback-mdc-monix3"), crossPr
271273
slf4jLogger % Test,
272274
)
273275
lazy val logbackMdcMonix3Jvm = logbackMdcMonix3.jvm
274-
lazy val logbackMdcMonix3Js = logbackMdcMonix3.js
276+
lazy val logbackMdcMonix3Js = logbackMdcMonix3.js.settings(commonJsSettings)
277+
278+
lazy val logbackMdcCatsEffect3 = module(ProjectName("logback-mdc-cats-effect3"), crossProject(JVMPlatform, JSPlatform))
279+
.settings(
280+
description := "Logger for F[_] - logback MDC context map support for Cats Effect 3",
281+
libraryDependencies ++= Seq(
282+
libs.logbackClassic,
283+
libs.logbackScalaInterop,
284+
libs.catsEffect3Eap,
285+
libs.tests.effectieCatsEffect3,
286+
libs.tests.extrasHedgehogCatsEffect3,
287+
) ++ libs.tests.hedgehogLibs,
288+
libraryDependencies := libraryDependenciesRemoveScala3Incompatible(
289+
scalaVersion.value,
290+
libraryDependencies.value,
291+
),
292+
javaOptions += "-Dcats.effect.ioLocalPropagation=true",
293+
)
294+
.dependsOn(
295+
core,
296+
monix % Test,
297+
slf4jLogger % Test,
298+
)
299+
lazy val logbackMdcCatsEffect3Jvm = logbackMdcCatsEffect3.jvm
300+
lazy val logbackMdcCatsEffect3Js = logbackMdcCatsEffect3.js.settings(commonJsSettings)
275301

276302
lazy val testKit =
277303
module(ProjectName("test-kit"), crossProject(JVMPlatform, JSPlatform))
@@ -289,7 +315,7 @@ lazy val testKit =
289315
)
290316
.dependsOn(core % props.IncludeTest)
291317
lazy val testKitJvm = testKit.jvm
292-
lazy val testKitJs = testKit.js
318+
lazy val testKitJs = testKit.js.settings(commonJsSettings)
293319

294320
lazy val catsEffect =
295321
module(ProjectName("cats-effect"), crossProject(JVMPlatform, JSPlatform))
@@ -304,7 +330,7 @@ lazy val catsEffect =
304330
.settings(noPublish)
305331
.dependsOn(core % props.IncludeTest, cats)
306332
lazy val catsEffectJvm = catsEffect.jvm
307-
lazy val catsEffectJs = catsEffect.js
333+
lazy val catsEffectJs = catsEffect.js.settings(commonJsSettings)
308334

309335
lazy val catsEffect3 =
310336
module(ProjectName("cats-effect3"), crossProject(JVMPlatform, JSPlatform))
@@ -322,7 +348,7 @@ lazy val catsEffect3 =
322348
.settings(noPublish)
323349
.dependsOn(core % props.IncludeTest, cats)
324350
lazy val catsEffect3Jvm = catsEffect3.jvm
325-
lazy val catsEffect3Js = catsEffect3.js
351+
lazy val catsEffect3Js = catsEffect3.js.settings(commonJsSettings)
326352

327353
lazy val monix =
328354
module(ProjectName("monix"), crossProject(JVMPlatform, JSPlatform))
@@ -337,7 +363,7 @@ lazy val monix =
337363
.settings(noPublish)
338364
.dependsOn(core % props.IncludeTest, cats)
339365
lazy val monixJvm = monix.jvm
340-
lazy val monixJs = monix.js
366+
lazy val monixJs = monix.js.settings(commonJsSettings)
341367

342368
lazy val testCatsEffectWithSlf4jLogger =
343369
testProject(
@@ -509,8 +535,8 @@ lazy val props =
509535
final val GitHubUsername = "Kevin-Lee"
510536
final val RepoName = "logger-f"
511537

512-
final val Scala3Versions = List("3.0.2")
513-
final val Scala2Versions = List("2.13.6", "2.12.13")
538+
final val Scala3Versions = List("3.3.0")
539+
final val Scala2Versions = List("2.13.11", "2.12.18")
514540

515541
// final val ProjectScalaVersion = Scala3Versions.head
516542
final val ProjectScalaVersion = Scala2Versions.head
@@ -576,6 +602,8 @@ lazy val libs =
576602

577603
lazy val catsEffect3 = "org.typelevel" %% "cats-effect" % props.CatsEffect3Version
578604

605+
lazy val catsEffect3Eap = "org.typelevel" %% "cats-effect" % "3.6-02a43a6"
606+
579607
lazy val monix3Execution = "io.monix" %% "monix-execution" % props.Monix3Version
580608

581609
lazy val effectieCore: ModuleID = "io.kevinlee" %% "effectie-core" % props.EffectieVersion
@@ -605,6 +633,8 @@ lazy val libs =
605633

606634
lazy val extrasCats = "io.kevinlee" %% "extras-cats" % props.ExtrasVersion % Test
607635

636+
lazy val effectieCatsEffect3 = "io.kevinlee" %% "effectie-cats-effect3" % props.EffectieVersion
637+
608638
lazy val extrasConcurrent = "io.kevinlee" %% "extras-concurrent" % props.ExtrasVersion % Test
609639
lazy val extrasConcurrentTesting = "io.kevinlee" %% "extras-concurrent-testing" % props.ExtrasVersion % Test
610640

@@ -619,14 +649,7 @@ def prefixedProjectName(name: String) = s"${props.RepoName}${if (name.isEmpty) "
619649
def libraryDependenciesRemoveScala3Incompatible(
620650
scalaVersion: String,
621651
libraries: Seq[ModuleID],
622-
): Seq[ModuleID] =
623-
(
624-
if (scalaVersion.startsWith("3."))
625-
libraries
626-
.filterNot(props.removeDottyIncompatible)
627-
else
628-
libraries
629-
)
652+
): Seq[ModuleID] = libraries
630653

631654
lazy val mavenCentralPublishSettings: SettingsDefinition = List(
632655
/* Publish to Maven Central { */
@@ -655,6 +678,7 @@ def projectCommonSettings(projectName: String, crossProject: CrossProject.Builde
655678
// , Compile / compile / wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value)
656679
// , Test / compile / wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value)
657680
wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value),
681+
fork := true,
658682
Compile / console / wartremoverErrors := List.empty,
659683
Compile / console / wartremoverWarnings := List.empty,
660684
Compile / console / scalacOptions :=
@@ -681,3 +705,11 @@ def projectCommonSettings(projectName: String, crossProject: CrossProject.Builde
681705
.settings(
682706
mavenCentralPublishSettings
683707
)
708+
709+
lazy val commonJsSettings: SettingsDefinition = List(
710+
Test / fork := false,
711+
// Test / scalacOptions ++= (if (scalaVersion.value.startsWith("3")) List.empty
712+
// else List("-P:scalajs:nowarnGlobalExecutionContext")),
713+
// Test / compile / scalacOptions ++= (if (scalaVersion.value.startsWith("3")) List.empty
714+
// else List("-P:scalajs:nowarnGlobalExecutionContext")),
715+
)

modules/logger-f-cats/shared/src/main/scala-3/loggerf/instances/show.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import loggerf.core.ToLog
77
* @since 2022-02-19
88
*/
99
trait show {
10-
inline given showToLog[A: Show]: ToLog[A] = Show[A].show(_)
10+
given showToLog[A: Show]: ToLog[A] with {
11+
inline def toLogMessage(a: A): String = Show[A].show(a)
12+
}
1113
}
1214

1315
object show extends show

modules/logger-f-core/shared/src/main/scala-3/loggerf/core/ToLog.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ object ToLog {
1515
@SuppressWarnings(Array("org.wartremover.warts.ToString"))
1616
def fromToString[A]: ToLog[A] = _.toString
1717

18-
inline given stringToLog: ToLog[String] = identity(_)
18+
given stringToLog: ToLog[String] with {
19+
inline def toLogMessage(a: String): String = a
20+
}
1921
}
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)