Guide d'Observabilité
Métriques, dashboards, alertes, tracing distribué, health checks et troubleshooting.
Table des matières
- Démarrage rapide (5 minutes)
- Métriques exposées
- Dashboard Grafana
- Alertes
- Tracing distribué (OpenTelemetry)
- Health Checks
- Métriques des Agents
- Logs
- Troubleshooting
- Flux d'audit → SIEM
1. Démarrage rapide (5 minutes)
Lancer Prometheus + Grafana en 1 commande
1docker compose -f deploy/monitoring/docker-compose-monitoring.yml up -d
Ouvrir :
- Grafana :
http://localhost:3000 (admin / streamflow) - Prometheus :
http://localhost:9090
Le dashboard StreamFlow est automatiquement provisionné.
Vérifier que les métriques arrivent
1# Raw metrics from StreamFlow
2curl http://localhost:8080/metrics
3
4# Verify in Prometheus
5curl 'http://localhost:9090/api/v1/query?query=streamflow_events_published_total'
2. Métriques exposées
StreamFlow expose des métriques au format Prometheus text sur GET /metrics (port 8080).
Engine (throughput, latence, disque)
| Métrique | Type | Description |
|---|
| streamflow_events_published_total | Counter | Événements publiés (total) |
| streamflow_events_consumed_total | Counter | Événements consommés |
| streamflow_batches_committed_total | Counter | Batches écrits sur disque |
| streamflow_publish_latency_ms | Histogram | Latence de publication (p50/p95/p99) |
| streamflow_fsync_latency_ms | Histogram | Latence fsync disque |
| streamflow_shard_count | Gauge | Nombre de shards actifs |
| streamflow_disk_usage_bytes | Gauge | Espace disque utilisé |
| streamflow_disk_total_bytes | Gauge | Espace disque total |
Consumer
| Métrique | Type | Description |
|---|
| streamflow_consumer_lag | Gauge | Retard consumer par groupe/topic |
Réplication Raft
| Métrique | Type | Description |
|---|
| streamflow_replication_lag_entries | Gauge | Retard de réplication (entrées) |
| streamflow_backpressure_throttle_ms | Gauge | Throttle back-pressure actif |
| streamflow_pool_overflow_total | Counter | Débordements du pool de batches |
JVM
| Métrique | Type | Description |
|---|
| jvm_memory_used_bytes{area="heap"} | Gauge | Mémoire heap utilisée |
| jvm_memory_max_bytes{area="heap"} | Gauge | Mémoire heap max |
| jvm_gc_pause_seconds_sum | Counter | Temps total de pause GC |
Agents
| Métrique | Type | Description |
|---|
| agent.events.processed | Counter | Événements traités par les agents |
| agent.events.errors | Counter | Erreurs de traitement |
| agent.llm.requests | Counter | Appels LLM (tous fournisseurs) |
| agent.llm.tokens.input | Counter | Tokens envoyés au LLM |
| agent.llm.tokens.output | Counter | Tokens reçus du LLM |
| agent.llm.latency.p99 | Gauge | Latence LLM p99 |
| agent.llm.circuit_breaker.open | Gauge | Circuit breaker ouvert (0/1) |
| agent.trigger.dropped | Counter | Événements dropped (queue pleine) |
MCP Tools
| Métrique | Type | Description |
|---|
| mcp_tool_calls_total | Counter | Appels d'outils MCP |
| mcp_tool_call_failures_total | Counter | Échecs d'outils |
| mcp_tool_call_latency_ms | Histogram | Latence des outils |
| mcp_tool_denied_total | Counter | Outils refusés (RBAC) |
| mcp_tool_timeouts_total | Counter | Timeouts d'outils |
Graphes d'agents
| Métrique | Type | Description |
|---|
| streamflow_graph_executions_started_total | Counter | Exécutions démarrées |
| streamflow_graph_executions_completed_total | Counter | Exécutions terminées |
| streamflow_graph_executions_failed_total | Counter | Exécutions échouées |
| streamflow_graph_execution_duration_ms | Histogram | Durée d'exécution |
| streamflow_graph_node_duration_ns | Histogram | Durée par nœud |
3. Dashboard Grafana
Le dashboard est provisionné automatiquement avec 9 panneaux — voir le sample dashboard live →
┌─────────────────────────────┬──────────────────────────────┐
│ Ingestion Throughput │ Publish Latency (p50/95/99) │
│ ████████████ 350K evt/s │ ▁▂▃▄ p99=5ms │
├─────────────────────────────┼──────────────────────────────┤
│ Consumer Lag by Group │ Raft Replication │
│ my-app: 1,200 events │ lag: 42 entries │
├─────────────────────────────┼──────────────────────────────┤
│ Backpressure (ms) │ Active Shards │
│ ▁▁▁▁▁ 0ms (healthy) │ 16 shards │
├─────────────────────────────┼──────────────────────────────┤
│ Fsync Latency p99 │ Batches Committed/s │
│ ▁▂▁▁ 2ms │ ████ 1,200 batches/s │
├─────────────────────────────┼──────────────────────────────┤
│ Pool Overflow Count │ │
│ 0 (healthy) │ │
└─────────────────────────────┴──────────────────────────────┘
Seuils visuels
| Panneau | Vert | Orange | Rouge |
|---|
| Publish Latency p99 | < 10ms | 10–50ms | > 50ms |
| Fsync Latency p99 | < 10ms | 10–50ms | > 50ms |
| Backpressure | 0ms | > 0ms | > 1000ms |
| Consumer Lag | < 10K | 10K–100K | > 100K |
Ajouter un dashboard custom
1# Copy dashboard JSON into Grafana
2cp my-dashboard.json deploy/monitoring/
3# Add to provider, then restart Grafana
4docker compose -f deploy/monitoring/docker-compose-monitoring.yml restart grafana
4. Alertes
StreamFlow inclut 16 règles d'alerte dans deploy/monitoring/streamflow-alerts.yml :
Critiques (action immédiate)
| Alerte | Condition | Durée |
|---|
| StreamFlowThroughputZero | Aucun événement publié | 2 min |
| StreamFlowFsyncLatencyHigh | fsync p99 > 100ms | 5 min |
| StreamFlowLeaderLost | Perte de leadership Raft | 30 sec |
| StreamFlowHeapUsageHigh | Heap > 90% | 5 min |
Warnings (surveiller)
| Alerte | Condition | Durée |
|---|
| StreamFlowThroughputDrop | Ingestion < 100 evt/s | 5 min |
| StreamFlowPublishLatencyHigh | p99 > 50ms | 5 min |
| StreamFlowConsumerLagHigh | Lag > 100K événements | 5 min |
| StreamFlowConsumerLagCritical | Lag > 1M événements | 5 min |
| StreamFlowReplicationLagHigh | Raft lag > 10K entrées | 2 min |
| StreamFlowBackpressureActive | Throttle > 0ms | 2 min |
| StreamFlowPoolOverflow | Pool de batches épuisé | 1 min |
| StreamFlowDiskUsageHigh | Disque > 85% | 10 min |
| StreamFlowSegmentCountHigh | 1000 segments ouverts | 10 min |
| StreamFlowGCPressureHigh | GC > 5% CPU | 5 min |
SLO
| Alerte | Condition | Durée |
|---|
| StreamFlowAvailabilitySLOBreach | Disponibilité < 99.9% | 1 heure |
| StreamFlowLatencySLOBreach | p99 > 10ms | 1 heure |
Intégrer avec Slack / PagerDuty
1# alertmanager.yml
2route:
3 receiver: 'slack-critical'
4 routes:
5 - match:
6 severity: critical
7 receiver: 'pagerduty'
8
9receivers:
10 - name: 'slack-critical'
11 slack_configs:
12 - api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
13 channel: '#streamflow-alerts'
14 title: '{{ .GroupLabels.alertname }}'
15 text: '{{ .Annotations.description }}'
16
17 - name: 'pagerduty'
18 pagerduty_configs:
19 - service_key: 'xxx-yyy-zzz'
5. Tracing distribué (OpenTelemetry)
Configurer OpenTelemetry (OTLP)
1# streamflow.properties
2otel.enabled=true
3otel.endpoint=http://jaeger:4317 # or Tempo, Datadog Agent
4otel.service.name=streamflow-node-1
5otel.metrics.interval.seconds=30
6otel.traces.delay.seconds=5
Ce qui est tracé
Trace: publish("orders", "order-123", payload)
├── Span: KafkaProtocolServer.handleConnection
│ ├── Span: KafkaRequestHandler.handleProduceFastPath
│ │ ├── Span: parseRecordSetIntoFlatBatch (69µs)
│ │ ├── Span: ShardedEventEngine.ingestFlatBatch (250µs)
│ │ │ ├── Span: DirectIOSegment.appendFlatBatch (160µs)
│ │ │ └── Span: writeOrDefer (0.1µs, deferred)
│ │ └── Span: PartitionFetchBuffer.appendBatch (56µs)
│ └── Span: TCP write response (7µs)
└── Total: 390µsTraces des graphes d'agents
Trace: GraphRunner.execute("order-processing")
├── Span: Node "validate", AgentNode (120ms)
│ └── Span: LLM call, cloud provider (100ms)
├── Span: Node "score-risk", AgentNode (80ms)
│ └── Span: LLM call, cloud provider (60ms)
├── Span: Node "risk-route", ConditionalRouter (0.1ms)
└── Span: Node "auto-approve", AgentNode (90ms)
├── Span: LLM call, cloud provider (70ms)
└── Span: Tool call "database-query" (15ms)
Total: 290msVoir les traces
- Jaeger :
http://localhost:16686 - Grafana Tempo : intégré dans le dashboard Grafana
- Datadog : via l'agent Datadog en OTLP
Time-travel par événement
Au-delà des traces agrégées, récupérez la trajectoire complète d'un événement et rejouez-la de façon déterministe — le moteur de replay rejoue l'événement capturé avec des I/O mockées, byte-identique sur deux exécutions (vérifié en CI).
1# Pull one event's full trajectory across sources, stages and sinks
2curl http://localhost:8080/api/pulse/trajectory/{eventId}
6. Health Checks
Endpoints
| Endpoint | Port | Usage | Réponse saine |
|---|
| GET /health | 8080 | Liveness (le process tourne) | {"status":"UP"} |
| GET /ready | 8080 | Readiness (prêt à servir) | {"status":"UP"} |
| gRPC Health | 9169 | Protocole gRPC health standard | SERVING |
Probes Kubernetes
1# Already configured in deploy/kubernetes/streamflow-statefulset.yaml
2livenessProbe:
3 httpGet:
4 path: /health
5 port: 8080
6 initialDelaySeconds: 30
7 periodSeconds: 10
8
9readinessProbe:
10 httpGet:
11 path: /ready
12 port: 8080
13 initialDelaySeconds: 15
14 periodSeconds: 5
Docker Health Check
1# docker-compose.yml
2healthcheck:
3 test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
4 interval: 10s
5 timeout: 5s
6 retries: 12
7 start_period: 30s
7. Métriques des Agents
Consulter via REST API
1# List agents with live metrics
2curl http://localhost:8080/api/v1/agents | jq '.[].metrics'
1{
2 "name": "fraud-detector",
3 "status": "RUNNING",
4 "instances": 5,
5 "metrics": {
6 "eventsPerSec": 1250.0,
7 "totalDecisions": 847293,
8 "errorRate": 0.02,
9 "avgLatencyMs": 45.3,
10 "p99LatencyMs": 120.0,
11 "queueDepth": 42,
12 "tokensUsedToday": 152847,
13 "costToday": 2.13
14 }
15}
Métriques par agent dans Grafana
1# Throughput per agent
2rate(agent_events_processed{agent="fraud-detector"}[5m])
3
4# LLM latency p99
5agent_llm_latency_p99{agent="fraud-detector"}
6
7# Circuit breaker open?
8agent_llm_circuit_breaker_open{agent="fraud-detector"} == 1
9
10# Tokens consumed per day
11increase(agent_llm_tokens_input[24h]) + increase(agent_llm_tokens_output[24h])
8. Logs
Configuration
1<!-- logback.xml (streamflow-app/src/main/resources/) -->
2<configuration>
3 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
4 <encoder>
5 <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
6 </encoder>
7 </appender>
8
9 <root level="WARN"/>
10 <logger name="com.streamflow" level="INFO"/>
11 <!-- Debug for a specific module -->
12 <logger name="com.streamflow.connect.kafka" level="DEBUG"/>
13</configuration>
Logs structurés (JSON)
Pour envoyer les logs vers Elasticsearch/Loki, configurer en JSON :
1<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
2 <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
3</appender>
Niveaux de log utiles
| Logger | Niveau | Ce qu'il montre |
|---|
| com.streamflow.core.storage | DEBUG | I/O détails (pwrite, fsync) |
| com.streamflow.connect.kafka.protocol | DEBUG | Requêtes Kafka détaillées |
| com.streamflow.core.replication | DEBUG | Raft replication steps |
| com.streamflow.agent | DEBUG | Agent reasoning trace |
| com.streamflow.graph | DEBUG | Graph execution trace |
9. Troubleshooting
Symptôme : Throughput en chute
1# 1. Check disk latency
2curl -s http://localhost:8080/metrics | grep fsync_latency
3# If p99 > 50ms → disk issue (saturated pd-ssd, IOPS throttled)
4
5# 2. Check GC
6curl -s http://localhost:8080/metrics | grep gc_pause
7# If GC > 5% CPU → increase -Xmx or reduce batch.size
8
9# 3. Check backpressure
10curl -s http://localhost:8080/metrics | grep backpressure
11# If throttle > 0 → consumers can't keep up
Symptôme : Consumer lag croissant
1# 1. Check lag
2curl -s http://localhost:8080/metrics | grep consumer_lag
3# lag > 100K → scale consumers
4
5# 2. Check replication
6curl -s http://localhost:8080/metrics | grep replication_lag
7# If raft lag > 0 → a follower is slow
8
9# 3. Check agents
10curl http://localhost:8080/api/v1/agents | jq '.[].metrics.queueDepth'
11# If queue > 5000 → scale agent instances
Symptôme : Agent ne répond plus
1# 1. Check circuit breaker
2curl -s http://localhost:8080/metrics | grep circuit_breaker
3# If open=1 → LLM is down or rate-limited
4
5# 2. Check errors
6curl -s http://localhost:8080/metrics | grep agent_events_errors
7# Error rate > 5% → check agent logs
8
9# 3. View recent traces
10curl http://localhost:8080/api/v1/agents/{id} | jq '.recentTraces[-3:]'
Symptôme : Nœud Raft isolé
1# 1. Check leadership
2curl -s http://localhost:8080/metrics | grep leader
3# If leader_lost → check network connectivity between nodes
4
5# 2. Check gRPC ports
6nc -z streamflow-2 9169 && echo "OK" || echo "BLOCKED"
7
8# 3. Verify TLS certificates
9openssl s_client -connect streamflow-2:9169 < /dev/null 2>&1 | grep "Verify return code"
Architecture de monitoring
┌──────────────┐ /metrics ┌────────────┐ query ┌─────────┐
│ StreamFlow │────────────────→│ Prometheus │←───────────│ Grafana │
│ Node 1-3 │ scrape 15s │ (retention │ │ :3000 │
│ :8080 │ │ 30 days) │ │ │
└──────┬───────┘ └──────┬──────┘ └─────────┘
│ │
│ OTLP/gRPC │ alerts
↓ ↓
┌──────────────┐ ┌──────────────┐
│ Jaeger/Tempo │ │ AlertManager │
│ (traces) │ │ → Slack │
│ :4317 │ │ → PagerDuty │
└──────────────┘ └──────────────┘Résumé des ports et protocoles
| Port | Protocole | Sécurité | Usage |
|---|
| 9092 | Kafka wire | SASL + TLS | Producers/Consumers |
| 8080 | HTTP REST | JWT/API Key + TLS | Admin API, Agents, Metrics |
| 9169 | gRPC | mTLS | Réplication Raft inter-nœuds |
| 4317 | OTLP/gRPC | TLS | Export de traces (Jaeger/Tempo) |
10. Flux d'audit → SIEM
Chaque ligne d'audit — qui a fait quoi, quand — est disponible en flux Server-Sent Events long-lived. Auth par bearer token (résiste au refresh JWT), reconnect via Last-Event-ID, heartbeat 25s. Le SIEM se connecte ; Pulse n'ouvre jamais de connexion sortante.
Endpoints
| Méthode | Path | Auth | Usage |
|---|
| GET | /api/audit/stream | Bearer | SSE long-lived des nouvelles lignes d'audit |
| GET | /api/audit/recent?limit=N | Bearer | Backfill REST one-shot (≤ 1000) |
| POST | /api/admin/audit/stream-tokens | Admin | Générer un bearer token |
| DELETE | /api/admin/audit/stream-tokens/{id} | Admin | Révoquer un token |
Streamer vers votre SIEM
1# Stream every audit row into Splunk / Elastic SIEM / Wazuh / Chronicle
2curl -N -H "Authorization: Bearer $TOKEN" \
3 http://pulse:8080/api/audit/stream
Événements exemples
1event: audit
2id: 48213
3data: {"actor":"mike@acme.io","action":"HA_ACTIVATE","ts":"2026-07-12T09:14:02Z","ip":"10.0.3.7"}
4
5event: audit
6id: 48214
7data: {"actor":"svc-deployer","action":"PIPELINE_DEPLOY","target":"orders-enrich","ts":"2026-07-12T09:14:07Z"}
Backfill après coupure : GET /api/audit/recent?limit=1000. Les tokens se génèrent, se listent et se révoquent sous /api/admin/audit/stream-tokens.