Guide d'Observabilité

    Métriques, dashboards, alertes, tracing distribué, health checks et troubleshooting.

    Table des matières

    1. Démarrage rapide (5 minutes)
    2. Métriques exposées
    3. Dashboard Grafana
    4. Alertes
    5. Tracing distribué (OpenTelemetry)
    6. Health Checks
    7. Métriques des Agents
    8. Logs
    9. Troubleshooting
    10. Flux d'audit → SIEM

    1. Démarrage rapide (5 minutes)

    Lancer Prometheus + Grafana en 1 commande

    bash
    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

    bash
    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étriqueTypeDescription
    streamflow_events_published_totalCounterÉvénements publiés (total)
    streamflow_events_consumed_totalCounterÉvénements consommés
    streamflow_batches_committed_totalCounterBatches écrits sur disque
    streamflow_publish_latency_msHistogramLatence de publication (p50/p95/p99)
    streamflow_fsync_latency_msHistogramLatence fsync disque
    streamflow_shard_countGaugeNombre de shards actifs
    streamflow_disk_usage_bytesGaugeEspace disque utilisé
    streamflow_disk_total_bytesGaugeEspace disque total

    Consumer

    MétriqueTypeDescription
    streamflow_consumer_lagGaugeRetard consumer par groupe/topic

    Réplication Raft

    MétriqueTypeDescription
    streamflow_replication_lag_entriesGaugeRetard de réplication (entrées)
    streamflow_backpressure_throttle_msGaugeThrottle back-pressure actif
    streamflow_pool_overflow_totalCounterDébordements du pool de batches

    JVM

    MétriqueTypeDescription
    jvm_memory_used_bytes{area="heap"}GaugeMémoire heap utilisée
    jvm_memory_max_bytes{area="heap"}GaugeMémoire heap max
    jvm_gc_pause_seconds_sumCounterTemps total de pause GC

    Agents

    MétriqueTypeDescription
    agent.events.processedCounterÉvénements traités par les agents
    agent.events.errorsCounterErreurs de traitement
    agent.llm.requestsCounterAppels LLM (tous fournisseurs)
    agent.llm.tokens.inputCounterTokens envoyés au LLM
    agent.llm.tokens.outputCounterTokens reçus du LLM
    agent.llm.latency.p99GaugeLatence LLM p99
    agent.llm.circuit_breaker.openGaugeCircuit breaker ouvert (0/1)
    agent.trigger.droppedCounterÉvénements dropped (queue pleine)

    MCP Tools

    MétriqueTypeDescription
    mcp_tool_calls_totalCounterAppels d'outils MCP
    mcp_tool_call_failures_totalCounterÉchecs d'outils
    mcp_tool_call_latency_msHistogramLatence des outils
    mcp_tool_denied_totalCounterOutils refusés (RBAC)
    mcp_tool_timeouts_totalCounterTimeouts d'outils

    Graphes d'agents

    MétriqueTypeDescription
    streamflow_graph_executions_started_totalCounterExécutions démarrées
    streamflow_graph_executions_completed_totalCounterExécutions terminées
    streamflow_graph_executions_failed_totalCounterExécutions échouées
    streamflow_graph_execution_duration_msHistogramDurée d'exécution
    streamflow_graph_node_duration_nsHistogramDuré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

    PanneauVertOrangeRouge
    Publish Latency p99< 10ms10–50ms> 50ms
    Fsync Latency p99< 10ms10–50ms> 50ms
    Backpressure0ms> 0ms> 1000ms
    Consumer Lag< 10K10K–100K> 100K

    Ajouter un dashboard custom

    bash
    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)

    AlerteConditionDurée
    StreamFlowThroughputZeroAucun événement publié2 min
    StreamFlowFsyncLatencyHighfsync p99 > 100ms5 min
    StreamFlowLeaderLostPerte de leadership Raft30 sec
    StreamFlowHeapUsageHighHeap > 90%5 min

    Warnings (surveiller)

    AlerteConditionDurée
    StreamFlowThroughputDropIngestion < 100 evt/s5 min
    StreamFlowPublishLatencyHighp99 > 50ms5 min
    StreamFlowConsumerLagHighLag > 100K événements5 min
    StreamFlowConsumerLagCriticalLag > 1M événements5 min
    StreamFlowReplicationLagHighRaft lag > 10K entrées2 min
    StreamFlowBackpressureActiveThrottle > 0ms2 min
    StreamFlowPoolOverflowPool de batches épuisé1 min
    StreamFlowDiskUsageHighDisque > 85%10 min
    StreamFlowSegmentCountHigh1000 segments ouverts10 min
    StreamFlowGCPressureHighGC > 5% CPU5 min

    SLO

    AlerteConditionDurée
    StreamFlowAvailabilitySLOBreachDisponibilité < 99.9%1 heure
    StreamFlowLatencySLOBreachp99 > 10ms1 heure

    Intégrer avec Slack / PagerDuty

    yaml
    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)

    Properties
    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µs

    Traces 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: 290ms

    Voir 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).

    bash
    1# Pull one event's full trajectory across sources, stages and sinks
    2curl http://localhost:8080/api/pulse/trajectory/{eventId}

    6. Health Checks

    Endpoints

    EndpointPortUsageRéponse saine
    GET /health8080Liveness (le process tourne){"status":"UP"}
    GET /ready8080Readiness (prêt à servir){"status":"UP"}
    gRPC Health9169Protocole gRPC health standardSERVING

    Probes Kubernetes

    yaml
    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

    yaml
    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

    bash
    1# List agents with live metrics
    2curl http://localhost:8080/api/v1/agents | jq '.[].metrics'
    JSON
    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

    bash
    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

    XML
    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 :

    XML
    1<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
    2    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
    3</appender>

    Niveaux de log utiles

    LoggerNiveauCe qu'il montre
    com.streamflow.core.storageDEBUGI/O détails (pwrite, fsync)
    com.streamflow.connect.kafka.protocolDEBUGRequêtes Kafka détaillées
    com.streamflow.core.replicationDEBUGRaft replication steps
    com.streamflow.agentDEBUGAgent reasoning trace
    com.streamflow.graphDEBUGGraph execution trace

    9. Troubleshooting

    Symptôme : Throughput en chute

    bash
    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

    bash
    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

    bash
    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é

    bash
    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

    PortProtocoleSécuritéUsage
    9092Kafka wireSASL + TLSProducers/Consumers
    8080HTTP RESTJWT/API Key + TLSAdmin API, Agents, Metrics
    9169gRPCmTLSRéplication Raft inter-nœuds
    4317OTLP/gRPCTLSExport 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éthodePathAuthUsage
    GET/api/audit/streamBearerSSE long-lived des nouvelles lignes d'audit
    GET/api/audit/recent?limit=NBearerBackfill REST one-shot (≤ 1000)
    POST/api/admin/audit/stream-tokensAdminGénérer un bearer token
    DELETE/api/admin/audit/stream-tokens/{id}AdminRévoquer un token

    Streamer vers votre SIEM

    bash
    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

    SSE
    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.