Observability

The Cache-Status header, Prometheus metrics, events, and which signals deserve an alert.

Weir reports every decision three ways: a header on the response, an event to an observer, and (with the observe/prom module) Prometheus metrics.

What to watch

Each response carries Cache-Status: Weir; ... (RFC 9211). hit; ttl=N is a fresh hit. fwd=uri-miss (no entry), fwd=vary-miss (entry exists, not for these Vary values), fwd=stale, fwd=request (the client asked), fwd=method and fwd=bypass say why the origin was asked. ; collapsed marks a request that joined another's fetch. detail=negative marks a negative-cache entry. A stale hit shows a negative ttl=. detail= names the stale reason: stale-while-revalidate, stale-if-error, shed, circuit-open, coalesce-timeout. The metric label reason uses short names for the first two: swr and sie.

With observe/prom (a separate module):

MetricRead it as
weir_requests_total{outcome}the hit ratio and the mix of outcomes
weir_origin_fetches_total{class,result}, weir_origin_fetch_secondsorigin load and latency, split foreground, background and warm
weir_origin_inflight, weir_limiter_queue_depththe limiter: sustained inflight at MaxConcurrent with a growing queue means the origin is the limit
weir_shed_total{reason}requests refused (clients get 503 with Retry-After)
weir_breaker_state (0 closed, 1 half-open, 2 open), weir_breaker_transitions_total{to}origin health as Weir sees it
weir_stale_served_total{reason}how often staleness is hiding a problem
weir_coalesce_timeouts_total{fallback}, weir_coalesced_totalflights that ran long, and how much coalescing saves
weir_store_errors_total{op}, weir_store_bytes, weir_evictions_total{queue}the store: errors mean it is unavailable, evictions are normal when full
weir_not_stored_total{reason}, weir_key_rejections_total{reason}responses refused by storability rules, and requests refused by validation
weir_miss_rate_anomalies_total{action}see 4.4
weir_purges_total{mode}, weir_negative_served_totalpurge activity and negative-cache use

Labelled series appear on their first event, so alert on absent() as well as rate(). Without Prometheus, implement weir.Observer (it runs on the request path: keep Observe fast) and poll Engine.Stats() for in-flight, queued, breaker state and store bytes. Request-derived strings such as Event.Partition are never metric labels.

On Caddy every weir_* series also carries a name label, the site's name, and the metrics are served by Caddy's own metrics handler.

Alerts

  • The breaker is open for more than a few minutes (weir_breaker_state == 2).
  • weir_shed_total is nonzero for more than a minute: the origin cannot keep up, or MaxConcurrent is set too low for it.
  • weir_stale_served_total{reason="sie"} or reason="circuit-open" is rising: you are serving through an outage.
  • weir_store_errors_total is rising (remote stores only).
  • Hit ratio falls by half against the same hour last week.

As Prometheus rules, the first two look like this. Thresholds are starting points:

- alert: WeirBreakerOpen
  expr: weir_breaker_state == 2
  for: 3m
- alert: WeirShedding
  expr: rate(weir_shed_total[1m]) > 0
  for: 1m

Events without Prometheus

Config.Observer receives one weir.Event per decision. It is called synchronously on the request path, so it must be fast, must not block and must not panic. Engine.Stats() returns in-flight fetches, queue depth, breaker state and store bytes for gauges.

Event kinds may gain values in a minor release. Switch on them with a default case.

Limits

The Cache-Status header never includes the cache key (T-27). Request-derived strings such as the partition are never metric labels, so a flood of distinct paths cannot explode your series count; the cost is that metrics do not tell you which path is misbehaving. The miss-rate warning in the log does.