正在加载,请稍候…

Observability: Distributed Tracing with OpenTelemetry and Jaeger

Implement distributed tracing across microservices with OpenTelemetry. Learn trace context propagation, span attributes, sampling strategies, and using Jaeger for visualization.

Observability: Distributed Tracing with OpenTelemetry

The Three Pillars of Observability

Logs:    What happened (discrete events)
         "User 123 failed login at 10:23:45"

Metrics: How much / how often (aggregated numbers)
         HTTP requests/sec, error rate, P99 latency

Traces:  Where time was spent (distributed request flow)
         Request: 250ms total
           API Gateway: 5ms
           User Service: 30ms
             Database: 25ms
           Order Service: 195ms
             Database: 150ms
             Payment API: 40ms

OpenTelemetry Setup

// tracing.ts - Must be imported BEFORE your app
import { NodeSDK } from '@opentelemetry/sdk-node';
import { Resource } from '@opentelemetry/resources';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';

const sdk = new NodeSDK({
  resource: new Resource({ 'service.name': 'user-service', 'service.version': '1.2.3' }),
  traceExporter: new OTLPTraceExporter({ url: 'http://jaeger:4318/v1/traces' }),
  instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
});
sdk.start();

Manual Instrumentation

import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('user-service');

async function processOrder(orderId: string): Promise<Order> {
  return tracer.startActiveSpan('processOrder', async (span) => {
    span.setAttributes({ 'order.id': orderId });
    try {
      const order = await fetchOrder(orderId);
      await chargePayment(order);
      span.setStatus({ code: SpanStatusCode.OK });
      return order;
    } catch (err) {
      span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
      span.recordException(err);
      throw err;
    } finally {
      span.end();
    }
  });
}

Context Propagation

import { propagation, context } from '@opentelemetry/api';

// Producer: inject trace context into Kafka message headers
async function publishEvent(event: object): Promise<void> {
  const headers: Record<string, string> = {};
  propagation.inject(context.active(), headers); // Adds traceparent header
  await kafka.producer.send({ topic: 'events', messages: [{ value: JSON.stringify(event), headers }] });
}

// Consumer: extract and continue trace
async function handleMessage(message: KafkaMessage): Promise<void> {
  const parentContext = propagation.extract(context.active(), message.headers ?? {});
  await context.with(parentContext, () =>
    tracer.startActiveSpan('handleEvent', span => {
      processEvent(JSON.parse(message.value!.toString()));
      span.end();
    })
  );
}

Sampling

import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-node';

const sdk = new NodeSDK({
  sampler: new ParentBasedSampler({
    root: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces
  }),
});
// If parent is sampled, child is sampled (distributed traces stay together)

Jaeger Quick Start

# docker-compose.yml
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"  # UI
      - "4318:4318"    # OTLP HTTP
    environment:
      COLLECTOR_OTLP_ENABLED: true

Distributed tracing answers "why is this request slow?" across microservice boundaries.