You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
apps/api/src/main.ts utilise le Logger Nest en sortie texte. Cloud Run capture stdout/stderr
dans Cloud Logging, qui sait lire une ligne JSON et son champ severity pour classer autre
chose que de l'INFO (WARNING, ERROR). En texte, tout ressort en INFO : le seul outil de
diagnostic une fois déployé (ADR 0004) devient aveugle aux erreurs — un 500 se noie dans les logs de
requête.
Cette issue remplace le logging app-wide par du JSON structuré. Elle a été détachée de #26,
qui ne garde que le minimum couplé à son filtre d'exception (logguer une erreur rattrapée en severity: ERROR). Ici, c'est l'axe observabilité complet.
Décision : pino (via nestjs-pino)
Log app-wide structuré, avec redaction, corrélation de requête et logging de requête automatique :
un logger maison réinventerait tout ça. pino est le choix — rapide, JSON natif, redact
intégré, intégration Nest éprouvée par nestjs-pino.
Ce choix ne contredit pas l'ethos « écrit à la main plutôt qu'une lib » d'environment.ts : là,
une poignée de variables ne justifie pas une lib de schéma ; ici, le JSON structuré + redaction +
trace + logging de requête est un vrai périmètre où pino gagne sa place. C'est l'arbitrage
qu'on s'était réservé en PR sur #26, tranché.
Périmètre
nestjs-pino branché : NestFactory.create(..., { bufferLogs: true }) puis app.useLogger(app.get(Logger)), pour que main.ts et tous les providers loggent via pino.
Mapping niveau → severity attendu par Cloud Logging. pino émet des niveaux numériques
(30 info, 40 warn, 50 error, 60 fatal) ; un formatters.level les traduit en { severity: 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL' }, avec messageKey: 'message' et timestamp ISO. Cloud Logging lit alors severity et message de la ligne JSON.
Corrélation de requête : dériver le trace id de l'en-tête X-Cloud-Trace-Context injecté par
Cloud Run, exposé sous logging.googleapis.com/trace (clé reconnue par Cloud Logging) — chaque
log d'une requête est ainsi rattaché à sa trace.
Redaction, non négociable (redact de pino, remove: true) : jamais de clé de provider
(GEMINI_API_KEY, OPENROUTER_API_KEY, en-tête authorization), jamais de bytes d'image. Les
serializers de requête/réponse ne loggent pas le corps.
Dev vs prod : en development, transport pino-pretty lisible ; en production, JSON brut sur
stdout (aucun transport). Sélection par NODE_ENV (déjà validé par environment.ts).
Logging de requête : autoLogging de nestjs-pino activé, mais serializers configurés pour ne
garder que méthode, chemin, statut, durée et trace id — pas le corps, pas les en-têtes sensibles.
#26 pose le filtre d'exception et lui fait logguer une erreur rattrapée en severity: ERROR
(minimum vital pour tenir sa promesse « pas de stack au client »). Ordre : #26 d'abord. Quand la
présente issue passe, le filtre logge à travers le logger pino injecté au lieu de son émission
minimale — un point de câblage, pas une réécriture. La forme severity: ERROR attendue par #26 est
exactement ce que le mapping ci-dessus produit.
Dépendances outillage
pino, nestjs-pino et pino-pretty (dev) épinglés à l'exact — jamais en plage, comme tout
l'outillage (CLAUDE.md, et #21 pour l'automatisation des bumps). pino-pretty en devDependencies
uniquement (il ne doit pas partir dans l'image de prod).
Hors périmètre
Pas de métriques ni de tracing distribué (OpenTelemetry) — corrélation par trace id suffit au MVP.
Pas de sink externe (Datadog, etc.) : Cloud Logging est le seul outil (ADR 0004).
Pas de nouvel ADR : applique l'ADR 0004 (diagnostic Cloud Run). Le choix pino est tracé ici et
dans le corps de la PR — pas une décision structurante d'architecture.
Critères d'acceptation
Chaque log applicatif est une ligne JSON avec un champ severity ; un logger.error(...)
produit severity: ERROR, un warnWARNING, un log/infoINFO.
Le trace id de X-Cloud-Trace-Context apparaît sous logging.googleapis.com/trace dans les
logs d'une requête portant cet en-tête.
Aucun log ne contient de clé de provider, d'en-tête authorization, ni de bytes d'image —
assertion de test sur le payload émis (destination pino capturée en mémoire).
En development, la sortie est lisible (pino-pretty) ; en production, JSON brut sur stdout.
main.ts et les providers loggent via pino (useLogger + bufferLogs), plus aucun Logger
Nest texte résiduel.
pino-pretty est en devDependencies, absent de l'image de prod.
yarn check vert.
Découpage en tâches (TDD, test rouge d'abord)
Config pino — un module/factory qui construit les options pino (formatters level → severity,
messageKey, timestamp ISO, redact) ; test sur le mapping de severity et sur la redaction (une clé
et des bytes d'image n'apparaissent pas dans la ligne émise, via une destination capturée).
Intégration Nest — nestjs-pino dans AppModule, bufferLogs + useLogger dans main.ts ;
test d'intégration qu'un log passe bien par pino.
Corrélation de requête — extraction du trace id depuis X-Cloud-Trace-Context ; test qu'une
requête avec l'en-tête produit logging.googleapis.com/trace.
Dev vs prod — pino-pretty conditionné à NODE_ENV=development ; test de sélection.
De quoi il s'agit
apps/api/src/main.tsutilise leLoggerNest en sortie texte. Cloud Run capture stdout/stderrdans Cloud Logging, qui sait lire une ligne JSON et son champ
severitypour classer autrechose que de l'
INFO(WARNING, ERROR). En texte, tout ressort enINFO: le seul outil dediagnostic une fois déployé (ADR 0004) devient aveugle aux erreurs — un 500 se noie dans les logs de
requête.
Cette issue remplace le logging app-wide par du JSON structuré. Elle a été détachée de #26,
qui ne garde que le minimum couplé à son filtre d'exception (logguer une erreur rattrapée en
severity: ERROR). Ici, c'est l'axe observabilité complet.Décision : pino (via
nestjs-pino)Log app-wide structuré, avec redaction, corrélation de requête et logging de requête automatique :
un logger maison réinventerait tout ça. pino est le choix — rapide, JSON natif,
redactintégré, intégration Nest éprouvée par
nestjs-pino.Périmètre
nestjs-pinobranché :NestFactory.create(..., { bufferLogs: true })puisapp.useLogger(app.get(Logger)), pour quemain.tset tous les providers loggent via pino.severityattendu par Cloud Logging. pino émet des niveaux numériques(30 info, 40 warn, 50 error, 60 fatal) ; un
formatters.levelles traduit en{ severity: 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL' }, avecmessageKey: 'message'ettimestampISO. Cloud Logging lit alorsseverityetmessagede la ligne JSON.X-Cloud-Trace-Contextinjecté parCloud Run, exposé sous
logging.googleapis.com/trace(clé reconnue par Cloud Logging) — chaquelog d'une requête est ainsi rattaché à sa trace.
redactde pino,remove: true) : jamais de clé de provider(
GEMINI_API_KEY,OPENROUTER_API_KEY, en-têteauthorization), jamais de bytes d'image. Lesserializers de requête/réponse ne loggent pas le corps.
development, transportpino-prettylisible ; enproduction, JSON brut surstdout (aucun transport). Sélection par
NODE_ENV(déjà validé parenvironment.ts).autoLoggingdenestjs-pinoactivé, mais serializers configurés pour negarder que méthode, chemin, statut, durée et trace id — pas le corps, pas les en-têtes sensibles.
Articulation avec #26
#26 pose le filtre d'exception et lui fait logguer une erreur rattrapée en
severity: ERROR(minimum vital pour tenir sa promesse « pas de stack au client »). Ordre : #26 d'abord. Quand la
présente issue passe, le filtre logge à travers le logger pino injecté au lieu de son émission
minimale — un point de câblage, pas une réécriture. La forme
severity: ERRORattendue par #26 estexactement ce que le mapping ci-dessus produit.
Dépendances outillage
pino,nestjs-pinoetpino-pretty(dev) épinglés à l'exact — jamais en plage, comme toutl'outillage (CLAUDE.md, et #21 pour l'automatisation des bumps).
pino-prettyendevDependenciesuniquement (il ne doit pas partir dans l'image de prod).
Hors périmètre
dans le corps de la PR — pas une décision structurante d'architecture.
Critères d'acceptation
severity; unlogger.error(...)produit
severity: ERROR, unwarnWARNING, unlog/infoINFO.X-Cloud-Trace-Contextapparaît souslogging.googleapis.com/tracedans leslogs d'une requête portant cet en-tête.
authorization, ni de bytes d'image —assertion de test sur le payload émis (destination pino capturée en mémoire).
development, la sortie est lisible (pino-pretty) ; enproduction, JSON brut sur stdout.main.tset les providers loggent via pino (useLogger+bufferLogs), plus aucunLoggerNest texte résiduel.
pino-prettyest endevDependencies, absent de l'image de prod.yarn checkvert.Découpage en tâches (TDD, test rouge d'abord)
messageKey, timestamp ISO, redact) ; test sur le mapping de severity et sur la redaction (une clé
et des bytes d'image n'apparaissent pas dans la ligne émise, via une destination capturée).
nestjs-pinodansAppModule,bufferLogs+useLoggerdansmain.ts;test d'intégration qu'un log passe bien par pino.
X-Cloud-Trace-Context; test qu'unerequête avec l'en-tête produit
logging.googleapis.com/trace.pino-prettyconditionné àNODE_ENV=development; test de sélection.CLAUDE.md(section Outillage / Commandes) sur le logging structuré ;.env.exampleinchangé (aucune variable nouvelle requise).Références
apps/api/src/main.ts(logger texte actuel),apps/api/src/config/environment.ts(
nodeEnvdéjà validé, réutilisé pour dev/prod).