Skip to main content

otel

OpenTelemetry integration for Webda — exports traces, metrics, and logs to any OTLP-compatible backend (Jaeger, Prometheus, Grafana, Datadog, etc.) with zero instrumentation code.

When to use it​

  • You want distributed tracing across Webda HTTP requests, store operations, and queue workers without manual span management.
  • You need to export metrics (request counts, durations) and structured logs to an OpenTelemetry collector.
  • You are deploying on a platform with OpenTelemetry support (GCP, AWS ADOT, Grafana Cloud, Datadog).

Install​

pnpm add @webda/otel

Configuration​

{
"services": {
"otel": {
"type": "OtelService",
"name": "my-webda-app",
"traceExporter": {
"type": "otlp",
"enable": true,
"sampling": 0.1
},
"metricExporter": {
"type": "otlp",
"enable": true
},
"loggerExporter": {
"enable": true,
"type": "otlp",
"url": "http://otel-collector:4317"
}
}
}
}
ParameterTypeDefaultRequiredDescription
namestringapp nameNoService name reported to OTLP as service.name
traceExporter.type"otlp" | "console""otlp"NoTrace export destination
traceExporter.enablebooleantrueNoEnable/disable trace exporting
traceExporter.samplingnumber0.01NoTrace sampling rate (0.0–1.0)
metricExporter.type"otlp" | "console""otlp"NoMetric export destination
metricExporter.enablebooleantrueNoEnable/disable metric exporting
loggerExporter.enablebooleantrueNoEnable/disable log exporting
loggerExporter.urlstring"http://localhost:4317"NoOTLP gRPC endpoint for log export
diagnosticstring"NONE"NoOTel SDK diagnostic level: NONE, ERROR, WARN, INFO, DEBUG, ALL
webTracing.allowedEmailsstring[][]NoEmails allowed to enable web tracing, regular expressions matching the whole email, case-insensitively ("*": any authenticated user). Empty: nobody
webTracing.durationnumber900NoTrace token lifetime in seconds
trustedTraceSourcesstring[][]NoIPs or CIDRs whose traceparent and baggage are always honored

Request tracing​

Webda starts the server span of every request itself, on every entry point: HttpServer (HTTP/1.1, HTTP/2, h2c), GrpcService, the AWS LambdaServer and the AzureFunctionServer. The incoming spans of the HTTP instrumentation and the AWS Lambda instrumentation are disabled. It records http.server.request.duration (seconds) or rpc.server.duration (milliseconds) for every request, traced or not.

An outside caller cannot force sampling or inject baggage. The incoming W3C traceparent and baggage are honored only in these cases:

RequesttraceparentbaggageSpan session.id
From a trustedTraceSources addresshonored (sampled flag too)honored, propagatedfrom baggage if present
Valid, unexpired X-Webda-Trace tokentrace and parent ids honored, always recorded (an unsampled parent is treated as sampled); none or invalid: a new root, always recordedhonored, propagated, session.id rewritten to the token sessiontoken session
Anything else (including an invalid or expired token)ignored: new root, sampling ratiodroppednone

The rows are checked in order: a request from a trusted source is handled as trusted even when it carries a token (its session.id then comes from its baggage only).

The client address is the one HttpServer resolves through its trustedProxies. X-Webda-Trace is never sent on outgoing calls; traceparent, tracestate and baggage are.

The tracing headers (traceparent, tracestate, baggage, X-Webda-Trace) are added to the CORS allowed headers. CORS response headers are only emitted for the origins accepted by a CORS filter: list the UI origins in the website parameter (exact origins, https://*.example.com for subdomains, or "*"), for example:

{
"parameters": {
"website": ["https://app.example.com"]
}
}

Services with the cors-filter capability, or useRouter().registerCORSFilter(), can accept more origins.

A @grpc/grpc-js server created by the application itself (not GrpcService) is still instrumented by the gRPC instrumentation, which honors any incoming trace context: do not expose such a server to untrusted callers, or disable @opentelemetry/instrumentation-grpc.

On AWS Lambda, spans are exported in batches and the runtime may freeze before they are sent: export them with a SimpleSpanProcessor (override getSpanProcessors) or flush the tracer provider at the end of the invocation.

On-demand web tracing​

An allowed user (webTracing.allowedEmails, or override canEnableWebTracing) calls the Otel.EnableWebTracing operation:

POST /otel/tracing
{ "sessionId": "optional-session-to-join" }

sessionId is 1 to 128 characters among A-Z a-z 0-9 . _ : - (anything else is a 400). It answers { token, sessionId, expiresAt }. The browser then sends X-Webda-Trace: <token>, a sampled traceparent and baggage: session.id=<sessionId> on its requests: every span of those requests carries session.id, so one session can be searched in the tracing backend. The token is signed with the application keys for the webda-trace audience: it is not a session token.

Upgrading​

Behaviour changes for existing @webda/otel users:

  • trustedTraceSources defaults to []: an upstream traceparent sent by another of your services is no longer honored until its addresses are listed. Otherwise the cross-service traces are split, each service starting its own root.
  • traceExporter.sampling now applies to every root span, the auto-instrumented HTTP and database spans included (a ParentBased ratio sampler).
  • The incoming SERVER spans of the HTTP instrumentation, and the span previously started by the router, are replaced by the Webda SERVER span described above.
  • On Lambda, an OPTIONS request without an Origin header no longer gets Access-Control-Max-Age and Access-Control-Allow-Headers.
  • The Router now emits the CORS response headers itself, for the origins accepted by a CORS filter, and the website parameter now registers one (matched on the request Origin header).

Profiling​

Continuous profiling is not done in process: run the OpenTelemetry collector eBPF profiler next to the application to export OTLP profiles, and correlate them with these traces.

Usage​

// No code changes needed — add the service to webda.config.json and it
// auto-instruments HTTP requests, MongoDB, and other supported libraries
// via @opentelemetry/auto-instrumentations-node.

// To instrument custom code, use the OpenTelemetry API directly:
import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("my-service");

async function expensiveOperation(): Promise<void> {
const span = tracer.startSpan("expensiveOperation");
try {
// ... work
} finally {
span.end();
}
}

// Logs from @webda/workout are automatically forwarded to the OTLP log exporter
// when loggerExporter is enabled — no extra code needed.

Reference​

  • API reference: see the auto-generated typedoc at docs/pages/Modules/otel/.
  • Source: packages/otel
  • Related: @webda/workout for the Webda logging system whose output this module exports; @webda/debug for local development tracing without an OTLP backend.

Classes​