Skip to content

Commit

Permalink
Close #441 - Add MdcAdapter for Cats Effect 3 to properly share conte…
Browse files Browse the repository at this point in the history
…xt through MDC with IO and IOLocal from Cats Effect 3
  • Loading branch information
kevin-lee committed Nov 25, 2023
1 parent c421319 commit 84d4667
Show file tree
Hide file tree
Showing 14 changed files with 1,205 additions and 45 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
strategy:
matrix:
scala:
- { name: "Scala 2", version: "2.12.13", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
- { name: "Scala 3", version: "3.0.2", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
- { name: "Scala 3", version: "3.3.0", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
scala:
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }

steps:
- uses: actions/checkout@v4
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:
strategy:
matrix:
scala:
- { name: "Scala 2", version: "2.12.13", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 3", version: "3.0.2", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 3", version: "3.3.0", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }

steps:
- uses: actions/checkout@v4
Expand Down
74 changes: 53 additions & 21 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ lazy val loggerF = (project in file("."))
catsJs,
logbackMdcMonix3Jvm,
logbackMdcMonix3Js,
logbackMdcCatsEffect3Jvm,
logbackMdcCatsEffect3Js,
testKitJvm,
testKitJs,
catsEffectJvm,
Expand All @@ -109,7 +111,7 @@ lazy val core =
),
)
lazy val coreJvm = core.jvm
lazy val coreJs = core.js
lazy val coreJs = core.js.settings(commonJsSettings)

lazy val slf4jLogger = module(ProjectName("slf4j"), crossProject(JVMPlatform, JSPlatform))
.settings(
Expand All @@ -124,7 +126,7 @@ lazy val slf4jLogger = module(ProjectName("slf4j"), crossProject(JVMPlatform,
)
.dependsOn(core)
lazy val slf4jLoggerJvm = slf4jLogger.jvm
lazy val slf4jLoggerJs = slf4jLogger.js
lazy val slf4jLoggerJs = slf4jLogger.js.settings(commonJsSettings)

lazy val log4sLogger =
module(ProjectName("log4s"), crossProject(JVMPlatform, JSPlatform))
Expand All @@ -140,7 +142,7 @@ lazy val log4sLogger =
)
.dependsOn(core)
lazy val log4sLoggerJvm = log4sLogger.jvm
lazy val log4sLoggerJs = log4sLogger.js
lazy val log4sLoggerJs = log4sLogger.js.settings(commonJsSettings)

lazy val log4jLogger =
module(ProjectName("log4j"), crossProject(JVMPlatform, JSPlatform))
Expand Down Expand Up @@ -200,7 +202,7 @@ lazy val log4jLogger =
)
.dependsOn(core)
lazy val log4jLoggerJvm = log4jLogger.jvm
lazy val log4jLoggerJs = log4jLogger.js
lazy val log4jLoggerJs = log4jLogger.js.settings(commonJsSettings)

lazy val sbtLogging =
module(ProjectName("sbt-logging"), crossProject(JVMPlatform, JSPlatform))
Expand Down Expand Up @@ -232,7 +234,7 @@ lazy val sbtLogging =
)
.dependsOn(core)
lazy val sbtLoggingJvm = sbtLogging.jvm
lazy val sbtLoggingJs = sbtLogging.js
lazy val sbtLoggingJs = sbtLogging.js.settings(commonJsSettings)

lazy val cats =
module(ProjectName("cats"), crossProject(JVMPlatform, JSPlatform))
Expand All @@ -251,7 +253,7 @@ lazy val cats =
)
.dependsOn(core % props.IncludeTest)
lazy val catsJvm = cats.jvm
lazy val catsJs = cats.js
lazy val catsJs = cats.js.settings(commonJsSettings)

lazy val logbackMdcMonix3 = module(ProjectName("logback-mdc-monix3"), crossProject(JVMPlatform, JSPlatform))
.settings(
Expand All @@ -274,7 +276,31 @@ lazy val logbackMdcMonix3 = module(ProjectName("logback-mdc-monix3"), crossPr
slf4jLogger % Test,
)
lazy val logbackMdcMonix3Jvm = logbackMdcMonix3.jvm
lazy val logbackMdcMonix3Js = logbackMdcMonix3.js
lazy val logbackMdcMonix3Js = logbackMdcMonix3.js.settings(commonJsSettings)

lazy val logbackMdcCatsEffect3 = module(ProjectName("logback-mdc-cats-effect3"), crossProject(JVMPlatform, JSPlatform))
.settings(
description := "Logger for F[_] - logback MDC context map support for Cats Effect 3",
libraryDependencies ++= Seq(
libs.logbackClassic,
libs.logbackScalaInterop,
libs.catsEffect3Eap,
libs.tests.effectieCatsEffect3,
libs.tests.extrasHedgehogCatsEffect3,
) ++ libs.tests.hedgehogLibs,
libraryDependencies := libraryDependenciesRemoveScala3Incompatible(
scalaVersion.value,
libraryDependencies.value,
),
javaOptions += "-Dcats.effect.ioLocalPropagation=true",
)
.dependsOn(
core,
monix % Test,
slf4jLogger % Test,
)
lazy val logbackMdcCatsEffect3Jvm = logbackMdcCatsEffect3.jvm
lazy val logbackMdcCatsEffect3Js = logbackMdcCatsEffect3.js.settings(commonJsSettings)

lazy val testKit =
module(ProjectName("test-kit"), crossProject(JVMPlatform, JSPlatform))
Expand All @@ -292,7 +318,7 @@ lazy val testKit =
)
.dependsOn(core % props.IncludeTest)
lazy val testKitJvm = testKit.jvm
lazy val testKitJs = testKit.js
lazy val testKitJs = testKit.js.settings(commonJsSettings)

lazy val catsEffect =
module(ProjectName("cats-effect"), crossProject(JVMPlatform, JSPlatform))
Expand All @@ -307,7 +333,7 @@ lazy val catsEffect =
.settings(noPublish)
.dependsOn(core % props.IncludeTest, cats)
lazy val catsEffectJvm = catsEffect.jvm
lazy val catsEffectJs = catsEffect.js
lazy val catsEffectJs = catsEffect.js.settings(commonJsSettings)

lazy val catsEffect3 =
module(ProjectName("cats-effect3"), crossProject(JVMPlatform, JSPlatform))
Expand All @@ -325,7 +351,7 @@ lazy val catsEffect3 =
.settings(noPublish)
.dependsOn(core % props.IncludeTest, cats)
lazy val catsEffect3Jvm = catsEffect3.jvm
lazy val catsEffect3Js = catsEffect3.js
lazy val catsEffect3Js = catsEffect3.js.settings(commonJsSettings)

lazy val monix =
module(ProjectName("monix"), crossProject(JVMPlatform, JSPlatform))
Expand All @@ -340,7 +366,7 @@ lazy val monix =
.settings(noPublish)
.dependsOn(core % props.IncludeTest, cats)
lazy val monixJvm = monix.jvm
lazy val monixJs = monix.js
lazy val monixJs = monix.js.settings(commonJsSettings)

lazy val testCatsEffectWithSlf4jLogger =
testProject(
Expand Down Expand Up @@ -514,8 +540,8 @@ lazy val props =
final val GitHubUsername = "Kevin-Lee"
final val RepoName = "logger-f"

final val Scala3Versions = List("3.0.2")
final val Scala2Versions = List("2.13.6", "2.12.13")
final val Scala3Versions = List("3.3.0")
final val Scala2Versions = List("2.13.11", "2.12.18")

// final val ProjectScalaVersion = Scala3Versions.head
final val ProjectScalaVersion = Scala2Versions.head
Expand Down Expand Up @@ -581,6 +607,8 @@ lazy val libs =

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

lazy val catsEffect3Eap = "org.typelevel" %% "cats-effect" % "3.6-02a43a6"

lazy val monix3Execution = "io.monix" %% "monix-execution" % props.Monix3Version

lazy val effectieCore: ModuleID = "io.kevinlee" %% "effectie-core" % props.EffectieVersion
Expand Down Expand Up @@ -611,6 +639,8 @@ lazy val libs =

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

lazy val effectieCatsEffect3 = "io.kevinlee" %% "effectie-cats-effect3" % props.EffectieVersion

lazy val extrasConcurrent = "io.kevinlee" %% "extras-concurrent" % props.ExtrasVersion % Test
lazy val extrasConcurrentTesting = "io.kevinlee" %% "extras-concurrent-testing" % props.ExtrasVersion % Test

Expand All @@ -625,14 +655,7 @@ def prefixedProjectName(name: String) = s"${props.RepoName}${if (name.isEmpty) "
def libraryDependenciesRemoveScala3Incompatible(
scalaVersion: String,
libraries: Seq[ModuleID],
): Seq[ModuleID] =
(
if (scalaVersion.startsWith("3."))
libraries
.filterNot(props.removeDottyIncompatible)
else
libraries
)
): Seq[ModuleID] = libraries

lazy val mavenCentralPublishSettings: SettingsDefinition = List(
/* Publish to Maven Central { */
Expand Down Expand Up @@ -667,6 +690,7 @@ def projectCommonSettings(projectName: String, crossProject: CrossProject.Builde
// , Compile / compile / wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value)
// , Test / compile / wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value)
wartremoverErrors ++= commonWarts((update / scalaBinaryVersion).value),
fork := true,
Compile / console / wartremoverErrors := List.empty,
Compile / console / wartremoverWarnings := List.empty,
Compile / console / scalacOptions :=
Expand All @@ -693,3 +717,11 @@ def projectCommonSettings(projectName: String, crossProject: CrossProject.Builde
.settings(
mavenCentralPublishSettings
)

lazy val commonJsSettings: SettingsDefinition = List(
Test / fork := false,
// Test / scalacOptions ++= (if (scalaVersion.value.startsWith("3")) List.empty
// else List("-P:scalajs:nowarnGlobalExecutionContext")),
// Test / compile / scalacOptions ++= (if (scalaVersion.value.startsWith("3")) List.empty
// else List("-P:scalajs:nowarnGlobalExecutionContext")),
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import loggerf.core.ToLog
* @since 2022-02-19
*/
trait show {
inline given showToLog[A: Show]: ToLog[A] = Show[A].show(_)
given showToLog[A: Show]: ToLog[A] with {
inline def toLogMessage(a: A): String = Show[A].show(a)
}
}

object show extends show
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ object ToLog {
@SuppressWarnings(Array("org.wartremover.warts.ToString"))
def fromToString[A]: ToLog[A] = _.toString

inline given stringToLog: ToLog[String] = identity(_)
given stringToLog: ToLog[String] with {
inline def toLogMessage(a: String): String = a
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package loggerf.logger.logback

import cats.effect.unsafe.IOLocals
import cats.effect.{IOLocal, SyncIO}
import cats.syntax.all._
import ch.qos.logback.classic.LoggerContext
import logback_scala_interop.JLoggerFMdcAdapter
import org.slf4j.{LoggerFactory, MDC}

import java.util.{Map => JMap, Set => JSet}
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal

/** @author Kevin Lee
* @since 2023-07-07
*/
class Ce3MdcAdapter extends JLoggerFMdcAdapter {

private[this] val localContext: IOLocal[Map[String, String]] =
IOLocal[Map[String, String]](Map.empty[String, String])
.syncStep(1)
.flatMap(
_.leftMap(_ =>
new Error(
"Failed to initialize the local context of the Ce3MdcAdapter."
)
).liftTo[SyncIO]
)
.unsafeRunSync()

override def put(key: String, `val`: String): Unit =
IOLocals.update(localContext)(_ + (key -> `val`))

@SuppressWarnings(Array("org.wartremover.warts.Null"))
override def get(key: String): String =
IOLocals.get(localContext).getOrElse(key, null) // scalafix:ok DisableSyntax.null

override def remove(key: String): Unit = IOLocals.update(localContext)(_ - key)

override def clear(): Unit = IOLocals.reset(localContext)

override def getCopyOfContextMap: JMap[String, String] = getPropertyMap0

override def setContextMap0(contextMap: JMap[String, String]): Unit =
IOLocals.set(localContext, contextMap.asScala.toMap)

private def getPropertyMap0: JMap[String, String] = IOLocals.get(localContext).asJava

override def getPropertyMap: JMap[String, String] = getPropertyMap0

override def getKeys: JSet[String] = IOLocals.get(localContext).keySet.asJava

}
object Ce3MdcAdapter {

@SuppressWarnings(Array("org.wartremover.warts.Null"))
private def initialize0(): Ce3MdcAdapter = {
val field = classOf[MDC].getDeclaredField("mdcAdapter")
field.setAccessible(true)
val adapter = new Ce3MdcAdapter
field.set(null, adapter) // scalafix:ok DisableSyntax.null
field.setAccessible(false)
adapter
}

@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", "scalafix:DisableSyntax.asInstanceOf"))
def initialize(): Ce3MdcAdapter = {
val loggerContext =
LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
initializeWithLoggerContext(loggerContext)
}

def initializeWithLoggerContext(loggerContext: LoggerContext): Ce3MdcAdapter = {
val adapter = initialize0()
try {
val field = classOf[LoggerContext].getDeclaredField("mdcAdapter")
field.setAccessible(true)
field.set(loggerContext, adapter)
field.setAccessible(false)
adapter
} catch {
case NonFatal(_) => adapter
}
}
}
Loading

0 comments on commit 84d4667

Please sign in to comment.