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

refactor: use scribe for logging #132

Merged
merged 1 commit into from
Dec 27, 2024
Merged
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
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ lazy val http4sServer =
"org.http4s" %% "http4s-ember-server" % Versions.http4sVersion,
"org.http4s" %% "http4s-circe" % Versions.http4sVersion,
"org.http4s" %% "http4s-dsl" % Versions.http4sVersion,
"org.typelevel" %% "log4cats-slf4j" % Versions.log4CatsVersion,
"ch.qos.logback" % "logback-classic" % Versions.logBackVersion
"com.outr" %% "scribe" % Versions.scribeVersion,
"com.outr" %% "scribe-cats" % Versions.scribeVersion
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import org.http4s.ember.server.EmberServerBuilder
import org.http4s.implicits.*
import org.http4s.server.websocket.WebSocketBuilder
import org.typelevel.log4cats.LoggerFactory
import org.typelevel.log4cats.slf4j.Slf4jFactory

object ServerMain extends IOApp {

Expand All @@ -44,7 +43,7 @@ object ServerMain extends IOApp {
def run(args: List[String]): IO[ExitCode] =
// TODO: configurable rules - make this configurable
given rules: Rules = Rules(DeckRule.WithNines)
given LoggerFactory[IO] = Slf4jFactory.create
given LoggerFactory[IO] = utils.logging.ScribeLogger.factory
for {
queue <- Queue.unbounded[IO, IncomingAction[ClientId]]
topic <- Topic[IO, Map[ClientId, Seq[MessageToClient]]]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package io.github.mahh.doko.http4sserver.utils.logging

import cats.syntax.flatMap.catsSyntaxIfM
import cats.effect.Sync
import org.typelevel.log4cats
import org.typelevel.log4cats.LoggerName
import scribe.Level
import scribe.Scribe
import scribe.cats.*
import scribe.format.*
import scribe.mdc.MDC
import scribe.mdc.MDCValue

class ScribeLogger[F[_]: Sync](private val scribeLogger: scribe.Logger)
extends log4cats.SelfAwareStructuredLogger[F] {

private val scribe: Scribe[F] = scribeLogger.f[F]

def error(message: => String): F[Unit] = scribe.error(message)

def warn(message: => String): F[Unit] = scribe.warn(message)

def info(message: => String): F[Unit] = scribe.info(message)

def debug(message: => String): F[Unit] = scribe.debug(message)

def trace(message: => String): F[Unit] = scribe.trace(message)

def error(t: Throwable)(message: => String): F[Unit] = scribe.error(message, t)

def warn(t: Throwable)(message: => String): F[Unit] = scribe.warn(message, t)

def info(t: Throwable)(message: => String): F[Unit] = scribe.info(message, t)

def debug(t: Throwable)(message: => String): F[Unit] = scribe.debug(message, t)

def trace(t: Throwable)(message: => String): F[Unit] = scribe.trace(message, t)

// starting here: SelfAwareLogger implementation
// (not really used by http4s, but required by LoggerFactory type signature)

private def isLevelEnabled(level: Level): F[Boolean] =
Sync[F].delay(scribeLogger.includes(level))

def isTraceEnabled: F[Boolean] = isLevelEnabled(Level.Trace)

def isDebugEnabled: F[Boolean] = isLevelEnabled(Level.Debug)

def isInfoEnabled: F[Boolean] = isLevelEnabled(Level.Info)

def isWarnEnabled: F[Boolean] = isLevelEnabled(Level.Warn)

def isErrorEnabled: F[Boolean] = isLevelEnabled(Level.Error)

// starting here: StructuredLogger implementation (not really used by http4s)
// (not really used by http4s, but required by LoggerFactory type signature)

private[this] def contextLog(
isEnabled: F[Boolean],
ctx: Map[String, String],
logging: () => Unit
): F[Unit] = {
val ifEnabled = Sync[F].delay {
MDC.context(ctx.map { (k, v) => k -> MDCValue(() => v) }.toSeq*) {
logging()
}
}

isEnabled.ifM(
ifEnabled,
Sync[F].unit
)
}

def trace(ctx: Map[String, String])(msg: => String): F[Unit] =
contextLog(isTraceEnabled, ctx, () => scribeLogger.trace(msg))

def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
contextLog(isTraceEnabled, ctx, () => scribeLogger.trace(msg, t))

def debug(ctx: Map[String, String])(msg: => String): F[Unit] =
contextLog(isDebugEnabled, ctx, () => scribeLogger.debug(msg))

def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
contextLog(isDebugEnabled, ctx, () => scribeLogger.debug(msg, t))

def info(ctx: Map[String, String])(msg: => String): F[Unit] =
contextLog(isInfoEnabled, ctx, () => scribeLogger.info(msg))

def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
contextLog(isInfoEnabled, ctx, () => scribeLogger.info(msg, t))

def warn(ctx: Map[String, String])(msg: => String): F[Unit] =
contextLog(isWarnEnabled, ctx, () => scribeLogger.warn(msg))

def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
contextLog(isWarnEnabled, ctx, () => scribeLogger.warn(msg, t))

def error(ctx: Map[String, String])(msg: => String): F[Unit] =
contextLog(isErrorEnabled, ctx, () => scribeLogger.error(msg))

def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] =
contextLog(isErrorEnabled, ctx, () => scribeLogger.error(msg, t))
}

object ScribeLogger:

// do not use default formatter because it tries to print source location which is useless
// because the ScribeLogger wrapper cannot forward the proper sourcecode implicits
private val formatter: Formatter =
formatter"[$date $threadName $level] $messages"

// install the formatter globally (this is ugly, but so far, I did not find a better
// way
scribe.Logger.root
.clearHandlers()
.withHandler(formatter = formatter)
.replace()

def getLogger[F[_]](
implicit f: Sync[F],
name: LoggerName
): log4cats.SelfAwareStructuredLogger[F] =
new ScribeLogger[F](scribe.Logger(name.value))

trait LoggerFactory[F[_]: Sync] extends log4cats.LoggerFactory[F]:

def getLoggerFromName(name: String): LoggerType =
given LoggerName = LoggerName(name)
ScribeLogger.getLogger

def fromName(name: String): F[LoggerType] =
Sync[F].delay(getLoggerFromName(name))

def factory[F[_]: Sync]: LoggerFactory[F] = new LoggerFactory[F] {}
2 changes: 1 addition & 1 deletion project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ object Versions {
val pekkoHttpVersion = "1.1.0"
val catsVersion = "2.12.0"
val logBackVersion = "1.3.14"
val log4CatsVersion = "2.7.0"
val circeVersion = "0.14.10"
val scalaJsDomVersion = "2.8.0"
val laminarVersion = "17.2.0"
val laminextVersion = "0.17.1"
val munitVersion = "1.0.3"
val munitScalacheckVersion = "1.0.0"
val scalacheckDerivedVersion = "0.5.0"
val scribeVersion = "3.15.2"
val http4sVersion = "1.0.0-M44"
}
Loading