Cours NestJS · Module 6: Gestion des erreurs et monitoring

Debugging Techniques - résoudre les problèmes

6 min de lecture
Dans cette leçon6

Un service renvoie un mauvais résultat et personne ne sait pourquoi. Tu places un console.log avant la ligne suspecte, tu redémarres, tu regardes. Insuffisant - tu en ajoutes un deuxième. Puis un troisième. Un quart d'heure plus tard, tu as une douzaine d'affichages, une console pleine de bruit, et toujours aucune idée de l'endroit où la valeur se gâte. Ensuite, la moitié de ces affichages reste dans le code pour toujours.

L'éclaireur de la légion ne travaille pas à l'aveugle. Il dispose d'une échelle d'outils : il observe d'abord depuis le camp, puis envoie une patrouille, et quand il le faut - il arrête la marche et examine le terrain pas à pas. Cette leçon dresse une telle échelle pour ton code.

L'échelle des outils

Quatre échelons, du plus intégré au plus externe :

  1. Le Logger de NestJS - intégré, toujours sous la main. Il montre ce qui s'est passé, mais n'arrête pas le programme.
  2. Node.js Inspector - lancé par le drapeau --inspect, il permet d'arrêter le programme et d'en regarder l'intérieur.
  3. Le débogueur de VS Code - le même mécanisme, avec des breakpoints posés dans l'éditeur plutôt que dans le navigateur.
  4. Les APM externes - New Relic, Datadog et leurs semblables. Ils observent l'application en production, où aucun breakpoint n'est possible.

L'ordre n'est pas arbitraire : chaque échelon coûte plus de préparation, tu n'y montes donc que lorsque le précédent n'a pas suffi.

Premier échelon : les niveaux de journalisation

NestJS possède un logger intégré à cinq niveaux : error, warn, log, verbose, debug. En mode développement, tous sont disponibles - précisément pour pouvoir descendre plus profond sans ajouter de code.

Tu choisis les niveaux à la création de l'application :

1// main.ts
2async function bootstrap() {
3  const app = await NestFactory.create(AppModule, {
4    logger: ['error', 'warn', 'log', 'verbose', 'debug'],
5  });
6
7  await app.listen(3000);
8}

Le tableau de l'option logger indique quels niveaux doivent être visibles. Ne passer que ['error', 'warn'] fait taire le reste - et c'est en général le réglage de production, car debug en production noierait les disques. En développement, tu passes le jeu complet.

Note que la configuration entre comme second argument de NestFactory.create, au moment de créer l'application. Il n'existe pas de méthode du genre app.setLogLevel() ni Logger.setLevel() - les niveaux se fixent une fois, au démarrage.

Un logger à la place de console.log

Les niveaux réglés, nous remplaçons les affichages par un vrai logger :

1@Injectable()
2export class LegionsService {
3  private readonly logger = new Logger(LegionsService.name);
4
5  async findOne(id: number) {
6    this.logger.debug(`Recherche de la légion ${id}`);
7
8    const legion = await this.repo.findOne({ where: { id } });
9
10    if (!legion) {
11      this.logger.warn(`La légion ${id} n'existe pas`);
12      throw new NotFoundException();
13    }
14
15    return legion;
16  }
17}

new Logger(LegionsService.name) donne au logger un contexte - le nom de la classe qui apparaîtra dans chaque entrée. Grâce à lui, tu vois dans la console d'où vient un message, sans l'écrire à la main dans le texte.

L'avantage sur console.log est pratique : les entrées debug disparaissent d'elles-mêmes dès que tu passes à la configuration de production. Tu n'as pas à les retirer avant un déploiement ni à te rappeler où tu les as laissées - c'est pourquoi je te le recommande comme habitude : pas des affichages, mais un logger au bon niveau.

Deuxième échelon : Node.js Inspector

Un logger montre ce que le programme a fait. Parfois, tu dois voir ce qu'il fait à l'instant - et alors tu l'arrêtes à un endroit choisi.

C'est le rôle du drapeau --inspect. Il active le protocole de débogage de V8 et permet de se rattacher à un processus en cours :

1node --inspect dist/main.js

Le chemin compte quatre étapes. Lance l'application avec le drapeau --inspect. Ouvre Chrome DevTools à l'adresse chrome://inspect et rattache-toi au processus. Pose un breakpoint dans le code - l'endroit où le programme doit s'arrêter. Analyse les variables et la call stack, la pile d'appels qui dit par où le programme est arrivé là.

Cette quatrième étape est ce que console.log ne peut pas donner : tu vois toutes les variables de la portée, et non seulement celles que tu avais devinées et affichées. Tu peux aussi parcourir le programme pas à pas et voir exactement où une valeur devient mauvaise.

Le drapeau lui-même ne vérifie rien et ne lance rien - il ouvre seulement la porte par laquelle un débogueur peut regarder à l'intérieur.

Les échelons supérieurs

Le débogueur de VS Code utilise le même protocole, seulement tu poses les breakpoints en cliquant à côté d'un numéro de ligne et tu inspectes les variables dans le panneau de l'éditeur. Rien de neuf dessous - une prise en main plus confortable du même mécanisme.

Les APM externes répondent à un autre problème : en production, tu ne peux pas arrêter l'application par un breakpoint, car elle sert du trafic réel. Des outils comme New Relic ou Datadog collectent des données en arrière-plan - temps de réponse, exceptions, requêtes lentes - et permettent de remonter à la cause après coup, à partir de ce qu'ils ont recueilli.

Récapitulatif

La reconnaissance se fait dans l'ordre, non à l'aveugle :

  • l'échelle des outils, de l'intégré à l'externe : Logger NestJS → Node.js Inspector → débogueur VS Code → APM externes,
  • le logger a cinq niveaux : error, warn, log, verbose, debug - en développement, tous sont disponibles,
  • tu règles les niveaux comme second argument de NestFactory.create(AppModule, { logger: [...] }) - il n'y a ni setLogLevel ni Logger.setLevel,
  • la production reçoit d'ordinaire ['error', 'warn'], le développement le jeu complet,
  • new Logger(NomDeClasse.name) donne un contexte aux entrées, et les entrées debug s'effacent seules dès que la configuration change,
  • le drapeau --inspect active le protocole de débogage de V8 et permet à un débogueur de se rattacher ; il ne vérifie rien et ne lance rien,
  • quatre étapes : lancer avec --inspect, ouvrir chrome://inspect, poser un breakpoint, analyser les variables et la call stack,
  • l'avantage sur les affichages : tu vois toutes les variables de la portée, pas seulement celles devinées à l'avance,
  • le débogueur de VS Code, c'est le même protocole avec une prise en main plus agréable,
  • en production, aucun breakpoint n'est possible - là travaillent les APM, qui collectent les données en arrière-plan.

Dans la prochaine leçon, nous verrons ce qui se passe à l'arrêt d'une application - le graceful shutdown, ou lever le camp sans abandonner les blessés. En attendant, retiens ceci : un logger dit ce qui s'est passé ; l'inspector permet d'arrêter la marche et de voir ce qui se passe maintenant.

Code de cette leçon : src/debugging/debug-techniques.ts
1// Debugging Techniques - Rozwiazywanie Problemow w Imperium
2// Outils et techniques de débogage NestJS
3import { Injectable, Logger } from '@nestjs/common';
4
5// ===========================================
6// 1. Le Logger comme outil de débogage
7// ===========================================
8
9@Injectable()
10export class DebugService {
11  private logger = new Logger('Debug');
12
13  // Débogage avec contexte
14  debugRequest(method: string, url: string, body: any) {
15    this.logger.debug('=== REQUEST DEBUG ===');
16    this.logger.debug('Method: ' + method);
17    this.logger.debug('URL: ' + url);
18    this.logger.debug('Body: ' + JSON.stringify(body, null, 2));
19    this.logger.debug('Timestamp: ' + new Date().toISOString());
20  }
21
22  // Mesure du temps d'exécution
23  async measureExecution<T>(
24    label: string,
25    fn: () => Promise<T>,
26  ): Promise<T> {
27    const start = Date.now();
28    this.logger.debug('START: ' + label);
29
30    try {
31      const result = await fn();
32      const duration = Date.now() - start;
33      this.logger.debug('END: ' + label + ' (' + duration + 'ms)');
34      return result;
35    } catch (error) {
36      const duration = Date.now() - start;
37      this.logger.error(
38        'FAIL: ' + label + ' (' + duration + 'ms) - ' + error.message
39      );
40      throw error;
41    }
42  }
43}
44
45// ===========================================
46// 2. Intercepteur de débogage
47// ===========================================
48
49import {
50  CallHandler,
51  ExecutionContext,
52  NestInterceptor,
53} from '@nestjs/common';
54import { Observable, tap } from 'rxjs';
55
56@Injectable()
57export class DebugInterceptor implements NestInterceptor {
58  private logger = new Logger('DebugInterceptor');
59
60  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
61    const request = context.switchToHttp().getRequest();
62    const method = request.method;
63    const url = request.url;
64    const start = Date.now();
65
66    this.logger.debug(method + ' ' + url + ' - START');
67
68    return next.handle().pipe(
69      tap({
70        next: (data) => {
71          const duration = Date.now() - start;
72          this.logger.debug(
73            method + ' ' + url + ' - ' + duration + 'ms'
74          );
75        },
76        error: (error) => {
77          const duration = Date.now() - start;
78          this.logger.error(
79            method + ' ' + url + ' - BLAD po ' + duration + 'ms: '
80            + error.message
81          );
82        },
83      }),
84    );
85  }
86}
87
88// ===========================================
89// 3. Détection de fuites mémoire
90// ===========================================
91
92function checkMemoryUsage() {
93  const usage = process.memoryUsage();
94  console.log('=== Utilisation de la mémoire ===');
95  console.log('RSS:       ' + Math.round(usage.rss / 1024 / 1024) + ' MB');
96  console.log('Heap Used: ' + Math.round(usage.heapUsed / 1024 / 1024) + ' MB');
97  console.log('Heap Total:' + Math.round(usage.heapTotal / 1024 / 1024) + ' MB');
98  console.log('External:  ' + Math.round(usage.external / 1024 / 1024) + ' MB');
99}
100
101checkMemoryUsage();
102
103console.log('');
104console.log('=== Debugging Techniques ===');
105console.log('Logger.debug() - informations détaillées');
106console.log('measureExecution() - mesure du temps');
107console.log('DebugInterceptor - logging automatique des requêtes');
108console.log('process.memoryUsage() - vérification de la mémoire');
109

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quels niveaux de logging sont disponibles en mode développement dans NestJS ?

  2. 2. Comment définit-on les niveaux de logging lors de la création d'une application NestJS ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Complète la fonction bootstrap() dans main.ts qui crée l'application avec logger défini sur l'ensemble complet ['error','warn','log','verbose','debug'] en développement, et uniquement ['error','warn','log'] en production

  • Tri vertical

    Ordonne les outils de débogage de l'intégré à l'externe

  • Clique dans l'ordre

    Ordonne les étapes de débogage avec Node.js Inspector, du lancement à l'analyse