NAME
Punk::OpenTelemetry - OpenTelemetry for Punk: traces, metrics and logs over OTLP
VERSION
Version 0.04
SYNOPSIS
package MyApp;
use Punk;
use Punk::Plugin::OpenTelemetry;
otel service_name => 'checkout';
plugin 'OpenTelemetry';
get '/orders/:id' => sub {
my ($c) = @_;
$c->json({ id => $c->param('id') });
};
That is the whole of it. Every request is now a span named for its route pattern, every outbound call a child span with the traceparent on the wire, every query a span carrying the prepared statement, and batches leave for the collector on a timer.
Or with no code at all, which is how a deployment usually wants it:
OTEL_SERVICE_NAME=checkout \
OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 \
plackup -s Hyperman app.psgi
DESCRIPTION
An OpenTelemetry SDK for Punk: the tracer and its spans, the OTLP encoders and transports, context propagation, resource detection, metrics, log records, and the plugin that wires all of it into an application.
There is a working demonstration in example/ - an instrumented application and a collector that prints what it receives to STDERR, so the whole path is visible without a vendor account.
What you get by registering the plugin
Server spans named for the route pattern,
"GET /users/:id"and never"GET /users/7". The span opens before routing, when only the method is known, and is renamed once the pattern exists. Naming it after the path would be a cardinality mistake that no dashboard recovers from.Client spans for outbound HTTP, with the
traceparentinjected on the way out. Without that injection the far side starts a new trace and the two halves of a call are never joined.Database spans carrying the prepared statement. Bind values are not passed to an observer at all, so the literal data cannot leak by accident.
Metrics and log records when they are asked for, the logs correlated to the trace that produced them.
Batching, on the worker's own event loop. Nothing is sent from the request path and nothing is waited on, so a collector that is down costs the application nothing.
Both OTLP transports, and gRPC
Protobuf over HTTP is the default, because it is three to five times smaller on the wire than the JSON form. JSON is supported for the times when being able to read a payload matters more than its size, which is most of them when something has gone wrong. OTLP/gRPC is there too, as the narrow fixed use of HTTP/2 that it is rather than as a framework dependency.
THE MODULES
Start with the plugin. The rest is what it is made of, and is documented because a telemetry layer nobody can read is a telemetry layer nobody can trust.
Punk::Plugin::OpenTelemetry - the plugin, the
otelkeyword, the punk.yml block, the boot diagnostic and the fork trap. Read this one.Punk::OpenTelemetry::Config - every
OTEL_*variable with its default, and the precedence between the three places configuration comes from. Read this one too, before deploying.Punk::OpenTelemetry::Tracer - spans, sampling and the batch queue.
Punk::OpenTelemetry::Instrument - what is instrumented, and the two rules it follows.
Punk::OpenTelemetry::Exporter - OTLP over HTTP: endpoints, what a response means, retries and backoff.
Punk::OpenTelemetry::GRPC - the same, over gRPC.
Punk::OpenTelemetry::Encode and Punk::OpenTelemetry::OTLP - the payload shape and the two encoders that render it.
Punk::OpenTelemetry::Propagate - W3C Trace Context and Baggage, B3 and Jaeger, behind one composite propagator.
Punk::OpenTelemetry::Resource - what produced this telemetry, and why
service.instance.idhas to be taken after a fork.Punk::OpenTelemetry::Meter and Punk::OpenTelemetry::Logs - the other two signals.
Punk::OpenTelemetry::Schema - converting a payload between semantic convention versions.
IF NOTHING ARRIVES
There is no default endpoint. With none configured the SDK builds, instruments the request path, records spans and drops them for want of anywhere to send them - it does not fall back to http://localhost:4318 the way some other SDKs do. That is the first thing to check, because everything else looks healthy when it happens.
The boot diagnostic prints the endpoint it resolved, along with the service name, the protocol, the sampler and the propagators. Read that line before reading anything else.
TURNING IT OFF
OTEL_SDK_DISABLED=true
Answered before anything is built and before a single hook is registered: no tracer, no exporter, nothing in the request path. An SDK that still builds a tracer and throws the spans away has not been disabled, it has been made pointless.
The value is the specification's boolean and not Perl's truth: only the string true, case-insensitively. Under any looser rule OTEL_SDK_DISABLED=false would switch telemetry off, using the word an operator reaches for to switch it on.
SEE ALSO
The OpenTelemetry specification: https://opentelemetry.io/docs/specs/otel/, and the OTLP protocol: https://opentelemetry.io/docs/specs/otlp/.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-punk-opentelemetry at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Punk-OpenTelemetry. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Punk::OpenTelemetry
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
https://rt.cpan.org/NoAuth/Bugs.html?Dist=Punk-OpenTelemetry
Search CPAN
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)