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
:
# 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.
You must call configureOpentelemetry
as early as possible and before importing other packages, because the OpenTelemetry SDK must patch libraries before you import them.
// 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.
Option | Description |
---|---|
dsn | A data source that is used to connect to uptrace.dev. For example, https://<token>@uptrace.dev/<project_id> . |
serviceName | service.name resource attribute. For example, myservice . |
serviceVersion | service.version resource attribute. For example, 1.0.0 . |
deploymentEnvironment | deployment.environment resource attribute. For example, production . |
resourceAttributes | Any other resource attributes. |
resource | Resource 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 var | Description |
---|---|
UPTRACE_DSN | A data source that is used to connect to uptrace.dev. For example, https://<token>@uptrace.dev/<project_id> . |
OTEL_RESOURCE_ATTRIBUTES | Key-value pairs to be used as resource attributes. For example, service.name=myservice,service.version=1.0.0 . |
OTEL_SERVICE_NAME=myservice | Sets the value of the service.name resource attribute. Takes precedence over OTEL_RESOURCE_ATTRIBUTES . |
OTEL_PROPAGATORS | Propagators 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:
# 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:
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:
# npm
npm install @uptrace/node --save
# yarn
yarn add @uptrace/node --save
Step 2: Create Example
Copy the code to main.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:
$ 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.
Recommendation | Signals | Significance |
---|---|---|
Use BatchSpanProcessor to export multiple spans in a single request. | All | Essential |
Enable gzip compression to compress data before sending and reduce traffic costs. | All | Essential |
Prefer delta metrics temporality, because such metrics are smaller and Uptrace converts cumulative metrics to delta anyway. | Metrics | Recommended |
Prefer Protobuf over JSON as a generally more efficient encoding. Feel free to continue using JSON encoding with JavaScript. | All | Recommended |
Prefer HTTP over gRPC as a more mature transport with better compatibility. | All | Recommended |
Use AWS X-Ray ID generator for OpenTelemetry. | Traces, Logs | Optional |
To configure OpenTelemetry to send data to Uptrace, use the provided endpoint and pass the DSN via uptrace-dsn
header:
Transport | Endpoint | Port |
---|---|---|
gRPC | https://api.uptrace.dev:4317 | 4317 |
HTTPS | https://api.uptrace.dev | 443 |
Most languages allow to configure OTLP exporter using environment variables:
# 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:
# 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:
'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:
'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:
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:
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
:
# npm
npm install opentelemetry-node-metrics --save
# yarn
yarn add opentelemetry-node-metrics --save
Then start collecting metrics:
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:
Span search
- 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
Log search
- 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:
- Start your application with the new configuration
- Generate activity by making HTTP requests, database queries, or triggering your instrumented code paths
- Check the Uptrace dashboard for incoming traces within 1-2 minutes
- 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:
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.