Helios
Operations

Observability

Three pillars: **metrics** (Prometheus), **logs** (Loki via structlog) and

Three pillars: metrics (Prometheus), logs (Loki via structlog) and traces (OpenTelemetry, optional). The Grafana stack ships pre-provisioned in docker-compose.yml.

Metrics

The FastAPI service exposes /metrics; long-running workers each bind a daemon HTTP server (shared.metrics_server.start_metrics_server) on per-worker ports:

ProcessPortNotes
FastAPI8000/metrics route via MetricsMiddleware
ingester9091start_http_server
aggregator9092
health_watchdog9093
synthetic_status9094dev only
userbot pool9095

All metrics are registered on a single prometheus_client.CollectorRegistry in shared/metrics.py. Adding a metric is one entry there, then the Grafana panel + Prometheus alert that references it both get caught by their respective CI validators if you forget to update them.

Key series

NameTypeLabelsUse
helios_api_requests_totalCountermethod,route,statusrequest rate, error share
helios_api_request_duration_secondsHistogrammethod,routep50/p95/p99
helios_api_in_flightGaugerouteconcurrency saturation
helios_status_events_ingested_totalCounteractionend-to-end pipeline health
helios_payments_totalCounterprovider,sku,statusrevenue + failures
helios_health_alerts_totalCounterkind,severitywatchdog activity
helios_notifications_sent_totalCounterkindDM fan-out volume
helios_stealth_violations_totalCounteruserbot_id,methodmust stay zero
helios_userbots_activeGaugebucketpool capacity

Adding a metric

  1. Define it in shared/metrics.py against the shared registry.
  2. Tick it from the relevant call site (use a local import to avoid pulling the prometheus dep into pure modules).
  3. Optionally reference it in a Grafana panel + Prometheus alert. CI validators in tests/unit/test_prometheus_rules.py and tests/unit/test_grafana_dashboards.py ensure renames don't silently break alerting.

Alerts

infra/prometheus/alerts.yml defines five groups:

GroupAlertTrigger
apiHighErrorRate5xx > 1 % for 5 min
apiP99LatencyHigh / Criticalp99 > 2.5 s / 5 s
stealthStealthViolationAttemptedany non-zero rate — P0
poolUserbotPoolEmptyhelios_userbots_active sums to 0 for 5 min
businessPaymentFailuresSpikefailed share > 10 % for 10 min
businessHealthAlertsBurstmirrors critical watchdog alerts

Critical alerts page on-call; warnings post to the team channel.

Logs

All processes use structlog with JSON output. Routed to Loki via the compose-provisioned config (infra/loki/local-config.yaml).

Standard fields on every event:

  • event — dotted name (e.g. billing.paid, userbot.failure)
  • timestamp — ISO 8601 UTC
  • level — info / warning / error
  • One of client_id, target_id, userbot_id where applicable

Never logged: session strings, API tokens, payment payloads, message bodies, media binary, parameters flagged secret=True. Use shared.redact.redact() if a struct holds anything sensitive.

Tracing

Opt-in. Set OTEL_EXPORTER_OTLP_ENDPOINT to enable; shared/tracing.py configures the OTLP exporter at boot. The hot path (userbot → Redis → ingester → notifier) is the natural first thing to trace.

Watchdog cross-check

The health_watchdog worker runs the same business-logic checks as the Prometheus alerts and DMs the owner directly via the bot. This is the secondary signal path — Prometheus alerts go to the team channel / PagerDuty; the watchdog goes straight to the owner. Both should agree.

On this page