Fermer

septembre 24, 2025

Ajouter une observabilité en temps réel à l’application NESTJS avec l’OpenTelemetry

Ajouter une observabilité en temps réel à l’application NESTJS avec l’OpenTelemetry


Apprenez à ajouter une observabilité en temps réel à une application NESTJS à l’aide d’OpenTelemetry, puis visualisez ce qui se passe dans votre application avec des données réelles. Identifiez les goulots d’étranglement, réparez-les et réduisez efficacement votre application.

Dans cet article, nous ajouterons une observabilité en temps réel à un Pas application en utilisant OpenTelemetry. Vous apprendrez à visualiser ce qui se passe dans votre application avec de vraies données. Cette approche vous aidera à identifier les goulots d’étranglement, à les réparer et à mettre à l’échelle votre application efficacement.

Nous allons construire une application NESTJS simple qui génère des données de télémétrie et les envoie à Jaeger, nous permettant de surveiller et d’obtenir visuellement des informations précieuses dans la façon dont notre application se comporte.

Condition préalable

Pour suivre ce post, vous devrez avoir des connaissances de base de HTTP et d’API RESTFul, familiarité avec Docker et une compréhension de base de Pas et Manuscrit.

Qu’est-ce que l’observabilité?

L’observabilité est la capacité de comprendre un système de l’extérieur en fonction des données qu’elle génère. Il nous permet de regarder le fonctionnement interne de notre système et de comprendre ce qui se passe et pourquoi cela se produit.

Par exemple, si vous remarquez que votre api/orders Le point de terminaison est lent, l’observabilité nous montre la chaîne de services appelée par ce point de terminaison. Il peut indiquer que le problème de performance est causé par la requête de la base de données prenant 1,5 seconde à exécuter, ce qui nous permet d’identifier et de traiter rapidement le goulot d’étranglement.

Les données générées sont appelées «télémétrie» et se présente sous la forme de traces, de métriques et de journaux:

  • Traces: Documenter l’ensemble du flux d’une demande à travers la chaîne de services
  • Métrique: Donnez un aperçu des performances du système et de l’utilisation des ressources
  • Bûches: Fournir des informations riches en contexte qui complètent les traces et les mesures

NESTJS utilise une architecture modulaire, ce qui est idéal pour l’évolutivité et l’organisation, mais cela signifie également:

  • Une seule demande peut interagir avec plusieurs couches.
  • Si X dépend de Y, des erreurs ou une lenteur en y peuvent entraîner un échec x.
  • Les journaux seuls ne sont pas suffisants pour bien comprendre ce qui se passe la plupart du temps.

Comprendre l’opentélémétrie

L’OpenTelemetry, ou Otel, est un cadre d’observabilité open source qui nous permet de collecter, traiter et exporter des données de télémétrie. Il nous donne une norme commune pour collecter des données de télémétrie et les traiter, ainsi que des outils, des API et des SDK pour l’instrumentation, la génération et l’exportation de données de télémétrie.

Otel sert de pont entre notre code en cours d’exécution et les outils d’observabilité externes comme Jaegerqui est utilisé pour visualiser les données de télémétrie. En dehors de Jaeger, Otel s’intègre bien à d’autres outils populaires comme Gratter, Prométhée et Zipkin.

Configuration du projet

Commencez par créer un projet NESTJS:

nest new Otel-project
cd Otel-project

Ensuite, créez un docker-compose.yml fichier et ajoutez la configuration suivante:

services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"
      - "4317:4317"

Dans la configuration ci-dessus, nous avons configuré notre backend Jaeger pour recevoir, stocker et afficher nos traces. Nous exposons deux ports: «16686» pour accéder à l’interface utilisateur de Jaeger, et «4317» que l’OpenTelemetry utilise pour envoyer des traces à Jaeger. Après avoir ajouté le code au docker-compose.yaml fichier, exécutez la commande ci-dessous:

docker compose up

Maintenant, exécutez la commande suivante dans votre terminal pour installer les dépendances de notre projet NESTJS:

npm install @nestjs/common @nestjs/core @nestjs/platform-express rxjs \
@opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc @opentelemetry/sdk-metrics

Ensuite, créez un tracing.ts dossier dans votre src dossier et ajoutez ce qui suit:

import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
import {
  PeriodicExportingMetricReader,
  ConsoleMetricExporter,
} from "@opentelemetry/sdk-metrics";

const traceExporter = new OTLPTraceExporter({
  url: "http://localhost:4317",
});

const metricReader = new PeriodicExportingMetricReader({
  exporter: new ConsoleMetricExporter(),
  exportIntervalMillis: 15000,
});

const sdk = new NodeSDK({
  serviceName: "nestjs-opentelemetry-demo",
  traceExporter,
  metricReader,
  instrumentations: [getNodeAutoInstrumentations()],
});

(async () => {
  await sdk.start();
  console.log("✅ OpenTelemetry tracing & metrics initialized");
})();

process.on("SIGTERM", async () => {
  await sdk.shutdown();
  console.log("🛑 OpenTelemetry shutdown complete");
  process.exit(0);
});

Dans le code ci-dessus, traceExporter est une instance de OTLPTraceExporter. Il indique à l’OpenTelemetry où envoyer nos données de trace Appel de procédure à distance Google. Le metricReader est une instance de PeriodicExportingMetricReaderqui rassemble et exporte des mesures toutes les 15 secondes.

Ensuite, nous configurons le SDK du nœud OpenTelelemetry. Nous définissons le serviceName Nous utiliserons dans Jaeger pour identifier notre application. Nous passons le traceExporter et metricReaderet le instrumentation Propriété, qui s’attend à une liste d’instances d’instrumentation qui définissent comment l’OpenTelemetry suit automatiquement les opérations de notre application.

Au lieu de les passer manuellement, getNodeAutoInstrumentations() détecte des bibliothèques couramment utilisées comme Express, HTTP et certains pilotes de base de données que vous pourriez avoir installés et les configure avec des options par défaut pour activer le traçage automatique.

Enfin, nous écoutons le SIGTERM Signal qui est envoyé par nœud lorsque notre serveur s’arrête. Lorsque cela se produit, nous appelons sdk.shutdown()qui efface toutes les données de télémétrie en attente, ferme les connexions ouvertes et libère des ressources.

Mettez à jour le code dans votre main.ts dossier avec les éléments suivants:

import "./tracing";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
  console.log("🚀 App running on http://localhost:3000");
}

bootstrap();

Le import './tracing'; La déclaration doit être en haut de pour que le traçage de l’OpenTelemetry soit initialisé avant que tout autre code d’application s’exécute. Nous disons essentiellement à Node.js de charger et d’exécuter tout dans ce fichier, configurant ainsi le traçage et les métriques. Étant donné que cela se produit avant de créer l’application NESTJS et de démarrer le serveur, toutes les demandes entrantes sont suivies et rapportées.

L’OpenTelemetry n’a pas besoin de connaître le port à l’avance. Lorsque nous importons le fichier, il corrige automatiquement les bibliothèques Node.js de bas niveau et express avant le début de NESTJS. C’est pourquoi il s’accroche au cycle de vie de la demande, quel que soit le port auquel l’application se lie finalement.

Ajout de traçage

Dès la sortie de la boîte, l’OpenTelemetry nous donne un traçage automatique, qui peut automatiquement tracer chaque demande HTTP. De cette façon, nous n’avons pas besoin d’écrire de code de traçage.

Créer un fichier appelé orders.controller.ts et ajoutez ce qui suit:

import { Controller, Get } from "@nestjs/common";

@Controller("orders")
export class OrderController {
  @Get()
  getOrders() {
    return [
      { id: 1, item: "Product A", qty: 2 },
      { id: 2, item: "Product B", qty: 1 },
    ];
  }
}

Maintenant, même si nous n’avons pas de code de traçage, lorsque nous atteindrons ce point de terminaison, l’OpenTelemetry le détectera.

Cela dit, le traçage automatique du SDK nœud du nœud OpenTelemetry est limité. Il nous donne des informations limitées, comme la durée d’un point final pour répondre, mais dans certains cas, nous voulons peut-être plus d’informations, et c’est là que les portées manuelles entrent en jeu.

Une durée est une seule unité de travail dans notre application, et les traces sont constituées de portées. Spans a une heure de départ, un nom, des attributs et peut avoir un parent, permettant ainsi la nidification.

Les étendues manuelles nous permettent de suivre et d’étiqueter des parties spécifiques de notre application, telles que les opérations de base de données, les calculs de serveurs et les appels API externes.

Créer un fichier appelé product.controller.ts et ajoutez ce qui suit:

import { Controller, Get } from "@nestjs/common";
import { trace, context } from "@opentelemetry/api";

@Controller("products")
export class ProductController {
  @Get()
  async getProducts() {
    const tracer = trace.getTracer("nestjs-opentelemetry-demo");
    const span = tracer.startSpan(
      "fetch_products",
      undefined,
      context.active()
    );

    
    await new Promise((resolve) =>
      setTimeout(resolve, 100 + Math.random() * 400)
    );

    span.end();

    return [
      { id: 1, name: "Product A" },
      { id: 2, name: "Product B" },
    ];
  }
}

Dans le code ci-dessus, nous obtenons d’abord un tracer Instance d’OpenTelemetry, puis utilisez-les pour démarrer une nouvelle portée. Nous l’appelons fetch_productset nous ne définissons aucun attribution, mais utilisons context.active() Pour définir son parent afin qu’il fasse une partie de la trace que l’OpenteLelemetry a commencé pour la demande.

Maintenant, mettons à jour le app.module.ts fichier pour utiliser notre ProductController:

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { ProductController } from "./product.controller";
import { OrderController } from "./orders.controler";

@Module({
  imports: [],
  controllers: [AppController, ProductController, OrderController],
  providers: [AppService],
})
export class AppModule {}

Ajout de mesures

Comme mentionné précédemment, les métriques nous donnent un aperçu des performances du système et de l’utilisation des ressources. Ils suivent les valeurs au fil du temps, et ici, nous les utiliserons pour suivre le nombre de HTTP de demande que notre application gère sur tous les itinéraires. Pour ce faire, nous utiliserons un intercepteur simple qui s’exécute pour chaque demande et le compte à l’aide d’OpenTelemetry.

Créer un observability.interceptor.ts fichier et ajouter ce qui suit:

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
  Logger,
} from "@nestjs/common";
import { Observable, tap } from "rxjs";
import { metrics } from "@opentelemetry/api";

const meter = metrics.getMeter("nestjs-meter");
const requestCount = meter.createCounter("http_requests_total", {
  description: "Count of all HTTP requests",
});

@Injectable()
export class ObservabilityInterceptor implements NestInterceptor {
  private readonly logger = new Logger("HTTP");

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req = context.switchToHttp().getRequest();
    const method = req.method;
    const route = req.url;
    const now = Date.now();

    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - now;
        this.logger.log(`${method} ${route} - ${duration}ms`);

        requestCount.add(1, {
          method,
          route,
        });
      })
    );
  }
}

Tout d’abord, nous avons récupéré le compteur actif que nous avons défini dans notre tracing.ts Fichier du SDK opentélémétrie en utilisant metrics.getMeter('nestjs-meter'). La chaîne nestjs-meter sert de nom unique pour étiqueter cette instance de compteur particulier.

Ensuite, nous définissons un counter métrique http_request_total Cela sera incrémenté pour chaque demande qui arrive. requestCount.add(1, { method, route }) incréments le compteur par 1 et attache deux étiquettes (method et route).

Avec cette configuration, nous pouvons visualiser le nombre de demandes que chaque point de terminaison de notre application reçoit. Étant donné que nous utilisons un intercepteur NESTJS pour exécuter automatiquement cette logique à chaque demande, il n’est pas nécessaire d’ajouter des mesures à chaque contrôleur.

Ensuite, nous devons mettre à jour le main.ts Fichier pour utiliser l’intercepteur à l’échelle mondiale:

import "./tracing";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ObservabilityInterceptor } from "./observability.interceptor";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new ObservabilityInterceptor()); 

  await app.listen(3000);
  console.log("🚀 App running on http://localhost:3000");
}

bootstrap();

Dans le code ci-dessus, nous avons configuré notre intercepteur pour être sommé à l’échelle mondiale, ce qui signifie qu’il sera automatiquement lié à tous les itinéraires de notre application.

Dans notre tracing.ts fichier, nous avons configuré nos mesures à utiliser ConsoleMetricExporterdonc toutes les quelques secondes, nous les verrons imprimés sur notre terminal, mais cela peut être remplacé par des systèmes de surveillance comme Prometheus.

Maintenant, démarrez le serveur:

npm run start:dev

Voir les traces à Jaeger

Avec Jaeger maintenant en cours d’exécution, ouvert http: // localhost: 16686 Dans votre navigateur et vous devriez voir l’interface utilisateur de Jaeger.

Jaeger ui

Dans le tableau de bord Jaeger, select nestjs-opentelemetry-demo comme le Service et cliquez sur le Trouver des traces bouton. Vous verrez une liste des traces récentes, chacune représentant une seule demande HTTP. Comme expliqué précédemment, chaque trace est composée d’une ou plusieurs portées, et nos portées manuelles apparaîtront imbriquées dans la période HTTP principale.

Tout d’abord, testons le traçage automatique de l’OpenTelemetry. Utilisez la commande curl ci-dessous pour appuyer sur le get /orders Point de terminaison, puis rendez-vous à la Jaeger UI une fois de plus:

curl http://localhost:3000/orders

Test des commandes de Jaeger UI

Après avoir déclenché le /orders Point de terminaison, vous verrez une trace pour ce /orders demande. Après avoir cliqué dessus, la section supérieure doit afficher les métadonnées clés comme le nom, la durée et les portées totales. Les portées montrent le nombre total d’opérations individuelles (8), tandis que la profondeur montre à quel point ces travées sont imbriquées (4).

Vous pouvez voir différents middleware appliqués, comme expressInit et jsonParserainsi que le gestionnaire de demande et la méthode de contrôleur de NESTJS. Cette trace a été automatiquement générée par OpenTelemetry sans aucun code de traçage. Il se fait en se connectant aux bibliothèques communes en utilisant les configurations que nous avons définies dans le tracing.ts déposer.

De même, pour tester le fonctionnement des étendues manuelles, utilisez la commande curl ci-dessous pour appuyer sur le get /products Point de terminaison, puis rendez-vous à la Jaeger UI une fois de plus:

curl http://localhost:3000/products

Test de produits d'interface utilisateur Jaeger

Après avoir déclenché le /products Point de terminaison, vous verrez une trace pour ce /products demande.

Remarquez que nous pouvons également voir combien de temps prend chaque portée, et nous pouvons voir notre portée personnalisée fetch_products qui simule un appel de base de données, qui passe le plus de temps à ~ 472 ms.

Quelle est la prochaine étape?

Pour améliorer notre application, envisagez d’ajouter un collecteur qui se trouve entre votre application et les outils d’observabilité. Il recevra des données de télémétrie, les verra et l’enverra à des outils comme Jaeger. Vous pouvez également utiliser Prometheus pour capturer des mesures et surveiller votre application mieux, en particulier dans les environnements de production.

En fonction de vos besoins, vous pouvez intégrer un certain nombre d’outils Cloud APM (Application Performance Surveillant), car la plupart d’entre eux prennent en charge l’OpenTelemetry.

Conclusion

Dans cet article, nous avons ajouté une observabilité à une application NESTJS avec opentélétrie, ce qui nous donne une visibilité sur ce qui se passe sous le capot avec des traces de demande, des portées personnalisées et des mesures de base. Vous avez appris sur l’OpenTelemetry et ses concepts principaux, comment utiliser son SDK de nœud avec NESTJS, et comment exporter des données de télémétrie vers des plates-formes comme Jaeger et Prometheus.




Source link