Node.js Instrumentation
One command instruments Express, Fastify, Koa, NestJS, pg, redis, and 40+ other libraries. Zero code changes.
Setup
bash
la init --dsn la_live_YOUR_TOKEN_HEREThat's the whole setup. la init installs @opentelemetry/auto-instrumentations-node, writes the env vars to .env, gitignores it, and patches your npm start script to load everything automatically. Then:
bash
npm start # OTel loads automatically — make a request and check the dashboardWhat gets captured automatically
- HTTP — every inbound route and outbound call: method, URL, status, duration
- Databases — pg, mysql2, mongoose, ioredis with query text and timing
- Exceptions — unhandled errors land in the Errors page with type, message, and stack trace
- Logs — pino, winston, and bunyan records ship to the Logs page, correlated to the active trace
console.log is not captured (true of every OTel-based tool). Use pino or winston and logs flow with zero config.Screenshot needed
Logs page showing Node.js service spans: time column, INFO/ERROR severity badges, body messages like 'GET /api/users', 'payment.process', service='my-service', durations in ms.
Verify it's working
bash
# Check spans are arriving (replace my-service with your OTEL_SERVICE_NAME)
la query "SELECT service, severity, body, duration_ms FROM recent_spans WHERE service = 'my-service' ORDER BY timestamp DESC LIMIT 10"
# Live tail while your app handles requests
la logs tail --service my-serviceOptional: capture request bodies
OTel can't buffer request streams, so bodies need one middleware after express.json():
javascript
const { trace } = require('@opentelemetry/api');
app.use((req, res, next) => {
const span = trace.getActiveSpan();
if (span && req.body) span.setAttribute('http.request.body', JSON.stringify(req.body).slice(0, 2048));
next();
});Custom spans
Add custom spans around business logic you want to trace explicitly:
javascript
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('my-service');
async function processPayment(orderId) {
return tracer.startActiveSpan('payment.process', async (span) => {
try {
span.setAttribute('order.id', orderId);
const result = await chargeCard(orderId);
span.setAttribute('payment.status', result.status);
return result;
} catch (err) {
span.recordException(err);
span.setStatus({ code: 2, message: err.message });
throw err;
} finally {
span.end();
}
});
}Manual setup (if you'd rather not use the CLI)
bash
npm install @opentelemetry/auto-instrumentations-nodeAdd to .env (and gitignore it — the DSN is a secret):
.env
OTEL_SERVICE_NAME=my-service
OTEL_EXPORTER_OTLP_ENDPOINT=https://la-ingest-gateway.lightarch.workers.dev
OTEL_EXPORTER_OTLP_HEADERS=x-la-dsn=la_live_YOUR_TOKEN_HERE
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_LOGS_EXPORTER=otlp
NODE_OPTIONS=--require @opentelemetry/auto-instrumentations-node/registerbash
node --env-file=.env server.jsAdvanced: custom config file (metrics intervals, custom resources, per-library settings)
bash
npm install @opentelemetry/sdk-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http \
@opentelemetry/auto-instrumentations-nodela.config.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
const LA_DSN = process.env.LA_DSN;
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: 'my-service',
'deployment.environment': process.env.NODE_ENV ?? 'production',
}),
traceExporter: new OTLPTraceExporter({
url: 'https://la-ingest-gateway.lightarch.workers.dev/v1/traces',
headers: { 'x-la-dsn': LA_DSN },
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'https://la-ingest-gateway.lightarch.workers.dev/v1/metrics',
headers: { 'x-la-dsn': LA_DSN },
}),
exportIntervalMillis: 30_000,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());Import
la.config.js as the very first line of your entry point — before Express, pg, or any other library you want traced.