> ## Documentation Index
> Fetch the complete documentation index at: https://docs.logbrew.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Logging Node.js avec Winston, Pino et Express

> Configurez un logger Winston ou Pino dans Node.js et Express, choisissez les niveaux de log et envoyez à LogBrew des logs structurés avec ID de trace.

LogBrew est une plateforme d’observabilité pour les développeurs et les agents
de codage, avec une offre gratuite généreuse et sans carte bancaire.

Ce guide configure le logging Winston et le logging Pino dans Node.js, ajoute
un logger Express et envoie chaque log à LogBrew avec la trace de sa requête.
Chaque exemple a tourné sous Node.js 22 avec Winston 3.19, Pino 10.4,
Express 5.3, `@logbrew/sdk` 0.1.40, `@logbrew/node` 0.1.24 et
`@logbrew/express` 0.1.10. Chaque bloc de sortie montre la sortie réelle.

## Choisir Winston ou Pino

Les deux bibliothèques écrivent des logs JSON structurés. Gardez la
bibliothèque que votre app utilise déjà. La configuration LogBrew diffère pour
chacune.

| | Winston | Pino |
| - | - | - |
| Sortie par défaut | Vous choisissez le format, comme `winston.format.json()`. | 1 ligne JSON par enregistrement. |
| Argument du message | `logger.info("message", { fields })` | `logger.info({ fields }, "message")` |
| Destinations | Transports dans le processus de l’app. | Transports dans un worker thread. |
| Loggers enfants | `logger.child({ fields })` | `logger.child({ fields })` |
| Masquage | Votre propre format. | Option `redact` intégrée. |
| Configuration LogBrew | Ajoutez `createLogBrewWinstonTransport()` à `transports`. | Appelez `installLogBrewPinoInstrumentation()` une seule fois. |

## Préparer un projet LogBrew

**1. Créer une clé de projet**

Connectez-vous avec la [CLI](https://docs.logbrew.co/fr/guides/cli.md). Créez un projet et stockez
sa clé d’ingestion dans un fichier lisible par son seul propriétaire.

```bash theme={null}
install -d -m 700 "$HOME/.logbrew"
logbrew projects create checkout-api \
  --runtime node \
  --environment development \
  --ingest-key-file "$HOME/.logbrew/checkout-api.ingest" \
  --json
```

La CLI n’affiche pas la clé. En production, utilisez le gestionnaire de
secrets de votre plateforme.

**2. Installer les paquets**

Les paquets exigent Node.js 18.19 ou plus récent. Installez Winston, Pino
ou les deux.

```bash theme={null}
npm install @logbrew/sdk @logbrew/node @logbrew/express express winston pino
```

**3. Définir la clé et l’identité de déploiement**

```bash theme={null}
export LOGBREW_SERVER_API_KEY="$(tr -d '\r\n' < "$HOME/.logbrew/checkout-api.ingest")"
export LOGBREW_ENVIRONMENT="development"
export LOGBREW_RELEASE="checkout-api@1.0.0"
```

`createLogBrewNodeClient()` lit la clé dans `LOGBREW_SERVER_API_KEY`. Le
nom du service est le `name` de `package.json`. Définissez
`LOGBREW_SERVICE_NAME` pour utiliser un autre nom. Les exemples utilisent
des modules ES. Définissez donc `"type": "module"` dans `package.json`.

## Niveaux de log Node.js

Chaque bibliothèque a ses propres noms et valeurs de niveau. LogBrew stocke 4
niveaux et garde le niveau d’origine dans les métadonnées.

| Niveau LogBrew | Niveau Winston (priorité) | Niveau Pino (valeur) |
| - | - | - |
| `info` | `info` (2), `http` (3), `verbose` (4), `debug` (5), `silly` (6) | `info` (30), `debug` (20), `trace` (10) |
| `warning` | `warn` (1) | `warn` (40) |
| `error` | `error` (0) | `error` (50) |
| `critical` | `fatal` ou `critical`, si vous les définissez | `fatal` (60) |

Dans Winston, une valeur plus basse est plus grave. Dans Pino, une valeur plus
haute est plus grave. Utilisez `info` en production, et `debug` seulement pour
un diagnostic court.

## Logger Winston

Enregistrez ce fichier sous `winston-app.js`. Le premier format écarte les
enregistrements sous le niveau du logger avant qu’un transport les reçoive. Le
transport LogBrew reçoit chaque enregistrement que le logger écrit. Il a donc
besoin de ce filtre.

```js theme={null}
import winston from "winston";
import { createLogBrewWinstonTransport } from "@logbrew/sdk";
import { createLogBrewNodeClient } from "@logbrew/node";

const client = createLogBrewNodeClient();

const logger = winston.createLogger({
  level: "info",
  format: winston.format.combine(
    winston.format((info) => (logger.isLevelEnabled(info.level) ? info : false))(),
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { component: "checkout" },
  transports: [
    new winston.transports.Console(),
    createLogBrewWinstonTransport({ client })
  ]
});

logger.debug("cart loaded");
logger.info("order created", { orderId: "ord_1001", totalCents: 4200 });
logger.warn("payment retry", { attempt: 2 });
logger.error(new Error("card declined"));

await client.shutdown();
```

Exécutez `node winston-app.js`. La console reçoit 1 ligne JSON par
enregistrement de niveau `info` ou plus. La trace de pile est raccourcie ici.

```text theme={null}
{"component":"checkout","level":"info","message":"order created","orderId":"ord_1001","timestamp":"2026-11-07T14:19:53.863Z","totalCents":4200}
{"attempt":2,"component":"checkout","level":"warn","message":"payment retry","timestamp":"2026-11-07T14:19:53.864Z"}
{"component":"checkout","level":"error","message":"card declined","stack":"Error: card declined\n    at file:///srv/app/winston-app.js:25:14\n ...","timestamp":"2026-11-07T14:19:53.864Z"}
```

LogBrew reçoit les mêmes 3 enregistrements. Les champs deviennent des
métadonnées `context.*`.

| `message` | `level` | `metadata` |
| - | - | - |
| `order created` | `info` | `winstonLevel` `info`, `context.component` `checkout`, `context.orderId` `ord_1001`, `context.totalCents` 4200 |
| `payment retry` | `warning` | `winstonLevel` `warn`, `context.component` `checkout`, `context.attempt` 2 |
| `card declined` | `error` | `winstonLevel` `error`, `context.component` `checkout`, `errorName` `Error`, `errorMessage` `card declined` |

Le transport envoie le nom et le message de l’erreur, mais pas la trace de
pile. Définissez `includeErrorStack: true` dans
`createLogBrewWinstonTransport()` pour envoyer la trace de pile. Chaque
message devient aussi un breadcrumb du problème suivant. Définissez
`breadcrumbs: false` pour désactiver ce comportement.

## Logger Pino

Enregistrez ce fichier sous `pino-app.js`. Appelez
`installLogBrewPinoInstrumentation()` une seule fois, avant le premier log.
La fonction lit chaque enregistrement Pino terminé. Pino écrit toujours vers sa
propre destination.

```js theme={null}
import pino from "pino";
import {
  createLogBrewNodeClient,
  installLogBrewPinoInstrumentation
} from "@logbrew/node";

const client = createLogBrewNodeClient();
installLogBrewPinoInstrumentation({ client });

const logger = pino({
  level: "info",
  redact: ["password", "token", "req.headers.authorization"]
});
const orderLogger = logger.child({ component: "checkout" });

orderLogger.debug("cart loaded");
orderLogger.info({ orderId: "ord_1001", totalCents: 4200 }, "order created");
orderLogger.warn({ attempt: 2, token: "tok_example" }, "payment retry");
orderLogger.error(new Error("card declined"), "payment failed");

await client.shutdown();
```

Exécutez `node pino-app.js`. Le nom d’hôte et la trace de pile sont raccourcis
ici.

```text theme={null}
{"level":30,"time":1794061059490,"pid":13985,"hostname":"app-host","component":"checkout","orderId":"ord_1001","totalCents":4200,"msg":"order created"}
{"level":40,"time":1794061059493,"pid":13985,"hostname":"app-host","component":"checkout","attempt":2,"token":"[Redacted]","msg":"payment retry"}
{"level":50,"time":1794061059494,"pid":13985,"hostname":"app-host","component":"checkout","err":{"type":"Error","message":"card declined","stack":"Error: card declined\n ..."},"msg":"payment failed"}
```

LogBrew reçoit 3 enregistrements. Pino écarte l’enregistrement `debug` avant
que LogBrew le voie.

| `message` | `level` | `metadata` |
| - | - | - |
| `order created` | `info` | `pinoLevel` `info`, `pinoLevelNumber` 30, `context.component` `checkout`, `context.orderId` `ord_1001`, `context.totalCents` 4200 |
| `payment retry` | `warning` | `pinoLevel` `warn`, `pinoLevelNumber` 40, `context.component` `checkout`, `context.attempt` 2 |
| `payment failed` | `error` | `pinoLevel` `error`, `pinoLevelNumber` 50, `context.component` `checkout`, `errorName` `Error`, `errorMessage` `card declined` |

Le champ `token` n’arrive pas dans LogBrew. LogBrew écarte les champs
d’identifiants, de cookies, de corps, de paramètres de requête, d’URL et de
trace de pile. Il garde vos chemins `redact` de Pino comme second contrôle.
`pid`, `hostname` et `time` sont des valeurs par défaut de Pino. LogBrew ne les
copie donc pas.

La capture automatique exige Pino 9.11 ou plus récent dans la branche 9.x, ou
Pino 10.1 ou plus récent. Pour les versions plus anciennes de Pino, utilisez
`createLogBrewPinoDestination()` de `@logbrew/sdk` comme destination Pino.

Pour lire la sortie de Pino en développement, envoyez-la vers `pino-pretty`.

```bash theme={null}
node pino-app.js | npx pino-pretty
```

## Logger Express

`logbrewMiddleware()` crée 1 span de requête par requête.
`logbrewErrorHandler()` signale les erreurs comme problèmes. Un log Pino dans
une requête reçoit l’ID de trace et l’ID de span de cette requête. Enregistrez
ce fichier sous `express-app.js`.

```js theme={null}
import express from "express";
import pino from "pino";
import { logbrewErrorHandler, logbrewMiddleware } from "@logbrew/express";
import {
  createLogBrewNodeClient,
  installLogBrewPinoInstrumentation
} from "@logbrew/node";

const client = createLogBrewNodeClient();
installLogBrewPinoInstrumentation({ client });
const logger = pino({ level: "info" });

const app = express();
app.use(logbrewMiddleware());

app.get("/orders/:orderId", (req, res) => {
  logger.info({ orderId: req.params.orderId }, "order loaded");
  res.json({ ok: true });
});

app.get("/fail", () => {
  throw new Error("inventory service timeout");
});

app.use(logbrewErrorHandler());
app.use((err, req, res, next) => {
  logger.error(err, "request failed");
  res.status(500).json({ error: "internal" });
});

const server = app.listen(3000, () => logger.info("listening on 3000"));

process.on("SIGTERM", () => {
  server.close(async () => {
    await client.shutdown();
    process.exit(0);
  });
});
```

Démarrez l’app. Envoyez ensuite 2 requêtes depuis un second terminal.

```bash theme={null}
curl -s http://127.0.0.1:3000/orders/ord_1001
curl -s http://127.0.0.1:3000/fail
```

LogBrew reçoit ces signaux. Le log et le span de chaque requête ont le même ID
de trace.

| Signal | Nom ou message | Trace |
| - | - | - |
| span | `GET /orders/:orderId`, statut `ok` | `4f18970f494d633c2164588fee9c692a` |
| log | `order loaded` | `4f18970f494d633c2164588fee9c692a` |
| problème | `GET /fail failed`, message `inventory service timeout` | `d273e106d73ce6c5a5955e5a8c74d2d0` |
| span | `GET /fail`, statut `error` | `d273e106d73ce6c5a5955e5a8c74d2d0` |
| log | `request failed` | `d273e106d73ce6c5a5955e5a8c74d2d0` |

Le nom du span utilise le modèle de route, pas le chemin concret. Le log
`listening on 3000` n’a pas de trace, car il est écrit hors d’une requête.

Laissez le middleware créer ses propres clients de requête. Ne passez pas le
`client` global du processus à `logbrewMiddleware()` ou
`logbrewErrorHandler()`. Le middleware arrête son client de requête après
chaque réponse.

### Ajouter la trace aux logs Winston

Pour Winston, donnez la trace active au transport.

```js theme={null}
import { createLogBrewWinstonTransport } from "@logbrew/sdk";
import { createLogBrewNodeClient, getActiveLogBrewTrace } from "@logbrew/node";

const client = createLogBrewNodeClient();
const logbrewTransport = createLogBrewWinstonTransport({
  client,
  traceProvider: getActiveLogBrewTrace
});
```

Ajoutez `logbrewTransport` à la liste `transports` du logger. Un log Winston
dans une requête Express a alors l’ID de trace et l’ID de span de la requête.

## Relire les logs

```bash theme={null}
logbrew read logs --service checkout-api --since 1h --json
logbrew read logs --severity error --search "request failed" --since 1h --json
logbrew read logs --trace "<trace_id>" --since 1h --json
```

La dernière commande lit tous les logs d’une seule requête. Copiez
`<trace_id>` depuis une trace ou un problème. Pour lire depuis la CI ou un
agent de codage, utilisez une clé de lecture du projet. Consultez
[Vérifier la configuration](https://docs.logbrew.co/fr/guides/verify-setup.md).

## Arrêter sans perdre de logs

Le client envoie les signaux en file d’attente 1 seconde après le premier
signal ou dès 50 signaux. Quand la boucle d’événements est vide, le client
envoie les signaux restants pendant 2 secondes au plus. `process.exit()` et les
signaux système sautent cette étape. Appelez `await client.shutdown()` avant
de quitter, comme le fait le handler `SIGTERM` de l’exemple Express.

## Dépannage

| Symptôme | Cause | Correctif |
| - | - | - |
| `createLogBrewNodeClient requires serverApiKey, apiKey, LOGBREW_SERVER_API_KEY, or LOGBREW_API_KEY` | La clé n’est pas définie dans ce shell. | Relancez l’étape `export`. |
| Des logs Winston `debug` apparaissent dans LogBrew mais pas dans la console | Le transport LogBrew n’applique pas le niveau du logger. | Ajoutez le format de filtre de niveau de l’exemple Winston. |
| Les logs Pino n’apparaissent pas dans LogBrew | La version de Pino n’a pas de tracing channel, ou les logs ont été écrits avant `installLogBrewPinoInstrumentation()`. | Utilisez Pino 9.11 ou plus récent, ou 10.1 ou plus récent. Installez la capture avant le premier log. |
| Les logs s’arrêtent après la première requête, et `client.shutdown()` lève `client is already shut down` | Le client global du processus a été passé au middleware Express. | Retirez l’option `client` de `logbrewMiddleware()` et `logbrewErrorHandler()`. |
| Les derniers logs manquent après un déploiement | Le processus s’est arrêté avant la livraison. | Appelez `await client.shutdown()` dans le handler `SIGTERM`. |

Pour choisir les champs de log, consultez
[Logs structurés](https://docs.logbrew.co/fr/concepts/structured-logging.md). Pour les autres
frameworks, consultez [Intégration du SDK](https://docs.logbrew.co/fr/guides/sdk-ingestion.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.