fleet-router

Documentation / Observability · v0.8.1

Observability

fleet-router reports through structured logs and an optional HTTP endpoint for health checks, readiness, fleet coverage and Prometheus metrics. The same endpoint can also serve an opt-in live dashboard; see Dashboard and control API.

View source on GitHub

On this page

Logging

Logging uses tracing. Control verbosity with the RUST_LOG environment variable (default info):

bash
# Everything at debug
RUST_LOG=debug fleet-router run --config config.json

# Debug for fleet-router, info for everything else
RUST_LOG=fleet_router=debug,info \
  fleet-router run --config config.json

Each client session ends with one info line, session closed, with the results it forwarded, its handoffs, failovers, dropped duplicates, keep-alives, reroutes and patched results, its duration and the reason it ended.

Health and metrics endpoint

Set metrics_port (and optionally metrics_address) in the config to enable an HTTP endpoint. It serves GET requests on:

Readiness and coverage

A listener can serve the chain tip when:

In a listener with live servers, an archive outage never makes /ready fail, so one dead frozen range server cannot pull the router out of a load balancer. Two scopes ask for more, on /ready, /readyz and the per-listener forms:

?scope=tip is the default. An unknown scope gets 400 (unknown scope '<x>'; use tip, history or api). Neither scope is ever part of plain /ready.

The body names the result. In single-port mode it is ready: <n> upstream(s) online (ready: <n> chain API upstream(s) up for the API scope), or not ready: <reasons>. In multi-listener mode it is ready: <k> listener(s) ready, <n> ..., or not ready: <listener>: <reasons>, .... One listener: ready (<name>): .... The reasons a listener is not ready:

Reason Scope
no upstreams online tip (pool, or a stitched listener with no live server)
no live upstreams online tip (stitched)
gap [a,b) has no upstream (trace, chain_state) history
segment [a,b) has no online upstream (trace) history
<n> frozen upstream(s) have neither reported nor declared a range history
chain API not listening api
no live chain API upstream up api
no verified frozen chain API upstream up api
no chain API configured api

When every unready listener is a pool listener with no online upstream, the multi-listener body keeps its older form: not ready: listener(s) with no online upstreams: <names>.

GET /coverage returns every listener (/coverage/<listener> one):

json
{"listeners":[{
  "name": "main", "topology": "stitched", "generation": 812,
  "head": {"num": 412345678, "id": "<hex>"}, "lib": {"num": 412345330, "id": "<hex>"},
  "chain_id": "<hex>", "status_ready": true, "frozen_unreported": 0,
  "abi": {"fingerprint": "9c1e0f2a4b6d8e10", "source": "live-1", "provisional": false, "mismatched": []},
  "logs": {
    "trace": {
      "reported": {"begin": 2, "end": 412345679},
      "gaps": [], "offline_only": [],
      "segments": [
        {"begin": 2, "end": 50000001, "upstreams": [
          {"server": "archive-0a", "role": "frozen", "online": true, "sessions": 12},
          {"server": "archive-0b", "role": "frozen", "online": true, "sessions": 9, "operator_disabled": true}
        ]}
      ]
    },
    "chain_state": {"...": "..."},
    "finality": {"...": "..."}
  },
  "chain_api": {
    "listen": "10.0.0.5:18888",
    "live": [{"server": "live-1", "up": true, "head": 412345680, "lib": 412345330, "earliest": 400000000, "version": "v1.2.2"}],
    "frozen": [{"server": "archive-0a", "up": true, "verified": true, "blocks": {"begin": 1, "end": 50000001}, "get_block": "off"}]
  }
}]}

A pool listener is listed too, with "topology": "pool".

Metrics

Per-upstream series carry endpoint (the server's SHiP endpoint) and server (its name). Per-listener series carry no label in single-port mode. In multi-listener mode, every series except fleet_router_up also carries listener="<name>". Label values escape \, ", and newlines per the Prometheus text format. No label value is text an upstream sent.

Upstream series

text
fleet_router_up
fleet_router_upstream_up{endpoint,server}
fleet_router_upstream_stale{endpoint,server}
fleet_router_upstream_operator_disabled{endpoint,server}
fleet_router_upstream_priority{endpoint,server}
fleet_router_upstream_weight{endpoint,server}
fleet_router_active_connections{endpoint,server}
fleet_router_upstream_trace_begin_block{endpoint,server}
fleet_router_upstream_trace_end_block{endpoint,server}
fleet_router_upstream_chain_state_begin_block{endpoint,server}
fleet_router_upstream_chain_state_end_block{endpoint,server}
fleet_router_upstream_finality_data_begin_block{endpoint,server}
fleet_router_upstream_finality_data_end_block{endpoint,server}
fleet_router_upstream_head_block{endpoint,server}
fleet_router_upstream_lib_block{endpoint,server}
fleet_router_upstream_block_failures_total{endpoint,server}
fleet_router_upstream_quarantined{endpoint,server}
fleet_router_upstream_quarantine_block{endpoint,server}
fleet_router_upstream_info{endpoint,server,role}
fleet_router_upstream_ranges_known{endpoint,server}
fleet_router_upstream_range_mismatch{endpoint,server}
fleet_router_upstream_abi_info{endpoint,server,fingerprint}
fleet_router_upstream_abi_compatible{endpoint,server}
fleet_router_upstream_abi_match{endpoint,server}
fleet_router_upstream_links_total{endpoint,server,purpose}
fleet_router_upstream_dial_failures_total{endpoint,server,reason}
fleet_router_upstream_stalls_total{endpoint,server}
fleet_router_upstream_sequence_jumps_total{endpoint,server}
fleet_router_upstream_contract_violations_total{endpoint,server,kind}
fleet_router_upstream_no_block_log{endpoint,server}
fleet_router_upstream_results_without_block_total{endpoint,server}
fleet_router_upstream_prev_blocks_filled_total{endpoint,server}

fleet_router_up is a process gauge and has no labels.

SHiP ranges and tip

trace_begin_block, chain_state_begin_block, and finality_data_begin_block are the first block of each SHiP range on that upstream, and trace_end_block and finality_data_end_block are one past its last block. chain_state_end_block is the chain-state end as the upstream reports it. head_block and lib_block are the head and LIB from its status. The gauges are always emitted. 0 is the value before a status result arrives. finality_data_begin_block (and finality_data_end_block) stay 0 on pre-Savanna nodes that only answer get_status_result_v0 and never report a finality range.

Roles, declared ranges and ABIs

Block failures and quarantine

fleet_router_upstream_block_failures_total counts unclean mid-get_blocks drops (the socket died with no WebSocket close frame) while a blocks request was in flight, and, for a frozen upstream, its stalls and archive contract violations.

fleet_router_upstream_quarantined is 1 while that upstream is excluded for any block range. fleet_router_upstream_quarantine_block is the start block of that exclusion, or 0 when none is active; if several ranges are active it is the lowest start block. The block number is the gauge value, not a label.

See Corrupted SHiP logs and Failover and strikes for when these move.

Frozen range servers

These are rendered for frozen upstreams only:

See fetch_block and block logs.

Operator overrides

fleet_router_upstream_priority and fleet_router_upstream_weight report the values in effect. They differ from the config file after an operator changes them through the control API. fleet_router_upstream_operator_disabled is 1 while an operator has disabled or drained that upstream. An operator-disabled upstream does not count toward /ready.

Listener series

These are rendered for every listener, pool and stitched.

text
fleet_router_fleet_head_block
fleet_router_fleet_lib_block
fleet_router_fleet_head_age_seconds
fleet_router_fleet_coverage_begin_block{log}
fleet_router_fleet_coverage_end_block{log}
fleet_router_fleet_coverage_gaps{log}
fleet_router_fleet_offline_coverage_blocks{log}
fleet_router_fleet_live_overlap_blocks{log}
fleet_router_fleet_frozen_unreported
fleet_router_sessions{state}
fleet_router_status_replies_total{version,source}
fleet_router_handoffs_total
fleet_router_handoff_latency_seconds (histogram)
fleet_router_failovers_total{reason}
fleet_router_reroutes_total
fleet_router_duplicates_dropped_total
fleet_router_fork_redeliveries_total
fleet_router_keepalives_total
fleet_router_credit_overruns_total
fleet_router_patched_results_total
fleet_router_holds_total{why}
fleet_router_hold_closes_total{why}
fleet_router_predials_total{result}
fleet_router_client_protocol_errors_total{kind}
fleet_router_too_large_closes_total

Fleet tip and coverage

Sessions and failovers

Chain API series

These are rendered only when at least one listener has a chain API. The per-upstream ones use the server's usual endpoint and server labels, so they join every other upstream series; endpoint is the SHiP endpoint, not the api_endpoint.

text
fleet_router_upstream_api_up{endpoint,server}
fleet_router_upstream_api_chain_id_mismatch{endpoint,server}
fleet_router_upstream_api_head_block{endpoint,server}            # live only
fleet_router_upstream_api_earliest_block{endpoint,server}        # live only
fleet_router_upstream_api_blocks_begin_block{endpoint,server}    # frozen only
fleet_router_upstream_api_blocks_end_block{endpoint,server}      # frozen only
fleet_router_upstream_api_verified{endpoint,server}              # frozen only
fleet_router_upstream_api_in_flight{endpoint,server}
fleet_router_upstream_api_max_in_flight{endpoint,server}
fleet_router_upstream_api_requests_total{endpoint,server,result}
fleet_router_api_requests_total{class,code}
fleet_router_api_request_duration_seconds{class} (histogram)
fleet_router_api_in_flight
fleet_router_api_rejected_total{reason}
fleet_router_api_retries_total{reason}
fleet_router_api_block_requests_total{region,role}
fleet_router_api_pushes_total{result}

Per upstream API:

Per listener:

Each chain API listener adds 173 series, and each upstream API 13 (live) or 14 (frozen).

Alerts

Starting points for a stitched listener:

Expression What it means
fleet_router_fleet_coverage_gaps > 0 A block range no upstream covers, such as a hole between the archives and the live tier; clients that reach it are closed with 1011.
fleet_router_fleet_offline_coverage_blocks > 0 for 2 minutes History with no online upstream, such as the blocks between the archives and the live tier while the live node that holds them is down; sessions there hold, then close after gap_wait_ms.
fleet_router_fleet_head_age_seconds > 30 The fleet head stopped advancing.
increase(fleet_router_upstream_contract_violations_total[10m]) > 0 An archive broke the contract; check its data.
increase(fleet_router_credit_overruns_total[10m]) > 0 An upstream sent results beyond the client's credit.
histogram_quantile(0.99, rate(fleet_router_handoff_latency_seconds_bucket[5m])) Slow handoffs between ranges.
fleet_router_upstream_no_block_log == 1 An archive serves without blocks; fetch_block clients get no block where no other upstream holds it.

Alert on holes with the two coverage gauges, not on fleet_router_fleet_live_overlap_blocks: an overlap of 0 or 1 block is the normal shape right after a cut-over, and a hole between the archives and the live tier already shows as a gap or as offline coverage.

For a chain API: fleet_router_upstream_api_up == 0 and fleet_router_upstream_api_chain_id_mismatch == 1 per upstream, and a rising share of fleet_router_api_requests_total{code="5xx"}.