Node.js / Bun SDK

Full observability for Node.js and Bun — databases, HTTP, gRPC, messaging, and logging captured automatically via OpenTelemetry

Built on OpenTelemetry

The Node.js SDK is a thin wrapper that configures OpenTelemetry with the Obtrace ingest endpoint. Installing it gives you every OTel auto-instrumentation for Node.js with zero extra code. You get full observability for 40+ libraries out of the box.

Installation

npm install @obtrace/sdk-js

Setup

src/obtrace.ts
import { initNodeSDK } from "@obtrace/sdk-js/node";
 
initNodeSDK({
  apiKey: process.env.OBTRACE_API_KEY!,
  serviceName: "payments-api",
});

Import this file early in your application entry point (e.g. top of src/index.ts or src/app.ts). That's it. No middleware, no wrappers, no extra code.

What's Captured Automatically

After initNodeSDK(), the SDK enables all available OpenTelemetry instrumentations for your installed packages. You don't need to write any code for any of this:

WhatHowNeeds code?
All console outputconsole.log, console.warn, console.error etc. are intercepted and sent as structured logsNo
Every inbound HTTP requestOTel http instrumentation covers all frameworks: Express, Fastify, Hono, NestJS, Koa, etc.No
Every outbound HTTP callfetch, http.request, and https.request are instrumented via OTelNo
Databasespg, mysql2, mongodb, ioredis, redis are instrumented when installedNo
Logging frameworkspino, winston, bunyan log output is captured and correlated with tracesNo
gRPC@grpc/grpc-js calls are instrumented with spansNo
Messagingamqplib (RabbitMQ), kafkajs (Kafka) are instrumentedNo
GraphQLgraphql queries and resolvers are instrumentedNo
DNS and Netdns and net module calls are tracedNo
Uncaught exceptionsprocess.on("uncaughtException")No
Unhandled promise rejectionsprocess.on("unhandledRejection")No
Trace propagationW3C traceparent header injected on outbound, read on inboundNo

No Middleware Needed

Express, Fastify, NestJS, and other framework middleware is not required. OpenTelemetry instruments the Node.js http module directly, so every framework built on it gets automatic spans with method, path, status, and duration.

Framework-specific middleware (expressObtraceMiddleware, fastifyObtraceHook, etc.) still exist if you want route-level granularity, but they are optional.

Optional: Custom Telemetry

The automatic instrumentation covers infrastructure telemetry. For business-specific events, use the SDK methods directly.

initNodeSDK returns an object you can use:

src/obtrace.ts
export const ob = initNodeSDK({
  apiKey: process.env.OBTRACE_API_KEY!,
  serviceName: "payments-api",
});

Business Metrics

Track KPIs that the auto-instrumentation can't know about:

src/routes/checkout.ts
import { ob } from "../obtrace";
 
const start = Date.now();
await processCheckout(cart);
ob.metric("checkout.duration_ms", Date.now() - start, "ms");
ob.metric("revenue.amount", order.total, "usd");

Structured Business Logs

console.log is captured automatically, but for events you want to query and alert on with specific attributes:

ob.log("info", "payment.success", {
  attrs: { order_id: orderId, amount, provider: "stripe" },
});

Error Capture

Unhandled errors are captured automatically. For handled errors you still want to track:

try {
  await db.query("INSERT INTO orders ...");
} catch (err) {
  ob.captureError(err, { attrs: { table: "orders" } });
}

Framework Examples

Express

No middleware needed for basic instrumentation. For route-level detail:

src/index.ts
import { initNodeSDK } from "@obtrace/sdk-js/node";
import express from "express";
 
const ob = initNodeSDK({
  apiKey: process.env.OBTRACE_API_KEY!,
  serviceName: "payments-api",
});
 
const app = express();
 
app.post("/checkout", async (req, res) => {
  ob.log("info", "checkout.started", { attrs: { user_id: req.user?.id } });
  const result = await processCheckout(req.body);
  ob.metric("checkout.total", result.total, "usd");
  res.json(result);
});
 
app.listen(3000);

Fastify

src/index.ts
import { initNodeSDK } from "@obtrace/sdk-js/node";
import Fastify from "fastify";
 
const ob = initNodeSDK({
  apiKey: process.env.OBTRACE_API_KEY!,
  serviceName: "payments-api",
});
 
const app = Fastify();
 
app.post("/checkout", async (request, reply) => {
  ob.log("info", "checkout.started");
  return { ok: true };
});
 
app.listen({ port: 3000 });

NestJS

src/main.ts
import { initNodeSDK } from "@obtrace/sdk-js/node";
 
initNodeSDK({
  apiKey: process.env.OBTRACE_API_KEY!,
  serviceName: "payments-api",
});
 
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
 
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

Hono

src/index.ts
import { initNodeSDK } from "@obtrace/sdk-js/node";
import { Hono } from "hono";
import { serve } from "@hono/node-server";
 
const ob = initNodeSDK({
  apiKey: process.env.OBTRACE_API_KEY!,
  serviceName: "payments-api",
});
 
const app = new Hono();
 
app.post("/checkout", async (c) => {
  ob.log("info", "checkout.started");
  return c.json({ ok: true });
});
 
serve(app);

Configuration Reference

OptionTypeRequiredDefaultDescription
apiKeystringYes-Your Obtrace API key
serviceNamestringYes-Identifies this service in Obtrace
tenantIdstringNo-Tenant ID (auto-resolved from API key)
projectIdstringNo-Project ID (auto-resolved from API key)
appIdstringNo-App ID within a project
envstringNo-Environment (production, staging, etc.)
serviceVersionstringNo-Deployment version
debugbooleanNofalseLog SDK internals to console
flushIntervalMsnumberNo2000Queue flush interval
maxQueueSizenumberNo1000Max queued items before dropping oldest
requestTimeoutMsnumberNo5000OTLP request timeout
defaultHeadersRecord<string, string>No{}Extra headers on OTLP requests
validateSemanticMetricsbooleanNofalseWarn on non-canonical metric names

Shutdown

Call shutdown() during graceful shutdown to flush buffered telemetry:

process.on("SIGTERM", async () => {
  await ob.shutdown();
  process.exit(0);
});

Validation

After deploying:

  • Open Obtrace UI — logs should appear under your serviceName
  • Make any HTTP request to your server — a span with method, path, status, and duration should appear (via OTel http instrumentation)
  • Call any external API with fetch() — an outbound span should appear
  • Run a database query (pg, mysql2, mongodb, redis) — a span should appear automatically
  • Add a console.error("test") — should appear as an error log
  • Trigger an unhandled exception in test — a fatal log should appear

On this page