Otel.Logs.LogRecordStorage (otel v0.4.1)

Copy Markdown View Source

ETS-backed FIFO queue for log records awaiting export.

Each row is {key, %Otel.Logs.LogRecord{}, inserted_at_ms} where the key is :erlang.unique_integer([:positive]) — a positive integer guaranteed to be unique within the BEAM lifetime — and inserted_at_ms is the millisecond timestamp stamped at insert/1 time, used solely by the sweep loop. The underlying table is :set (hash-based), matching Otel.Trace.SpanStorage's shape and giving O(1) insert in the hot emit path. Take order is hash-iteration order (not emission order); this is fine because each log record carries its own timestamp / observed_timestamp and the collector orders by those.

Logs are atomic events — there is no :active / :completed lifecycle like spans. Records enter via insert/1 and leave via take/1 when the exporter drains them.

Public API

FunctionRole
insert/1enqueue a log record (back-pressure aware)
take/1take + delete a batch of oldest records (Exporter only)

Mutation flow used by Otel.Logs.Logger:

Otel.Logs.LogRecordStorage.insert(record)

No get or update — log records are immutable once enqueued.

Concurrency

Multi-writer + single-reader (the Exporter):

Backpressure

insert/1 silently drops the record when the ETS table is already at @max_queue_size, matching the spec's maxQueueSize parameter for the Batching processor (logs/sdk.md L540-L541 "After the size is reached logs are dropped"). Drop is a normal lifecycle event, not a failure — callers don't branch on the result.

Sweep — stale records

The GenServer runs a self-scheduled sweep every @sweep_interval_ms (10 minutes) that issues a single :ets.select_delete/2 removing rows whose inserted_at_ms (row position 3) is older than @log_record_ttl_ms (30 minutes). This is the safety net for records that never get drained — if Otel.Logs.LogRecordExporter is dead or hung, records would otherwise pile up until the @max_queue_size backpressure starts dropping fresh records. With the sweep, the oldest stale records age out instead so fresh inserts keep landing.

Defaults match Otel.Trace.SpanStorage's sweeper for symmetry. Sweep strategy is drop only — exporting half-aged records serves no spec purpose.

References

Summary

Functions

Returns a specification to start this module under a supervisor.

Enqueue a log record. Always returns :ok — silent drop when the table is at @max_queue_size (spec logs/sdk.md L540-L541 "After the size is reached logs are dropped": drop is a normal lifecycle event, not a failure).

Take up to n log records, deleting them from the table. Records come back in hash-iteration order (not emission order). Called only by Otel.Logs.LogRecordExporter (single reader).

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

insert(record)

@spec insert(log_record :: Otel.Logs.LogRecord.t()) :: :ok

Enqueue a log record. Always returns :ok — silent drop when the table is at @max_queue_size (spec logs/sdk.md L540-L541 "After the size is reached logs are dropped": drop is a normal lifecycle event, not a failure).

start_link(opts \\ [])

@spec start_link(opts :: keyword()) :: GenServer.on_start()

take(n)

@spec take(n :: pos_integer()) :: [Otel.Logs.LogRecord.t()]

Take up to n log records, deleting them from the table. Records come back in hash-iteration order (not emission order). Called only by Otel.Logs.LogRecordExporter (single reader).