Monitoring and Alerting

This article explains how RustFS exports metrics, traces, and logs over OTLP, how to wire the pipeline into Prometheus and Grafana, which health endpoints to probe, and which signals to alert on.

How RustFS Exposes Telemetry

RustFS pushes metrics, traces, and logs over OTLP (OpenTelemetry Protocol). It does not expose a native Prometheus /metrics scrape endpoint — there is no HTTP route on port 9000 or 9001 that serves Prometheus text format. To get RustFS metrics into Prometheus you must run an OpenTelemetry Collector that receives OTLP from RustFS and re-exposes the data in Prometheus format:

RustFS (RUSTFS_OBS_ENDPOINT) --OTLP--> OpenTelemetry Collector --> Prometheus --> Grafana
                                                              \--> Loki (logs)
                                                              \--> Tempo / Jaeger (traces)

Point RustFS at the Collector with:

# OTLP over HTTP (the Collector's default HTTP receiver port is 4318; gRPC is 4317)
RUSTFS_OBS_ENDPOINT=http://otel-collector:4318

Related environment variables (all defined in the server configuration):

VariablePurpose
RUSTFS_OBS_ENDPOINTBase OTLP endpoint for traces, metrics, and logs
RUSTFS_OBS_TRACE_ENDPOINT / RUSTFS_OBS_METRIC_ENDPOINT / RUSTFS_OBS_LOG_ENDPOINTPer-signal endpoint overrides
RUSTFS_OBS_METRICS_EXPORT_ENABLED / RUSTFS_OBS_TRACES_EXPORT_ENABLED / RUSTFS_OBS_LOGS_EXPORT_ENABLEDToggle each signal
RUSTFS_OBS_METER_INTERVALMetric export interval
RUSTFS_OBS_SERVICE_NAME / RUSTFS_OBS_ENVIRONMENTResource attributes attached to exported telemetry
RUSTFS_OBS_LOGGER_LEVELLog verbosity (e.g. info)

Reference Deployment (Docker Compose Observability Profile)

The upstream rustfs/rustfs repository ships a complete reference stack in docker-compose.yml behind the observability profile: otel-collector, Prometheus, Grafana, Loki (logs), Tempo and Jaeger (traces). Its Collector configuration receives OTLP on 4317 (gRPC) and 4318 (HTTP) and re-exports metrics for Prometheus on port 8889:

# OpenTelemetry Collector (excerpt from the upstream reference config)
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [prometheus]

Prometheus then scrapes the Collector, not RustFS:

# prometheus.yml (excerpt)
scrape_configs:
  - job_name: "rustfs-app-metrics"
    static_configs:
      - targets: ["otel-collector:8889"]   # RustFS application metrics
  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:8888"]   # Collector self-metrics

:::note If Prometheus shows no rustfs_* series, check the chain in order: is RUSTFS_OBS_ENDPOINT set on every node, is the Collector reachable from the nodes, and is Prometheus scraping the Collector's 8889 exporter port. :::

Health Endpoints

The S3 listener on port 9000 serves the probe endpoints; the console on port 9001 has its own health path.

EndpointPortMeaning
GET /health, GET /health/live9000Liveness — returns 200 as long as the process is up
GET /health/ready9000Readiness — 200 only when storage, IAM, and peer health are ready; 503 otherwise
GET /minio/health/ready9000MinIO-compatible alias of the readiness probe
GET /minio/health/cluster, GET /minio/health/cluster/read9000Cluster write/read health (additionally require lock quorum)
GET /rustfs/console/health9001Console process health

A ready node answers 200 with "ready": true. A degraded node answers 503 with per-dependency detail:

curl -s http://<node>:9000/health/ready | jq

The details object reports storage / iam / lock (and kms when configured), and degradedReasons lists machine-readable causes such as storage_quorum_unavailable, iam_not_ready, or lock_quorum_unavailable. See Cold Start and Quorum Loss for the full degraded-startup semantics.

Key Metrics

All metric names below are exported via OTLP and appear in Prometheus after the Collector re-exports them (label set as recorded in the server source; the Collector may add resource labels). See the Metrics Reference for the full verified list.

MetricTypeWhat it tells you
rustfs_runtime_readiness_readygauge1 when the node is fully ready, 0 while degraded
rustfs_runtime_readiness_degraded_totalcounter (reason)Degraded-readiness evaluations by reason
rustfs_scanner_objects_scanned_totalcounterObjects visited by the background scanner
rustfs_scanner_cycles_totalcounter (result: success/error/partial)Completed scanner cycles
rustfs_scanner_cycle_duration_secondsgaugeDuration of the last scanner cycle
rustfs_heal_task_runninggauge (type, set)Currently running heal tasks per erasure set
rustfs_heal_queue_delay_secondshistogram (type, set)Time heal tasks spend queued before starting
rustfs_heal_task_start_totalcounter (type, set)Heal tasks started
rustfs_capacity_current_bytesgaugeCurrent used capacity in bytes
rustfs_start_totalcounterProcess starts (spikes indicate restart loops)

Suggested Alerts

AlertSignalSuggested rule
Node not ready > 2 minrustfs_runtime_readiness_ready gauge, or an external probe on GET /health/ready returning non-200rustfs_runtime_readiness_ready == 0 for 2m
Restart looprustfs_start_totalincrease(rustfs_start_total[10m]) > 3
Heal backlog growingrustfs_heal_task_running and rustfs_heal_queue_delay_secondssustained non-zero running tasks plus rising queue delay for 30m
Scanner stalledrustfs_scanner_cycles_totalincrease(rustfs_scanner_cycles_total[24h]) == 0
Cluster used capacity watermarkrustfs_capacity_current_bytescompare against your deployed raw capacity (a constant you set), e.g. rustfs_capacity_current_bytes > 0.8 * <total-bytes>

:::note Per-disk usage and disk online/offline state are currently surfaced through the console and the admin API (server/storage info), not as dedicated OTLP metrics. For a per-disk watermark alert, poll the admin API from your own exporter or watch the console dashboard. :::

Log Collection

Where logs go is controlled by RUSTFS_OBS_LOG_DIRECTORY:

  • Unset — logs go to stdout. In containers, use your log driver; on systemd hosts, stdout/stderr is captured by journald, so journalctl -u rustfs -f works without extra configuration.
  • A local directory (e.g. /logs) — RustFS writes rotating log files there. Rotation is tuned with RUSTFS_OBS_LOG_FILENAME, RUSTFS_OBS_LOG_ROTATION_SIZE_MB, RUSTFS_OBS_LOG_ROTATION_TIME, and RUSTFS_OBS_LOG_KEEP_FILES.
  • A URL (contains ://) — logs are shipped to a remote endpoint.

When RUSTFS_OBS_ENDPOINT is set and log export is enabled, logs are additionally exported via OTLP; the reference stack routes them into Loki for querying from Grafana.

Example: file logs plus OTLP export
RUSTFS_OBS_LOG_DIRECTORY=/var/log/rustfs
RUSTFS_OBS_LOGGER_LEVEL=info
RUSTFS_OBS_ENDPOINT=http://otel-collector:4318

On this page