Install
openclaw skills install @ivangdavila/observabilityLogs, metrics, and traces as complementary signals, high-cardinality dimensions, and the instrumentation budget.
openclaw skills install @ivangdavila/observabilityrequest_id, a log you count instead of a counter.trace_id exemplar turns "latency is high" into "here is one slow request." Reach for an exemplar before you add a label.rate() it), gauge (current value, never rate), histogram (bucketed distribution, the workhorse), summary (client-side quantiles, legacy, not aggregatable across instances). Use histogram, not summary: summaries cannot be averaged across pods.avg(p99) across instances is meaningless: average the buckets, not the quantiles.histogram_quantile(0.99, rate(http_duration_bucket[5m])) is the standard query; it reads bucket boundaries, not raw values. rate needs >= 2 samples, a range >= 2x the scrape interval. A 1m range at 15s scrape = 4 samples, noisy; use 5m.rate() handles counter resets on restart; increase() over a long range can overcount across resets. Trust rate for trends, not increase for exact counts.5xx / total, emit both as counters from the code; do not derive the total from log lines. Deriving metrics from logs is logs doing metrics' job at ~100x cost.user_id (1M values) x endpoint (50) = 50M series. One bad label sinks the pipeline.user_id, email, request_id, session_id, trace_id, ip, raw URL. These belong on spans and logs, or as exemplars, never as metric labels./api/v1/status/tsdb (Prometheus) or the equivalent top-series endpoint: it lists labels by distinct value count. Run it monthly; a label with 100k+ values is the smoking gun.http.target (raw URL) and keeps http.route (templated). The collector is the last free transform.timestamp, level, message, trace_id, span_id, plus contextual fields. Free text is unqueryable at scale.user logged in + {user_id: 12345} is one log stream; user 12345 logged in is 1M streams. Log aggregation indexes the template, not the values; high-cardinality messages = index bloat.trace_id on every log line is non-negotiable: it is the bridge from metric (count rose) -> trace (which request) -> logs (what it said). Without it, you have three disconnected silos.operation, start/duration, status, attributes, span_id, parent_span_id, trace_id. The tree shape is the request's actual path.traceparent must be injected on send and extracted on receive at every boundary, HTTP, queue, RPC. A missing hop = a broken trace = two disconnected halves.status != ok at 100%, keep latency > p99 at 100%, sample the rest at 1-10%. This is how you get full failure signal without full volume.user_id, request_id, http.method, db.statement, http.status_code go on spans, not metric labels. Sampling bounds the cardinality: a 5% sample makes a 1M-user label ~50k spans.GET /users/{id}, not GET /users/12345. Raw URLs explode span-name cardinality and break aggregation; the route template is what you group by.rows count attribute, or sample the inner spans. Over-spanning is the trace equivalent of a memory leak.rate(errors[5m]) / rate(total[5m]) (a ratio), not on errors (which scales with traffic). A traffic spike doubles errors without any bug./healthz, /readyz, /metrics paths at the collector or they dominate trace volume.| Situation | Play |
|---|---|
| p99 latency spiked, no obvious cause | Pull an exemplar trace_id from the latency histogram; open the trace; read the critical path, not the longest span. |
| Metrics storage bill jumped overnight | Audit cardinality via /api/v1/status/tsdb; a new high-card label multiplied across others. Roll back the label to a span attribute. |
| Traces break at one service boundary | Propagation: W3C traceparent not injected/extracted. Check the queue consumer or RPC client; the trace splits where context drops. |
| On-call pages nightly, no one acts | Alert fatigue: delete alerts firing > 1x/week without a runbook action; re-baseline on SLO burn, not CPU. |
| Debug one user's failed request | Do not add a metric label. Search logs by trace_id (on every line) -> open the trace -> read the spans. |
| Service logs ~800GB/day | Metric the per-request count and errors; log only errors and warnings; tail-sample the rest at 1-5%. |
| Dashboard query times out | Move it to a recording rule (pre-compute p99 per service every 30s); the dashboard reads the new metric. |
| Need all errors kept, sample successes | Tail-based sampling: keep status != ok at 100%, slow traces at 100%, sample the rest 1-10%, at the collector. |
| Cardinality spike after a deploy | A raw URL or user_id landed as a label. Strip it at the collector, move it to a span attribute. |
| SLI green but users complain | Wrong SLI: averaging latency, or measuring uptime not user-perceived speed. Re-derive from what users notice. |
Install with clawhub install <slug> if the user confirms:
metrics - the counter/gauge/histogram types and Prometheus queries this skill emitsgrafana - the dashboards and query layer over the metrics, traces, and logs pipelinealerts - alerting rules, runbooks, and fatigue this skill's SLO burn-rate method targetsmonitoring - the monitoring-vs-observability boundary and known-unknowns coveragedevops - the operating rhythm and on-call culture that consumes the signals