Comment utiliser Winston pour journaliser les applications Node.js sur Ubuntu 20.04

Introduction

Une solution de journalisation efficace est cruciale pour le succès de toute application. Winston est une bibliothèque de journalisation polyvalente et une solution de journalisation populaire disponible pour les applications Node.js. Les fonctionnalités de Winston incluent la prise en charge de plusieurs options de stockage, des niveaux de journalisation, des requêtes de journalisation et un profileur intégré.

Dans ce tutoriel, vous utiliserez Winston pour journaliser une application Node/Express que vous créerez dans le cadre de ce processus. Vous verrez également comment combiner Winston avec Morgan, un autre middleware de journalisation de requêtes HTTP populaire pour Node.js, pour consolider les journaux de données de requêtes HTTP avec d’autres informations. Après avoir terminé ce tutoriel, votre serveur Ubuntu exécutera une petite application Node/Express, et Winston sera implémenté pour journaliser les erreurs et les messages dans un fichier et sur la console.

Prérequis

Pour suivre ce tutoriel, vous aurez besoin de :

Étape 1 — Création d’une application Node/Express de base

Winston est souvent utilisé pour enregistrer des événements provenant d’applications web construites avec Node.js. Dans cette étape, vous allez créer une application web Node.js simple en utilisant le framework Express. Vous utiliserez express-generator, un outil en ligne de commande, pour démarrer rapidement une application web Node/Express.

Étant donné que vous avez installé le Gestionnaire de Paquets Node lors des prérequis, vous pouvez utiliser la commande npm pour installer express-generator:

  1. sudo npm install express-generator -g

Le drapeau -g installe le package globalement, ce qui signifie qu’il peut être utilisé comme un outil en ligne de commande en dehors d’un projet/module Node existant.

Avec express-generator installé, vous pouvez créer votre application en utilisant la commande express, suivie du nom du répertoire que vous souhaitez utiliser pour le projet:

  1. express myApp

Pour ce tutoriel, le projet s’appellera myApp.

Remarque : Il est également possible d’exécuter l’outil express-generator directement sans l’installer globalement comme une commande système au préalable. Pour ce faire, exécutez cette commande:

  1. npx express-generator myApp

La commande npx est un exécuteur de commandes fourni avec le Gestionnaire de Packages Node qui facilite l’exécution d’outils en ligne de commande à partir du registre npm.

Lors du premier lancement, il vous demandera si vous acceptez de télécharger le package:

Output
Need to install the following packages:
  express-generator
Ok to proceed? (y)

Répondez y et appuyez sur ENTRÉE. Maintenant, vous pouvez utiliser npx express-generator à la place de express.

Ensuite, installez Nodemon, qui rechargera automatiquement l’application chaque fois que vous apporterez des modifications. Une application Node.js doit être redémarrée chaque fois que des modifications sont apportées au code source pour que ces modifications prennent effet, donc Nodemon surveillera automatiquement les modifications et redémarrera l’application. Comme vous souhaitez pouvoir utiliser nodemon comme un outil en ligne de commande, installez-le avec le drapeau -g:

  1. sudo npm install nodemon -g

Pour terminer la configuration de l’application, déplacez-vous vers le répertoire de l’application et installez les dépendances comme suit:

  1. cd myApp
  2. npm install

Par défaut, les applications créées avec express-generator s’exécutent sur le port 3000, donc vous devez vous assurer que le pare-feu ne bloque pas ce port.

Pour ouvrir le port 3000, exécutez la commande suivante :

  1. sudo ufw allow 3000

Vous avez maintenant tout ce dont vous avez besoin pour démarrer votre application web. Pour ce faire, exécutez la commande suivante :

  1. nodemon bin/www

Cette commande lance l’application sur le port 3000. Vous pouvez vérifier si elle fonctionne en pointant votre navigateur vers http://votre_adresse_ip_du_serveur:3000. Vous devriez voir quelque chose comme ceci :

À ce stade, vous pouvez ouvrir une deuxième session SSH vers votre serveur pour le reste de ce tutoriel, en laissant l’application web que vous venez de démarrer s’exécuter dans la session d’origine. Pour le reste de cet article, la session SSH initiale exécutant actuellement l’application sera appelée Session A. Toutes les commandes dans la Session A apparaîtront sur un fond bleu foncé comme ceci :

  1. nodemon bin/www

Vous utiliserez la nouvelle session SSH pour exécuter des commandes et modifier des fichiers. Cette session sera appelée Session B. Toutes les commandes dans la Session B apparaîtront sur un fond bleu clair comme ceci :

  1. cd ~/myApp

Sauf indication contraire, vous exécuterez toutes les commandes restantes dans la Session B.

Dans cette étape, vous avez créé l’application de base. Ensuite, vous allez la personnaliser.

Étape 2 — Personnalisation des variables de journalisation

Bien que l’application par défaut créée par express-generator soit un bon point de départ, vous devez personnaliser l’application afin qu’elle appelle le bon journalisateur au besoin.

express-generator inclut le middleware de journalisation HTTP Morgan que vous utiliserez pour enregistrer des données sur toutes les requêtes HTTP. Étant donné que Morgan prend en charge les flux de sortie, il s’associe parfaitement avec la prise en charge des flux intégrée à Winston, vous permettant de consolider les journaux de données des requêtes HTTP avec tout ce que vous choisissez de journaliser avec Winston.

Le modèle de base de express-generator utilise la variable logger lors de la référence au package morgan. Puisque vous utiliserez à la fois morgan et winston, qui sont tous deux des packages de journalisation, il peut être confus d’appeler l’un ou l’autre d’entre eux logger. Pour spécifier la variable que vous souhaitez, vous pouvez modifier les déclarations de variable en éditant le fichier app.js.

Pour ouvrir app.js pour l’édition, utilisez nano ou votre éditeur de texte préféré:

  1. nano ~/myApp/app.js

Recherchez la ligne suivante près du début du fichier:

~/myApp/app.js
...
var logger = require('morgan');
...

Changez le nom de la variable de logger à morgan:

~/myApp/app.js
...
var morgan = require('morgan');
...

Cette mise à jour spécifie que la variable déclarée morgan appellera la méthode require() liée au journalisateur de requêtes Morgan.

Vous devez trouver où la variable logger a été référencée ailleurs dans le fichier et la changer en morgan. Vous devrez également changer le format de journal utilisé par le package morgan en combined, qui est le format de journal Apache standard et inclura des informations utiles dans les journaux, telles que l’adresse IP distante et l’en-tête de requête HTTP de l’utilisateur.

Pour ce faire, trouvez la ligne suivante :

~/myApp/app.js
...
app.use(logger('dev'));
...

Modifiez-la comme suit :

~/myApp/app.js
...
app.use(morgan('combined'));
...

Ces modifications vous aideront à comprendre quel package de journalisation est référencé à tout moment après l’intégration de la configuration de Winston.

Une fois terminé, enregistrez et fermez le fichier.

Maintenant que votre application est configurée, vous pouvez commencer à travailler avec Winston.

Étape 3 — Installation et configuration de Winston

Dans cette étape, vous installerez et configurerez Winston. Vous explorerez également les options de configuration disponibles dans le cadre du package winston et créerez un journal pour enregistrer des informations dans un fichier et sur la console.

Installez winston avec la commande suivante :

  1. cd ~/myApp
  2. npm install winston

Il est utile de conserver tous les fichiers de configuration de support ou d’utilité pour vos applications dans un répertoire spécial. Créez un dossier config qui contiendra la configuration de winston :

  1. mkdir ~/myApp/config

Ensuite, créez un dossier qui contiendra vos fichiers journaux :

  1. mkdir ~/myApp/logs

Enfin, installez app-root-path :

  1. npm install app-root-path --save

Le package app-root-path est utile lors de la spécification des chemins dans Node.js. Bien que ce package ne soit pas directement lié à Winston, il est utile pour déterminer les chemins vers les fichiers dans Node.js. Vous l’utiliserez pour spécifier l’emplacement des fichiers journaux de Winston depuis la racine du projet et pour éviter une syntaxe de chemin relative moche.

Maintenant que la configuration de gestion des journaux est en place, vous pouvez définir vos paramètres. Créez et ouvrez ~/myApp/config/winston.js pour l’édition :

  1. nano ~/myApp/config/winston.js

Le fichier winston.js contiendra votre configuration winston.

Ensuite, ajoutez le code suivant pour requérir les packages app-root-path et winston :

~/myApp/config/winston.js
const appRoot = require('app-root-path');
const winston = require('winston');

Avec ces variables en place, vous pouvez définir les paramètres de configuration pour vos transports. Les transports sont un concept introduit par Winston qui fait référence aux mécanismes de stockage/sortie utilisés pour les journaux. Winston est livré avec quatre transports principaux intégrés : Console, File, HTTP et Stream.

Vous vous concentrerez sur les transports console et fichier pour ce tutoriel. Le transport console enregistrera les informations dans la console, et le transport fichier enregistrera les informations dans un fichier spécifié. Chaque définition de transport peut contenir des paramètres de configuration, tels que la taille du fichier, les niveaux de journalisation et le format du journal.

Voici un résumé rapide des paramètres que vous utiliserez pour chaque transport :

  • niveau: niveau des messages à journaliser.
  • nom_fichier: le fichier à utiliser pour écrire les données de journal.
  • gérerExceptions: intercepter et journaliser les exceptions non gérées.
  • tailleMax: taille maximale du fichier journal, en octets, avant qu’un nouveau fichier ne soit créé.
  • maxFichiers: limiter le nombre de fichiers créés lorsque la taille du fichier journal est dépassée.
  • format: comment la sortie du journal sera formatée.

Niveaux de journalisation indiquent la priorité des messages et sont représentés par un entier. Winston utilise les niveaux de journalisation npm qui sont priorisés de 0 à 6 (le plus élevé au plus bas) :

  • 0: erreur
  • 1: avertissement
  • 2: info
  • 3: http
  • 4: verbeux
  • 5: débogage
  • 6: bête

Lorsque vous spécifiez un niveau de journalisation pour un transport particulier, tout ce niveau ou plus élevé sera journalisé. Par exemple, en définissant un niveau de info, tout ce qui est au niveau erreur, avertissement, ou info sera journalisé.

Les niveaux de journalisation sont spécifiés lors de l’appel du journaliseur, ce qui signifie que vous pouvez exécuter la commande suivante pour enregistrer une erreur: journaliseur.erreur('message d'erreur de test').

Ajoutez encore dans le fichier de configuration, le code suivant pour définir les paramètres de configuration des transports fichier et console dans la configuration de winston:

~/myApp/config/winston.js
...
// Définir les paramètres personnalisés pour chaque transport (fichier, console)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5 Mo
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

Ensuite, ajoutez le code suivant pour instancier un nouveau journaliste winston avec des transports de fichier et de console en utilisant les propriétés définies dans la variable options:

~/myApp/config/winston.js
...
// Instancier un nouveau journaliste Winston avec les paramètres définis ci-dessus
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // Ne pas quitter en cas d'exceptions gérées
});

Par défaut, morgan ne produit des sorties que dans la console, donc vous définirez une fonction de flux qui sera capable de récupérer la sortie générée par morgan dans les fichiers journaux winston. Vous utiliserez le niveau de journal info pour récupérer la sortie par les deux transports (fichier et console). Ajoutez le code suivant au fichier de configuration :

~/myApp/config/winston.js
...
// Créer un objet de flux avec une fonction 'write' qui sera utilisée par `morgan`
logger.stream = {
  write: function(message, encoding) {
    // Utiliser le niveau de journal 'info' pour que la sortie soit capturée par les deux
    // transports (fichier et console)
    logger.info(message);
  },
};

Enfin, ajoutez le code ci-dessous pour exporter le journaliste afin qu’il puisse être utilisé dans d’autres parties de l’application :

~/myApp/config/winston.js
...
module.exports = logger;

Le fichier de configuration complet de winston ressemblera désormais à ceci :

~/myApp/config/winston.js
const appRoot = require("app-root-path");
const winston = require("winston");

// Définir les paramètres personnalisés pour chaque transport (fichier, console)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5 Mo
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

// Instancier un nouveau journal Winston avec les paramètres définis ci-dessus
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // Ne pas quitter en cas d'exceptions gérées
});

// Créer un objet de flux avec une fonction 'write' qui sera utilisée par `morgan`
logger.stream = {
  write: function (message, encoding) {
    // Utiliser le niveau de journal 'info' afin que la sortie soit captée par les deux
    // transports (fichier et console)
    logger.info(message);
  },
};

module.exports = logger;

Enregistrez et fermez le fichier.

Vous avez maintenant configuré le journal, mais votre application n’en est toujours pas consciente, ni comment l’utiliser, vous devez donc l’intégrer avec l’application.

Étape 4 — Intégration de Winston avec l’application

Pour faire fonctionner votre journal avec l’application, vous devez informer express. Vous avez vu à l’étape 2 que votre configuration express est située dans app.js, vous pouvez donc importer votre journal dans ce fichier.

Ouvrez le fichier pour l’édition:

  1. nano ~/myApp/app.js

Ajoutez une déclaration de variable winston près du haut du fichier avec les autres déclarations require:

~/myApp/app.js
...
var winston = require('./config/winston');
...

Le premier endroit où vous utiliserez winston est avec morgan. Toujours dans app.js, trouvez la ligne suivante :

~/myApp/app.js
...
app.use(morgan('combined'));
...

Met à jour pour inclure l’option stream :

~/myApp/app.js
...
app.use(morgan('combined', { stream: winston.stream }));
...

Ici, vous définissez l’option stream sur l’interface de flux que vous avez créée dans la configuration de winston.

Enregistrez et fermez le fichier.

Dans cette étape, vous avez configuré votre application Express pour fonctionner avec Winston. Ensuite, vous examinerez les données de journal.

Étape 5 — Accès aux données de journal et enregistrement de messages de journal personnalisés

Maintenant que l’application est configurée, vous êtes prêt à voir des données de journal. Dans cette étape, vous examinerez les entrées de journal et mettrez à jour vos paramètres avec un exemple de message de journal personnalisé.

Si vous rechargez la page dans le navigateur Web, vous devriez voir quelque chose de similaire à la sortie suivante dans la console de la session SSH A :

Output
[nodemon] restarting due to changes... [nodemon] starting `node bin/www` info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET / HTTP/1.1" 200 170 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Il y a deux entrées de journal ici : la première pour la demande de la page HTML ; la deuxième pour la feuille de style associée. Étant donné que chaque transport est configuré pour gérer les données de journal de niveau info, vous devriez également voir des informations similaires dans le transport de fichier situé à ~/myApp/logs/app.log.

Pour afficher le contenu du fichier journal, exécutez la commande suivante :

  1. tail ~/myApp/logs/app.log

tail va afficher les dernières parties du fichier dans votre terminal.

Vous devriez voir quelque chose de similaire à ce qui suit :

{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.573Z"}
{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://localhost:3000/\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.588Z"}

La sortie dans le fichier transport sera écrite sous forme d’objet JSON puisque vous avez utilisé winston.format.json() dans l’option format pour la configuration du transport de fichier. Vous pouvez en apprendre plus sur JSON dans Une introduction à JSON.

Jusqu’à présent, votre enregistreur ne consigne que les requêtes HTTP et les données associées. Ces informations sont essentielles à avoir dans vos journaux.

À l’avenir, vous voudrez peut-être enregistrer des messages de journal personnalisés, tels que l’enregistrement des erreurs ou le profilage des performances des requêtes de base de données. Par exemple, vous appellerez le journal depuis la route de gestion des erreurs. Par défaut, le package express-generator inclut déjà une route de gestion des erreurs 404 et 500, donc vous travaillerez avec cela.

Ouvrez le fichier ~/monApp/app.js :

  1. nano ~/myApp/app.js

Trouvez le bloc de code en bas du fichier qui ressemble à ceci :

~/myApp/app.js
...
// gestionnaire d'erreurs
app.use(function(err, req, res, next) {
  // définir les variables locales, en fournissant uniquement l'erreur en développement
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // rendre la page d'erreur
  res.status(err.status || 500);
  res.render('error');
});
...

Cette section est la route de gestion d’erreurs finale qui enverra finalement une réponse d’erreur au client. Étant donné que toutes les erreurs côté serveur passeront par cette route, c’est un bon endroit pour inclure le journal winston.

Parce que vous traitez désormais avec des erreurs, vous voulez utiliser le niveau de journalisation error. Les deux transports sont configurés pour journaliser les messages de niveau error, donc vous devriez voir la sortie dans les journaux de la console et du fichier.

Vous pouvez inclure tout ce que vous voulez dans le journal, y compris des informations telles que :

  • err.status : Le code de statut d’erreur HTTP. S’il n’est pas déjà présent, défaut à 500.
  • err.message : Détails de l’erreur.
  • req.originalUrl : L’URL qui a été demandée.
  • req.path : La partie chemin de l’URL de la demande.
  • req.method : Méthode HTTP de la demande (GET, POST, PUT, etc.).
  • req.ip : Adresse IP distante de la demande.

Mettez à jour la route de gestionnaire d’erreur pour inclure la journalisation winston :

~/myApp/app.js
...
// gestionnaire d'erreurs
app.use(function(err, req, res, next) {
  // définir les variables locales, fournir uniquement l'erreur en développement
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // inclure la journalisation winston
  winston.error(
    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
  );

  // rendre la page d'erreur
  res.status(err.status || 500);
  res.render('error');
});
...

Enregistrez et fermez le fichier.

Pour tester ce processus, essayez d’accéder à une page qui n’existe pas dans votre projet. Accéder à une page inexistante provoquera une erreur 404. Dans votre navigateur web, essayez de charger l’URL suivante : http://votre_adresse_ip_du_serveur:3000/foo. Grâce à la structure de base créée par express-generator, l’application est configurée pour répondre à une telle erreur.

Votre navigateur affichera un message d’erreur comme ceci :

Lorsque vous regardez la console dans la session SSH A, il devrait y avoir une entrée de journal pour l’erreur. Grâce au format colorize appliqué, il devrait être facile à repérer :

Output
[nodemon] starting `node bin/www` error: 404 - Not Found - /foo - GET - ::1 info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /foo HTTP/1.1" 404 982 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/foo" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Quant au journal de fichiers, en exécutant à nouveau la commande tail, vous devriez voir les nouveaux enregistrements de journal :

  1. tail ~/myApp/logs/app.log

Vous verrez un message comme le suivant :

{"level":"error","message":"404 - Not Found - /foo - GET - ::1","timestamp":"2022-04-25T18:08:33.508Z"}

Le message d’erreur comprend toutes les données que vous avez spécifiquement demandé à winston de journaliser dans le gestionnaire d’erreurs. Ces informations incluront le statut d’erreur (404 – Non trouvé), l’URL demandée (localhost/foo), la méthode de requête (GET), l’adresse IP effectuant la requête, et l’horodatage de la demande.

Conclusion

Dans ce tutoriel, vous avez construit une application web Node.js simple et intégré une solution de journalisation Winston qui fonctionnera comme un outil efficace pour fournir un aperçu des performances de l’application.

Vous pouvez faire beaucoup plus pour créer des solutions de journalisation robustes pour vos applications, particulièrement lorsque vos besoins deviennent plus complexes. Pour en savoir plus sur les transports de Winston, consultez la Documentation des Transports de Winston. Pour créer vos propres transports, consultez Ajout de Transports Personnalisés. Pour créer un point de terminaison HTTP à utiliser avec le transport principal HTTP, consultez winstond. Pour utiliser Winston comme un outil de profilage, consultez Profilage.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications-on-ubuntu-20-04