Aller au contenu

ADR-0036 — Accès contextualisé aux logs applicatifs depuis SIRRAT

  • Statut : Proposée
  • Date : 2026-09-03
  • Contexte amont : ADR-0016 (cercles), ADR-0017 (couloir/lane), ADR-0035 (trois identités), R-236 (Forge propriétaire des slots)

0. Ce que l'audit a trouvé avant tout arbitrage

La fonctionnalité existe déjà pour l'essentiel. Cet ADR ne conçoit pas une nouveauté : il ratifie un mécanisme livré, corrige son contrat de contexte, et nomme ce qui l'empêche aujourd'hui de fonctionner.

Déjà en place :

Élément État
LogConsoleLinkService livré — résout un gabarit d'URL avec le contexte
GET /v1/sirrat/logs/console-links livré, gardé (requireRead(env) + requireProject)
sirrat_log_provider.console_url_template migration 040, managed_by_forge
Garde anti-DSN en base chk_log_provider_console_url_sans_dsn
Encodage d'URL des valeurs injectées livré
Front sirrat-api.service.ts:829 consomme la route
Template Forge dynors-logging-multi-provider-2.0.0 40+ variables, presets DEV/PROD/AUDIT

Ce qui bloque n'est donc pas la conception, mais l'alimentation. Trois faits, mesurés :

  1. Aucun conteneur applicatif ne porte le label docker dynors.service. Promtail filtre dessus (docker_sd_configs.filters) ; seul uptime-kuma le porte. Aucun log applicatif n'atteint Loki aujourd'hui.
  2. La pile monitoring/ n'est déployée par aucun playbook. docker-compose.monitoring.yml n'est référencé que dans un README.
  3. sirrat_log_provider est vide — la migration 040 le documente explicitement et refuse de semer une URL plausible mais fausse : « un lien qui s'affiche, se clique, et tombe sur une page d'erreur — pire qu'un lien absent, qui lui se voit tout de suite ».

Le provider par défaut est console (DYNORS_LOGGING_PROVIDER:console chez DAWALALE) : les logs vont sur la sortie standard, et le service le dit franchement plutôt que d'offrir un lien.


1. Objectif

Donner au DEV et au recetteur, depuis SIRRAT, l'endroit où regarder les logs d'une instance applicative — sans qu'il ait à retrouver lui-même les bons filtres dans Grafana.

SIRRAT indique OÙ observer.
La plateforme d'observabilité fournit l'expérience d'observation.

2. Non-objectifs

SIRRAT ne stocke pas les logs applicatifs
SIRRAT ne devient pas Loki / Datadog / Sentry
SIRRAT ne proxy pas les logs
SIRRAT ne fournit pas un dashboard infra DEV/QA
SIRRAT ne fusionne pas ses logs avec ceux de l'application
SIRRAT ne gère pas la navigation temporelle (période, from/to, « 15 dernières minutes »)

Ce dernier point est un arbitrage explicite : Grafana, Sentry et Datadog savent déjà naviguer dans le temps. Le dupliquer dans SIRRAT ajouterait une seconde manière de se tromper de fenêtre.

3. Deux natures de logs, jamais fusionnées

Logs de déploiement Logs applicatifs
Producteur SIRRAT, deploy-agent, Ansible l'application
Rattachement un Deployment précis une instance logique (app × cercle × couloir)
Contenu préparation, agent, playbook, preuve de santé, verdict ce que l'application émet
Durée de vie l'exécution permanente, indépendante de tout déploiement
Accès logs_url du Deployment, servi par l'agent deep-link externe

Un déploiement réussi dont l'application plante ensuite : les premiers disent « succès », les seconds disent « erreur ». Les deux ont raison — c'est exactement pourquoi ils ne se mélangent pas.

4. Responsabilités

Acteur Responsabilité Ne fait pas
Application émet ses logs, alimente le MDC ne connaît pas SIRRAT
dynors-logging format ECS, abstraction provider, lecture MDC — c'est lui qui doit garantir les dimensions standardisées ne décide pas de la destination
Forge déclare le provider, le gabarit d'URL, le contrat structurel des dimensions n'invente pas les valeurs d'un cercle
SIRRAT fournit les valeurs concrètes (app, cercle, couloir) et compose le lien n'invente pas les labels, ne lit pas les logs
Backend d'observabilité stocke, indexe, cherche, navigue dans le temps

SIRRAT ne charge pas dynors-logging. Il reste un producteur de logs indépendant, avec sa propre identité. Même backend possible, producteurs distincts.

5. Dimensions de corrélation réellement disponibles

Dimension Existe ? Source Stable ? Dans les logs ?
appCode (couche 2) ✅ côté SIRRAT cleFonctionnelle(project) oui non émis
appTechCode (couche 3) ✅ slot CODEX_APP_TECH_CODE Forge/recette oui non émis
circle / environment MDC envservice.environment ; label docker dynors.env oui
lane (couloir) LaneContextFilter → MDC lanedeployment.lane oui
slot (au sens instance) n'existe pas sous ce nom — voir §6
version ⚠️ SIRRAT le connaît oui ❌ non émis
deploymentId ⚠️ SIRRAT le connaît oui ❌ non émis

Ce qui identifie l'application dans les logs aujourd'hui est service.name, alimenté par spring.application.name. Pour DAWALALE cela vaut dawalale-backend.

Correction après contre-audit (2026-09-03). Une première lecture y voyait une « quatrième identité de fait ». C'est faux, et la preuve est dans le codex : SirratCodexConstants documente CORE_APP_NAME comme « Nom Spring de l'application — spring.application.name ». dawalale-backend est donc CORE_APP_NAME, une variable gouvernée par Forge — le nom de service observable, distinct de l'appCode et légitime.

Le défaut réel est plus étroit, et le codex le dit lui-même à la ligne suivante : CORE_APP_CODE = « Code court de l'app — utilisé dans les logs/SLY ». Le contrat prévoyait déjà l'appCode dans les logs ; JsonStdoutLoggingProvider n'a simplement jamais été branché dessus. Il n'y a pas d'identité en trop : il y a une dimension qui manque.

Double vocabulaire dans Loki, à corriger un jour : les mêmes concepts arrivent par deux chemins — labels docker (service, environment, lane) et champs JSON promus (service_name, service_env, deployment_lane).

Classement décidé

CONTEXTE V1 (contrat)          appTechCode · circle · lane
UTILE SI DÉJÀ EN PLACE         appCode
HORS CONTRAT V1                version · deploymentId · période · timestamp construit par SIRRAT

Aucune application n'est modifiée pour propager version ou deploymentId. Si l'information existe, elle enrichit ; son absence ne bloque rien.

6. Décision de vocabulaire : slot ≠ couloir

Point de collision à trancher avant tout code.

Dans le modèle DYNORS, slot désigne une variable de recette (CORE_APP_CODE, TAIL_DB_URL) — table sirrat_environment_slot_value, définie et activée par Forge (R-236). Le « slot blue » du besoin d'observabilité désigne autre chose : une unité d'exécution parallèle, c'est-à-dire le couloir (lane) d'ADR-0017.

Décision : l'observabilité utilise lane, jamais slot.

Deux raisons, dont une décisive :

  • lane est déjà émis dans les logs (deployment.lane, label Loki lane), alimenté par LaneContextFilter. La dimension recherchée existe donc — sous son vrai nom.
  • Employer « slot » ici créerait un homonyme au cœur d'un modèle où R-236 vient précisément d'établir qui possède les slots. Le premier lecteur qui croirait que l'observabilité manipule des slots conclurait que SIRRAT en gère le cycle de vie. R-236 reste intact.

Dans l'UI, le mot affiché peut rester celui que l'équipe emploie ; le contrat technique, lui, dit lane.

7. Contexte du lien, et rien de plus

appTechCode  +  circle  +  lane

Composé exclusivement à partir des données persistées de SIRRAT — projet, cercle, couloir, identité applicative. Jamais à partir de paramètres composés par le frontend : c'est déjà la règle appliquée par LogConsoleLinkController, qui dérive le code applicatif du projet chargé plutôt que de l'accepter en paramètre.

Le gabarit reste une donnée portée par Forge. Le mécanisme ne connaît ni Grafana ni Sentry.

Ce que cela change par rapport à l'existant

LogConsoleLinkService injecte aujourd'hui APP_CODE · ENV · LANE · VERSION · LEVEL.

Variable Décision
ENV, LANE conservées — cœur du contrat
APP_CODE conservée — lisibilité et navigation
APP_TECH_CODE à ajouter — corrélation technique stable
VERSION, LEVEL sortent du contrat V1 — restent utilisables par un gabarit qui les réclame, mais leur absence ne doit plus rien empêcher

Aucune quatrième identité n'est créée. Une clé de fournisseur (projet Sentry, datasource Grafana, service Datadog) reste une configuration du provider, portée par configJson, jamais une identité métier DYNORS.

8. Historique d'un Deployment

Deux boutons, deux informations différentes :

Deployment #842
[ Logs déploiement ]   → logs_url de l'exécution, servi par l'agent
[ Logs ↗ ]             → même deep-link que la vue instance

Aucune logique temporelle spéciale en V1. SIRRAT connaît startedAt/finishedAt, mais ne s'en sert pas pour construire une fenêtre : la plateforme d'observabilité contient déjà l'historique, et le DEV/QA y remonte lui-même. Si un besoin d'ergonomie apparaît plus tard (« ouvrir autour de l'heure du déploiement »), ce sera un confort de navigation, pas une extension du contrat d'observabilité.

SIRRAT → construit une URL → le navigateur ouvre la plateforme

Et non :

SIRRAT → interroge le backend → récupère → republie

Conséquences recherchées : aucun stockage supplémentaire, aucune duplication, aucun identifiant de backend manipulé par SIRRAT, autonomie complète du système d'observabilité.

10. Authentification

Aucun jeton dans le lien. L'authentification appartient au fournisseur : SSO, session existante, reverse proxy.

La distinction est déjà gravée en base : le DSN sert à émettre, c'est une clé d'écriture, il vit au coffre ; le gabarit d'URL sert à regarder, il n'est pas secret. La contrainte chk_log_provider_console_url_sans_dsn refuse tout gabarit qui ressemble à un DSN — pour qu'un secret ne finisse pas affiché à l'écran, copié dans un ticket, collé dans une conversation.

11. Autorisation — aucune nouvelle capacité

État réel de la route : accessGuard.requireRead(env) puis requireProject(appCode), plus le cloisonnement d'instance (environmentPolicy.assertVisible). Ni DEPLOY, ni capacité dédiée.

Décision : on ne crée ni VIEW_LOGS ni OBSERVE.

  • Exiger DEPLOY exclurait le recetteur — précisément l'utilisateur cible.
  • Créer une capacité dédiée pour une action de lecture déjà couverte par le périmètre projet ajouterait une dimension à administrer sans rien fermer de plus. Le lien ne révèle qu'une URL composée d'informations que l'appelant voit déjà à l'écran.

Une capacité OBSERVE deviendrait justifiée le jour où un accès aux logs donnerait accès à des données (contenu de logs, donc données métier) et non plus à une adresse. Ce n'est pas le cas d'un deep-link. Arbitrage à rouvrir si SIRRAT devait un jour lire le contenu — ce que le §2 interdit.

12. Provider V1

Aucune abstraction multi-provider supplémentaire. Elle existe déjà à deux niveaux — les LoggingProvider de dynors-logging, et le console_url_template par provider dans SIRRAT — et aucun des deux n'a besoin d'être étendu.

Le provider V1 est celui que Forge déclarera, ce qui n'engage pas cet ADR. Constat factuel : la pile Loki/Promtail/Grafana est la seule outillée dans dynors-ops, mais elle n'est ni déployée ni alimentée (§0). Sentry est câblé côté code, sans DSN.

Conséquence à assumer : la V1 du bouton ne produira un lien utilisable qu'une fois la chaîne d'émission réellement branchée. D'ici là, le service dit correctement pourquoi il n'y a pas de lien — c'est son comportement actuel, et il vaut mieux que le contraire.

13. UI V1

DAWALALE
INT
Couloir ref

Déployer    Historique    Logs ↗
  • Libellé : Logs ↗. Le chevron dit « vous quittez SIRRAT ».
  • Jamais le nom du fournisseur dans le libellé : Grafana, Sentry ou Datadog sont des détails d'infrastructure qui doivent pouvoir changer sans toucher à l'interface.
  • Pas de menu Logs / Metrics / Traces / APM / Infrastructure. Les métriques d'infrastructure ne sont pas un besoin DEV/QA.
  • Erreurs ↗ seulement si un provider d'erreurs est réellement déclaré. Pas de bouton pour une capacité absente.

Quand aucun lien n'est utilisable, l'écran dit lequel des deux cas s'applique — provider non déclaré, gabarit incomplet, ou logs sur sortie standard — plutôt que d'afficher un bouton mort.

14. Stratégie d'évolution

Étape Contenu Bloquant pour la V1 ?
Prérequis d'émission labels docker dynors.service / .env / .lane posés par les composes ; déploiement de la pile monitoring oui, en pratique
Identité dans les logs émettre appTechCode (et appCode) via dynors-logging plutôt que spring.application.name oui pour le contrat §7
Déclaration Forge provider + console_url_template renseignés oui
Vocabulaire Loki résorber le double jeu de labels non
Enrichissement version, deploymentId dans les logs non
Navigation temporelle ouvrir autour d'un déploiement non

Décisions restant à arbitrer

  1. ~~service.name : que doit-il porter ?~~ TRANCHÉservice.name reste CORE_APP_NAME (le nom de service observable, ex. dawalale-backend). C'est un nom de composant, pas une identité applicative : SuperGest le prouve, avec supergest-api et supergest-acquisition sous une même application. On ajoute dynors.app_code plutôt que de détourner service.name. appTechCode est écarté du contrat V1 : il désigne une Trust Unit, et aucun projet ne le porte en valeur de recette aujourd'hui — l'inscrire au contrat rendrait la V1 dépendante d'une donnée absente.
  2. Qui pose les métadonnées runtime — voir §14 ; le rôle dynors-app est recommandé, avec la réserve du fichier de surcharge (labels par service, pas par pile).
  3. Provider V1 effectivement retenu — Loki/Promtail/Grafana, seule chaîne outillée.
  4. ERREURS_APPLICATION : Sentry est câblé sans DSN, et aucun autre backend d'erreurs n'existe. Le bouton n'existe pas tant que la capacité n'est pas réelle.