Askr documentation
Server & APIs

OpenTelemetry

OpenTelemetry at the Askr server boundary, with validated input and explicit failure states.

Example

Create the function-first telemetry bridge after installing an application-owned OpenTelemetry provider and wrap work at semantic boundaries.

import { createTelemetry } from '@askrjs/otel';

const telemetry = createTelemetry({ tracerName: 'project-api' });
return telemetry.request({ requestId }, () => router.handle(request));

Instrumentation boundary

@askrjs/otel only talks to the standard @opentelemetry/api peer dependency — it never bundles an SDK, span processor, exporter, or vendor backend. Call createTelemetry(options) once during app composition, and every span or log call routes through whatever provider your app registers; without one, the no-op implementation from the standard API absorbs the calls silently. That keeps this package usable in a test suite or a small app with zero telemetry infrastructure attached.

Spans and fields

The returned Telemetry object exposes one helper per lifecycle stage — request, routeMatch, loader, action, apiOperation, queryPrefetch, ssrRender, viteDocument — each wrapping a unit of work and recording it under a fixed askr.* TelemetryOperation name. span(operation, fields, work) is the general form underneath all of them, but operation isn't a free-form string — it's restricted to that same closed set of askr.* operation names, so there's no custom-operation escape hatch here despite span being the lower-level call. TelemetryFields is a narrow, typed shape — requestId, traceId, route, action, status, durationMs — so there's no open-ended bag of attributes to accidentally overload a span with; per-field redaction goes through sanitizeField, capped further by maxFieldLength (256 characters by default). Exceptions are the one thing NOT captured by default: createTelemetry only exports an exception on a span if you supply sanitizeException and it returns a value — without it, a thrown error inside telemetry.span(...) produces no exception record at all, not an auto-redacted one.

Redaction

Because TelemetryFields is a closed interface rather than Record<string, unknown>, there's no field for request bodies, submitted form values, cookies, tokens, or arbitrary user attributes — you literally cannot pass them in through the typed API. The route field is meant to hold a route pattern like /projects/{projectId}, not the raw, potentially sensitive URL a user actually requested. This is what keeps span data safe to forward to third-party backends by default, without a separate scrubbing pass.

Exporter setup

Choosing and configuring an actual OpenTelemetry SDK, processor, and exporter is application work, done in your composition root alongside createTelemetry. TelemetryOptions lets you customize the tracer's tracerName/tracerVersion, plug in a logger callback to mirror span-level events into your own structured logs, and override now for deterministic timing in tests. extract, inject, and withContext round out the object for propagating trace context across process or transport boundaries once a real exporter is wired up.