Three signals.
One
language.

observe

Punk::Observe receives OpenTelemetry, stores it, queries it and draws it. Traces, metrics and logs land in a storage engine built for each signal’s shape, and one query language spans all three - so a spike, the traces that caused it and the lines those traces logged are one expression, not three tabs and a copied trace id.

It mounts into an application you already have, as a plugin, under a guard you control.

use Punk::Plugin::Observe;

plugin 'Observe' => {
    guard  => 'Web::Auth#observe_admin',   # required, like Queue's
    store  => '/var/lib/punk-observe',
    ingest => { prefix => '/v1', keys => '/etc/punk-observe/keys' },
    limits => { series => 1_000_000, rate_records => 50_000 },
};

The whole point

Three stages re-key the stream, and they are the reason this is one language rather than three sharing a prompt box. A source, then stages; every stage takes rows and returns rows, so where, by and count are one implementation each. The signals differ only in which columns exist.

metric http.server.request.count | where http.route = "/checkout" | bucket(5m) p99
  | exemplars     # the trace ids recorded alongside the spike
  | traces        # those traces, in full
  | logs          # every log line correlated by trace_id

| traces needs a trace identifier on its rows. Logs and spans already have one; a metric stream does not, which is what | exemplars is for - it re-keys the stream onto the trace ids recorded alongside the points. So metric x | traces is a parse error, and the error tells you to add | exemplars first. It is the feature, so it is discoverable by typing rather than by reading a manual.

The Observe query explorer: an OQL expression, its result table and the chart drawn from it
Explore - one language over all three signals

What you get

Sending it something

The receiver speaks the protocol the SDKs already speak, so there is nothing to instrument twice and no collector in the middle. An application already exporting through the OpenTelemetry Collector points that here as one more OTLP endpoint.

From Perl, Punk::OpenTelemetry is the client half and has had nowhere to export to since its first release. This is where.

# Any OpenTelemetry SDK, in any language, points at it the same way.
export OTEL_EXPORTER_OTLP_ENDPOINT=https://myapp.example/v1
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $INGEST_KEY"

The screens

All of it served by the plugin, inside your application, wearing your guard. No second service to deploy and no dashboard tool to point at it.

The Observe status screen: ingest rates per signal, store size, segment counts and the overflow counter
Status - what is arriving and what it costs
Inspect your income traces
Inspect incoming traces
A trace waterfall: spans laid out by start and duration with their service and attributes
One trace - the waterfall
The log explorer: a filtered stream with severities, the live tail running and a line expanded in context
Logs - filtered, and live
The service map: services as nodes, calls as edges, error rate on each
The service map
A dashboard of saved panels, each one an OQL query
Dashboards - saved panels, each one a query
The alerts screen: rules with their per-series state, ok, pending and firing
Alerts - ok, pending, firing, per series

What it is not

Alert evaluation and delivery run as jobs on the host application’s Punk::Queue, so a queue worker has to exist - which any queue-using Punk application already has. Rules and dashboards live in a configuration database the plugin migrates.

Get it

cpanm Punk::Observe

Artistic License 2.0, like the rest of it. Needs Punk, and Punk::Queue for alerting. SQLite by default for the configuration database, Postgres when told.