NAME
Punk::Plugin::OpenTelemetry - OpenTelemetry for a Punk application
SYNOPSIS
package MyApp;
use Punk;
use Punk::Plugin::OpenTelemetry;
plugin 'OpenTelemetry';
otel service_name => 'checkout',
endpoint => 'http://collector:4318';
get '/orders/:id' => sub {
my ($c) = @_;
$c->otel; # the tracer
$c->render(json => { ok => 1 });
};
Or entirely from the environment, with no code at all:
OTEL_SERVICE_NAME=checkout \
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 \
plackup -s Hyperman app.psgi
DESCRIPTION
Registering this plugin turns on server, client and database spans, the metrics the HTTP conventions ask for, and log records correlated by trace id. The instrumentation goes through C ABI observer tables in Punk, Fetch and DBIx::Loop, so an instrumented request pays no Perl frame for being instrumented, and an unsampled one allocates nothing at all.
CONFIGURATION
Three sources, in this order:
otel keyword > punk.yml otel: block > OTEL_* environment > default
The specification defines three configuration interfaces - programmatic, environment variable and declarative file - and says programmatic configuration is the foundation the others should be built on. It states no precedence between programmatic and environment configuration, and gives exactly one precedence rule: a declarative config file takes precedence over the SDK configuration environment variables. The order above matches the spec where it speaks and follows its stated principle where it does not. It is also Punk's own convention, which layers punk.yml under what the app class declared.
The punk.yml otel: block is not the spec's declarative configuration format, and the two should not be conflated. Supporting OTEL_EXPERIMENTAL_CONFIG_FILE is separate work; were it added, that file would take precedence over the OTEL_* variables as the spec requires, and would sit between the punk.yml block and the environment.
# punk.yml
otel:
service_name: checkout
endpoint: http://collector:4318
sampler: traceidratio
sampler_arg: 0.05
The environment
The OTEL_* variables are read by "from_env" in Punk::OpenTelemetry::Config, once, at boot. Re-reading %ENV per request would be both slower and wrong: a worker that picked up a mid-flight change would disagree with its siblings, and telemetry that disagrees about its own configuration is worse than telemetry that is uniformly stale.
Turning it off
OTEL_SDK_DISABLED=true is checked before anything is built and before a single hook is registered. Nothing is allocated, the request path is not wrapped, and the process is not paying for telemetry it is not sending.
The value is the spec's boolean, not Perl truth: only the string true, case-insensitively, disables the SDK. OTEL_SDK_DISABLED=false does not, and it would under any looser rule.
Credentials
OTEL_EXPORTER_OTLP_HEADERS carries the token the exporter authenticates with. Header values are never printed in the boot diagnostic, never written to a span attribute and never included in a self-diagnostic. The diagnostic prints the header count, because "did my credentials arrive" is a real question and a number answers it without answering anything else.
THE BOOT DIAGNOSTIC
One line at info, stating whether it is enabled, the service name, the protocol, the endpoint, the sampler and its argument, and the propagators. Almost every OpenTelemetry support question is answered by those six facts, and almost no SDK prints them.
OpenTelemetry enabled service=checkout protocol=http/protobuf
endpoint=http://collector:4318 sampler=traceidratio:0.05
propagators=tracecontext,baggage
THE FORK TRAP
The resource is built at boot, in the parent, and every attribute on it is inherited by every worker - which is right for all of them except one. service.instance.id must differ per worker, so each takes a fresh one from Hyperman's on_worker_start.
This is the single most common way a home-grown metrics layer is broken, and it is invisible: a collector receiving several workers' cumulative series under one identity does not report a conflict, it resolves it, and the numbers come out wrong by a factor of however many workers are running.
KEYWORDS
otel %opt
Records configuration. Declaring it more than once merges, so a base class can set the service name and a subclass add the endpoint. Called with no arguments after the plugin is registered, it returns the tracer.
HELPERS
$c->otel
The tracer.
$c->otel_meter
The meter, when the metrics signal is on.
SEE ALSO
Punk::OpenTelemetry, Punk::OpenTelemetry::Config, Punk::OpenTelemetry::Instrument.
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)