Created
September 16, 2025 08:03
-
-
Save iRevive/8e82a7df34af15c4bec10cfe03ab5130 to your computer and use it in GitHub Desktop.
otel4s + log4cats example
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
| import java.io.{PrintWriter, StringWriter} | |
| import cats.Functor | |
| import cats.effect.{Async, IO, IOApp, Resource} | |
| import cats.syntax.all._ | |
| import cats.effect.std.Dispatcher | |
| import org.typelevel.log4cats.{LoggerFactory, SelfAwareStructuredLogger} | |
| import org.typelevel.otel4s.{AnyValue, Attribute, Attributes} | |
| import org.typelevel.otel4s.logs.{Logger, LoggerProvider, Severity} | |
| import org.typelevel.otel4s.sdk.OpenTelemetrySdk | |
| import org.typelevel.otel4s.semconv.attributes.ExceptionAttributes | |
| import org.typelevel.otel4s.sdk.exporter.otlp.autoconfigure.OtlpExportersAutoConfigure | |
| object Main extends IOApp.Simple { | |
| def run: IO[Unit] = | |
| OpenTelemetrySdk | |
| .autoConfigured[IO](_.addExportersConfigurer(OtlpExportersAutoConfigure[IO])) | |
| .flatMap(otel4s => Otel4sLoggerFactory.create(otel4s.sdk.loggerProvider).tupleLeft(otel4s)) | |
| .use { case (otel4s, loggerFactory) => | |
| for { | |
| logger <- loggerFactory.fromName("service.logger") | |
| _ <- logger.info("my first log message") | |
| } yield () | |
| } | |
| } | |
| private final class Otel4sLoggerFactory[F[_]: Functor, Ctx](lp: LoggerProvider[F, Ctx], dispatcher: Dispatcher[F]) | |
| extends LoggerFactory[F] { | |
| def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] = | |
| dispatcher.unsafeRunSync(fromName(name)) | |
| def fromName(name: String): F[SelfAwareStructuredLogger[F]] = | |
| lp.get(name).map(new Otel4sLogger[F, Ctx](_)) | |
| } | |
| object Otel4sLoggerFactory { | |
| def create[F[_]: Async, Ctx](loggerProvider: LoggerProvider[F, Ctx]): Resource[F, LoggerFactory[F]] = | |
| for { | |
| dispatcher <- Dispatcher.sequential[F] | |
| } yield new Otel4sLoggerFactory[F, Ctx](loggerProvider, dispatcher) | |
| } | |
| private final class Otel4sLogger[F[_], Ctx](logger: Logger[F, Ctx]) extends SelfAwareStructuredLogger[F] { | |
| def isTraceEnabled: F[Boolean] = logger.meta.isEnabled // todo: must be managed by the log4cats layer | |
| def isDebugEnabled: F[Boolean] = logger.meta.isEnabled // todo: must be managed by the log4cats layer | |
| def isInfoEnabled: F[Boolean] = logger.meta.isEnabled // todo: must be managed by the log4cats layer | |
| def isWarnEnabled: F[Boolean] = logger.meta.isEnabled // todo: must be managed by the log4cats layer | |
| def isErrorEnabled: F[Boolean] = logger.meta.isEnabled // todo: must be managed by the log4cats layer | |
| def trace(ctx: Map[String, String])(msg: => String): F[Unit] = log(Severity.trace, ctx, None, msg) | |
| def trace(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = log(Severity.trace, ctx, Some(t), msg) | |
| def debug(ctx: Map[String, String])(msg: => String): F[Unit] = log(Severity.debug, ctx, None, msg) | |
| def debug(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = log(Severity.debug, ctx, Some(t), msg) | |
| def info(ctx: Map[String, String])(msg: => String): F[Unit] = log(Severity.info, ctx, None, msg) | |
| def info(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = log(Severity.info, ctx, Some(t), msg) | |
| def warn(ctx: Map[String, String])(msg: => String): F[Unit] = log(Severity.warn, ctx, None, msg) | |
| def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = log(Severity.warn, ctx, Some(t), msg) | |
| def error(ctx: Map[String, String])(msg: => String): F[Unit] = log(Severity.error, ctx, None, msg) | |
| def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit] = log(Severity.error, ctx, Some(t), msg) | |
| def error(t: Throwable)(message: => String): F[Unit] = log(Severity.error, Map.empty, Some(t), message) | |
| def warn(t: Throwable)(message: => String): F[Unit] = log(Severity.warn, Map.empty, Some(t), message) | |
| def info(t: Throwable)(message: => String): F[Unit] = log(Severity.info, Map.empty, Some(t), message) | |
| def debug(t: Throwable)(message: => String): F[Unit] = log(Severity.debug, Map.empty, Some(t), message) | |
| def trace(t: Throwable)(message: => String): F[Unit] = log(Severity.trace, Map.empty, Some(t), message) | |
| def error(message: => String): F[Unit] = log(Severity.error, Map.empty, None, message) | |
| def warn(message: => String): F[Unit] = log(Severity.warn, Map.empty, None, message) | |
| def info(message: => String): F[Unit] = log(Severity.info, Map.empty, None, message) | |
| def debug(message: => String): F[Unit] = log(Severity.debug, Map.empty, None, message) | |
| def trace(message: => String): F[Unit] = log(Severity.trace, Map.empty, None, message) | |
| private def log(severity: Severity, ctx: Map[String, String], error: Option[Throwable], msg: => String): F[Unit] = | |
| logger.logRecordBuilder | |
| .withSeverity(severity) | |
| .withBody(AnyValue.string(msg)) | |
| .addAttributes(ctx.map { case (key, value) => Attribute(key, value) }) | |
| .addAttributes(error.map(exceptionAttributes).getOrElse(Attributes.empty)) | |
| .emit | |
| private def exceptionAttributes(exception: Throwable) = { | |
| val builder = Attributes.newBuilder | |
| builder.addOne( | |
| ExceptionAttributes.ExceptionType, | |
| exception.getClass.getName | |
| ) | |
| val message = exception.getMessage | |
| if (message != null) { | |
| builder.addOne(ExceptionAttributes.ExceptionMessage, message) | |
| } | |
| if (exception.getStackTrace.nonEmpty) { | |
| val stringWriter = new StringWriter() | |
| val printWriter = new PrintWriter(stringWriter) | |
| exception.printStackTrace(printWriter) | |
| builder.addOne( | |
| ExceptionAttributes.ExceptionStacktrace, | |
| stringWriter.toString | |
| ) | |
| } | |
| builder.result() | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment