Skip to content
Open
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
26 changes: 26 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ lazy val loggerF = (project in file("."))
slf4jMdcNative,
logbackMdcMonix3Jvm,
testLogbackMdcMonix3Jvm,
logbackMdcCatsEffect3Jvm,
doobie1Jvm,
testKitJvm,
testKitJs,
Expand Down Expand Up @@ -342,6 +343,30 @@ lazy val testLogbackMdcMonix3 = testProject(ProjectName("logback-mdc-monix3")
)
lazy val testLogbackMdcMonix3Jvm = testLogbackMdcMonix3.jvm

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

lazy val doobie1 = module(ProjectName("doobie1"), crossProject(JVMPlatform))
.settings(
description := "Logger for F[_] - for Doobie v1",
Expand Down Expand Up @@ -890,6 +915,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 Down
4 changes: 4 additions & 0 deletions changelogs/2.2.0-beta1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## [2.2.0-beta1](https://github.com/Kevin-Lee/logger-f/issues?q=is%3Aissue+milestone%3Av2-m3) - 2025-01-03

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

* Add `MdcAdapter` for Cats Effect 3 to properly share context through `MDC` with `IO` and `IOLocal` from Cats Effect 3 (#441)
* This is a beta released for testing the new feature with cats-effect `3.6.0-RC1`.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ trait ConcurrentSupport {
val stringWriter = new StringWriter()
val printWriter = new PrintWriter(stringWriter)
th.printStackTrace(printWriter)
logger(s"⚠️ Error in Executor: ${stringWriter.toString}")

@SuppressWarnings(Array("org.wartremover.warts.ToString"))
val message = stringWriter.toString
logger(s"⚠️ Error in Executor: $message")
},
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package loggerf.logger.logback

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

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

/** @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(100)
.flatMap(
_.leftMap(_ =>
new Error(
"Failed to initialize the local context of the Ce3MdcAdapter."
)
).liftTo[SyncIO]
)
.unsafeRunSync()

override def put(key: String, `val`: String): Unit = {
val unsafeThreadLocal = localContext.unsafeThreadLocal()
unsafeThreadLocal.set(unsafeThreadLocal.get + (key -> `val`))
}

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

override def remove(key: String): Unit = {
val unsafeThreadLocal = localContext.unsafeThreadLocal()
unsafeThreadLocal.set(unsafeThreadLocal.get - key)
}

override def clear(): Unit = localContext.unsafeThreadLocal().set(Map.empty[String, String])

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

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

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

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

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

}
object Ce3MdcAdapter extends Ce3MdcAdapterOps

trait Ce3MdcAdapterOps {

@SuppressWarnings(Array("org.wartremover.warts.Null"))
protected def initialize0(ce3MdcAdapter: Ce3MdcAdapter): Ce3MdcAdapter = {
org.slf4j.SetMdcAdapter(ce3MdcAdapter)
ce3MdcAdapter
}

@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", "scalafix:DisableSyntax.asInstanceOf"))
protected def getLoggerContext(): LoggerContext =
LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]

def initialize(): Ce3MdcAdapter =
initializeWithCe3MdcAdapterAndLoggerContext(new Ce3MdcAdapter, getLoggerContext())

def initializeWithCe3MdcAdapter(ce3MdcAdapter: Ce3MdcAdapter): Ce3MdcAdapter =
initializeWithCe3MdcAdapterAndLoggerContext(ce3MdcAdapter, getLoggerContext())

def initializeWithLoggerContext(loggerContext: LoggerContext): Ce3MdcAdapter =
initializeWithCe3MdcAdapterAndLoggerContext(new Ce3MdcAdapter, loggerContext)

@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def initializeWithCe3MdcAdapterAndLoggerContext(
ce3MdcAdapter: Ce3MdcAdapter,
loggerContext: LoggerContext,
): Ce3MdcAdapter = {
val adapter = initialize0(ce3MdcAdapter)

loggerContext.setMDCAdapter(adapter)
if (loggerContext.getMDCAdapter == adapter) {
// println("[LoggerContext] It's set by setMDCAdapter.")
adapter
} else {
// println(
// "[LoggerContext] The old setMDCAdapter doesn't replace `mdcAdapter` if it has already been set, " +
// "so it will use reflection to set it in the `mdcAdapter` field."
// )
val loggerContextClass = classOf[LoggerContext]
val field = loggerContextClass.getDeclaredField("mdcAdapter")
field.setAccessible(true)
field.set(loggerContext, adapter)
field.setAccessible(false)
adapter
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package loggerf.logger.logback

import cats.effect.{IOLocal, unsafe}
import ch.qos.logback.classic.LoggerContext
import logback_scala_interop.JLoggerFMdcAdapter
import org.slf4j.LoggerFactory

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

/** @author Kevin Lee
* @since 2023-07-07
*/
class Ce3MdcAdapterWithIoRuntime(private val ioRuntime: unsafe.IORuntime) extends JLoggerFMdcAdapter {

private[this] val localContext: IOLocal[Map[String, String]] =
IOLocal[Map[String, String]](Map.empty[String, String])
.unsafeRunSync()(ioRuntime)

override def put(key: String, `val`: String): Unit = {
val unsafeThreadLocal = localContext.unsafeThreadLocal()
unsafeThreadLocal.set(unsafeThreadLocal.get + (key -> `val`))
}

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

override def remove(key: String): Unit = {
val unsafeThreadLocal = localContext.unsafeThreadLocal()
unsafeThreadLocal.set(unsafeThreadLocal.get - key)
}

override def clear(): Unit = localContext.unsafeThreadLocal().set(Map.empty[String, String])

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

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

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

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

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

}
object Ce3MdcAdapterWithIoRuntime extends Ce3MdcAdapterWithIoRuntimeOps

trait Ce3MdcAdapterWithIoRuntimeOps {

@SuppressWarnings(Array("org.wartremover.warts.Null"))
protected def initialize0(ce3MdcAdapter: Ce3MdcAdapterWithIoRuntime): Ce3MdcAdapterWithIoRuntime = {
org.slf4j.SetMdcAdapter(ce3MdcAdapter)
ce3MdcAdapter
}

@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", "scalafix:DisableSyntax.asInstanceOf"))
protected def getLoggerContext(): LoggerContext =
LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]

def initialize()(implicit ioRuntime: unsafe.IORuntime): Ce3MdcAdapterWithIoRuntime =
initializeWithCe3MdcAdapterWithIoRuntimeAndLoggerContext(
new Ce3MdcAdapterWithIoRuntime(ioRuntime),
getLoggerContext(),
)

def initializeWithCe3MdcAdapterWithIoRuntime(ce3MdcAdapter: Ce3MdcAdapterWithIoRuntime): Ce3MdcAdapterWithIoRuntime =
initializeWithCe3MdcAdapterWithIoRuntimeAndLoggerContext(ce3MdcAdapter, getLoggerContext())

def initializeWithLoggerContext(loggerContext: LoggerContext)(
implicit ioRuntime: unsafe.IORuntime
): Ce3MdcAdapterWithIoRuntime =
initializeWithCe3MdcAdapterWithIoRuntimeAndLoggerContext(new Ce3MdcAdapterWithIoRuntime(ioRuntime), loggerContext)

@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def initializeWithCe3MdcAdapterWithIoRuntimeAndLoggerContext(
ce3MdcAdapter: Ce3MdcAdapterWithIoRuntime,
loggerContext: LoggerContext,
): Ce3MdcAdapterWithIoRuntime = {
val adapter = initialize0(ce3MdcAdapter)

loggerContext.setMDCAdapter(adapter)
if (loggerContext.getMDCAdapter == adapter) {
// println("[LoggerContext] It's set by setMDCAdapter.")
adapter
} else {
// println(
// "[LoggerContext] The old setMDCAdapter doesn't replace `mdcAdapter` if it has already been set, " +
// "so it will use reflection to set it in the `mdcAdapter` field."
// )
val loggerContextClass = classOf[LoggerContext]
val field = loggerContextClass.getDeclaredField("mdcAdapter")
field.setAccessible(true)
field.set(loggerContext, adapter)
field.setAccessible(false)
adapter
}
}
}
Loading
Loading