Otel.SDK.Logs.LogRecordExporter.Console (otel v0.2.0)

Copy Markdown View Source

Standard-output LogRecordExporter for debugging (logs/sdk_exporters/stdout.md).

Outputs each Otel.SDK.Logs.LogRecord to stdout in a human-readable single-line format. Spec L16-L18 — "This exporter is intended for debugging and learning purposes. It is not recommended for production use. The output format is not standardized and can change at any time."

Spec L29-L34 — recommended pairing is Otel.SDK.Logs.LogRecordProcessor.Simple. The spec SHOULD is conditional on the SDK providing an auto-configuration mechanism (e.g. OTEL_LOGS_EXPORTER); this SDK does not currently, so the pairing is informational rather than mandated.

Output format

Single line per record, prefixed with [otel]. Fields are joined with single spaces; absent fields (empty scope) are dropped entirely so no double-space gaps appear.

[otel] <severity> [scope=<name>] trace=<hex> span=<hex> body=<inspect> attributes=<inspect>
  • severity — combined display per data-model.md §Displaying Severity L365-L372: short name derived from severity_number (e.g. INFO), with severity_text appended in parentheses when both are present (INFO (info)). Falls back to severity_text alone when severity_number == 0, the short name alone when severity_text is empty, and UNSPECIFIED when both are the proto3 zero value. The UNSPECIFIED choice follows data-model.md L298-L302 option 1 (distinct display of missing severity) over option 2 (interpret as INFO) — a debug exporter benefits from preserving the source signal rather than fabricating a level.
  • scope — emitted only when scope.name is non-empty.
  • trace context — always rendered as 32-hex trace_id and 16-hex span_id. When no Context is active, Otel.SDK.Logs.LogRecord defaults both to the all-zeros invalid sentinel; the field is still emitted so the absence is visible at a glance, matching Otel.SDK.Trace.SpanExporter.Console.

Concurrency

Spec logs/sdk.md L559-L560 — "Each implementation MUST document the concurrency characteristics the SDK requires of the exporter." Console is stateless (the user's config is opaque, never mutated):

  • export/2 writes to :stdio via IO.puts/1. The OTel SDK's bundled processors (Simple, Batch) serialise export/2 calls per spec L572-L573, so Console assumes a single export/2 in flight at a time. IO.puts/1 itself is thread-safe (the IO server serialises writes), so even concurrent invocation by a non-bundled processor would not corrupt output, only interleave records.
  • force_flush/1 and shutdown/1 are no-ops and therefore safe to call concurrently with export/2 per spec L659-L660.

Shutdown semantics

Spec L637-L639 says "After the call to Shutdown subsequent calls to Export are not allowed and SHOULD return a Failure result." Console does not enforce this internally — shutdown/1 returns :ok without mutating state, and export/2 continues to work afterward. The LogRecordExporter behaviour returns :ok | {:error, term()} from shutdown/1 (no new state), so tracking the shut-down flag would require side-channel storage (process dictionary, ETS, or a GenServer wrapper) that is overkill for a debug stdout exporter. The bundled processors never call export/2 after shutdown/1 by construction, so the SHOULD is satisfied at the processor layer in practice.

Public API

FunctionRole
init/1, export/2, force_flush/1, shutdown/1SDK (Console implementation)

References

  • OTel Logs SDK Stdout: opentelemetry-specification/specification/logs/sdk_exporters/stdout.md
  • SeverityNumber short-name table: opentelemetry-specification/specification/logs/data-model.md §Displaying Severity L338-L363
  • Parent behaviour: Otel.SDK.Logs.LogRecordExporter