Bridges Erlang's :logger to OpenTelemetry Logs.

Application env

import Config

config :kernel,
  logger: [
    {:handler, :otel, Otel.LoggerHandler, %{
      config: %{
        scope_name: "my_app",
        scope_version: "1.0.0"
      }
    }}
  ]

At runtime

:logger.add_handler(:otel, Otel.LoggerHandler, %{
  config: %{
    scope_name: "my_app",
    scope_version: "1.0.0"
  }
})

Config keys

All under :config. Optional.

KeyDefaultDescription
scope_name""InstrumentationScope name — your app/library name
scope_version""typically Application.spec(:my_app, :vsn) |> to_string()
scope_schema_url""InstrumentationScope schema URL
scope_attributes%{}InstrumentationScope attributes (primitives + homogeneous arrays)

What gets emitted

LogRecord fieldSource
severity_number / severity_text:logger level (:emergency → 21, :alert → 19, :critical → 18, :error → 17, :warning → 13, :notice → 10, :info → 9, :debug → 5)
body:logger msg field — {:string, _} / {:report, _} / {format, args}. Reports preserve structure; :report_cb callbacks flatten them
timestampmeta.time (µs → ns)
attributesmeta.mfacode.function.name; meta.filecode.file.path; meta.linecode.line.number; meta.domainlog.domain. Other non-reserved meta keys pass through verbatim
exceptionmeta.crash_reason = {exception, stacktrace} → exception struct attached + exception.stacktrace attribute

Reserved meta keys (never emitted as attributes): :time, :gl, :report_cb, :crash_reason.

Pairing with the SDK

Pair with the SDK's processor pipeline (default :batch):

config :otel, logs: [exporter: :otlp, processor: :batch]

See Configuration for Logs-pillar knobs.