Imperium Rzymskie posiadało rozbudowany system cursus publicus — państwowej poczty kurierskiej. Każda depesza miała znacznik identyfikacyjny i była śledzona od nadawcy do odbiorcy, przez wszystkie stacje pośrednie. W aplikacjach mikroservisowych, tę samą rolę pełni distributed tracing — śledzenie żądań przez wiele serwisów.
W systemie monolitycznym łatwo prześledzić przepływ żądania — wszystko jest w jednym procesie. Ale w architekturze mikroservisowej żądanie przechodzi przez wiele serwisów:
1// Żądanie przechodzi przez wiele serwisów
2const requestFlow = {
3 krok1: 'Użytkownik -> API Gateway',
4 krok2: 'API Gateway -> Auth Service',
5 krok3: 'Auth Service -> User Service (weryfikacja)',
6 krok4: 'API Gateway -> Legion Service (logika)',
7 krok5: 'Legion Service -> MongoDB (zapis)',
8 krok6: 'Legion Service -> Redis (cache)',
9 krok7: 'Legion Service -> Notification Service (powiadomienie)',
10
11 problem: 'Które żądanie jest wolne? Gdzie jest wąskie gardło?',
12 rozwiazanie: 'Distributed Tracing — śledź żądanie przez WSZYSTKIE serwisy',
13};Distributed tracing to technika śledzenia żądania przez wiele serwisów. Każde żądanie otrzymuje unikalny identyfikator (trace ID), który jest propagowany do każdego serwisu:
1// Kluczowe pojęcia
2interface TracingConcepts {
3 trace: 'Pełna podróż żądania (od klienta do odpowiedzi)';
4 span: 'Pojedyncza operacja w ramach trace (np. zapytanie DB)';
5 traceId: 'Unikalny ID całego żądania (jak numer depeszy)';
6 spanId: 'Unikalny ID pojedynczej operacji';
7 parentSpanId: 'ID operacji nadrzędnej (łączy spany w drzewo)';
8 context: 'Propagacja — przekazywanie traceId między serwisami';
9}
10
11// Przykład drzewa spanów
12const traceExample = {
13 traceId: 'abc-123-def-456',
14 rootSpan: {
15 name: 'POST /legiones',
16 duration: '250ms',
17 children: [
18 { name: 'auth.verify', duration: '15ms' },
19 {
20 name: 'legion.create',
21 duration: '180ms',
22 children: [
23 { name: 'mongodb.insert', duration: '45ms' },
24 { name: 'redis.set', duration: '5ms' },
25 { name: 'notification.send', duration: '120ms' },
26 ],
27 },
28 ],
29 },
30};OpenTelemetry (OTel) to otwarty standard zbierania danych telemetrycznych (traces, metrics, logs). Jest wspierany przez CNCF i wszystkich głównych dostawców chmury:
1# Instalacja OpenTelemetry SDK dla NestJS
2yarn add @opentelemetry/sdk-node
3yarn add @opentelemetry/auto-instrumentations-node
4yarn add @opentelemetry/exporter-trace-otlp-http
5yarn add @opentelemetry/resources
6yarn add @opentelemetry/semantic-conventions1// tracing/tracing.ts — WAŻNE: załaduj PRZED importem NestJS!
2import { NodeSDK } from '@opentelemetry/sdk-node';
3import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
4import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5import { Resource } from '@opentelemetry/resources';
6import {
7 ATTR_SERVICE_NAME,
8 ATTR_SERVICE_VERSION,
9} from '@opentelemetry/semantic-conventions';
10
11const sdk = new NodeSDK({
12 resource: new Resource({
13 [ATTR_SERVICE_NAME]: 'roman-imperium-api',
14 [ATTR_SERVICE_VERSION]: '1.0.0',
15 }),
16 traceExporter: new OTLPTraceExporter({
17 url: process.env.OTEL_EXPORTER_URL || 'http://jaeger:4318/v1/traces',
18 }),
19 instrumentations: [
20 getNodeAutoInstrumentations({
21 '@opentelemetry/instrumentation-http': { enabled: true },
22 '@opentelemetry/instrumentation-express': { enabled: true },
23 '@opentelemetry/instrumentation-mongoose': { enabled: true },
24 }),
25 ],
26});
27
28sdk.start();
29console.log('OpenTelemetry zainicjalizowany');
30
31// Graceful shutdown
32process.on('SIGTERM', () => {
33 sdk.shutdown().then(() => console.log('Tracing zamkniety'));
34});1// main.ts
2import './tracing/tracing'; // MUSI być pierwszym importem!
3import { NestFactory } from '@nestjs/core';
4import { AppModule } from './app.module';
5
6async function bootstrap() {
7 const app = await NestFactory.create(AppModule);
8 await app.listen(4000);
9}
10bootstrap();Auto-instrumentacja obsługuje HTTP i bazę danych, ale możemy tworzyć własne spany dla logiki biznesowej:
1import { Injectable } from '@nestjs/common';
2import { trace, SpanStatusCode } from '@opentelemetry/api';
3
4@Injectable()
5export class LegionService {
6 private tracer = trace.getTracer('legion-service');
7
8 async recruitLegionary(dto: CreateLegionaryDto) {
9 return this.tracer.startActiveSpan('legion.recruit', async (span) => {
10 try {
11 span.setAttribute('legionary.name', dto.name);
12 span.setAttribute('legionary.rank', dto.rank);
13
14 // Każda operacja to osobny span
15 const legionary = await this.tracer.startActiveSpan(
16 'mongodb.create',
17 async (dbSpan) => {
18 const result = await this.legionRepository.create(dto);
19 dbSpan.setAttribute('db.collection', 'legionaries');
20 dbSpan.end();
21 return result;
22 },
23 );
24
25 span.setStatus({ code: SpanStatusCode.OK });
26 return legionary;
27 } catch (error) {
28 span.setStatus({
29 code: SpanStatusCode.ERROR,
30 message: error.message,
31 });
32 span.recordException(error);
33 throw error;
34 } finally {
35 span.end();
36 }
37 });
38 }
39}Trace ID jest automatycznie przekazywany między serwisami przez nagłówki HTTP:
1// Nagłówki propagacji (W3C Trace Context)
2const traceHeaders = {
3 'traceparent': '00-abc123def456-span789-01',
4 // version-traceId-parentSpanId-flags
5};
6
7// Auto-instrumentacja automatycznie dodaje te nagłówki
8// Nie musisz tego robić ręcznie!Jaeger to popularne narzędzie do wizualizacji distributed traces:
1# docker-compose.yml — Jaeger
2services:
3 jaeger:
4 image: jaegertracing/all-in-one:latest
5 environment:
6 - COLLECTOR_OTLP_ENABLED=true
7 ports:
8 - "16686:16686" # Jaeger UI
9 - "4318:4318" # OTLP HTTPŁączenie logów z trace ID pozwala na pełną diagnostykę:
1// Dodanie traceId do logów
2import { context, trace } from '@opentelemetry/api';
3
4function getTraceId(): string {
5 const span = trace.getSpan(context.active());
6 return span?.spanContext().traceId || 'no-trace';
7}
8
9// W loggerze
10this.logger.log({
11 message: 'Legionista zrekrutowany',
12 traceId: getTraceId(),
13 legionaryId: 'leg_42',
14});
15
16// Teraz w Jaeger widzisz trace, a w logach — szczegółyDistributed tracing to system cursus publicus naszego Imperium — każde żądanie jest śledzone od początku do końca, przez wszystkie serwisy. Dzięki OpenTelemetry i Jaeger widzimy dokładnie, gdzie żądanie spędza czas i gdzie powstają problemy.