# OpenTelemetry

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

Source: [https://askrjs.com/docs/platform-services/open-telemetry](https://askrjs.com/docs/platform-services/open-telemetry)

Status: stable. Packages: @askrjs/otel.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

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

```tsx
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.

## Documentation navigation

[Previous](https://askrjs.com/docs/platform-services/internationalization/index.md) | [Next](https://askrjs.com/docs/components/index.md)
