Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close #441 - Add MdcAdapter for Cats Effect 3 to properly share context through MDC with IO and IOLocal from Cats Effect 3 #467

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading