OpenTelemetry Is Becoming the Observability Standard
Learn why OpenTelemetry is becoming the standard for distributed tracing, metrics, and logging, and how to instrument your Node.js and Next.js apps.
Tags
OpenTelemetry Is Becoming the Observability Standard
TL;DR
OpenTelemetry (OTel) provides a vendor-neutral standard for collecting traces, metrics, and logs from your applications. Instrument your code once with OTel, and export to any observability backend --- Datadog, Grafana, Honeycomb, Jaeger --- without changing application code. It is becoming the observability equivalent of what SQL is for databases.
What's Happening
The observability landscape used to require choosing a vendor early and instrumenting your code with their proprietary SDK. Switch from Datadog to Grafana Cloud? Rewrite your instrumentation. Want to try Honeycomb alongside New Relic? Run two SDKs with overlapping overhead.
OpenTelemetry, a CNCF (Cloud Native Computing Foundation) project, has changed this by providing a single instrumentation standard that works with any backend. It has reached stable status for traces and metrics, with logs nearing general availability. Major observability vendors --- Datadog, Grafana, Splunk, Honeycomb, New Relic, Dynatrace --- all support OTel ingestion natively.
The shift is practical, not ideological. Teams adopt OTel because it reduces vendor lock-in, simplifies instrumentation, and provides a unified data model across languages. Next.js has built-in OTel support. Node.js has mature auto-instrumentation packages. The barrier to entry has dropped significantly.
Why It Matters
Observability is not optional for production applications. When a request is slow, an error rate spikes, or a deployment causes regressions, you need traces, metrics, and logs to diagnose the issue. The question is how you collect that telemetry.
Vendor-specific SDKs create lock-in. Once you have instrumented thousands of lines of code with Datadog's dd-trace, switching to another vendor is a significant engineering project. OTel eliminates this by decoupling instrumentation (how you collect data) from export (where you send it).
For Node.js and Next.js developers specifically, OTel means you can add production-grade observability with minimal effort and switch backends as your needs evolve.
How It Works / What's Changed
The Three Signals
OpenTelemetry collects three types of telemetry data:
Traces track a request's journey through your system, from the initial HTTP request through database queries, external API calls, and back to the response. Each operation is a "span" within the trace.
Metrics are numerical measurements over time: request count, response latency percentiles, error rates, queue depths, CPU usage.
Logs are timestamped event records. OTel correlates logs with traces so you can see exactly which log entries belong to which request.
Setting Up OTel in Node.js
The setup involves installing the SDK, configuring auto-instrumentation, and pointing the exporter to your backend:
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http// instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: 'my-api',
[ATTR_SERVICE_VERSION]: '1.0.0',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + '/v1/traces',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + '/v1/metrics',
}),
exportIntervalMillis: 30000,
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-express': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true },
'@opentelemetry/instrumentation-redis': { enabled: true },
}),
],
});
sdk.start();The getNodeAutoInstrumentations function automatically instruments HTTP requests, database queries, Redis calls, and other common operations without modifying your application code.
OTel in Next.js
Next.js has native OpenTelemetry support through the instrumentation.ts file at the project root:
// instrumentation.ts (Next.js project root)
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { NodeSDK } = await import('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = await import(
'@opentelemetry/exporter-trace-otlp-http'
);
const { getNodeAutoInstrumentations } = await import(
'@opentelemetry/auto-instrumentations-node'
);
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
}
}Next.js also enables the experimental.instrumentationHook in next.config.js:
// next.config.js
module.exports = {
experimental: {
instrumentationHook: true,
},
};With this configuration, Next.js automatically creates spans for:
- ›Server-side rendering
- ›API route execution
- ›Middleware processing
- ›
fetchcalls made from Server Components - ›Static page generation
Custom Spans and Attributes
Auto-instrumentation captures the framework-level operations, but you often need custom spans for business logic:
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('my-api');
export async function processOrder(orderId: string) {
return tracer.startActiveSpan('processOrder', async (span) => {
try {
span.setAttribute('order.id', orderId);
// Validate the order
const order = await tracer.startActiveSpan('validateOrder', async (validationSpan) => {
const result = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
});
validationSpan.setAttribute('order.status', result?.status ?? 'not_found');
validationSpan.end();
return result;
});
if (!order) {
span.setStatus({ code: SpanStatusCode.ERROR, message: 'Order not found' });
throw new Error('Order not found');
}
// Charge payment
await tracer.startActiveSpan('chargePayment', async (paymentSpan) => {
paymentSpan.setAttribute('payment.amount', order.total);
paymentSpan.setAttribute('payment.currency', 'USD');
await paymentService.charge(order.paymentMethodId, order.total);
paymentSpan.end();
});
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
span.recordException(error as Error);
throw error;
} finally {
span.end();
}
});
}This creates a trace showing processOrder as the parent span with validateOrder and chargePayment as child spans, each with relevant attributes.
Custom Metrics
Beyond traces, you can record custom metrics for business and performance monitoring:
import { metrics } from '@opentelemetry/api';
const meter = metrics.getMeter('my-api');
// Counters for event counting
const orderCounter = meter.createCounter('orders.processed', {
description: 'Number of orders processed',
});
// Histograms for measuring distributions
const orderValueHistogram = meter.createHistogram('orders.value', {
description: 'Order value distribution in USD',
unit: 'USD',
});
// Up-down counters for gauges
const activeConnections = meter.createUpDownCounter('db.connections.active', {
description: 'Number of active database connections',
});
export async function handleOrder(order: Order) {
orderCounter.add(1, { 'order.type': order.type });
orderValueHistogram.record(order.total, { 'order.currency': 'USD' });
// ...
}Backend Compatibility
The same instrumentation code works with any OTel-compatible backend. You only change the exporter configuration:
// For Jaeger (self-hosted)
const exporter = new OTLPTraceExporter({
url: 'http://jaeger:4318/v1/traces',
});
// For Grafana Cloud
const exporter = new OTLPTraceExporter({
url: 'https://otlp-gateway-prod-us-central-0.grafana.net/otlp/v1/traces',
headers: {
Authorization: `Basic ${btoa(`${instanceId}:${apiKey}`)}`,
},
});
// For Datadog
const exporter = new OTLPTraceExporter({
url: 'http://datadog-agent:4318/v1/traces',
});
// For Honeycomb
const exporter = new OTLPTraceExporter({
url: 'https://api.honeycomb.io/v1/traces',
headers: {
'x-honeycomb-team': process.env.HONEYCOMB_API_KEY!,
},
});This is the core value of OTel: your instrumentation code stays the same regardless of which backend you use. Switching from Jaeger to Grafana Cloud is a configuration change, not a code change.
The OTel Collector
For production deployments, the OTel Collector acts as a proxy between your application and your observability backend:
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1000
memory_limiter:
check_interval: 1s
limit_mib: 512
exporters:
otlp/grafana:
endpoint: https://otlp-gateway.grafana.net/otlp
headers:
Authorization: "Basic ${GRAFANA_TOKEN}"
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/grafana, otlp/jaeger]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/grafana]The Collector lets you send telemetry to multiple backends simultaneously, apply sampling and filtering, batch data for efficiency, and keep sensitive credentials out of application code.
My Take
OTel is one of those technologies that pays for itself the first time you need to debug a production issue. Before I adopted it, debugging a slow API endpoint meant checking logs, guessing where the bottleneck was, and adding ad-hoc timing code. With OTel traces, I can see the exact breakdown: 200ms in the database, 150ms waiting for a third-party API, 50ms in serialization.
The auto-instrumentation is genuinely impressive. Installing the Node.js auto-instrumentation package and pointing it at a backend gives you useful traces with zero application code changes. Custom spans are worth adding for business-critical paths, but the automatic instrumentation covers the majority of what you need.
My one complaint is the SDK's API surface area. The OTel JavaScript SDK has many packages, configurations, and concepts. The getting-started experience could be simpler. But once you have a working configuration, it is largely set-and-forget.
What This Means for You
If you have no observability: Start with OTel and a free backend like Jaeger (self-hosted) or Grafana Cloud's free tier. Auto-instrumentation gets you useful traces immediately.
If you are using a vendor SDK: Plan a migration to OTel. Most vendors now support OTel ingestion, so you can switch incrementally without changing your backend.
If you are building a Next.js application: Enable the instrumentation hook and set up the OTel SDK. The built-in integration makes Next.js one of the easiest frameworks to instrument.
If you are choosing an observability backend: Choose based on features, pricing, and UI --- not on SDK compatibility. OTel makes the backend a swappable layer, so you can change your mind later without re-instrumenting.
FAQ
What is OpenTelemetry?
OpenTelemetry is a CNCF project providing vendor-neutral APIs, SDKs, and tools for collecting distributed traces, metrics, and logs from applications.
Why should I use OpenTelemetry instead of vendor SDKs?
OpenTelemetry lets you instrument once and export to any backend like Datadog, Grafana, or Honeycomb, avoiding vendor lock-in and reducing instrumentation effort.
Does Next.js support OpenTelemetry natively?
Yes, Next.js has built-in OpenTelemetry support via the instrumentation.ts file, automatically tracing server-side rendering, API routes, and middleware.
Collaboration
Need help with a project?
Let's Build It
I help startups and established companies design, build, and scale world-class digital products. From deep technical architecture to pixel-perfect UI — let's bring your vision to life.
Related Articles
Turbopack Is Replacing Webpack: What You Need to Know
Understand why Turbopack is replacing Webpack as the default bundler in Next.js, with benchmarks showing 10x faster builds and what it means for you.
pnpm vs Yarn vs npm: Package Managers in 2026
Compare pnpm, Yarn, and npm in 2026 across speed, disk usage, monorepo support, and security to choose the right package manager for your team.
Multi-Agent AI Systems: What Developers Should Know
Understand multi-agent AI architectures where specialized LLM agents collaborate on complex tasks, with patterns for orchestration, memory, and tooling.