Merge forward from phase6/7 and apply the same fix to phase9's own
dashboards and alerting rules. exported_instance is a Prometheus scrape
artifact absent in push-based pipelines; service_instance_id is always
present via resource_to_telemetry_conversion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
exported_instance is a Prometheus scrape artifact that only exists when
Prometheus scrapes a target with a conflicting instance label. Users
running push-based pipelines (remote_write, OTLP) never get this label.
service_instance_id is always present via resource_to_telemetry_conversion
regardless of ingestion path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
exported_instance is a Prometheus scrape artifact that only exists when
Prometheus scrapes a target with a conflicting instance label. Users
running push-based pipelines (remote_write, OTLP) never get this label.
service_instance_id is always present via resource_to_telemetry_conversion
regardless of ingestion path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
xrpld resolves a relative [debug_logfile] against the config file's own
directory (docker/telemetry), not the working directory. The value
`docker/telemetry/data/logs/devnet/debug.log` therefore doubled the
prefix to docker/telemetry/docker/telemetry/... — the collector's mount
never saw the log, so no lines reached Loki.
Use the config-dir-relative form `data/logs/devnet/debug.log`, which
resolves to docker/telemetry/data/logs/devnet/debug.log — exactly the
dir the compose stack bind-mounts as /var/log/xrpld.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The collector's log ingestion used a container path named after the old
`rippled` binary and defaulted its host mount to a location that either
needed root or diverged from where the telemetry configs actually write.
Consolidate on one accessible log root:
- container mount target renamed /var/log/rippled -> /var/log/xrpld
- filelog glob -> /var/log/xrpld/*/debug.log
- compose mount source defaults to the repo-relative ./data/logs
(user-owned, no root), overridable via XRPLD_LOG_DIR
- devnet telemetry cfg writes to data/logs/devnet/debug.log so it lands
one subdir below the mount root and matches the glob
- integration-test.sh sets XRPLD_LOG_DIR to its own workdir
- docs/runbook/task-list updated to match
The default xrpld config (cfg/xrpld-example.cfg) is intentionally left
untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Correct log-timestamp examples to real Logs::format() output
(2024-Jan-15 10:30:45.123456 UTC) in docs, TESTING.md, and reference.
- Update Loki to v3.4.2 and switch the collector exporter to
otlphttp/loki on the native /otlp endpoint (docs + task list).
- Make the collector log-mount path configurable via XRPLD_LOG_DIR.
- Remove implementation-phase references from shipped config/script
comments, keeping the functional descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer noted implementation-phase references should not appear in
shipped config comments. Drop the phase-8-owned 'Phase 8' tokens from
loki.yaml and tempo.yaml, keeping the functional descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer noted implementation-phase references should not appear in
shipped config comments. Drop the "Phase 3/4" token, keep the description.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer noted implementation-phase references should not appear in
shipped config comments. Drop the "Phase 4" tokens, keep descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer noted implementation-phase references should not appear in
shipped config comments. Drop the "Phase 3" tokens, keep descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer noted implementation-phase references should not appear in
shipped config comments. Drop the "Phase 2" tokens, keep descriptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer noted implementation-phase references should not appear in
shipped config comments. Drop the "Phase 1b" token, keep the filter
description.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- node-health.json: repoint the two "All Jobs" detail panels from the
__name__ regex over old xrpld_<job>_milliseconds_bucket names to the
native xrpld_job_running/queued_duration_us_bucket histograms, grouping
by the job_type label and legending on {{job_type}}.
- OTelCollector::callHooks: copy the hook list under mutex_ and invoke
handlers outside the lock. A handler can drop the last reference to an
OTelHookImpl, whose destructor calls removeHook() and re-acquires the
non-recursive mutex_ — invoking handlers under the lock could deadlock.
- ~OTelGaugeImpl: document that the SDK ObservableRegistry serializes
RemoveCallback against the Observe() callback pass with one mutex, so
callback removal is synchronous and gaugeCallback cannot run on a
dangling pointer after the destructor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Key Jobs execution/dequeue panels queried StatsD-era per-job metric
names (xrpld_<job>_milliseconds_bucket, xrpld_<job>_q_milliseconds_bucket)
that the native OTel path no longer emits. Phase 9's MetricsRegistry emits
job timings as two label-dimensioned histograms instead:
xrpld_job_running_duration_us_bucket{job_type="<job>"}
xrpld_job_queued_duration_us_bucket{job_type="<job>"}
Rewrite the 22 affected panel queries (11 job types x running/queued) to
the label-dimensioned form, preserving the histogram_quantile / rate /
sum-by structure and all template-variable filters.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- MetricsRegistry: service.name was recorded as boolean true because the
string literal "xrpld" bound to the OTel AttributeValue variant's
char-const* -> bool overload. Assign std::string so it selects the
string alternative; Prometheus now shows service_name=xrpld (and
exported_job round-trips correctly) on every MetricsRegistry series.
- Grafana alerting contact points: the Slack url / email addresses used
${ENV_VAR} references that expand to empty when unset, and Grafana's
provisioning validator (which lacks ${VAR:-default} support) then
crashes the whole stack on startup. Use non-empty disabled placeholders
(unroutable webhook host, .invalid email) so the stack boots with zero
configuration; delivery stays off until a real destination is supplied.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ValidationTracker: remove dead hard-trim Pass 1. The preceding
eviction loop already erases every reconciled entry older than the
late-repair cutoff using the same condition, so Pass 1 could never
match. The "drop any reconciled entry" fallback is retained.
- integration-test.sh: probe the StatsD port with a UDP-aware check
(ss -ulnp) instead of curl. StatsD is UDP-only on 8125, so the TCP
curl probe always reported "refused" — a false negative. Guarded by
a command -v ss check that logs a skip when ss is unavailable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the placeholder webhook contact point with two env-driven tiers:
- xrpld-default (Slack) receives warning-severity alerts
- xrpld-critical (Slack + email) receives critical-severity alerts, via a
severity=critical child route in the notification policy
Secrets stay out of git: contactpoints.yaml references ${SLACK_WEBHOOK_URL},
${SLACK_CHANNEL}, and ${ALERT_EMAIL_TO}, which Grafana expands from a
gitignored .env.alerting (loaded via an optional env_file on the grafana
service). .env.alerting.example documents the variables; email additionally
needs GF_SMTP_* configured.
Fold the ALERTING.md runbook content into the Alerting section of
docs/telemetry-runbook.md (operator docs live in one place) and delete
ALERTING.md. Move the .env.* ignore rule into docker/telemetry/.gitignore.
Verified live: Grafana 11.5.2 provisioned both contact points, the
severity-routed policy, and all 6 rules with no errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change the six remaining `type: static` search filters in tempo.yaml to
`type: dynamic` so Grafana queries Tempo for available tag values instead
of requiring manual free-text entry. Aligns service-name, node-id,
network-type, span-name, span-status, and span-duration with the existing
node-version and network-id filters.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final four dashboards introduced in this phase: replace the "pratik-xrpld"
$service_name example with "xrpld-validator".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same cleanup for the five system-metric dashboards introduced in this phase:
replace the "pratik-xrpld" $service_name example with "xrpld-validator".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>