Dashboard Get started

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_HERE

That'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 dashboard

What 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-service

Optional: 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-node

Add 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/register
bash
node --env-file=.env server.js
Advanced: 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-node
la.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.