NAME

Punk::OpenTelemetry::Exporter - OTLP over HTTP

SYNOPSIS

my $exp = Punk::OpenTelemetry::Exporter->new(
    endpoint => 'http://localhost:4318',
    protocol => 'http/protobuf',
    headers  => { 'x-api-key' => $key },
);

my $bytes = $exp->encode(traces => $payload);

DESCRIPTION

The OTLP/HTTP transport: endpoint resolution, what a response means, the partial-success readers, Retry-After and the backoff policy.

The object is a blessed hash and stays one, with the keys new has always set. $e->{protocol} and $e->{stats}{dropped}++ are part of the interface, not an implementation detail that happened to be visible.

Endpoints

endpoint  => 'http://collector:4318'         # /v1/traces is APPENDED
endpoints => { traces => 'https://x/ingest' } # used EXACTLY as given

That asymmetry is in the spec and surprises everybody once. A per-signal endpoint keeps whatever path it has, because collectors are routinely deployed behind a path prefix and appending to it would break every one of them.

What a response means

  • 2xx is success - unless the body carries a partial_success naming rejected spans, which is not a failure and not retryable, but has to be counted. Data silently disappearing while every dashboard stays green is the worst failure mode this component has.

  • 429, 502, 503, 504 are retryable, honouring Retry-After.

  • Everything else is permanent. Drop it, count it, and do not spend the next hour asking a collector that has already said no.

Backoff

Exponential from one second to a thirty second ceiling, with full jitter. The jitter is not decoration: a fleet of workers that all failed at the same moment and all back off by the same amount retries in a thundering herd, which is how a collector that was briefly slow stays down. backoff is pure, so the policy is tested directly rather than inferred from timings.

METHODS

new(%opt)

endpoint, endpoints, protocol (http/protobuf or http/json), headers, timeout (default 10), compression (none or gzip), max_retries (default 5), ua. An unknown protocol croaks here rather than at the first export. Without a ua one is built: Fetch->new at the configured timeout.

encode($signal, $payload)

The payload as bytes for the configured protocol.

backoff($attempt, $retry_after)

The delay before attempt $attempt. A Retry-After from the server wins outright.

stats

A copy of the counters: what was exported, rejected, dropped, retried, and how many attempts failed. A telemetry layer that cannot report its own losses is asking to be trusted for no reason.

SEE ALSO

Punk::OpenTelemetry::GRPC, the same job over gRPC, and Punk::OpenTelemetry::Encode, which renders what this sends. Punk::Plugin::OpenTelemetry is what drives it, and Punk::OpenTelemetry::Config documents the OTEL_EXPORTER_OTLP_* variables that configure it.

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)