Architecture¶
setupTracing assembles a tracer provider, registers it as the global provider, and registers the instrumentations against it. Everything the OpenTelemetry Node SDK needs is configured in one place. A caller passes a service name and an endpoint, not a pipeline.
The pipeline¶
flowchart LR
A[Application] --> B[Instrumentations]
B --> C[TracerProvider]
C --> D[BatchSpanProcessor]
D --> E[OTLPTraceExporter<br/>gRPC]
E --> F[Collector]
B --> G[MeterProvider]
G --> H[PeriodicExportingMetricReader]
H --> I[OTLPMetricExporter<br/>gRPC]
I --> F
B --> J[LoggerProvider]
J --> K[BatchLogRecordProcessor]
K --> L[OTLPLogExporter<br/>gRPC]
L --> F
R[Resource detectors] --> C
R --> G
R --> J
| Stage | Component | Configuration |
|---|---|---|
| Provider | TracerProvider |
Resource from detectors, merged with the explicit service and container attributes |
| Processor | BatchSpanProcessor |
maxQueueSize 4096, maxExportBatchSize 1024, scheduledDelayMillis 2000, exportTimeoutMillis 10000 |
| Exporter | OTLPTraceExporter |
OTLP over gRPC, timeoutMillis 10000, concurrencyLimit from the options (default 10) |
| Registration | trace, context and propagation APIs |
Global tracer provider, context manager and propagators, see Propagation |
| Meter provider | MeterProvider |
Same resource as the tracer provider, registered as the global meter provider |
| Metric reader | PeriodicExportingMetricReader |
exportIntervalMillis from the options (default 60000) |
| Metric exporter | OTLPMetricExporter |
OTLP over gRPC, cumulative temporality, metricsUrl from the options (default the trace endpoint) |
| Logger provider | LoggerProvider |
Same resource as the tracer provider, registered as the global logger provider |
| Log processor | BatchLogRecordProcessor |
maxQueueSize 4096, maxExportBatchSize 1024, scheduledDelayMillis 2000, exportTimeoutMillis 10000 |
| Log exporter | OTLPLogExporter |
OTLP over gRPC, logsUrl from the options (default the trace endpoint) |
The processor batches spans instead of exporting them one at a time, which delays a span's arrival in the backend by up to scheduledDelayMillis after it ends. Shutdown covers the flush a process needs before it exits.
Metrics¶
The instrumentations record their measurements against whichever meter provider is registered when registerInstrumentations runs. The HTTP and undici instrumentations record request duration histograms. The AWS SDK instrumentation records Bedrock token usage and operation duration. Where no meter provider is registered, those instruments come from the no-op meter and every measurement is discarded.
setupTracing registers a MeterProvider and passes it to registerInstrumentations. That turns the measurements into exported metrics. RuntimeNodeInstrumentation joins them for the metrics no span can carry: event loop delay and utilisation, garbage collection duration, and heap occupancy.
Recording happens outside the sampler. The HTTP instrumentation records the duration after the span ends and never consults the sampling decision, so metrics describe every request while traces describe a sampled subset.
Logs¶
The Pino instrumentation does two separate things. Log correlation adds trace_id, span_id and trace_flags to each record written to the application's own stream. Log sending routes a copy of each record to the OpenTelemetry logs API, and it is on by default.
Log sending needs a registered logger provider to reach a backend. Without one, the record is still parsed and rebuilt as a LogRecord, then handed to a no-op logger. Every log line pays that cost. Nothing arrives. setupTracing registers a LoggerProvider, which turns the work into records delivered over OTLP, and it sets disableLogSending when enableLogs is false so the work stops instead of continuing for nothing.
Shared resource¶
The resource is built once and passed to all three providers. Grafana pairs a metric and a log line with a trace on service.name, and that pairing needs the three to be identical.
Resource attributes¶
The resource is built in two steps. Detection runs first, then the explicit attributes are merged on top. An explicitly passed service name wins over one the environment detector found.
| Source | Attributes |
|---|---|
envDetector |
Anything set in OTEL_RESOURCE_ATTRIBUTES |
hostDetector |
host.name, host.arch |
osDetector |
os.type, os.version |
processDetector |
process.pid, process.command, process.runtime.* |
serviceInstanceIdDetector |
service.instance.id |
| Explicit | service.name, and container.name when a hostname is resolved |
Where no hostname is passed and neither CONTAINER_NAME nor HOSTNAME is set, container.name is omitted from the resource altogether. It is never written as an undefined value.
Propagation¶
setupTracing registers the AsyncLocalStorageContextManager as the global context manager, and a composite propagator of W3C Trace Context and W3C Baggage as the global propagator. An incoming request carrying traceparent continues the caller's trace, and every outgoing HTTP or fetch call injects one.
Propagation is what pairs a caller's client span with the callee's server span. A service graph is built from those pairs, so no propagation means no graph.
Service graph attributes¶
Tempo names a service graph node from peer.service, and no instrumentation emits that attribute. The library sets it in the request hooks instead.
| Call | Where peer.service comes from |
|---|---|
| Outgoing HTTP | The host of the outgoing ClientRequest, matched against the known peer list |
| Outgoing fetch | The origin of the undici request, matched against the same list |
| IORedis | Set to redis unconditionally |
| AWS SDK | The AWS service name from the request, lower cased |
The known peer list holds elasticsearch and redis. A host containing either substring sets both peer.service and db.system.name to that value.
Only outgoing requests carry host, so the HTTP hook returns without setting anything when there is none. That keeps server spans out of the graph as peers. peer.service has to name the remote service being called, never the local one.
Idempotency¶
The provider is held in module scope. setupTracing returns early when it is already set, logging a warning and returning a tracer from the existing provider. Repeated initialisation cannot register a second set of instrumentations, or a second exporter, against the same process.
stopTracing shuts each provider down in turn, then clears every module scope reference and unregisters the global meter and logger providers, since the API will not replace either while one is in place. A later setupTracing call then builds a fresh pipeline. Shutdown covers what each step flushes.
Diagnostics¶
The OpenTelemetry diagnostic logger is set to a console logger at INFO level when the module is imported, before setupTracing runs. Exporter failures, instrumentation warnings and the messages this library emits all reach the console through it.