Skip to content

Instrumentation

setupTracing registers every instrumentation below, apart from the three that wait on an option. Each entry lists what this library adds on top of the attributes the instrumentation already emits.

Some instrumentations record metrics alongside their spans. A meter provider has to be registered before those measurements leave the process, and setupTracing registers one unless enableMetrics is false.

Metric Emitted by
http.server.request.duration HTTP
http.client.request.duration HTTP, Undici
gen_ai.client.token.usage, gen_ai.client.operation.duration AWS SDK, for Bedrock calls
Instrumentation Package Enabled
HTTP @opentelemetry/instrumentation-http Always
Undici (fetch) @opentelemetry/instrumentation-undici Always
Express @opentelemetry/instrumentation-express Always
Connect @opentelemetry/instrumentation-connect Always
Pino @opentelemetry/instrumentation-pino Always
AWS SDK @opentelemetry/instrumentation-aws-sdk Always
IORedis @opentelemetry/instrumentation-ioredis Always
Elasticsearch opentelemetry-instrumentation-elasticsearch When the package is installed
Node runtime @opentelemetry/instrumentation-runtime-node enableMetrics
File system @opentelemetry/instrumentation-fs enableFsInstrumentation
DNS @opentelemetry/instrumentation-dns enableDnsInstrumentation

HTTP

An incoming request to a path starting with /metrics or /healthz produces no span. Scrape and probe traffic stays out of the trace store.

Attribute Source Set on
http.request.content_type content-type request header Client and server spans
http.request.content_length content-length request header Client and server spans
http.request_id x-request-id request or response header Client and server spans
http.correlation_id x-correlation-id request header Client and server spans
http.response.content_type content-type response header Client and server spans
http.response.content_length content-length response header Client and server spans
peer.service Remote host of an outgoing request Client spans only
db.system.name Remote host of an outgoing request Client spans only

The hook parses a content length header as an integer and writes it only when the result is a non-negative number. A malformed header is ignored, never recorded as text.

Outgoing and incoming requests expose their headers differently: getHeaders() on a ClientRequest, .headers on an IncomingMessage. The hook reads both, which is why the same attributes appear on each side of a call.

Undici (fetch)

globalThis.fetch runs on undici. Undici never touches the http and https modules that the HTTP instrumentation patches, so without this instrumentation a fetch call produces no client span and injects no traceparent. The callee then starts a fresh trace. Nothing pairs the two services into a service graph edge.

Attribute Source
peer.service The request origin, matched against the known peer list
db.system.name The request origin, matched against the known peer list

Express

The instrumentation creates one span per layer: every middleware, every router, and the request handler. It calls the request hook on each. Only the request handler layer carries the matched route, so the hook records attributes there and returns early everywhere else. Middleware and router spans keep the names the instrumentation gives them, middleware - expressInit and router - /work among them. Those names show where time inside a request went.

Attribute Source
express.route The matched route, when there is one
express.params Route parameters, JSON encoded, when the object is not empty
express.query_keys The names of the query string parameters, sorted
user.id request.user.id, when the application sets one

Query values are never recorded. A query string carries access tokens and personal data, and OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT is unbounded by default, which means a value written to a span attribute is exported in full. The key names describe the shape of a request. The contents stay out.

The server span is named METHOD /route, for example GET /work/:id. That name comes from the HTTP instrumentation, which reads http.route as the response finishes. The Express instrumentation supplies the route it reads.

Connect

The library registers this one with defaults. Its configuration accepts the base options alone, with no request or ignore hooks, so the middleware spans it emits reach the collector untouched.

Pino

The instrumentation injects trace_id, span_id and trace_flags into every log record by default. Grafana needs those ids in the record before it can correlate a log line with its trace. The log hook adds service.name, and a log line then carries the same service identity as the span it belongs to.

It also sends a copy of each record to the OpenTelemetry logs API. setupTracing exports those over OTLP unless enableLogs is false. The application's own stream still receives every record, so the export adds to container logs without changing them.

Field Source
Severity The Pino level, mapped onto the OpenTelemetry severity numbers
Timestamp The record time, converted according to the logger's timestamp function
Body The record message
Trace context The active span, so a record written inside a request carries its trace

Log sending goes through pino.multistream, which arrived in Pino 7. On an older version the instrumentation skips it.

AWS SDK

Setting Value
suppressInternalInstrumentation false, so the underlying HTTP calls are still traced
sqsExtractContextPropagationFromPayload true, so a trace continues across an SQS message
Attribute Source
peer.service The AWS service name, lower cased
aws.service The AWS service name, lower cased
aws.request_id The request id from the response

IORedis

requireParentSpan is false. A Redis command issued outside a request still produces a span.

Attribute Source
peer.service Always redis
db.redis.key The first command argument
db.redis.args_count The argument count, when there is more than one
db.response.type The JavaScript type of the response
db.response.count The response length, when it is an array

The library renames each span to redis.COMMAND, for example redis.SET. db.system.name, db.operation.name and the server.* attributes come from the instrumentation itself.

The statement serialiser truncates each argument to 100 characters and appends an ellipsis. It slices a Buffer argument before decoding it, which avoids converting a large value in full only to discard most of it.

Node runtime

The library registers this one when enableMetrics is set, the default. It produces no spans at all. Its collectors begin sampling the moment it is constructed, so construction waits on the option.

Metric Description
nodejs.eventloop.delay.min, .max, .mean, .stddev, .p50, .p90, .p99 Event loop delay distribution
nodejs.eventloop.utilization Fraction of the loop spent active
nodejs.eventloop.time Time in the loop, split by nodejs.eventloop.state of active or idle
v8js.gc.duration Garbage collection pause duration, by v8js.gc.type
v8js.memory.heap.used, v8js.memory.heap.space.available_size, v8js.memory.heap.space.physical_size Heap occupancy per heap space
v8js.resource.active Active handles and requests, by v8js.resource.type

Event loop saturation slows every operation in a process at once. No span attribute carries it. These metrics exist to make it visible.

Elasticsearch

opentelemetry-instrumentation-elasticsearch is an optional peer dependency. Install it alongside the library to register it:

npm install opentelemetry-instrumentation-elasticsearch --save

It stays optional because it pins @opentelemetry/core to the 1.x line, which carries GHSA-8988-4f7v-96qf. Installing it resolves a second copy of @opentelemetry/core under the instrumentation, and a security audit of the resulting tree reports that advisory.

With the package installed, the library registers it with defaults and each call produces a span named elasticsearch.request.

Attribute Value
db.system elasticsearch
db.operation The client method called
db.statement The serialised query
elasticsearch.request.indices The index the request targets
net.transport, net.peer.name, net.peer.port The connection to the cluster

Those are the superseded 1.x semantic conventions. They differ from the stable names the rest of this library emits.

Without the package, an Elasticsearch call still produces a client span from the HTTP or undici instrumentation, carrying the HTTP attributes and the request timing. The peer service hook matches elasticsearch in the remote host and sets both peer.service and db.system.name to it, so the service graph keeps its edge to the cluster. The query, the operation and the index name are not recorded.

File system

The library leaves this one off by default, and enableFsInstrumentation turns it on. The instrumentation patches fs the moment it is constructed, so construction waits on the option.

DNS

enableDnsInstrumentation turns this one on, and it stays off otherwise. Lookups of localhost, 127.0.0.1 and ::1 are ignored. Its configuration accepts an ignore list alone, with no hooks, so nothing is added to its spans.