OpenTelemetry Express.js instrumentation

Vladimir Mihailenco
January 28, 2026
7 min read

By combining OpenTelemetry with Express.js, you can collect and export telemetry data, including traces, metrics, and logs, to gain insight into the behavior and performance of your application.

Quick Setup

StepActionCode/Command
1. InstallInstall OpenTelemetry packagesnpm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
2. ConfigureCreate otel.js with SDK configurationSee Usage section below
3. RunStart your app with the --require flagnode --require ./otel.js app.js
4. VerifyCheck your observability backend for tracesTraces collected automatically

Minimal working example:

js
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')

const sdk = new NodeSDK({
  serviceName: 'my-express-app',
  instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()

This configuration automatically instruments Express.js, HTTP requests, and common Node.js libraries without additional code changes.

What is Express.js?

Express.js is a fast and minimalist web application framework for Node.js, a JavaScript runtime. It simplifies the development of web applications and APIs by providing a robust set of features and utilities, while maintaining a lightweight and unobtrusive approach.

Express.js is widely recognized for its simplicity, flexibility, and extensibility, making it a popular choice for building server-side applications in Node.js.

What is OpenTelemetry?

OpenTelemetry is an open-source observability framework that aims to standardize and simplify the collection, processing, and export of telemetry data from applications and systems.

OpenTelemetry supports multiple programming languages and platforms, making it suitable for a wide range of applications and environments.

OpenTelemetry enables developers to instrument their code and collect telemetry data, which can then be exported to various OpenTelemetry backends or observability platforms for analysis and visualization. The OpenTelemetry architecture follows a modular design with SDKs, APIs, and exporters as its core components.

Express.js instrumentation

Using the OpenTelemetry Node.js library, you can easily add distributed tracing capabilities to your Express.js applications.

To instrument an Express.js app, you need to install OpenTelemetry Node.js SDK and available instrumentations:

shell
# Using npm
npm install @opentelemetry/sdk-node @opentelemetry/api @opentelemetry/auto-instrumentations-node

# Using yarn
yarn add @opentelemetry/sdk-node @opentelemetry/api @opentelemetry/auto-instrumentations-node

You can let OpenTelemetry instrument your application automatically or do it explicitly by installing required instrumentations:

shell
# Using npm
npm install @opentelemetry/instrumentation-http @opentelemetry/instrumentation-express

# Using yarn
yarn add @opentelemetry/instrumentation-http @opentelemetry/instrumentation-express

Usage

After installing OpenTelemetry, you need to configure OpenTelemetry SDK to export data to an OpenTelemetry backend for storage and visualization.

js
'use strict'

const otel = require('@opentelemetry/api')
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base')
const { Resource } = require('@opentelemetry/resources')
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http')
const { AWSXRayIdGenerator } = require('@opentelemetry/id-generator-aws-xray')
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
const {
  ExpressInstrumentation,
} = require('@opentelemetry/instrumentation-express')

const dsn = process.env.UPTRACE_DSN
console.log('using dsn:', dsn)

const exporter = new OTLPTraceExporter({
  url: 'http://localhost:14318/v1/traces',
  headers: { 'uptrace-dsn': dsn },
  compression: 'gzip',
})
const bsp = new BatchSpanProcessor(exporter, {
  maxExportBatchSize: 1000,
  maxQueueSize: 1000,
})

const sdk = new NodeSDK({
  spanProcessor: bsp,
  resource: new Resource({
    'service.name': 'myservice',
    'service.version': '1.0.0',
  }),
  idGenerator: new AWSXRayIdGenerator(),
  instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
})
sdk.start()

To avoid potential issues, it is strongly recommended to put the OpenTelemetry initialization code into a separate file called otel.js and use the --require flag to load the tracing code before the application code:

shell
# JavaScript
node --require ./otel.js app.js

# TypeScript
ts-node --require ./otel.ts app.ts

See OpenTelemetry Express.js example for details.

Instrumentation Options

The Express instrumentation supports several configuration options for customizing behavior:

OptionDescription
ignoreLayersArray of Express layer names to ignore (e.g., router, middleware)
ignoreLayersTypeArray of layer types to ignore: middleware, router, request_handler
requestHookHook for adding custom attributes to request spans
spanNameHookCustomize how span names are generated

Filtering health checks

Use the ignoreIncomingRequestHook option from the HTTP instrumentation to exclude certain endpoints from tracing:

js
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express')

const httpInstrumentation = new HttpInstrumentation({
  ignoreIncomingRequestHook: (request) => {
    // Return true to ignore this request
    const ignorePatterns = ['/health', '/ready', '/metrics', '/favicon.ico']
    return ignorePatterns.some(pattern => request.url?.includes(pattern))
  },
})

const expressInstrumentation = new ExpressInstrumentation({
  // Ignore specific middleware layers
  ignoreLayers: ['query', 'expressInit'],
  ignoreLayersType: ['middleware'],
})

const sdk = new NodeSDK({
  instrumentations: [httpInstrumentation, expressInstrumentation],
})

Adding custom attributes

Use the requestHook option to add custom attributes to request spans:

js
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express')

const expressInstrumentation = new ExpressInstrumentation({
  requestHook: (span, info) => {
    span.setAttribute('http.request.id', info.request.headers['x-request-id'])
    span.setAttribute('express.route.params', JSON.stringify(info.request.params))
  },
})

Custom span names

Customize how span names are generated for Express routes:

js
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express')

const expressInstrumentation = new ExpressInstrumentation({
  spanNameHook: (info, defaultName) => {
    // Use custom naming convention
    return `${info.request.method} ${info.layerPath || info.route}`
  },
})

HTTP Metrics

OpenTelemetry automatically collects HTTP server metrics when you enable metrics instrumentation:

MetricDescription
http.server.request.durationDuration of HTTP server requests
http.server.request.body.sizeSize of HTTP server request bodies
http.server.response.body.sizeSize of HTTP server response bodies
http.server.active_requestsNumber of concurrent active requests

To enable metrics collection:

js
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics')
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http')
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express')

const sdk = new NodeSDK({
  serviceName: 'my-express-app',
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: 'http://localhost:4318/v1/metrics',
    }),
    exportIntervalMillis: 10000,
  }),
  instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
})

Creating Custom Spans

In addition to automatic instrumentation, you can create custom spans to trace specific operations:

js
const { trace, SpanStatusCode } = require('@opentelemetry/api')

const tracer = trace.getTracer('my-express-app')

app.get('/users/:id', async (req, res) => {
  // Create a custom span for database operation
  const span = tracer.startSpan('fetch-user-from-database')
  try {
    span.setAttribute('user.id', req.params.id)
    const user = await db.findUser(req.params.id)
    span.setAttribute('user.found', !!user)
    res.json(user)
  } catch (error) {
    span.recordException(error)
    span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
    res.status(500).json({ error: 'Internal server error' })
  } finally {
    span.end()
  }
})

Error Handling

OpenTelemetry Express instrumentation automatically captures errors. You can also manually record exceptions:

js
const { trace, SpanStatusCode } = require('@opentelemetry/api')

app.use((err, req, res, next) => {
  const span = trace.getActiveSpan()
  if (span) {
    span.recordException(err)
    span.setStatus({ code: SpanStatusCode.ERROR, message: err.message })
  }
  res.status(500).json({ error: 'Something went wrong' })
})

What is Uptrace?

Uptrace is a OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues. For Node.js instrumentation, see the OpenTelemetry JavaScript guide.

Uptrace Overview

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.

Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.

In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.

FAQ

What is @opentelemetry/instrumentation-express? The @opentelemetry/instrumentation-express package is the official OpenTelemetry library for automatically instrumenting Express.js applications. It creates spans for incoming HTTP requests, middleware execution, and route handlers without requiring manual instrumentation code.

How do I filter health check endpoints from traces? Use the ignoreIncomingRequestHook option from @opentelemetry/instrumentation-http to filter requests by URL path. Return true for paths like /health or /ready to exclude them from tracing.

Does Express instrumentation work with async/await? Yes, OpenTelemetry Express instrumentation fully supports async route handlers and middleware. Context propagation works correctly across async boundaries.

How do I trace requests across microservices? OpenTelemetry automatically propagates trace context through HTTP headers. When one Express service calls another, the trace context is included in the request headers, allowing you to see the full distributed trace across services.

What's the performance impact of OpenTelemetry? OpenTelemetry is designed for production use with minimal overhead. Using batch processors and appropriate sampling rates keeps the performance impact under 5% in most applications.

Can I use OpenTelemetry with Express middleware like Morgan? Yes, OpenTelemetry works alongside existing middleware. The instrumentation hooks into Express at a lower level and doesn't interfere with logging middleware like Morgan or compression middleware.

What's next?

By integrating OpenTelemetry with Express.js, you can gain insight into your application's distributed traces, OpenTelemetry metrics, and logs. This helps you understand the behavior of your Express.js application across multiple services or microservices, and facilitates troubleshooting and performance optimization.

Next steps to enhance your observability: