NestJS एंटरप्राइज़ बैकएंड API · पाठ

OpenTelemetry के साथ वितरित ट्रेसिंग

सेवाओं के बीच संबद्ध स्पैन उत्पन्न करने के लिए नियंत्रकों, प्रदाताओं और HTTP क्लाइंटों में इंस्ट्रुमेंटेशन कीजिए।

पाठ 3, कुल 4 में से13 चरण

OpenTelemetry के साथ वितरित ट्रेसिंग, CoddyKit पर NestJS एंटरप्राइज़ बैकएंड API का एक निःशुल्क पाठ है। यह 4 में से 3वाँ पाठ है। आप नीचे पूरा पाठ निःशुल्क पढ़ सकते हैं—फिर अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर के साथ ब्राउज़र में इसका व्यावहारिक अभ्यास कर सकते हैं। यह NestJS एंटरप्राइज़ बैकएंड API सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। NestJS एंटरप्राइज़ बैकएंड API पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

Distributed Tracing क्यों

माइक्रोसर्विस के समूह में एक उपयोगकर्ता अनुरोध gateway, orders सेवा, payments सेवा और किसी तृतीय-पक्ष HTTP API तक पहुँच सकता है। जब latency बढ़ती है, तो केवल logs से यह पता नहीं चलता कि कौन-सा hop धीमा था।

Distributed tracing इन hops को एक साथ जोड़ता है। काम की प्रत्येक इकाई एक span बन जाती है; साझा trace_id से जुड़े spans एक end-to-end trace बनाते हैं।

  • trace_id — एक अनुरोध में हर सेवा के बीच समान value
  • span_id — प्रत्येक operation के लिए विशिष्ट
  • parent_span_id — spans एक tree में कैसे nested होते हैं

OpenTelemetry (OTel) इन spans को बनाने और आगे propagate करने का vendor-neutral standard है, जिसे हम NestJS में जोड़ेंगे।

एक Span की संरचना

span समय के दौरान होने वाले किसी एक operation का वर्णन करने वाला typed object मात्र है। NestJS को छूने से पहले यह मॉडल करना उपयोगी है कि OTel वास्तव में क्या emit करता है। नीचे SDK द्वारा भरे जाने वाले fields का साधारण TypeScript प्रारूप दिया गया है।

kind पर ध्यान दें: SERVER spans आने वाले अनुरोधों को दर्शाते हैं, जबकि CLIENT spans बाहर जाने वाली calls को। सेवा A में CLIENT span को सेवा B में SERVER span के साथ correlate करना ही context propagation का लाभ है।

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`);

OTel SDK इंस्टॉल करना

NestJS सेवा के लिए आपको OTel packages की तीन परतें चाहिए:

  • @opentelemetry/sdk-node — Node SDK और lifecycle
  • @opentelemetry/auto-instrumentations-node — HTTP, Express, Nest, pg, ioredis आदि के लिए zero-code patches
  • @opentelemetry/exporter-trace-otlp-http जैसा exporter, जो spans को collector या backend (Jaeger, Tempo, Honeycomb) तक भेजता है

केवल auto-instrumentation ही आने वाले HTTP और बाहर जाने वाले fetch/axios calls के लिए correlated SERVER और CLIENT spans बना देती है। Manual spans (बाद के दृश्यों में) इसके ऊपर business meaning जोड़ते हैं।

npm install @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/resources \
  @opentelemetry/semantic-conventions

Nest से पहले Tracing शुरू करना

सबसे महत्वपूर्ण नियम: किसी भी application module के import होने से पहले OTel SDK शुरू करें। Auto-instrumentation require के समय http जैसे modules में monkey-patching करके काम करती है। यदि Nest पहले लोड हो गया, तो patches उस तक नहीं पहुँचेंगे।

SDK को अपनी tracing.ts में रखें और उसे main.ts के बिल्कुल ऊपर import करें, या node --require ./dist/tracing.js से preload करें।

// 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));
});

इसे main.ts में जोड़ना

क्योंकि side-effect imports का क्रम महत्वपूर्ण है, tracing import पहला statement होना चाहिए — Nest factory और AppModule से भी ऊपर। जब तक यह file सबसे पहले है, ES module hoisting SDK के sdk.start() side effect के लिए physical order का पालन करेगी।

कोई भी hoisting ambiguity हटाने वाला अधिक सुरक्षित विकल्प आपके start script में node --require ./dist/tracing.js dist/main.js preload flag है।

// 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();

सेवाओं के बीच Context कैसे Propagate होता है

Spans तभी एकल trace बनते हैं जब trace_id सेवाओं के बीच प्रवाहित हो। OTel W3C के Trace Context मानक के माध्यम से ऐसा करता है: बाहर जाने वाले CLIENT spans में traceparent HTTP header जोड़कर और अंदर आने वाले SERVER spans में उसे निकालकर।

यह header इस प्रकार दिखता है:

  • traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
  • प्रारूप: version-traceId-parentSpanId-flags

ऑटो-इंस्ट्रुमेंटेशन के साथ HTTP के लिए यह अपने-आप होता है। मुख्य आवश्यकता यह है कि सक्रिय संदर्भ आपके async कोड में प्रवाहित होता रहे, ताकि बाहर जाने वाली कॉल को पता हो कि वह किस trace से संबंधित है।

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);

किसी Provider में मैन्युअल Spans

ऑटो-इंस्ट्रुमेंटेशन आपको HTTP-स्तर के spans देता है, लेकिन व्यावसायिक तर्क ("reserve inventory", "charge card") के लिए अपने spans होने चाहिए। एक Tracer प्राप्त करें और startActiveSpan से उस प्रक्रिया को घेरें, ताकि child spans वर्तमान SERVER span के अंदर सही ढंग से नेस्ट हों।

startActiveSpan अपने callback की अवधि तक span को सक्रिय रखता है। इसका अर्थ है कि कोई भी nested span या बाहर जाने वाली HTTP कॉल अपने-आप इसकी child बन जाती है।

// 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 };
  }
}

Controller के सक्रिय Span को समृद्ध करना

NestJS controller handler पहले से ही अनुरोध के लिए बनाए गए ऑटो-जेनरेटेड SERVER span के भीतर चलता है। नया span बनाने के बजाय सक्रिय span प्राप्त करें और उसमें उच्च-cardinality वाले व्यावसायिक attributes (tenant id, user id, order id) जोड़ें, ताकि traces में आसानी से खोज की जा सके।

trace.getActiveSpan() का उपयोग करें — यह वह SERVER span लौटाता है जिसके भीतर अनुरोध handler चल रहा है। यहाँ attributes जोड़ने से trace के एक ही node पर सारी जानकारी रहती है और वह अलग-अलग हिस्सों में नहीं बँटती।

// 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);
  }
}

बाहर जाने वाले HTTP Clients को Trace करना

जब orders service HTTP के माध्यम से payments service को कॉल करती है, तो आपको ऐसा CLIENT span चाहिए जो traceparent header को आगे भेजे, ताकि payments उसी trace को जारी रखे।

यदि आप ऑटो-इंस्ट्रुमेंट किए गए http module (Node fetch, axios, Nest का HttpService) का उपयोग करते हैं, तो propagation अपने-आप होता है — बशर्ते कॉल सक्रिय संदर्भ के भीतर हो। startActiveSpan callback के भीतर कॉल करने से बाहर जाने वाला CLIENT span आपके business span के अंदर नेस्ट होना सुनिश्चित होता है।

// 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();
      }
    });
  }
}

Sampling और लागत नियंत्रण

एंटरप्राइज़ स्तर पर 100% traces रिकॉर्ड करना महँगा होता है। OTel यह तय करने के लिए samplers का उपयोग करता है कि किन traces को रखना है। यह निर्णय traceparent में मौजूद sampled flag के माध्यम से आगे भेजा जाता है, ताकि सभी सेवाओं में किसी trace को लगातार रिकॉर्ड किया जाए या छोड़ दिया जाए।

  • ParentBasedSampler — upstream service के निर्णय का सम्मान करता है (मानक default)
  • TraceIdRatioBasedSampler — एक निश्चित अंश रखता है, जैसे 10%
  • Tail sampling — Collector में किया जाता है: सभी error/slow traces रखें और बाकी का sample लें

Root sampler को OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG env vars या SDK के sampler विकल्प से configure करें।

// 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

Logs को Traces से संबद्ध करना

अंतिम महत्वपूर्ण कदम आपके structured logs को traces से जोड़ना है। सक्रिय span context से trace_id और span_id निकालकर उन्हें प्रत्येक log line में जोड़ें। इसके बाद Jaeger/Tempo में आप किसी धीमे span से सीधे उसके logs पर और फिर वापस जा सकते हैं।

वर्तमान ids पढ़ने के लिए trace.getActiveSpan()?.spanContext() का उपयोग करें और उन्हें अपने Pino/Winston logger में fields के रूप में दें।

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));

त्वरित जाँच

आपकी NestJS orders service SERVER spans भेजती है, लेकिन payments service को की गई बाहर जाने वाली calls एकदम नए trace_id के साथ अलग, असंबद्ध traces के रूप में दिखाई देती हैं। HTTP और Nest दोनों के लिए ऑटो-इंस्ट्रुमेंटेशन स्थापित है। इसका सबसे संभावित मूल कारण क्या है?

पुनरावलोकन

आपने OpenTelemetry के साथ NestJS service को शुरू से अंत तक instrument किया:

  • सबसे पहले Bootstrap करें — AppModule से पहले NodeSDK शुरू करें (या node --require का उपयोग करें), ताकि ऑटो-इंस्ट्रुमेंटेशन समय रहते HTTP/Nest में बदलाव कर सके।
  • ऑटो + मैन्युअल spans — ऑटो-इंस्ट्रुमेंटेशन SERVER/CLIENT HTTP spans देता है; tracer.startActiveSpan ऐसे business spans जोड़ता है जो सही ढंग से नेस्ट होते हैं।
  • सक्रिय span को समृद्ध करें — controllers में trace.getActiveSpan() का उपयोग करके tenant/user/order attributes जोड़ें।
  • Propagation — W3C का traceparent header trace_id और sampling decision को ले जाता है; संबद्ध बने रहने के लिए बाहर जाने वाली calls सक्रिय संदर्भ के भीतर चलनी चाहिए।
  • Sampling और logs — ParentBased + ratio (या Collector में tail sampling) लागत नियंत्रित करते हैं; traces और logging के बीच कड़ी बनाने के लिए logs में trace_id/span_id जोड़ें।

मूल नियम: trace तभी पूरी तरह जुड़ी रहती है जब सक्रिय संदर्भ हर async hop में प्रवाहित होता रहे।

शुरुआत निःशुल्क

एआई शिक्षक के साथ TypeScript सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
20
पाठ
76

अक्सर पूछे जाने वाले प्रश्न

क्या “OpenTelemetry के साथ वितरित ट्रेसिंग” पाठ निःशुल्क है?

हाँ—“OpenTelemetry के साथ वितरित ट्रेसिंग” का पूरा पाठ यहाँ वेब पर निःशुल्क पढ़ा जा सकता है। इंटरैक्टिव अभ्यास (अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर) करने और NestJS एंटरप्राइज़ बैकएंड API पाठ्यक्रम का बाकी हिस्सा अनलॉक करने के लिए CoddyKit PRO लें। NestJS एंटरप्राइज़ बैकएंड API पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“OpenTelemetry के साथ वितरित ट्रेसिंग” में मैं क्या सीखूँगा?

सेवाओं के बीच संबद्ध स्पैन उत्पन्न करने के लिए नियंत्रकों, प्रदाताओं और HTTP क्लाइंटों में इंस्ट्रुमेंटेशन कीजिए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ NestJS एंटरप्राइज़ बैकएंड API का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या NestJS एंटरप्राइज़ बैकएंड API शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर NestJS एंटरप्राइज़ बैकएंड API शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 3वाँ पाठ है।

“OpenTelemetry के साथ वितरित ट्रेसिंग” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस NestJS एंटरप्राइज़ बैकएंड API पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर NestJS एंटरप्राइज़ बैकएंड API पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. इंटरसेप्टर के साथ समय-सीमा, पुनःप्रयास और बल्कहेड
  2. डाउनस्ट्रीम विफलताओं के लिए सर्किट ब्रेकर
  3. OpenTelemetry के साथ वितरित ट्रेसिंग
  4. SLO और त्रुटि बजट निर्धारित करना
← NestJS एंटरप्राइज़ बैकएंड API पर वापस जाएँ