UXDL Docs

OpenTelemetry

Vendor-neutral instrumentation for traces, metrics, and logs.

OpenTelemetry (OTel) is the open standard for collecting telemetry from applications. It provides a single instrumentation layer that exports to Sentry, Axiom, or any OTLP-compatible backend.

What OpenTelemetry provides

SignalPurposeExample
TracesFollow a request across servicesLogin → API → Database
MetricsNumeric measurements over timeRequest rate, latency p95, error count
LogsStructured event records{ level: "error", requestId: "..." }

Architecture in our stack

Node.js backend setup

bash
pnpm add @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http
typescript
// instrumentation.ts — load before app starts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
 
const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
  }),
  instrumentations: [getNodeAutoInstrumentations()],
  serviceName: process.env.OTEL_SERVICE_NAME ?? "backend-api",
});
 
sdk.start();

Start the app with the instrumentation hook:

bash
node --import ./instrumentation.ts dist/main.js

Next.js setup

Use the official OpenTelemetry package for Next.js:

bash
pnpm add @vercel/otel
typescript
// instrumentation.ts (project root)
import { registerOTel } from "@vercel/otel";
 
export function register() {
  registerOTel({ serviceName: "web-portal" });
}

Enable instrumentation in next.config.ts:

typescript
const nextConfig = {
  experimental: {
    instrumentationHook: true,
  },
};
export default nextConfig;

Manual spans

Add custom spans for business-critical operations:

typescript
import { trace } from "@opentelemetry/api";
 
const tracer = trace.getTracer("checkout-service");
 
async function processCheckout(orderId: string) {
  return tracer.startActiveSpan("processCheckout", async (span) => {
    span.setAttribute("order.id", orderId);
    try {
      const result = await chargePayment(orderId);
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (err) {
      span.recordException(err as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw err;
    } finally {
      span.end();
    }
  });
}

Environment variables

dotenv
OTEL_SERVICE_NAME=backend-api
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.company.com/v1/traces
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1
VariableDescription
OTEL_SERVICE_NAMEIdentifies the service in traces
OTEL_EXPORTER_OTLP_ENDPOINTWhere to send telemetry
OTEL_TRACES_SAMPLER_ARGSample rate (0.1 = 10% in prod)

Propagation

OpenTelemetry propagates trace context via HTTP headers (traceparent). When the frontend calls the backend, the same trace ID links both spans — essential for debugging cross-service requests.

Official documentation