Telemetry Guide
Vona can export backend distributed traces through the optional a-telemetry module. It is disabled by default and keeps Winston as the application logging API.
Enable tracing
Configure the following deployment environment variables:
TELEMETRY_ENABLED=true
TELEMETRY_SERVICE_NAME=cabloy-basic
TELEMETRY_OTLP_HTTP_URL=https://collector.example.com/v1/traces
TELEMETRY_OTLP_HTTP_HEADERS=authorization=Bearer%20token
TELEMETRY_SAMPLING_ROOT_RATIO=0.1
# Optional: continue trusted internal HTTP traces only
TELEMETRY_INGRESS_TRUSTED_PROXY_CIDRS=10.0.0.0/8
TELEMETRY_INGRESS_INTERNAL_HEADER=x-vona-telemetry-ingress
TELEMETRY_INGRESS_INTERNAL_HEADER_VALUE=internalThe module exports traces through OTLP/HTTP protobuf. Use an OpenTelemetry Collector as the stable integration boundary; the Collector may export to Tempo, Jaeger, or a managed observability platform.
TELEMETRY_SAMPLING_ROOT_RATIO is the root sampling ratio. Begin with a low value such as 0.01 to 0.1, then increase only after measuring exporter drops, CPU, memory, and trace volume.
Propagation
The module uses W3C Trace Context:
- public HTTP ingress ignores caller-supplied
traceparentandtracestate, so local root sampling always applies - a trusted internal ingress may continue W3C trace context only when its direct socket peer matches
TELEMETRY_INGRESS_TRUSTED_PROXY_CIDRSand the protected classification header has the configured value - outgoing queue jobs and Redis Broadcast messages carry a versioned technical trace carrier
- queue and Broadcast consumers create child spans in a new Vona context
- internal
performAction(...)calls create an internal child span
The trusted-CIDR list defaults to empty. Keep it empty unless a controlled reverse proxy or gateway is responsible for classifying internal traffic. The application evaluates the direct socket peer, not ctx.innerAccess, ctx.ip, or generic proxy settings. A trusted proxy must remove any client-provided copy of the classification header and overwrite it with its own decision before forwarding the request. When public and internal traffic use the same proxy, CIDR matching alone is insufficient; the protected header is required as the second trust condition.
Vona emits x-request-id for HTTP requests. It is a request diagnostic identifier and is different from OpenTelemetry trace_id and span_id.
Existing domain correlationId values remain business or idempotency identifiers. Do not replace them with trace IDs or automatically attach raw business IDs to span attributes.
Logging correlation
When an active telemetry span is present, existing Vona log entries receive these technical fields:
request_id
trace_id
span_id
trace_flagsContinue to use $logger and $loggerChild(...). Do not create request-specific cached loggers.
Custom module spans
Use the global telemetry facade from a container-managed bean when adding a bounded custom operation:
return await this.bean.telemetry.withNamedSpan('payment.validate', async () => {
return await this._validatePayment();
});All facade methods are safe to call without checking enabled. When telemetry is disabled, startSpan(...) returns a non-recording span, withSpan(...) directly invokes its callback, and recordException(...) does nothing; no trace is created, activated, propagated, or exported.
withNamedSpan(...) is the preferred API: it creates an active child span, records thrown errors, and always attempts to end the span. For an operation whose lifecycle must be controlled manually, use startSpan(...), withSpan(...), and recordException(...) without an enabled branch:
const span = this.bean.telemetry.startSpan('payment.validate');
try {
return await this.bean.telemetry.withSpan(span, () => this._validatePayment());
} catch (error) {
this.bean.telemetry.recordException(span, error);
throw error;
} finally {
span.end();
}The facade intentionally does not expose carrier propagation, HTTP ingress trust, HTTP span handling, or provider lifecycle. Use a stable, bounded operation name and follow the privacy and cardinality rules below.
Privacy and cardinality
The built-in spans use HTTP method, route templates, status codes, module/controller/action names, queue names, Broadcast names, and retry counts. They do not record request or response bodies, cookies, authorization headers, raw query values, user IDs, tenant names, or business document IDs.
Only add business attributes after an explicit privacy, tenancy, cardinality, and retention review. Prefer bounded operation categories over opaque identifiers.
Operations
Every Vona worker owns and flushes its own exporter. Export failures and shutdown timeouts must not fail user requests. Configure Collector-side tail sampling for error or slow-trace retention instead of trying to force sampling in application code.
Browser telemetry, WebSocket message tracing, database instrumentation, and third-party SDK instrumentation are intentionally separate follow-up work.