Created
March 10, 2026 06:51
-
-
Save iRevive/27333f48cbfc560c16c41287c8a10348 to your computer and use it in GitHub Desktop.
log4cats + otel4s
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.typelevel.log4cats.otel4s | |
| import cats.effect.{IO, IOApp} | |
| import cats.syntax.all._ | |
| import org.typelevel.otel4s.oteljava.OtelJava | |
| import org.typelevel.otel4s.oteljava.context.Context | |
| object Main extends IOApp.Simple { | |
| def run: IO[Unit] = | |
| OtelJava | |
| .autoConfigured[IO]() | |
| .flatMap { otelJava => | |
| implicit val LP = otelJava.loggerProvider | |
| implicit val A = otelJava.localContext | |
| Otel4sLoggerKernel.factory[IO, Context].tupleLeft(otelJava) | |
| } | |
| .use { case (otel4s, factory) => | |
| val logger = factory.getLoggerFromClass(getClass) | |
| otel4s.tracerProvider.get("tracer").flatMap { tracer => | |
| tracer.spanBuilder("test-span").build.surround { | |
| logger.error(new RuntimeException("Oops, something went wrong"))( | |
| "something went wrong" | |
| ) | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.typelevel.log4cats.otel4s | |
| import cats.Monad | |
| import cats.effect.Async | |
| import cats.effect.kernel.Resource | |
| import cats.effect.std.Dispatcher | |
| import cats.mtl.Ask | |
| import cats.syntax.all._ | |
| import org.typelevel.log4cats._ | |
| import org.typelevel.otel4s.logs.{LoggerProvider, Severity, Logger => Otel4sLogger} | |
| import org.typelevel.otel4s.semconv.attributes.CodeAttributes | |
| import org.typelevel.otel4s.{AnyValue, Attribute, Attributes} | |
| import scala.util.chaining._ | |
| private class Otel4sLoggerKernel[F[_], Otel4sCtx, LogCtx]( | |
| underlying: Otel4sLogger[F, Otel4sCtx] | |
| )(implicit | |
| F: Monad[F], | |
| M: Attributes.Make[Map[String, LogCtx]], | |
| A: Ask[F, Otel4sCtx] | |
| ) extends LoggerKernel[F, LogCtx] { | |
| def log( | |
| level: KernelLogLevel, | |
| record: Log.Builder[LogCtx] => Log.Builder[LogCtx] | |
| ): F[Unit] = { | |
| val logRecord = record(Log.mutableBuilder[LogCtx]()).build() | |
| val severity = level match { | |
| case KernelLogLevel.Trace => Severity.trace | |
| case KernelLogLevel.Debug => Severity.debug | |
| case KernelLogLevel.Info => Severity.info | |
| case KernelLogLevel.Warn => Severity.warn | |
| case KernelLogLevel.Error => Severity.error | |
| case KernelLogLevel.Fatal => Severity.fatal | |
| } | |
| val attributes = Attributes.from(logRecord.context) | |
| A.ask | |
| .flatMap(ctx => underlying.meta.isEnabled(ctx, Some(severity), None)) | |
| .flatMap { | |
| case true => | |
| underlying.logRecordBuilder | |
| .withSeverity(severity) | |
| .withSeverityText(level.name) | |
| .withBody(AnyValue.string(logRecord.message())) | |
| .pipe(b => logRecord.timestamp.fold(b)(t => b.withTimestamp(t))) | |
| .pipe(b => logRecord.throwable.fold(b)(t => b.withException(t))) | |
| .pipe(b => | |
| logRecord.className.fold(b)(c => b.addAttribute(Attribute("code.class.name", c))) | |
| ) | |
| .pipe(b => | |
| logRecord.fileName.fold(b)(f => b.addAttribute(CodeAttributes.CodeFilePath(f))) | |
| ) | |
| .pipe(b => | |
| logRecord.methodName.fold(b)(f => b.addAttribute(CodeAttributes.CodeFunctionName(f))) | |
| ) | |
| .pipe(b => | |
| logRecord.line.fold(b)(f => b.addAttribute(CodeAttributes.CodeLineNumber(f.toLong))) | |
| ) | |
| .addAttributes(attributes) | |
| .emit | |
| case false => | |
| F.unit | |
| } | |
| } | |
| } | |
| object Otel4sLoggerKernel { | |
| def factory[F[_], Otel4sCtx](implicit | |
| F: Async[F], | |
| LP: LoggerProvider[F, Otel4sCtx], | |
| A: Ask[F, Otel4sCtx] | |
| ): Resource[F, LoggerFactoryGen[F]] = | |
| for { | |
| dispatcher <- Dispatcher.sequential[F](await = false) | |
| } yield new Factory(dispatcher, LP) | |
| class Factory[F[_]: Monad, Otel4sCtx]( | |
| dispatcher: Dispatcher[F], | |
| provider: LoggerProvider[F, Otel4sCtx] | |
| )(implicit A: Ask[F, Otel4sCtx]) | |
| extends LoggerFactoryGen[F] { | |
| type LoggerType = Logger[F] | |
| private implicit val make: Attributes.Make[Map[String, String]] = | |
| map => Attributes.fromSpecific(map.map { case (k, v) => Attribute(k, v) }) | |
| def getLoggerFromName(name: String): Logger[F] = | |
| dispatcher.unsafeRunSync(fromName(name)) | |
| def fromName(name: String): F[Logger[F]] = | |
| for { | |
| logger <- provider.get(name) | |
| } yield Logger.wrap(new Otel4sLoggerKernel[F, Otel4sCtx, String](logger)) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment