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

SLF4J-2: Add slf4j-2 module with ServiceProvider (Closes #107) #136

Merged
merged 7 commits into from
Mar 10, 2023
Merged
Changes from 6 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
56 changes: 30 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -26,11 +26,12 @@ A **pure** _(in both senses of the word!)_ **Scala 3** logging library with **no

### Cross platform

| Module | JVM | scala.js | native |
| ------ | --- | -------- | ------ |
| core ||||
| http4s ||||
| slf4j || 🚫 | 🚫 |
| Module | JVM | scala.js | native |
|---------|-------|-----------|--------|
| core ||||
| http4s ||||
| slf4j || 🚫 | 🚫 |
| slf4j-2 || 🚫 | 🚫 |

## Installation

@@ -98,10 +99,10 @@ and running it yields:
```scala
import cats.effect.unsafe.implicits.global
main.unsafeRunSync()
// 2022-11-15 08:35:04 [DEBUG] repl.MdocSession$.MdocApp: This is some debug (README.md:27)
// 2022-11-15 08:35:04 [INFO ] repl.MdocSession$.MdocApp: HEY! (README.md:28)
// 2022-11-15 08:35:04 [WARN ] repl.MdocSession$.MdocApp: I'm warning you (README.md:29)
// 2022-11-15 08:35:04 [ERROR] repl.MdocSession$.MdocApp: I give up (README.md:30)
// 2023-03-07 08:45:23 [DEBUG] repl.MdocSession$.MdocApp: This is some debug (README.md:27)
// 2023-03-07 08:45:23 [INFO ] repl.MdocSession$.MdocApp: HEY! (README.md:28)
// 2023-03-07 08:45:23 [WARN ] repl.MdocSession$.MdocApp: I'm warning you (README.md:29)
// 2023-03-07 08:45:23 [ERROR] repl.MdocSession$.MdocApp: I give up (README.md:30)
```

We can also re-use the program and add context to our logger:
@@ -120,11 +121,11 @@ And running with context yields:

```scala
mainWithContext.unsafeRunSync()
// 2022-11-15 08:35:04 [DEBUG] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: This is some debug (README.md:27)
// 2022-11-15 08:35:04 [INFO ] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: HEY! (README.md:28)
// 2022-11-15 08:35:04 [WARN ] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: I'm warning you (README.md:29)
// 2022-11-15 08:35:04 [ERROR] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: I give up (README.md:30)
// 2022-11-15 08:35:04 [INFO ] repl.MdocSession$.MdocApp: Now the context is gone (README.md:61)
// 2023-03-07 08:45:23 [DEBUG] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: This is some debug (README.md:27)
// 2023-03-07 08:45:23 [INFO ] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: HEY! (README.md:28)
// 2023-03-07 08:45:23 [WARN ] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: I'm warning you (README.md:29)
// 2023-03-07 08:45:23 [ERROR] trace-id=4d334544-6462-43fa-b0b1-12846f871573 repl.MdocSession$.MdocApp: I give up (README.md:30)
// 2023-03-07 08:45:23 [INFO ] repl.MdocSession$.MdocApp: Now the context is gone (README.md:61)
```

## Can I use `SLF4J`?
@@ -156,12 +157,15 @@ To use this program with woof
```scala
import org.legogroup.woof.slf4j.*
import cats.effect.std.Dispatcher
val mainSlf4j: IO[Unit] =
for
woofLogger <- DefaultLogger.makeIo(consoleOutput)
_ <- woofLogger.registerSlf4j
_ <- programWithSlf4j
yield ()
Dispatcher.sequential[IO].use( implicit dispatcher =>
for
woofLogger <- DefaultLogger.makeIo(consoleOutput)
_ <- woofLogger.registerSlf4j
_ <- programWithSlf4j
yield ()
)
```

and running it:
@@ -219,8 +223,8 @@ the correlation ID is also returned in the header of the response.
```scala
mainHttp4s.unsafeRunSync()
// 2022-11-15 08:35:04 [INFO ] X-Trace-Id=db188825-ab1f-4e47-92d9-cdbe05b12209 repl.MdocSession$.MdocApp: I got a request with trace id! :D (README.md:121)
// 2022-11-15 08:35:04 [INFO ] repl.MdocSession$.MdocApp: Got response headers: Headers(X-Trace-Id: db188825-ab1f-4e47-92d9-cdbe05b12209) (README.md:142)
// 2023-03-07 08:45:24 [INFO ] X-Trace-Id=aba486cd-d6bc-4f1a-b64e-15eed546bca3 repl.MdocSession$.MdocApp: I got a request with trace id! :D (README.md:126)
// 2023-03-07 08:45:24 [INFO ] repl.MdocSession$.MdocApp: Got response headers: Headers(X-Trace-Id: aba486cd-d6bc-4f1a-b64e-15eed546bca3) (README.md:147)
```

## Structured Logging
@@ -245,11 +249,11 @@ And running with context yields:

```scala
contextAsJson.unsafeRunSync()
// {"level":"Debug","epochMillis":1668497704365,"timeStamp":"2022-11-15T07:35:04Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":26,"message":"This is some debug","context":{"bar":"1337","foo":"42"}}
// {"level":"Info","epochMillis":1668497704366,"timeStamp":"2022-11-15T07:35:04Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":27,"message":"HEY!","context":{"bar":"1337","foo":"42"}}
// {"level":"Warn","epochMillis":1668497704366,"timeStamp":"2022-11-15T07:35:04Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":28,"message":"I'm warning you","context":{"bar":"1337","foo":"42"}}
// {"level":"Error","epochMillis":1668497704366,"timeStamp":"2022-11-15T07:35:04Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":29,"message":"I give up","context":{"bar":"1337","foo":"42"}}
// {"level":"Info","epochMillis":1668497704367,"timeStamp":"2022-11-15T07:35:04Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":163,"message":"Now the context is gone","context":{}}
// {"level":"Debug","epochMillis":1678175124381,"timeStamp":"2023-03-07T07:45:24Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":26,"message":"This is some debug","context":{"bar":"1337","foo":"42"}}
// {"level":"Info","epochMillis":1678175124383,"timeStamp":"2023-03-07T07:45:24Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":27,"message":"HEY!","context":{"bar":"1337","foo":"42"}}
// {"level":"Warn","epochMillis":1678175124383,"timeStamp":"2023-03-07T07:45:24Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":28,"message":"I'm warning you","context":{"bar":"1337","foo":"42"}}
// {"level":"Error","epochMillis":1678175124383,"timeStamp":"2023-03-07T07:45:24Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":29,"message":"I give up","context":{"bar":"1337","foo":"42"}}
// {"level":"Info","epochMillis":1678175124383,"timeStamp":"2023-03-07T07:45:24Z","enclosingClass":"repl.MdocSession$.MdocApp","lineNumber":168,"message":"Now the context is gone","context":{}}
```

> We are considering if we should support matching different printers with different outputs: Maybe you want human readable logs for standard out and structured logging for your monitoring tools. However, this will be a breaking change.
17 changes: 14 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -9,11 +9,13 @@ val V = new {
val munitCatsEffect = "2.0.0-M3"
val scala = "3.2.2"
val slf4j = "1.7.36"
val slf4j2 = "2.0.6"
val tzdb = "2.5.0"
}

val D = new {
val slf4jApi = "org.slf4j" % "slf4j-api" % V.slf4j
val slf4jApi = "org.slf4j" % "slf4j-api" % V.slf4j
val slf4jApi2 = "org.slf4j" % "slf4j-api" % V.slf4j2

val catsCore = Def.setting("org.typelevel" %%% "cats-core" % V.cats)
val catsEffect = Def.setting("org.typelevel" %%% "cats-effect" % V.catsEffect)
@@ -79,7 +81,8 @@ lazy val root =
List(
core,
http4s,
slf4j
slf4j,
slf4j2
).flatMap(_.componentProjects).map(_.project): _*
)
.settings(
@@ -117,7 +120,15 @@ lazy val http4s = crossProject(JSPlatform, JVMPlatform, NativePlatform)

lazy val slf4j = woofProject(file("./modules/slf4j"))
.settings(libraryDependencies += D.slf4jApi)
.dependsOn(core.jvm % "compile->compile;test->test")
.dependsOn(slf4jCommon % "compile->compile;test->test")

lazy val slf4j2 = woofProject(file("./modules/slf4j-2"))
.settings(libraryDependencies += D.slf4jApi2)
.dependsOn(slf4jCommon % "compile->compile;test->test")

lazy val slf4jCommon =
woofProject(file("./modules/slf4j-common"))
.dependsOn(core.jvm % "compile->compile;test->test")

lazy val examples = project
.in(file("./modules/examples"))
24 changes: 14 additions & 10 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -26,11 +26,12 @@ A **pure** _(in both senses of the word!)_ **Scala 3** logging library with **no

### Cross platform

| Module | JVM | scala.js | native |
| ------ | --- | -------- | ------ |
| core ||||
| http4s ||||
| slf4j || 🚫 | 🚫 |
| Module | JVM | scala.js | native |
|---------|-------|-----------|--------|
| core ||||
| http4s ||||
| slf4j || 🚫 | 🚫 |
| slf4j-2 || 🚫 | 🚫 |

## Installation

@@ -147,12 +148,15 @@ To use this program with woof
```scala mdoc:silent
import org.legogroup.woof.slf4j.*
import cats.effect.std.Dispatcher
val mainSlf4j: IO[Unit] =
for
woofLogger <- DefaultLogger.makeIo(consoleOutput)
_ <- woofLogger.registerSlf4j
_ <- programWithSlf4j
yield ()
Dispatcher.sequential[IO].use( implicit dispatcher =>
for
woofLogger <- DefaultLogger.makeIo(consoleOutput)
_ <- woofLogger.registerSlf4j
_ <- programWithSlf4j
yield ()
)
```

and running it:
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.legogroup.woof.slf4j2.WoofLoggerServiceProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.legogroup.woof.slf4j2

import cats.Id
import cats.effect.IO
import cats.effect.std.Dispatcher
import cats.effect.unsafe.IORuntime
import cats.effect.unsafe.IORuntime.global
import org.legogroup.woof.slf4j.Slf4jWoofLoggerImpl
import org.legogroup.woof.{EnclosingClass, LogInfo, LogLevel, LogLine, Logger as WLogger}
import org.slf4j.{Logger, Marker}

import java.io.File
import scala.util.Try

class WoofLogger(name: String) extends Logger with Slf4jWoofLoggerImpl[IO, Marker](name):
override def logger: Option[WLogger[IO]] = WoofLogger.logger
override def dispatcher: Option[Dispatcher[IO]] = WoofLogger.dispatcher
end WoofLogger

object WoofLogger:
var logger: Option[WLogger[IO]] = None
var dispatcher: Option[Dispatcher[IO]] = None
end WoofLogger

extension (w: WLogger[IO])
def registerSlf4j(using d: Dispatcher[IO]): IO[Unit] = IO.delay {
WoofLogger.logger = Some(w)
WoofLogger.dispatcher = Some(d)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.legogroup.woof.slf4j2

import org.slf4j.ILoggerFactory
import org.slf4j.helpers.NOP_FallbackServiceProvider

class WoofLoggerServiceProvider extends NOP_FallbackServiceProvider:
override def getLoggerFactory: ILoggerFactory = (name: String) => new WoofLogger(name)
end WoofLoggerServiceProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.legogroup.woof.slf4j2.WoofLoggerServiceProvider
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.legogroup.woof.slf4j2

import cats.Id
import cats.effect.IO
import cats.effect.kernel.Clock
import cats.effect.std.Dispatcher
import org.legogroup.woof.*
import org.slf4j.LoggerFactory

import scala.concurrent.duration.*

class Slf4j2Suite extends munit.CatsEffectSuite:

override def munitIOTimeout = 10.minutes


val dispatcher = ResourceFunFixture(Dispatcher.sequential[IO](true))

dispatcher.test("should log stuff") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)

given Filter = Filter.everything

given Clock[IO] = leetClock

for
stringOutput <- newStringWriter
woofLogger <- DefaultLogger.makeIo(stringOutput)
_ <- woofLogger.registerSlf4j
slf4jLogger <- IO.delay(LoggerFactory.getLogger(this.getClass))
_ <- IO.delay(slf4jLogger.info("HELLO, SLF4J!"))
result <- stringOutput.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [INFO ] org.legogroup.woof.slf4j2.Slf4j2Suite: HELLO, SLF4J! (Slf4j2Suite.scala:31)\n",
)
end for
}

dispatcher.test("should log arrays of objects") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)

given Filter = Filter.everything

given Clock[IO] = leetClock

for
stringOutput <- newStringWriter
woofLogger <- DefaultLogger.makeIo(stringOutput)
_ <- woofLogger.registerSlf4j
slf4jLogger <- IO.delay(LoggerFactory.getLogger(this.getClass))
_ <- IO.delay(slf4jLogger.info("HELLO, ARRAYS!", 1, Some(42), List(1337)))
result <- stringOutput.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [INFO ] org.legogroup.woof.slf4j2.Slf4j2Suite: HELLO, ARRAYS! 1, Some(42), List(1337) (Slf4j2Suite.scala:52)\n",
)
end for
}

dispatcher.test("should respect log levels") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)

given Filter = Filter.exactLevel(LogLevel.Warn)

given Clock[IO] = leetClock

for
stringWriter <- newStringWriter
woofLogger <- DefaultLogger.makeIo(stringWriter)
_ <- woofLogger.registerSlf4j
slf4jLogger <- IO.delay(LoggerFactory.getLogger(this.getClass))
_ <- IO.delay(slf4jLogger.info("INFO, SLF4J!"))
_ <- IO.delay(slf4jLogger.debug("DEBUG, SLF4J!"))
_ <- IO.delay(slf4jLogger.warn("WARN, SLF4J!"))
_ <- IO.delay(slf4jLogger.error("ERROR, SLF4J!"))
result <- stringWriter.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [WARN ] org.legogroup.woof.slf4j2.Slf4j2Suite: WARN, SLF4J! (Slf4j2Suite.scala:75)\n",
)
end for
}

dispatcher.test("should not fail on null throwable") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)

given Filter = Filter.everything

given Clock[IO] = leetClock

for
stringWriter <- newStringWriter
woofLogger <- DefaultLogger.makeIo(stringWriter)
_ <- woofLogger.registerSlf4j
slf4jLogger <- IO.delay(LoggerFactory.getLogger(this.getClass))
_ <- IO.delay(slf4jLogger.debug("null exception", null))
result <- stringWriter.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [DEBUG] org.legogroup.woof.slf4j2.Slf4j2Suite: null exception (Slf4j2Suite.scala:97)\n",
)
end for
}


end Slf4j2Suite
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.legogroup.woof.slf4j

import cats.Functor
import cats.effect.std.Dispatcher
import org.legogroup.woof.*
import cats.syntax.all.*

import scala.util.Try

trait Slf4jWoofLoggerImpl[F[_], Marker](name: String):

private def getLogInfo() =
val stacktraceElements = Thread.currentThread().getStackTrace()
val lastIndex = stacktraceElements.reverse.indexWhere(s =>
s.getClassName == this.getClass.getName
) // after last mention of this class
val callingMethodIndex = stacktraceElements.size - lastIndex
val callingMethod: StackTraceElement = stacktraceElements(callingMethodIndex)
val enclosingClassName = EnclosingClass(callingMethod.getClassName)
val fileName = (enclosingClassName.fullName.replace('.', '/') + ".scala").split("\\/").takeRight(1).mkString
val lineNumber = callingMethod.getLineNumber - 1
LogInfo(enclosingClassName, fileName, lineNumber)
end getLogInfo

def getName(): String = name

def logger: Option[Logger[F]]
def dispatcher: Option[Dispatcher[F]]

private def log(level: LogLevel, msg: String): Unit =
(logger, dispatcher).mapN { (l, dispatcher) =>
val logInfo = getLogInfo()
val _ = dispatcher.unsafeRunSync(l.doLog(level, msg)(using logInfo))
}

def info(msg: String): Unit = log(LogLevel.Info, msg)
def debug(msg: String): Unit = log(LogLevel.Debug, msg)
def error(msg: String): Unit = log(LogLevel.Error, msg)
def trace(msg: String): Unit = log(LogLevel.Trace, msg)
def warn(msg: String): Unit = log(LogLevel.Warn, msg)

private def throwableMessage(msg: String, throwable: Throwable) = s"$msg " + Try(throwable.getMessage).getOrElse("")

def debug(msg: String, obj: Object): Unit = debug(s"$msg $obj")
def debug(msg: String, obj1: Object, obj2: Object): Unit = debug(s"$msg $obj1, $obj2")
def debug(msg: String, objs: Array[? <: Object]): Unit = debug(s"$msg ${objs.mkString(", ")}")
def debug(msg: String, throwable: Throwable): Unit = debug(throwableMessage(msg, throwable))
def debug(x$0: Marker, msg: String): Unit = debug(msg)
def debug(x$0: Marker, msg: String, obj: Object): Unit = debug(s"$msg, $obj")
def debug(x$0: Marker, msg: String, obj1: Object, obj2: Object): Unit = debug(s"$msg, $obj1, $obj2")
def debug(x$0: Marker, msg: String, objs: Array[? <: Object]): Unit = debug(s"$msg ${objs.mkString(", ")}")
def debug(x$0: Marker, msg: String, throwable: Throwable): Unit = debug(throwableMessage(msg, throwable))

def error(msg: String, obj: Object): Unit = error(s"$msg $obj")
def error(msg: String, obj1: Object, obj2: Object): Unit = error(s"$msg $obj1, $obj2")
def error(msg: String, objs: Array[? <: Object]): Unit = error(s"$msg ${objs.mkString(", ")}")
def error(msg: String, throwable: Throwable): Unit = error(throwableMessage(msg, throwable))
def error(x$0: Marker, msg: String): Unit = error(msg)
def error(x$0: Marker, msg: String, obj: Object): Unit = error(s"$msg $obj")
def error(x$0: Marker, msg: String, obj1: Object, obj2: Object): Unit = error(s"$msg $obj1, $obj2")
def error(x$0: Marker, msg: String, objs: Array[? <: Object]): Unit = error(s"$msg ${objs.mkString(", ")}")
def error(x$0: Marker, msg: String, throwable: Throwable): Unit = error(throwableMessage(msg, throwable))

def info(msg: String, obj: Object): Unit = info(s"$msg, $obj")
def info(msg: String, obj1: Object, obj2: Object): Unit = info(s"$msg, $obj1, $obj2")
def info(msg: String, objs: Array[? <: Object]): Unit = info(s"$msg ${objs.mkString(", ")}")
def info(msg: String, throwable: Throwable): Unit = info(throwableMessage(msg, throwable))
def info(x$0: Marker, msg: String): Unit = info(msg)
def info(x$0: Marker, msg: String, obj: Object): Unit = info(s"$msg, $obj")
def info(x$0: Marker, msg: String, obj1: Object, obj2: Object): Unit = info(s"$msg, $obj1, $obj2")
def info(x$0: Marker, msg: String, objs: Array[? <: Object]): Unit = info(s"$msg ${objs.mkString(", ")}")
def info(x$0: Marker, msg: String, throwable: Throwable): Unit = info(throwableMessage(msg, throwable))

def trace(msg: String, obj: Object): Unit = trace(s"$msg, $obj")
def trace(msg: String, obj1: Object, obj2: Object): Unit = trace(s"$msg, $obj1, $obj2")
def trace(msg: String, objs: Array[? <: Object]): Unit = trace(s"$msg ${objs.mkString(", ")}")
def trace(msg: String, throwable: Throwable): Unit = trace(throwableMessage(msg, throwable))
def trace(x$0: Marker, msg: String): Unit = trace(msg)
def trace(x$0: Marker, msg: String, obj: Object): Unit = trace(s"$msg, $obj")
def trace(x$0: Marker, msg: String, obj1: Object, obj2: Object): Unit = trace(s"$msg, $obj1, $obj2")
def trace(x$0: Marker, msg: String, objs: Array[? <: Object]): Unit = trace(s"$msg ${objs.mkString(", ")}")
def trace(x$0: Marker, msg: String, throwable: Throwable): Unit = trace(throwableMessage(msg, throwable))

def warn(msg: String, obj: Object): Unit = warn(s"$msg, $obj")
def warn(msg: String, obj1: Object, obj2: Object): Unit = warn(s"$msg, $obj1, $obj2")
def warn(msg: String, objs: Array[? <: Object]): Unit = warn(s"$msg ${objs.mkString(", ")}")
def warn(msg: String, throwable: Throwable): Unit = warn(throwableMessage(msg, throwable))
def warn(x$0: Marker, msg: String): Unit = warn(msg)
def warn(x$0: Marker, msg: String, obj: Object): Unit = warn(s"$msg, $obj")
def warn(x$0: Marker, msg: String, obj1: Object, obj2: Object): Unit = warn(s"$msg, $obj1, $obj2")
def warn(x$0: Marker, msg: String, objs: Array[? <: Object]): Unit = warn(s"$msg ${objs.mkString(", ")}")
def warn(x$0: Marker, msg: String, throwable: Throwable): Unit = warn(throwableMessage(msg, throwable))

private def testLevel(logLevel: LogLevel): Boolean = true

def isDebugEnabled(): Boolean = testLevel(LogLevel.Debug)
def isErrorEnabled(): Boolean = testLevel(LogLevel.Error)
def isInfoEnabled(): Boolean = testLevel(LogLevel.Info)
def isTraceEnabled(): Boolean = testLevel(LogLevel.Trace)
def isWarnEnabled(): Boolean = testLevel(LogLevel.Warn)

def isInfoEnabled(x$0: Marker): Boolean = isInfoEnabled()
def isTraceEnabled(x$0: Marker): Boolean = isTraceEnabled()
def isDebugEnabled(x$0: Marker): Boolean = isDebugEnabled()
def isErrorEnabled(x$0: Marker): Boolean = isErrorEnabled()
def isWarnEnabled(x$0: Marker): Boolean = isWarnEnabled()

end Slf4jWoofLoggerImpl
108 changes: 12 additions & 96 deletions modules/slf4j/src/main/scala/org/legogroup/woof/slf4j/WoofLogger.scala
Original file line number Diff line number Diff line change
@@ -2,111 +2,27 @@ package org.legogroup.woof.slf4j

import cats.Id
import cats.effect.IO
import cats.effect.std.Dispatcher
import cats.effect.unsafe.IORuntime
import cats.effect.unsafe.IORuntime.global
import org.legogroup.woof.{EnclosingClass, LogInfo, LogLevel, LogLine, Logger as WLogger}
import org.slf4j.Logger
import org.slf4j.{Logger, Marker}

import java.io.File
import scala.util.Try
class WoofLogger(name: String) extends Logger:

import WoofLogger.{given_IORuntime, logger}

private def getLogInfo() =
val stacktraceElements = Thread.currentThread().getStackTrace()
val lastIndex = stacktraceElements.reverse.indexWhere(s =>
s.getClassName == this.getClass.getName
) // after last mention of this class
val callingMethodIndex = stacktraceElements.size - lastIndex
val callingMethod: StackTraceElement = stacktraceElements(callingMethodIndex)
val enclosingClassName = EnclosingClass(callingMethod.getClassName)
val fileName = (enclosingClassName.fullName.replace('.', '/') + ".scala").split("\\/").takeRight(1).mkString
val lineNumber = callingMethod.getLineNumber - 1
LogInfo(enclosingClassName, fileName, lineNumber)
end getLogInfo

def getName(): String = name

private def log(level: LogLevel, msg: String) =
logger.foreach(_.doLog(level, msg)(using getLogInfo()).unsafeRunSync())
def info(msg: String): Unit = log(LogLevel.Info, msg)
def debug(msg: String): Unit = log(LogLevel.Debug, msg)
def error(msg: String): Unit = log(LogLevel.Error, msg)
def trace(msg: String): Unit = log(LogLevel.Trace, msg)
def warn(msg: String): Unit = log(LogLevel.Warn, msg)

private def throwableMessage(msg: String, throwable: Throwable) = s"$msg " + Try(throwable.getMessage).getOrElse("")

def debug(msg: String, obj: Object): Unit = debug(s"$msg $obj")
def debug(msg: String, obj1: Object, obj2: Object): Unit = debug(s"$msg $obj1, $obj2")
def debug(msg: String, objs: Array[? <: Object]): Unit = debug(s"$msg ${objs.mkString(", ")}")
def debug(msg: String, throwable: Throwable): Unit = debug(throwableMessage(msg, throwable))
def debug(x$0: org.slf4j.Marker, msg: String): Unit = debug(msg)
def debug(x$0: org.slf4j.Marker, msg: String, obj: Object): Unit = debug(s"$msg, $obj")
def debug(x$0: org.slf4j.Marker, msg: String, obj1: Object, obj2: Object): Unit = debug(s"$msg, $obj1, $obj2")
def debug(x$0: org.slf4j.Marker, msg: String, objs: Array[? <: Object]): Unit = debug(s"$msg ${objs.mkString(", ")}")
def debug(x$0: org.slf4j.Marker, msg: String, throwable: Throwable): Unit = debug(throwableMessage(msg, throwable))

def error(msg: String, obj: Object): Unit = error(s"$msg $obj")
def error(msg: String, obj1: Object, obj2: Object): Unit = error(s"$msg $obj1, $obj2")
def error(msg: String, objs: Array[? <: Object]): Unit = error(s"$msg ${objs.mkString(", ")}")
def error(msg: String, throwable: Throwable): Unit = error(throwableMessage(msg, throwable))
def error(x$0: org.slf4j.Marker, msg: String): Unit = error(msg)
def error(x$0: org.slf4j.Marker, msg: String, obj: Object): Unit = error(s"$msg $obj")
def error(x$0: org.slf4j.Marker, msg: String, obj1: Object, obj2: Object): Unit = error(s"$msg $obj1, $obj2")
def error(x$0: org.slf4j.Marker, msg: String, objs: Array[? <: Object]): Unit = error(s"$msg ${objs.mkString(", ")}")
def error(x$0: org.slf4j.Marker, msg: String, throwable: Throwable): Unit = error(throwableMessage(msg, throwable))

def info(msg: String, obj: Object): Unit = info(s"$msg, $obj")
def info(msg: String, obj1: Object, obj2: Object): Unit = info(s"$msg, $obj1, $obj2")
def info(msg: String, objs: Array[? <: Object]): Unit = info(s"$msg ${objs.mkString(", ")}")
def info(msg: String, throwable: Throwable): Unit = info(throwableMessage(msg, throwable))
def info(x$0: org.slf4j.Marker, msg: String): Unit = info(msg)
def info(x$0: org.slf4j.Marker, msg: String, obj: Object): Unit = info(s"$msg, $obj")
def info(x$0: org.slf4j.Marker, msg: String, obj1: Object, obj2: Object): Unit = info(s"$msg, $obj1, $obj2")
def info(x$0: org.slf4j.Marker, msg: String, objs: Array[? <: Object]): Unit = info(s"$msg ${objs.mkString(", ")}")
def info(x$0: org.slf4j.Marker, msg: String, throwable: Throwable): Unit = info(throwableMessage(msg, throwable))

def trace(msg: String, obj: Object): Unit = trace(s"$msg, $obj")
def trace(msg: String, obj1: Object, obj2: Object): Unit = trace(s"$msg, $obj1, $obj2")
def trace(msg: String, objs: Array[? <: Object]): Unit = trace(s"$msg ${objs.mkString(", ")}")
def trace(msg: String, throwable: Throwable): Unit = trace(throwableMessage(msg, throwable))
def trace(x$0: org.slf4j.Marker, msg: String): Unit = trace(msg)
def trace(x$0: org.slf4j.Marker, msg: String, obj: Object): Unit = trace(s"$msg, $obj")
def trace(x$0: org.slf4j.Marker, msg: String, obj1: Object, obj2: Object): Unit = trace(s"$msg, $obj1, $obj2")
def trace(x$0: org.slf4j.Marker, msg: String, objs: Array[? <: Object]): Unit = trace(s"$msg ${objs.mkString(", ")}")
def trace(x$0: org.slf4j.Marker, msg: String, throwable: Throwable): Unit = trace(throwableMessage(msg, throwable))

def warn(msg: String, obj: Object): Unit = warn(s"$msg, $obj")
def warn(msg: String, obj1: Object, obj2: Object): Unit = warn(s"$msg, $obj1, $obj2")
def warn(msg: String, objs: Array[? <: Object]): Unit = warn(s"$msg ${objs.mkString(", ")}")
def warn(msg: String, throwable: Throwable): Unit = warn(throwableMessage(msg, throwable))
def warn(x$0: org.slf4j.Marker, msg: String): Unit = warn(msg)
def warn(x$0: org.slf4j.Marker, msg: String, obj: Object): Unit = warn(s"$msg, $obj")
def warn(x$0: org.slf4j.Marker, msg: String, obj1: Object, obj2: Object): Unit = warn(s"$msg, $obj1, $obj2")
def warn(x$0: org.slf4j.Marker, msg: String, objs: Array[? <: Object]): Unit = warn(s"$msg ${objs.mkString(", ")}")
def warn(x$0: org.slf4j.Marker, msg: String, throwable: Throwable): Unit = warn(throwableMessage(msg, throwable))

private def testLevel(logLevel: LogLevel): Boolean = true

def isDebugEnabled(): Boolean = testLevel(LogLevel.Debug)
def isErrorEnabled(): Boolean = testLevel(LogLevel.Error)
def isInfoEnabled(): Boolean = testLevel(LogLevel.Info)
def isTraceEnabled(): Boolean = testLevel(LogLevel.Trace)
def isWarnEnabled(): Boolean = testLevel(LogLevel.Warn)

def isInfoEnabled(x$0: org.slf4j.Marker): Boolean = isInfoEnabled()
def isTraceEnabled(x$0: org.slf4j.Marker): Boolean = isTraceEnabled()
def isDebugEnabled(x$0: org.slf4j.Marker): Boolean = isDebugEnabled()
def isErrorEnabled(x$0: org.slf4j.Marker): Boolean = isErrorEnabled()
def isWarnEnabled(x$0: org.slf4j.Marker): Boolean = isWarnEnabled()

class WoofLogger(name: String) extends Logger with Slf4jWoofLoggerImpl[IO, Marker](name):
override def logger: Option[WLogger[IO]] = WoofLogger.logger
override def dispatcher: Option[Dispatcher[IO]] = WoofLogger.dispatcher
end WoofLogger

object WoofLogger:
private given IORuntime = global
var logger: Option[WLogger[IO]] = None
var logger: Option[WLogger[IO]] = None
var dispatcher: Option[Dispatcher[IO]] = None
end WoofLogger

extension (w: WLogger[IO]) def registerSlf4j: IO[Unit] = IO.delay(WoofLogger.logger = Some(w))
extension (w: WLogger[IO])
def registerSlf4j(using d: Dispatcher[IO]): IO[Unit] = IO.delay {
WoofLogger.logger = Some(w)
WoofLogger.dispatcher = Some(d)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.slf4j.impl

import cats.effect.IO
import cats.effect.std.Dispatcher
import org.legogroup.woof.slf4j.WoofLogger
import org.slf4j.spi.LoggerFactoryBinder
import org.slf4j.{ILoggerFactory, Logger, LoggerFactory}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package org.legogroup.woof.slf4j
import cats.Id
import cats.effect.IO
import cats.effect.kernel.Clock
import cats.effect.std.Dispatcher
import org.legogroup.woof.*
import org.slf4j.LoggerFactory
import org.slf4j.impl.StaticLoggerBinder
@@ -13,7 +14,9 @@ class Slf4jSuite extends munit.CatsEffectSuite:

override def munitIOTimeout = 10.minutes

test("should log stuff") {
val dispatcher = ResourceFunFixture(Dispatcher.sequential[IO](true))

dispatcher.test("should log stuff") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)
given Filter = Filter.everything
given Clock[IO] = leetClock
@@ -26,12 +29,12 @@ class Slf4jSuite extends munit.CatsEffectSuite:
result <- stringOutput.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [INFO ] org.legogroup.woof.slf4j.Slf4jSuite: HELLO, SLF4J! (Slf4jSuite.scala:25)\n",
"1987-05-31 13:37:00 [INFO ] org.legogroup.woof.slf4j.Slf4jSuite: HELLO, SLF4J! (Slf4jSuite.scala:28)\n",
)
end for
}

test("should log arrays of objects") {
dispatcher.test("should log arrays of objects") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)
given Filter = Filter.everything
given Clock[IO] = leetClock
@@ -44,12 +47,12 @@ class Slf4jSuite extends munit.CatsEffectSuite:
result <- stringOutput.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [INFO ] org.legogroup.woof.slf4j.Slf4jSuite: HELLO, ARRAYS! 1, Some(42), List(1337) (Slf4jSuite.scala:43)\n",
"1987-05-31 13:37:00 [INFO ] org.legogroup.woof.slf4j.Slf4jSuite: HELLO, ARRAYS! 1, Some(42), List(1337) (Slf4jSuite.scala:46)\n",
)
end for
}

test("should respect log levels") {
dispatcher.test("should respect log levels") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)
given Filter = Filter.exactLevel(LogLevel.Warn)
given Clock[IO] = leetClock
@@ -65,12 +68,12 @@ class Slf4jSuite extends munit.CatsEffectSuite:
result <- stringWriter.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [WARN ] org.legogroup.woof.slf4j.Slf4jSuite: WARN, SLF4J! (Slf4jSuite.scala:63)\n",
"1987-05-31 13:37:00 [WARN ] org.legogroup.woof.slf4j.Slf4jSuite: WARN, SLF4J! (Slf4jSuite.scala:66)\n",
)
end for
}

test("should not fail on null throwable") {
dispatcher.test("should not fail on null throwable") { implicit dispatcher =>
given Printer = NoColorPrinter(testFormatTime)
given Filter = Filter.everything
given Clock[IO] = leetClock
@@ -84,7 +87,7 @@ class Slf4jSuite extends munit.CatsEffectSuite:
result <- stringWriter.get
yield assertEquals(
result,
"1987-05-31 13:37:00 [DEBUG] org.legogroup.woof.slf4j.Slf4jSuite: null exception (Slf4jSuite.scala:83)\n",
"1987-05-31 13:37:00 [DEBUG] org.legogroup.woof.slf4j.Slf4jSuite: null exception (Slf4jSuite.scala:86)\n",
)
end for
}