Aller au contenu

Logs techniques

Les logs techniques sont ce qu’écrivent l’API, les workers et le reverse proxy. Ils se lisent depuis la console d’administration, page Logs techniques. Ce qu’ils contiennent, et ce qui les distingue du journal métier, est décrit sur Traçabilité.

flowchart LR
    S["api, worker-outbox, worker-cleanup (stdout)"] --> AL["Alloy"]
    CA["Caddy (fichiers d'accès)"] --> AL
    AL --> L["Loki"]
    L --> API["API : /v1/admin/logs"]
    API --> CO["console-admin : /logs"]

Le navigateur n’interroge jamais Loki. Il envoie des critères à la console, qui les passe à l’API ; c’est l’API qui compose la requête LogQL (api/src/infrastructure/logs/logql-builder.ts). Loki n’est joignable que sur le réseau Docker interne.

Sur le serveur :

Fenêtre de terminal
tep obs on # mémorise l'activation (fichier .observability)
tep up # démarre Prometheus, Loki, Alloy et node-exporter
tep obs status # cibles Prometheus, place occupée sur le disque
tep obs off # arrête la collecte

tep obs on ajoute la surcouche deploy/docker-compose.observability.yml à la stack. C’est elle qui pose LOKI_URL=http://loki:3100 sur le service api. La variable ne se pose pas à la main.

Sans LOKI_URL :

  • les routes de logs répondent 503 LOGS_DISABLED ;
  • GET /v1/admin/logs/labels répond 200 avec enabled: false ;
  • la page affiche « La collecte des logs n’est pas activée » ;
  • l’onglet Trace reste utilisable : il montre le journal métier seul.

Toutes les routes exigent la portée admin. Fichier : api/src/http/routes/admin-logs.routes.ts.

Route Rôle
GET /v1/admin/logs Recherche de lignes, paginée
GET /v1/admin/logs/histogram Volume par tranche de temps et par niveau, totaux par service
GET /v1/admin/logs/labels Services connus sur 24 h, niveaux, types d’appel, collecte activée ou non
GET /v1/admin/traces/:correlationId Trace d’une requête
GET /v1/admin/sessions/:id/trace Trace d’un paiement entier

Toutes sauf labels sont limitées à 120 requêtes par minute.

Communs à la recherche et à l’histogramme. Le schéma est strict : un paramètre inconnu donne 400.

Paramètre Forme Effet
from, to Dates ISO 8601 Plage. Absente : les 15 dernières minutes
service Liste, 20 valeurs au plus api, worker-outbox, worker-cleanup, caddy…
level Liste trace, debug, info, warn, error, fatal, unknown
q Texte, 200 caractères au plus Cherché dans la ligne, sans tenir compte de la casse
correlationId, merchantId, legId Identifiant La ligne porte ce champ avec cette valeur
sessionId Identifiant Cherché partout dans la ligne : l’adresse /v1/sessions/<id> compte aussi
kind Liste Types du journal d’appels (http_in, provider_call…)
provider Liste stripe, adyen, titreo, merchant, smtp
probes true, false, 1, 0 true montre aussi le bruit de fond, écarté par défaut

Une liste s’écrit ?service=a&service=b ou ?service=a,b. Un identifiant accepte 128 caractères au plus parmi lettres, chiffres, _ . : -.

La recherche accepte en plus :

Paramètre Effet
limit 1 à 1000, 200 par défaut
direction backward (défaut, de la plus récente à la plus ancienne) ou forward
before Page suivante : la valeur nextBefore de la réponse précédente
after Suivi en direct : la valeur newest de la réponse précédente. Force forward

before et after sont des horodatages en nanosecondes. Ils priment sur from et to.

Sondes de santé, relevé des métriques, fichiers statiques des consoles (/_next/static, /_next/image, /favicon), et les requêtes de la page de logs elle-même (/v1/admin/logs, /v1/admin/traces, /logs). probes=true les rend.

Une ligne (LogLine, api/src/application/ports/log-store.ts) :

Champ Contenu
id Horodatage en nanosecondes et empreinte : stable pour une ligne donnée
ts, tsNs Instant en ISO 8601 et en nanosecondes
service Étiquette service, à défaut le conteneur
level Niveau normalisé. Les niveaux numériques de pino (30, 50) sont traduits
message msg de la ligne, ou méthode et adresse pour une ligne d’accès
fields Champs de la ligne si elle est en JSON, sinon null
raw Ligne brute, bornée à 16 384 caractères

Une trace rend des couloirs (lanes), des segments (spans), des repères (markers), les corrélations et sessions rencontrées, des compteurs par issue et les lignes elles-mêmes. truncated dit qu’il y en avait plus ; logsAvailable: false dit que seul le journal métier a répondu.

Code Statut Cause
LOGS_DISABLED 503 Aucun collecteur raccordé
LOGS_UNAVAILABLE 502 Loki ne répond pas en 15 s, ou refuse la requête
VALIDATION_ERROR 400 Paramètre inconnu, plage illisible, inversée ou de plus de 7 jours, curseur illisible

Adresse /logs, entrée « Logs techniques » du menu. Fichiers : console-admin/app/(wide)/logs/, console-admin/components/logs/, console-admin/lib/logs/, actions serveur dans console-admin/lib/actions/logs.ts.

Le groupe de routes (wide) reprend la console sans la colonne de lecture bornée : un journal se lit en largeur. Deux vues, par onglet.

Élément Comportement Fichier
Plages 15 min, 1 h, 6 h, 24 h, 7 j LogsApp.tsx
Barre de requête Ce qui est tapé devient des pastilles à la validation QueryBar.tsx, lib/logs/query.ts
Histogramme Volume par tranche, empilé par niveau. Glisser choisit une plage, cliquer une barre zoome sur sa tranche Histogram.tsx
Facettes Services et niveaux avec leurs compteurs, types d’appel, prestataires, et « Sondes et métriques » pour montrer le bruit Facets.tsx
Liste 500 lignes par page, de la plus récente à la plus ancienne ; « Charger les 500 lignes suivantes » LogRows.tsx
Ligne ouverte Ses champs, un clic sur une valeur pour filtrer dessus, « Trace de cette requête », « Trace du paiement », « Copier la ligne » LogRows.tsx
Direct Interroge toutes les 3 s ce qui est arrivé depuis la dernière ligne reçue LogsApp.tsx

Le suivi en direct garde 3 000 lignes au plus à l’écran, se met en pause quand l’onglet du navigateur est masqué, et s’arrête dès qu’une plage est choisie à la main. L’histogramme est rafraîchi un tour sur cinq.

Les filtres sont portés par l’adresse : un lien vers la page rouvre la même recherche.

Deux entrées : Paiement (session) ou Requête (corrélation). Des liens y mènent depuis la fiche d’une transaction, depuis le journal métier et depuis une ligne de l’explorateur (/logs?view=trace&session=…, /logs?view=trace&corr=…).

Une trace de paiement part du journal métier : il donne toutes les corrélations qui ont touché la session, 20 au plus, et la fenêtre de temps à fouiller (api/src/application/logs/get-trace.ts).

Élément Comportement
Couloirs Un par acteur : services (caddy, api, worker-outbox, worker-cleanup), prestataires, boutique du marchand, journal métier
Segments Une ligne du journal d’appels, ou une ligne d’accès de Caddy. La ligne est écrite à la fin de l’appel : le début se déduit de la durée
Repères Les autres lignes, et les faits du journal métier
Silences Une pause de plus de 1,5 s entre deux périodes d’activité est repliée sur une largeur fixe, avec sa durée (lib/logs/scale.ts)
Résumé Durée totale, appels, réussis, refusés, en échec, requêtes reliées
Détail Cliquer un segment affiche son type, son statut, son issue, la référence du prestataire

Une trace écarte ce que fait la console d’administration elle-même : l’adresse d’une page de trace contient l’identifiant cherché.

Une ligne de log dit qu’un appel a eu lieu et comment il s’est terminé. L’échange dit ce qui a été envoyé et ce qui a été répondu : en-têtes et corps, requête et réponse. Ouvrir une ligne qui porte un exchangeId, ou cliquer un segment de trace, affiche le panneau Requête / Réponse, avec « Copier le corps », « Copier l’échange » et « Copier en cURL » pour un appel sortant.

Ils ne sont pas dans les logs, et c’est voulu :

  • un même corps figurerait trois ou quatre fois par paiement (requête reçue, opération, appel sortant, journal métier) ;
  • Docker coupe les lignes au-delà de 16 Ko ;
  • le Loki des logs peut être ouvert au Grafana du client (vhost logs.), et une requête LogQL ne se filtre pas à l’entrée d’un proxy.

Les échanges vivent donc dans une seconde instance Loki, loki-exchanges, sans vhost ni collecte Alloy, conservée 7 jours (deploy/observability/loki-exchanges.yml). L’API et les workers y écrivent directement, par lots, en arrière-plan ; la console les lit par GET /v1/admin/exchanges/:id, et chaque lecture est tracée dans le journal d’audit (admin.exchange.read).

Capturé Où
Requêtes reçues : navigateur, CMS, SDK, webhooks PSP api/src/http/plugins/call-log.ts
Appels à Titréo et à Stripe observability/logged-fetch.ts, psp/stripe-logged-fetch.ts
Appels à Adyen psp/adyen-logged-client.ts
Webhooks envoyés aux boutiques, réponse lue jusqu’à 64 Ko webhooks/http-webhook-sender.ts

Non capturés : les courriers (le corps porte des liens de vérification) et les lectures de logs par la console elle-même.

EXCHANGE_CAPTURE, dans .env.prod :

Valeur En-têtes Corps
all toujours toujours — préproduction
errors (défaut) toujours appels en échec, et tout appel d’un marchand en mode diagnostic — production
off non non

Le mode diagnostic se pose sur la fiche du marchand dans la console d’administration, pour 1, 4 ou 24 h (PUT /v1/admin/merchants/:id/diagnostic, 24 h au plus, migration 027). Il est lu en base au plus toutes les 15 s. Un corps absent dit pourquoi : appel réussi hors diagnostic, jeton, binaire.

  • en-têtes : liste blanche d’en-têtes sans secret ; tout autre en-tête garde son nom, pas sa valeur (Authorization, Cookie, signatures…) ;
  • corps : carte cadeau réduite à ses quatre derniers caractères, CVV, clés, jetons, client_secret censurés, adresses e-mail masquées (j•••@exemple.fr), clés imbriquées des formulaires Stripe reconnues (card[number]), jetons repérés dans le texte libre ;
  • coordonnées de l’acheteur (billing_details, shipping, address, shopper…) censurées, sauf le pays et les valeurs vides ; téléphone et receipt_url censurés partout ; le numéro de commande reste lisible, number n’étant masqué qu’à côté d’un CVC ou d’une expiration ;
  • adresse IP réduite à son réseau, IPv4 au /24 et IPv6 au /48 (observability/ip.ts), dans les échanges, dans les lignes http_in et dans les journaux d’accès de Caddy (ip_mask) ;
  • la réponse d’AuthToken Titréo, qui est un jeton, n’est jamais conservée ;
  • corps plafonnés à EXCHANGE_BODY_MAX_BYTES (32 Ko par défaut).

Code : api/src/infrastructure/observability/exchange.ts, api/src/infrastructure/exchanges/.

Le lot en cours est perdu, compté, et signalé une fois par minute sur la sortie d’erreur du processus. Aucun appel de paiement n’attend l’enregistrement de son échange.

clé:valeur filtre. Tout le reste est du texte cherché dans les lignes. Les clés s’écrivent en français ou en anglais.

Clé Alias Filtre Valeurs
service: services:, svc: Service Liste séparée par des virgules
niveau: level:, lvl: Niveau Liste ; warn+ pour « ce niveau et les plus graves »
type: kind: Type d’appel http_in, http_out, provider_call, webhook_in, webhook_out, outbox, email_out
prestataire: provider:, psp: Prestataire stripe, adyen, titreo, merchant, smtp
session: sessionid: Session Un identifiant
corr: correlation:, correlationid: Corrélation Un identifiant
marchand: merchant:, merchantid: Marchand Un identifiant
leg: legid: Moyen de paiement Un identifiant
client: cms:, extension: Extension appelante prestashop, shopify, sdk-js, sdk-php

Exemples :

service:api niveau:warn+
type:http_out prestataire:titreo
session:<identifiant> type:provider_call,outbox
prestataire:titreo "Titreo HTTP"
niveau:error timeout

Règles, telles que les applique applyQueryText :

  • les guillemets gardent une expression entière, espaces compris ;
  • un mot dont la clé est inconnue (foo:bar) est cherché comme du texte ;
  • les listes s’ajoutent aux filtres déjà posés, un identifiant remplace le précédent ;
  • une valeur hors de [A-Za-z0-9_.:-] est ignorée ;
  • le texte est borné à 200 caractères ;
  • retour arrière sur un champ vide retire la dernière pastille.
Limite Valeur Où
Plage d’une recherche 7 jours api/src/application/logs/log-range.ts
Rétention des logs 720 h (30 jours) deploy/observability/loki.yml
Rétention des échanges 168 h (7 jours) deploy/observability/loki-exchanges.yml
Corps d’un échange 32 Ko, puis tronqué EXCHANGE_BODY_MAX_BYTES
Mode diagnostic 24 h au plus set-merchant-diagnostic.ts
Lignes par recherche 1 000 search-logs.ts
Lignes d’une trace 1 000, au-delà truncated: true get-trace.ts
Faits métier d’une trace 200 get-trace.ts
Corrélations suivies par trace 20 get-trace.ts
Fenêtre d’une trace De 5 min avant le premier fait à 48 h après le dernier, 7 jours au plus get-trace.ts
Délai d’une requête à Loki 15 s loki-log-store.ts
Cadence 120 requêtes par minute et par route admin-logs.routes.ts

À savoir :

  • le correlationId n’est pas une étiquette Loki : ce serait une série par requête. Il est cherché dans le corps de la ligne (deploy/observability/commun.alloy) ;
  • une recherche en plein texte sur 7 jours parcourt beaucoup de données. Réduire la plage ou ajouter un filtre service: d’abord ;
  • sans collecte, tep logs [service…] suit les journaux des conteneurs sur le serveur ;
  • la page est réservée à la console d’administration. La console marchand n’y a pas accès.