Monitor OpenTelemetry Node.js with Uptrace

This guide explains how to configure the OpenTelemetry JavaScript SDK for Node.js to export spans (traces), metrics, and logs using the OTLP/HTTP protocol. You'll learn how to quickly integrate OpenTelemetry into your Node.js applications for effective monitoring and observability.

To learn about the OpenTelemetry API in more detail, see OpenTelemetry JS Tracing API and OpenTelemetry JS Metrics API.

Prerequisites

Before you begin, ensure you have:

  • A Node.js application running on Node.js 16+
  • An Uptrace account with a valid DSN (Data Source Name)
  • Basic familiarity with your application's architecture

Uptrace JS

uptrace-js is a lightweight wrapper around opentelemetry-js that pre-configures the OpenTelemetry SDK to export data to Uptrace. It doesn't add new functionality but simplifies the setup process for your convenience.

Installation

To install @uptrace/node:

shell
# npm
npm install @uptrace/node --save

# yarn
yarn add @uptrace/node --save

Note: You can skip this step if you're already using the OTLP exporter.

Configuration

Configure the Uptrace client using a DSN (Data Source Name) from your project settings page. Replace <FIXME> with your actual Uptrace DSN and myservice with a name that identifies your application.

js
// The very first import must be Uptrace and/or OpenTelemetry.
const uptrace = require('@uptrace/node')
const otel = require('@opentelemetry/api')

// Start OpenTelemetry SDK and invoke instrumentations to patch the code.
uptrace.configureOpentelemetry({
  // Copy your project DSN here or use UPTRACE_DSN env var
  //dsn: '<FIXME>',
  serviceName: 'myservice',
  serviceVersion: '1.0.0',
  deploymentEnvironment: 'production',
})

// Other imports. Express is monkey-patched by OpenTelemetry at this point.
const express = require('express')

// Create a tracer.
const tracer = otel.trace.getTracer('app_or_package_name')

// Start the app.
const app = express()
app.listen(3000)

You can use the following options to configure Uptrace client.

OptionDescription
dsnA data source that is used to connect to uptrace.dev. For example, https://<token>@uptrace.dev/<project_id>.
serviceNameservice.name resource attribute. For example, myservice.
serviceVersionservice.version resource attribute. For example, 1.0.0.
deploymentEnvironmentdeployment.environment resource attribute. For example, production.
resourceAttributesAny other resource attributes.
resourceResource contains attributes representing an entity that produces telemetry. Resource attributes are copied to all spans and events.

You can also use environment variables to configure the client:

Env varDescription
UPTRACE_DSNA data source that is used to connect to uptrace.dev. For example, https://<token>@uptrace.dev/<project_id>.
OTEL_RESOURCE_ATTRIBUTESKey-value pairs to be used as resource attributes. For example, service.name=myservice,service.version=1.0.0.
OTEL_SERVICE_NAME=myserviceSets the value of the service.name resource attribute. Takes precedence over OTEL_RESOURCE_ATTRIBUTES.
OTEL_PROPAGATORSPropagators to be used as a comma separated list. The default is tracecontext,baggage.

See OpenTelemetry documentation for details.

Best practices

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

If you are using AWS Lambda, you need to set the NODE_OPTIONS environment variable:

shell
export NODE_OPTIONS="--require otel.js"

See express-pg for a working example.

Quickstart

Follow this 5-minute guide to install the OpenTelemetry distro, generate your first trace, and view it in the Uptrace dashboard.

Step 0: Create Uptrace Project

Create an Uptrace project to obtain a DSN (connection string), for example: https://<secret>@api.uptrace.dev?grpc=4317.

Step 1: Install Dependencies

Install @uptrace/node:

shell
# npm
npm install @uptrace/node --save

# yarn
yarn add @uptrace/node --save

Step 2: Create Example

Copy the code to main.js:

js
'use strict'

// The very first import must be Uptrace/OpenTelemetry.
const otel = require('@opentelemetry/api')
const uptrace = require('@uptrace/node')

// Start OpenTelemetry SDK and invoke instrumentations to patch the code.
uptrace.configureOpentelemetry({
  // Set dsn or UPTRACE_DSN env var.
  //dsn: '<FIXME>',
  serviceName: 'myservice',
  serviceVersion: '1.0.0',
})

// Create a tracer. Usually, tracer is a global variable.
const tracer = otel.trace.getTracer('app_or_package_name', '1.0.0')

// Create a root span (a trace) to measure some operation.
tracer.startActiveSpan('main-operation', (main) => {
  tracer.startActiveSpan('GET /posts/:id', (child1) => {
    child1.setAttribute('http.method', 'GET')
    child1.setAttribute('http.route', '/posts/:id')
    child1.setAttribute('http.url', 'http://localhost:8080/posts/123')
    child1.setAttribute('http.status_code', 200)
    child1.recordException(new Error('error1'))
    child1.end()
  })

  tracer.startActiveSpan('SELECT', (child2) => {
    child2.setAttribute('db.system', 'mysql')
    child2.setAttribute('db.statement', 'SELECT * FROM posts LIMIT 100')
    child2.end()
  })

  // End the span when the operation we are measuring is done.
  main.end()

  console.log(uptrace.traceUrl(main))
})

setTimeout(async () => {
  // Send buffered spans and free resources.
  await uptrace.shutdown()
})

Step 3: Run the Example

Execute the example, replacing <FIXME> with your Uptrace DSN:

shell
$ UPTRACE_DSN="<FIXME>" node main.js
https://app.uptrace.dev/traces/<trace_id>

Step 4: View the Trace

Click the generated link to view your trace in the Uptrace dashboard:

Already using OTLP exporter?

If you are already using OTLP exporter, you can continue to use it with Uptrace by changing some configuration options.

To maximize performance and efficiency, consider the following recommendations when configuring OpenTelemetry SDK.

RecommendationSignalsSignificance
Use BatchSpanProcessor to export multiple spans in a single request.AllEssential
Enable gzip compression to compress data before sending and reduce traffic costs.AllEssential
Prefer delta metrics temporality, because such metrics are smaller and Uptrace converts cumulative metrics to delta anyway.MetricsRecommended
Prefer Protobuf over JSON as a generally more efficient encoding. Feel free to continue using JSON encoding with JavaScript.AllRecommended
Prefer HTTP over gRPC as a more mature transport with better compatibility.AllRecommended
Use AWS X-Ray ID generator for OpenTelemetry.Traces, LogsOptional

To configure OpenTelemetry to send data to Uptrace, use the provided endpoint and pass the DSN via uptrace-dsn header:

TransportEndpointPort
gRPChttps://api.uptrace.dev:43174317
HTTPShttps://api.uptrace.dev443

Most languages allow to configure OTLP exporter using environment variables:

shell
# Uncomment the appropriate protocol for your programming language.
# Only for OTLP/gRPC
#export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.uptrace.dev:4317"
# Only for OTLP/HTTP
#export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.uptrace.dev"

# Pass Uptrace DSN in gRPC/HTTP headers.
export OTEL_EXPORTER_OTLP_HEADERS="uptrace-dsn=<FIXME>"

# Enable gzip compression.
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip

# Enable exponential histograms.
export OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=BASE2_EXPONENTIAL_BUCKET_HISTOGRAM

# Prefer delta temporality.
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=DELTA

When configuring BatchSpanProcessor, use the following settings:

shell
# Maximum allowed time to export data in milliseconds.
export OTEL_BSP_EXPORT_TIMEOUT=10000

# Maximum batch size.
# Using larger batch sizes can cause issues,
# because Uptrace rejects requests larger than 32MB.
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=10000

# Maximum queue size.
# Increase queue size if you have lots of RAM, for example,
# `10000 * number_of_gigabytes`.
export OTEL_BSP_MAX_QUEUE_SIZE=30000

# Max concurrent exports.
# Setting this to the number of available CPUs might be a good idea.
export OTEL_BSP_MAX_CONCURRENT_EXPORTS=2

Exporting traces

Here is how you can export traces to Uptrace following the recommendations above:

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 dsn = process.env.UPTRACE_DSN
console.log('using dsn:', dsn)

const exporter = new OTLPTraceExporter({
  url: 'https://api.uptrace.dev/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(),
})
sdk.start()

const tracer = otel.trace.getTracer('app_or_package_name', '1.0.0')

tracer.startActiveSpan('main', (main) => {
  main.end()
  console.log('trace id:', main.spanContext().traceId)
})

// Send buffered spans.
setTimeout(async () => {
  await sdk.shutdown()
}, 1000)

Exporting metrics

Here is how you can export metrics to Uptrace following the recommendations above:

js
'use strict'

const otel = require('@opentelemetry/api')
const { Resource } = require('@opentelemetry/resources')
const { NodeSDK } = require('@opentelemetry/sdk-node')
const {
  OTLPMetricExporter,
} = require('@opentelemetry/exporter-metrics-otlp-http')
const {
  PeriodicExportingMetricReader,
  AggregationTemporality,
} = require('@opentelemetry/sdk-metrics')

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

const exporter = new OTLPMetricExporter({
  url: 'https://api.uptrace.dev/v1/metrics',
  headers: { 'uptrace-dsn': dsn },
  compression: 'gzip',
  temporalityPreference: AggregationTemporality.DELTA,
})
const metricReader = new PeriodicExportingMetricReader({
  exporter: exporter,
  exportIntervalMillis: 15000,
})

const sdk = new NodeSDK({
  metricReader: metricReader,
  resource: new Resource({
    'service.name': 'myservice',
    'service.version': '1.0.0',
  }),
})
sdk.start()

const meter = otel.metrics.getMeter('app_or_package_name', '1.0.0')

const requestCounter = meter.createCounter('requests', {
  description: 'Example of a counter',
})

setInterval(() => {
  requestCounter.add(1, { environment: 'staging' })
}, 1000)

Exporting logs

Here is how you can export logs to Uptrace following the recommendations above:

js
const { SeverityNumber } = require('@opentelemetry/api-logs')
const {
  LoggerProvider,
  BatchLogRecordProcessor,
} = require('@opentelemetry/sdk-logs')
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http')
const { CompressionAlgorithm } = require('@opentelemetry/otlp-exporter-base')

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

const loggerExporter = new OTLPLogExporter({
  url: `https://api.uptrace.dev/v1/logs`,
  headers: { 'uptrace-dsn': dsn },
  compression: CompressionAlgorithm.GZIP,
})
const loggerProvider = new LoggerProvider()

loggerProvider.addLogRecordProcessor(
  new BatchLogRecordProcessor(loggerExporter),
)

const logger = loggerProvider.getLogger('app_or_package_name', '1.0.0')
logger.emit({
  severityNumber: SeverityNumber.INFO,
  severityText: 'info',
  body: 'this is a log body',
  attributes: { 'log.type': 'custom' },
})

loggerProvider.shutdown().catch(console.error)

Automatic instrumentation

One of the key benefits of OpenTelemetry with Node.js is automatic instrumentation. Whenever you load a module, OpenTelemetry automatically checks if there's a matching instrumentation plugin and uses it to patch the original package.

Frameworks and libraries

OpenTelemetry automatically instruments popular Node.js libraries including:

  • HTTP/HTTPS: Built-in Node.js HTTP modules
  • Express: Web application framework
  • Fastify: Fast and efficient web framework
  • Koa: Expressive middleware framework
  • Database: PostgreSQL, MySQL, MongoDB, Redis
  • gRPC: High-performance RPC framework
  • GraphQL: Query language and runtime

Manual instrumentation

You can also register instrumentations manually for more control:

js
const uptrace = require('@uptrace/node')
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http')
const { PgInstrumentation } = require('@opentelemetry/instrumentation-pg')

uptrace.configureOpentelemetry({
  // Set dsn or UPTRACE_DSN env var.
  dsn: '<FIXME>',

  serviceName: 'myservice',
  serviceVersion: '1.0.0',

  instrumentations: [new PgInstrumentation(), new HttpInstrumentation()],
})

Runtime Metrics

To collect Node.js runtime metrics such as memory usage, event loop lag, and garbage collection statistics, install opentelemetry-node-metrics:

shell
# npm
npm install opentelemetry-node-metrics --save

# yarn
yarn add opentelemetry-node-metrics --save

Then start collecting metrics:

js
const otel = require('@opentelemetry/api')
const uptrace = require('@uptrace/node')
const startNodeMetrics = require('opentelemetry-node-metrics')

uptrace.configureOpentelemetry({...})

// Must be called AFTER OpenTelemetry is configured.
const meterProvider = otel.metrics.getMeterProvider()
startNodeMetrics(meterProvider)

This will automatically collect metrics including:

  • Memory Usage: Heap and external memory statistics
  • Event Loop: Event loop lag and utilization
  • Garbage Collection: GC duration and frequency
  • CPU Usage: Process CPU utilization
  • File Descriptors: Open file descriptor count

Querying data

Once your application is instrumented and sending data to Uptrace, you can query spans and logs using:

  • Service filtering: Filter traces by service name, operation name, and duration
  • Attribute search: Search by custom attributes like http.status_code, db.statement
  • Error filtering: Find traces containing errors or specific exception types
  • Performance analysis: Identify slow operations and bottlenecks
  • Text search: Search logs by message content and severity level
  • Trace correlation: Navigate from logs to related traces using trace IDs
  • Structured logging: Query structured log attributes and metadata
  • Time-based filtering: Filter logs by timestamp ranges

Advanced querying

  • SQL-like syntax: Use complex queries for data analysis and aggregation
  • Custom dashboards: Create visualizations for your specific metrics
  • Alerting: Set up alerts based on error rates, latency, or custom metrics

Verifying your setup

After configuration, your application will automatically start sending telemetry data to Uptrace. To verify the setup:

  1. Start your application with the new configuration
  2. Generate activity by making HTTP requests, database queries, or triggering your instrumented code paths
  3. Check the Uptrace dashboard for incoming traces within 1-2 minutes
  4. Review trace details to ensure spans are being created with proper attributes and timing

Troubleshooting

If OpenTelemetry is not working as expected, you can enable verbose logging to check for potential issues:

js
const { DiagConsoleLogger, DiagLogLevel, diag } = require('@opentelemetry/api')
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG)

Common issues

No data appearing in Uptrace:

  • Verify your DSN is correctly configured in the environment or code
  • Check that your application is generating spans/metrics through activity
  • Ensure network connectivity to Uptrace endpoints (https://api.uptrace.dev)

Performance issues:

  • Adjust sampling rates if collecting too much data
  • Review instrumentation configuration for unnecessary overhead
  • Use batch processors instead of simple processors for better performance

Import order issues:

  • Ensure OpenTelemetry initialization happens before other imports
  • Use the --require flag to load tracing code before application code
  • Check that instrumentations are patching libraries correctly

What's next?

Next, instrument more operations to get a more detailed picture. Try to prioritize network calls, disk operations, database queries, errors, and logs.

You can also create your own instrumentations using OpenTelemetry JS Tracing API.