NAME

Punk::OpenTelemetry::Logs - the logs signal

SYNOPSIS

my $logs = Punk::OpenTelemetry::Logs->new(
    resource   => $resource,
    scope_name => 'Punk::OpenTelemetry',
);

$logs->emit('error', 'db down', { dsn => $dsn }, $span);

my $payload = $logs->drain;
my $bytes   = Punk::OpenTelemetry::Encode::logs_protobuf($payload);

DESCRIPTION

The cheapest of the three signals, because Punk 0.19 already did the hard part: a log record is a message plus fields, which is exactly what an OTLP LogRecord is. What remains is severity, trace correlation, and a queue.

A TAP, NOT A REPLACEMENT

These records are a copy. The application's logs still go wherever they were going - stderr, psgix.logger, a to coderef - and the collector gets a duplicate.

Letting the exporter take over the sink is the obvious implementation and it is wrong twice over: a telemetry layer would be silently redirecting an operator's logs, and the failure mode when its collector is unreachable would be that the logs vanish. Nobody should have to choose between having their logs and exporting them.

Punk 0.20's pk_abi v3 on_log is the registration point, and it is a tap for exactly this reason.

SEVERITY

Punk has five levels; OTLP has a 24-point scale in bands of four.

trace  1     debug  5     info   9
warn  13     error 17     fatal 21

The mapping picks the first value of each band rather than its middle. That is what every other SDK emits, and it is what a backend filter written as >= 13 compares against - a number in the right band is not enough if the threshold sits at the band start.

An unknown level falls back to info, not to zero: zero is SEVERITY_NUMBER_UNSPECIFIED, which most backends treat as "unfiltered" and would make a mis-spelled level louder rather than quieter.

TRACE CORRELATION

Passing the active span attaches its trace_id and span_id to the record. This is the highest-value, lowest-cost part of the whole signal: it is what makes somebody actually click from a log line into a trace.

trace_id and span_id became reserved keys in Punk::Logger for this, so an application field of the same name cannot forge a correlation and point a reader at somebody else's trace.

A line emitted outside a span carries no ids rather than empty ones.

THE QUEUE

Bounded at 4096 records - larger than the span queue, because logs are far higher volume - dropping the oldest and counting what it dropped. Same arrangement and the same reasoning as "THE QUEUE" in Punk::OpenTelemetry::Tracer, and the queue belongs to the process that filled it, so a forked child starts empty.

THE RECURSION TRAP

The exporter sends over HTTP with Fetch, Fetch is instrumented, and the exporter logs its own failures. Without a guard, the first collector outage becomes an infinite loop of telemetry about failing to send telemetry.

A record emitted while "suppress_begin" in Punk::OpenTelemetry::Instrument is in effect is not queued - so the exporter's own diagnostics reach the operator's log and go no further.

METHODS

new(%opt)

resource, scope_name, scope_version.

emit($level, $body, \%attributes, $span)

Queue one log record. $level is mapped to an OTLP severity by severity, $body is the message, and \%attributes is optional structured detail.

$span is optional and is the part worth passing: it is what stamps the record with the trace and span ids, and so what lets somebody click from a log line into the trace that produced it. It is the highest-value, lowest-cost part of this whole signal.

Emitting while the SDK is exporting is ignored rather than queued, or the exporter's own diagnostics would come back round through here - see "THE RECURSION TRAP".

drain($max)

An OTLP logs payload ready for "logs_protobuf" in Punk::OpenTelemetry::Encode, or undef when there is nothing to send.

severity($level)

The OTLP severity number for a Punk level.

stats

emitted, dropped, queued.

SEE ALSO

Punk::OpenTelemetry::Tracer, which supplies the ids these records are correlated by, and Punk::Plugin::OpenTelemetry, which turns the signal on. Punk::OpenTelemetry is the index.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)