Jejak Teragih dengan OpenTelemetry
Instrumentasikan pengawal, penyedia dan klien HTTP untuk menjana rentang berkorelasi merentas perkhidmatan.
Jejak Teragih dengan OpenTelemetry ialah pelajaran API Bahagian Belakang Perusahaan NestJS percuma di CoddyKit. Ini ialah pelajaran 3 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran API Bahagian Belakang Perusahaan NestJS, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus API Bahagian Belakang Perusahaan NestJS merangkumi sejumlah 4 pelajaran.
Sebab Penjejakan Teragih
Dalam kumpulan perkhidmatan mikro, satu permintaan pengguna mungkin melalui get laluan, perkhidmatan pesanan, perkhidmatan pembayaran dan API HTTP pihak ketiga. Apabila kependaman meningkat secara mendadak, log sahaja tidak dapat memberitahu anda hop yang mana menjadi perlahan.
Penjejakan teragih menyatukan hop ini. Setiap unit kerja menjadi satu span; span yang dipautkan melalui trace_id yang dikongsi membentuk satu trace hujung ke hujung.
trace_id— nilai yang sama merentas setiap perkhidmatan dalam satu permintaanspan_id— unik bagi setiap operasiparent_span_id— cara span bersarang membentuk pepohon
OpenTelemetry (OTel) ialah standard neutral vendor untuk menghasilkan dan menyebarkan span ini, yang akan kita sambungkan ke NestJS.
Anatomi Satu Span
Span hanyalah objek bertip yang menerangkan satu operasi mengikut masa. Sebelum menyentuh NestJS, adalah berguna untuk memodelkan perkara yang sebenarnya dikeluarkan oleh OTel. Berikut ialah lakaran TypeScript biasa bagi medan yang diisi oleh SDK.
Perhatikan kind: span SERVER mewakili permintaan masuk, manakala span CLIENT mewakili panggilan keluar. Mengaitkan span CLIENT dalam perkhidmatan A dengan span SERVER dalam perkhidmatan B ialah manfaat sebenar penyebaran konteks.
type SpanKind = 'SERVER' | 'CLIENT' | 'INTERNAL';
interface Span {
traceId: string;
spanId: string;
parentSpanId?: string;
name: string;
kind: SpanKind;
startTimeMs: number;
endTimeMs: number;
attributes: Record<string, string | number | boolean>;
}
function durationMs(span: Span): number {
return span.endTimeMs - span.startTimeMs;
}
const span: Span = {
traceId: '4bf92f3577b34da6a3ce929d0e0e4736',
spanId: '00f067aa0ba902b7',
name: 'GET /orders/:id',
kind: 'SERVER',
startTimeMs: 1000,
endTimeMs: 1042,
attributes: { 'http.method': 'GET', 'http.route': '/orders/:id', 'http.status_code': 200 },
};
console.log(`${span.name} took ${durationMs(span)}ms`);Memasang SDK OTel
Bagi perkhidmatan NestJS, anda memerlukan tiga lapisan pakej OTel:
@opentelemetry/sdk-node— SDK Node dan kitar hayatnya@opentelemetry/auto-instrumentations-node— tampalan tanpa kod untuk HTTP, Express, Nest, pg, ioredis dan lain-lain- Eksportir seperti
@opentelemetry/exporter-trace-otlp-httpuntuk menghantar span kepada pengumpul atau bahagian belakang (Jaeger, Tempo, Honeycomb)
Instrumentasi automatik sahaja sudah menghasilkan span SERVER dan CLIENT yang berkaitan untuk HTTP masuk serta panggilan fetch/axios keluar. Span manual (dalam babak kemudian) menambahkan makna perniagaan di atasnya.
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/resources \
@opentelemetry/semantic-conventionsMemulakan Penjejakan Sebelum Nest
Peraturan yang paling penting: mulakan SDK OTel sebelum mana-mana modul aplikasi diimport. Instrumentasi automatik berfungsi dengan menampal modul seperti http secara dinamik pada masa require. Jika Nest dimuatkan terlebih dahulu, tampalan itu tidak akan mengenainya.
Letakkan SDK dalam tracing.ts tersendiri dan importkannya di bahagian paling atas main.ts, atau pramuatnya dengan node --require ./dist/tracing.js.
// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
export const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'orders-service',
[ATTR_SERVICE_VERSION]: process.env.APP_VERSION ?? '0.0.0',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on('SIGTERM', () => {
sdk.shutdown().finally(() => process.exit(0));
});Menyambungkannya ke main.ts
Oleh sebab susunan import yang menghasilkan kesan sampingan adalah penting, import tracing mesti menjadi pernyataan pertama — di atas kilang Nest malah di atas AppModule. Pengangkatan modul ES masih menghormati susunan fizikal bagi kesan sampingan sdk.start() SDK selagi fail ini berada di tempat pertama.
Alternatif yang lebih selamat dan mengelakkan sebarang kekeliruan tentang pengangkatan ialah bendera pramuat node --require ./dist/tracing.js dist/main.js dalam skrip permulaan anda.
// main.ts
import './tracing'; // MUST be first
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();Cara Konteks Disebarkan Merentas Perkhidmatan
Span menjadi satu jejak hanya jika trace_id bergerak antara perkhidmatan. OTel melakukan ini dengan piawaian W3C Konteks Jejak, dengan memasukkan pengepala HTTP traceparent pada span CLIENT keluar dan mengekstraknya pada span SERVER masuk.
Pengepala itu kelihatan seperti:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01- Format:
version-traceId-parentSpanId-flags
Dengan instrumentasi automatik, perkara ini berlaku secara automatik untuk HTTP. Keperluan utama ialah konteks aktif mengalir melalui kod async anda supaya panggilan keluar mengetahui jejak yang dimilikinya.
function parseTraceparent(header: string) {
const [version, traceId, parentId, flags] = header.split('-');
return { version, traceId, parentId, sampled: (parseInt(flags, 16) & 1) === 1 };
}
const h = '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01';
const ctx = parseTraceparent(h);
console.log(ctx.traceId);
console.log('sampled:', ctx.sampled);Span Manual dalam Pembekal
Instrumentasi automatik memberikan anda span pada tahap HTTP, tetapi logik perniagaan ("menempah inventori", "mengenakan caj pada kad") wajar mempunyai span tersendiri. Dapatkan Tracer dan balut operasi dengan startActiveSpan supaya span anak tersusun dengan betul di bawah span SERVER semasa.
startActiveSpan menetapkan span sebagai aktif sepanjang tempoh panggil baliknya, bermakna sebarang span tersarang atau panggilan HTTP keluar secara automatik menjadi anaknya.
// orders.service.ts
import { Injectable } from '@nestjs/common';
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('orders-service');
@Injectable()
export class OrdersService {
async reserveInventory(orderId: string, sku: string, qty: number) {
return tracer.startActiveSpan('reserveInventory', async (span) => {
span.setAttribute('order.id', orderId);
span.setAttribute('inventory.sku', sku);
span.setAttribute('inventory.qty', qty);
try {
const result = await this.doReserve(sku, qty);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message });
throw err;
} finally {
span.end();
}
});
}
private async doReserve(sku: string, qty: number) {
return { sku, qty, reserved: true };
}
}Memperkaya Span Aktif Pengawal
Pengendali pengawal NestJS sudah berjalan dalam span SERVER yang dijana secara automatik untuk permintaan tersebut. Daripada mencipta span baharu, dapatkan span aktif dan perkayakannya dengan atribut perniagaan berkeardinalan tinggi (id penyewa, id pengguna, id pesanan) supaya jejak boleh dicari.
Gunakan trace.getActiveSpan() — ia mengembalikan span SERVER tempat pengendali permintaan sedang dilaksanakan. Penambahan atribut di sini mengekalkan semuanya pada satu nod jejak, bukannya memecahkannya.
// orders.controller.ts
import { Controller, Post, Body, Headers } from '@nestjs/common';
import { trace } from '@opentelemetry/api';
import { OrdersService } from './orders.service';
@Controller('orders')
export class OrdersController {
constructor(private readonly orders: OrdersService) {}
@Post()
async create(@Body() dto: { sku: string; qty: number }, @Headers('x-tenant-id') tenantId: string) {
const span = trace.getActiveSpan();
span?.setAttribute('tenant.id', tenantId);
span?.setAttribute('order.sku', dto.sku);
span?.addEvent('order.create.received');
return this.orders.reserveInventory(crypto.randomUUID(), dto.sku, dto.qty);
}
}Menjejak Klien HTTP Keluar
Apabila perkhidmatan pesanan memanggil perkhidmatan pembayaran melalui HTTP, anda mahukan span CLIENT yang menyebarkan pengepala traceparent supaya pembayaran meneruskan jejak yang sama.
Jika anda menggunakan modul http yang diinstrumentasikan secara automatik (Node fetch, axios, HttpService Nest), penyebaran berlaku secara automatik — dengan syarat panggilan berlaku dalam konteks aktif. Membuat panggilan dalam panggil balik startActiveSpan menjamin span CLIENT keluar tersarang di bawah span perniagaan anda.
// payments.client.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('orders-service');
@Injectable()
export class PaymentsClient {
constructor(private readonly http: HttpService) {}
async charge(orderId: string, amount: number) {
return tracer.startActiveSpan('payments.charge', async (span) => {
span.setAttribute('order.id', orderId);
span.setAttribute('payment.amount', amount);
try {
// traceparent header is injected automatically by http instrumentation
const res = await firstValueFrom(
this.http.post('http://payments-svc/charges', { orderId, amount }),
);
span.setStatus({ code: SpanStatusCode.OK });
return res.data;
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw err;
} finally {
span.end();
}
});
}
}Pensampelan dan Kawalan Kos
Pada skala perusahaan, merekod 100% jejak adalah mahal. OTel menggunakan pensampel untuk menentukan jejak yang perlu disimpan, dan keputusan itu disebarkan melalui bendera sampled dalam traceparent supaya jejak direkodkan (atau digugurkan) secara konsisten merentas semua perkhidmatan.
ParentBasedSampler— menghormati keputusan perkhidmatan huluan (lalai piawai)TraceIdRatioBasedSampler— mengekalkan pecahan tetap, contohnya 10%- Pensampelan hujung — dilakukan dalam Collector: mengekalkan semua jejak ralat/perlahan dan mengambil sampel bagi selebihnya
Konfigurasikan pensampel akar dengan pemboleh ubah persekitaran OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG atau pilihan sampler SDK.
// tracing.ts (excerpt)
import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
const sampler = new ParentBasedSampler({
// when this service starts a trace, keep 10%
root: new TraceIdRatioBasedSampler(0.1),
});
// pass `sampler` into the NodeSDK({ ... }) optionsMengaitkan Log dengan Jejak
Manfaat terakhir ialah memautkan log berstruktur anda kepada jejak. Ambil trace_id dan span_id daripada konteks span aktif dan masukkannya ke dalam setiap baris log. Dalam Jaeger/Tempo, anda kemudiannya boleh pergi terus daripada span yang perlahan kepada lognya dan kembali semula.
Gunakan trace.getActiveSpan()?.spanContext() untuk membaca id semasa dan memasukkannya sebagai medan ke dalam pengelog Pino/Winston anda.
import { trace } from '@opentelemetry/api';
function traceFields(): Record<string, string> {
const ctx = trace.getActiveSpan()?.spanContext();
if (!ctx) return {};
return { trace_id: ctx.traceId, span_id: ctx.spanId };
}
// simulate a log line enriched with correlation ids
const entry = {
level: 'info',
msg: 'order created',
...{ trace_id: '4bf92f3577b34da6a3ce929d0e0e4736', span_id: '00f067aa0ba902b7' },
};
console.log(JSON.stringify(entry));Semakan Pantas
Perkhidmatan pesanan NestJS anda mengeluarkan span SERVER, tetapi panggilan keluar kepada perkhidmatan pembayaran muncul sebagai jejak berasingan yang tidak bersambung dengan trace_id baharu. Instrumentasi automatik untuk HTTP dan Nest telah dipasang. Apakah punca utama yang paling berkemungkinan?
Rumusan
Anda telah menginstrumentasikan perkhidmatan NestJS dari hujung ke hujung dengan OpenTelemetry:
- Mulakan bootstrap dahulu — mulakan NodeSDK sebelum AppModule (atau gunakan
node --require) supaya instrumentasi automatik menampal HTTP/Nest pada waktunya. - Span automatik + manual — instrumentasi automatik memberikan span HTTP SERVER/CLIENT;
tracer.startActiveSpanmenambahkan span perniagaan yang tersarang dengan betul. - Perkayakan span aktif —
trace.getActiveSpan()dalam pengawal untuk melampirkan atribut penyewa/pengguna/pesanan. - Penyebaran — pengepala W3C
traceparentmembawatrace_id+ keputusan pensampelan; panggilan keluar mesti berjalan dalam konteks aktif untuk kekal berkorelasi. - Pensampelan & log — ParentBased + nisbah (atau pensampelan hujung dalam Collector) mengawal kos; masukkan
trace_id/span_idke dalam log untuk menghubungkan jejak dan pengelogan.
Peraturan utama: jejak kekal utuh hanya apabila konteks aktif mengalir melalui setiap lompatan async.
Pelajari TypeScript dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 20
- Pelajaran
- 76
Soalan Lazim
Adakah pelajaran “Jejak Teragih dengan OpenTelemetry” percuma?
Ya — teks penuh “Jejak Teragih dengan OpenTelemetry” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus API Bahagian Belakang Perusahaan NestJS, tingkat taraf kepada CoddyKit PRO. Kursus API Bahagian Belakang Perusahaan NestJS merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Jejak Teragih dengan OpenTelemetry”?
Instrumentasikan pengawal, penyedia dan klien HTTP untuk menjana rentang berkorelasi merentas perkhidmatan. Anda berlatih API Bahagian Belakang Perusahaan NestJS menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan API Bahagian Belakang Perusahaan NestJS?
Tiada pengalaman terdahulu diperlukan. Pembelajaran API Bahagian Belakang Perusahaan NestJS di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 3 daripada 4.
Berapa lamakah pelajaran “Jejak Teragih dengan OpenTelemetry” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran API Bahagian Belakang Perusahaan NestJS ini?
Ya. Setiap pelajaran API Bahagian Belakang Perusahaan NestJS menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Had Masa, Percubaan Semula dan Sekatan dengan Interceptor
- Pemutus Litar untuk Kegagalan Perkhidmatan Hiliran
- Jejak Teragih dengan OpenTelemetry
- Mentakrifkan SLO dan Belanjawan Ralat