API Bahagian Belakang Perusahaan NestJS · Pelajaran

Jejak Teragih dengan OpenTelemetry

Instrumentasikan pengawal, penyedia dan klien HTTP untuk menjana rentang berkorelasi merentas perkhidmatan.

Pelajaran 3 daripada 413 langkah

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 permintaan
  • span_id — unik bagi setiap operasi
  • parent_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-http untuk 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-conventions

Memulakan 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({ ... }) options

Mengaitkan 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.startActiveSpan menambahkan span perniagaan yang tersarang dengan betul.
  • Perkayakan span aktif — trace.getActiveSpan() dalam pengawal untuk melampirkan atribut penyewa/pengguna/pesanan.
  • Penyebaran — pengepala W3C traceparent membawa trace_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_id ke dalam log untuk menghubungkan jejak dan pengelogan.

Peraturan utama: jejak kekal utuh hanya apabila konteks aktif mengalir melalui setiap lompatan async.

Percuma untuk bermula

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

  1. Had Masa, Percubaan Semula dan Sekatan dengan Interceptor
  2. Pemutus Litar untuk Kegagalan Perkhidmatan Hiliran
  3. Jejak Teragih dengan OpenTelemetry
  4. Mentakrifkan SLO dan Belanjawan Ralat
← Kembali ke API Bahagian Belakang Perusahaan NestJS