Observabilité & traçabilité DYNORS — suivre une requête de bout en bout¶
Répond à la question centrale : « il y a de la communication partout — comment suivre une requête jusqu'à la fin ? ». Opérationnalise la trajectoire déjà décidée dans
docs/architecture/reference-resilience/CAHIER_EXPLOITATION_ET_TRAJECTOIRE.md(« logs + metrics (Prometheus) + traces (OpenTelemetry) corrélés »).
1. Les trois piliers (et l'état réel)¶
| Pilier | Question | Outil DYNORS | État |
|---|---|---|---|
| Logs | Que s'est-il passé ? | Loki + Promtail + Grafana | ✅ en place |
| Metrics | Combien / à quelle vitesse / où ça sature ? | Prometheus + Micrometer | 🟡 posé dans ce lot (manquait) |
| Traces | Par où est passée CETTE requête, et où part le temps ? | OpenTelemetry + Tempo | 🔴 à brancher |
| Uptime | Est-ce up ? | Uptime-Kuma | ✅ |
| Erreurs | Quelles exceptions ? | Sentry (Forge) | ✅ |
Le diagnostic : DYNORS avait les logs, l'uptime et les erreurs, mais pas la couche
métriques (pas de Prometheus, pas de /actuator/prometheus) ni le tracing. Donc on pouvait
dire « c'est la même requête » (par id) mais pas « voici son trajet et son goulot ».
2. Suivre une requête : deux niveaux complémentaires¶
Niveau 1 — Corrélation par requestId (presque en place)¶
RequestIdFilter(core/web-errors) : à l'entrée HTTP, prend le headerX-Request-Identrant ou génère un UUID, le met en MDC (requestId) → chaque ligne de log le porte, et le renvoie dans la réponse.InterAppCallServicepropageX-Request-Idsur chaque appel inter-app (HTTP via SLY).ApiErrorResponserenvoie lerequestIdau client.
→ Aujourd'hui : on peut filtrer Loki par requestId à travers les services HTTP. C'est la
traçabilité « par identifiant ».
Dans ce lot : le pattern de log d'
auth/entitlementsinclut désormais[req:%X{requestId}](prérequis :RequestIdFilterdecore/web-errorssur le classpath).
Niveau 2 — Tracing distribué (le « en profondeur »)¶
Le requestId dit « même requête » ; il ne dit pas « le hop entitlements a pris 800 ms sur
la requête DB ». Pour voir le trajet et le goulot, il faut des traces : un traceId,
des spans parent/enfant, une vue en cascade.
Cible (alignée CAHIER) : Micrometer Tracing + OpenTelemetry (natif Spring Boot 3) → export OTLP
→ Grafana Tempo. Le contexte W3C traceparent se propage automatiquement sur HTTP (servlet,
RestClient/WebClient instrumentés). Bénéfice : vue waterfall SLY → app → entitlements → DB →
bus, latence par hop, et corrélation trace ↔ logs (Loki) ↔ metrics (Prometheus exemplars).
3. ⚠️ Le maillon critique : la frontière asynchrone (le bus)¶
C'était LE point qui cassait le « jusqu'à la fin » : sans propagation, le consommateur (ex. projection entitlements) repartait sans requestId ni traceparent.
HTTP requête (requestId=R, traceId=T)
└─ app publie un event ──► metadata { requestId, traceparent }
└─ consumer restaure MDC ──► logs [req:R] corrélés
Implémenté (P2) — core/events/correlation/EventCorrelation :
✅ Implémenté (P2) :
core/events/correlation/EventCorrelation—enrich()copie le MDC du producteur (requestId,traceparent) dansDomainEvent.metadataà la publication (EventPublisher) ;runWithContext()les restaure en MDC pendant le handler à la consommation (EventSubscriber, donc tous les consommateurs — ex.EntitlementsEventConsumer— en bénéficient automatiquement). Test :EventCorrelationTest. La clétraceparentest déjà prévue (active dès que le palier tracing P4 peuple le MDC/baggage OpenTelemetry).
4. Métrologie & bottlenecks (ce que Prometheus rend visible)¶
Posé dans ce lot : Prometheus dans dynors-ops/monitoring (+ prometheus.yml), et
micrometer-registry-prometheus + endpoint /actuator/prometheus + tags app/env/lane sur
auth et entitlements. Restent à instrumenter (les goulots) :
| Mesure | Métrique (Micrometer) | Révèle |
|---|---|---|
| Débit / erreurs de publication | events.published{type,tenant,result} (Counter) |
producteur en panne, pics |
| Latence handler/projection | events.handle.latency{type} (Timer p95/p99) |
consommateur lent |
| Profondeur de file / lag | RabbitMQ exporter (rabbitmq_queue_messages, consumers) |
le bottleneck classique |
| Échecs / DLQ | events.failed, DLQ count |
poison messages |
| Skips d'idempotence | events.duplicate.skipped |
rejeux |
| Appels inter-app | seam InterAppCallMetrics → impl Micrometer livrée (P3) : interapp.calls (Timer), interapp.calls.errors/.started (Counter), tags target,outcome |
latence/erreurs par cible |
✅ Implémenté (P3) :
MicrometerInterAppCallMetrics(interapp-client) — branchée automatiquement dès qu'uneMeterRegistryest présente (apps avec actuator), sinon repli no-op. Câblage déterministe (@ConditionalOnClass/@ConditionalOnMissingClass, pas d'@ConditionalOnBean). Cardinalité maîtrisée : tagstarget+outcomeseulement ;requestId/tenantrestent dans logs/traces. Tests :MicrometerInterAppCallMetricsTest.
5. Tags par couloir — observabilité alignée sur le modèle couloirs¶
Toutes les métriques et traces portent env + lane (couloir) + tenant → on compare un
couloir rc1 à ref, on isole un tenant, on suit une promotion int → rmoa → prod. C'est la
contrepartie observabilité du modèle couloirs (ADR-0017/0018).
6. Forge — standardiser, pas réinventer¶
Un fragment observabilité dans les templates Forge (cf. ta question « template métrologie ») :
- dépendances micrometer-registry-prometheus (+ plus tard micrometer-tracing-bridge-otel,
opentelemetry-exporter-otlp),
- exposition /actuator/prometheus, RequestIdFilter actif, pattern de log [req:%X{requestId}],
- tags communs app/env/lane/tenant injectés depuis les seeds SIRRAT du couloir.
→ chaque app hérite d'une observabilité identique et comparable. Forge cadre la forme ; l'app fournit le contenu (ses métriques métier).
7. Plan par paliers¶
| Palier | Contenu | État |
|---|---|---|
| P1 — Metrics | Prometheus dans la stack + /actuator/prometheus + tags app/env/lane |
🟡 fait (ce lot) : auth, entitlements ; reste sly/autres |
| P2 — Corrélation bus | requestId (+traceparent) dans DomainEvent.metadata à la publication, restauré à la consommation |
✅ livré : EventCorrelation dans EventPublisher/EventSubscriber |
| P2.5 — DynorsLogger + MDC | Merge MDC dans DynorsLogger ; stdout JSON Logback |
⬜ |
| P3 — Inter-app metrics | impl Micrometer de InterAppCallMetrics |
✅ livré : MicrometerInterAppCallMetrics (auto-branchée si MeterRegistry présente) |
| P4 — Tracing | Micrometer Tracing + OTel + Tempo (décommenter le service) + datasource Grafana | ⬜ |
| P5 — Bus instrumentation | compteurs/timers events + exporter RabbitMQ (lag/DLQ) + dashboards/alertes | ⬜ |
| P6 — Forge | fragment observabilité standardisé | ⬜ |
8. Réponse synthétique¶
- Court terme (déjà possible) : suivre par
requestIddans Loki (HTTP). Avec ce lot, les métriques deviennent collectables (Prometheus). - Le trou à fermer en priorité : propager le contexte à travers le bus (P2) — sinon « jusqu'à la fin » s'arrête au premier événement asynchrone.
- La profondeur : OpenTelemetry + Tempo (P4) donne la vue trajet + latence par hop + le lien trace↔logs↔metrics — c'est ce qui montre où est le bottleneck, pas seulement qu'il y en a un.
Réf. : core/web-errors/RequestIdFilter, core/commons/interapp/InterAppCallMetrics & InterAppCallService,
core/events/DomainEvent (metadata), dynors-ops/monitoring/ (Prometheus + Loki + Grafana + Tempo prévu),
CAHIER d'exploitation (trajectoire observabilité). Tags couloir : ADR-0017/0018.