feat(metrics): integrate OpenTelemetry for metrics collection and export - #425
Open
GauravRawat369 wants to merge 2 commits into
Open
feat(metrics): integrate OpenTelemetry for metrics collection and export#425GauravRawat369 wants to merge 2 commits into
GauravRawat369 wants to merge 2 commits into
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
There are a few concrete reliability/operability issues (label/value arity enforcement only in debug builds, unvalidated export interval edge cases, and missing port preflight checks in oneclick) that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR migrates the service’s metrics pipeline from a Prometheus scrape endpoint to OpenTelemetry metrics instruments, exported over OTLP/gRPC to an OpenTelemetry collector (with Prometheus scraping the collector in local/dev setups).
Changes:
- Replaced Prometheus client metrics and
/metricsserver with an OpenTelemetry-based metrics facade (CounterVec/HistogramVec/IntGaugeVec) and OTLP push exporter initialization. - Added
log.telemetryconfiguration to control metrics enablement/export endpoint/interval and wired early initialization inopen_routerstartup. - Updated local/dev tooling and docs (Compose + oneclick + Prometheus config) to include an otel-collector and scrape it instead of the app.
File summaries
| File | Description |
|---|---|
| src/metrics.rs | Replaces Prometheus registry instruments with an OTel metrics facade and OTLP push pipeline initialization + tests. |
| src/logger/config.rs | Adds log.telemetry config section (LogTelemetry) for OTLP metrics export settings. |
| src/decider/gatewaydecider/volume_commitment/server.rs | Updates import to use the crate-wide ConfigurationError after metrics server removal. |
| src/config.rs | Removes now-obsolete metrics server config from GlobalConfig. |
| src/bin/open_router.rs | Initializes OTel metrics pipeline early during startup and removes metrics server task. |
| README.md | Updates local oneclick description to include otel-collector/Prometheus and new metrics endpoints. |
| oneclick.sh | Brings up otel-collector + Prometheus, adds health checks, and enables OTLP metrics for the native run. |
| docs/local-setup.md | Updates profiles/flow docs to reflect OTel collector + Prometheus and enablement flags. |
| docs/configuration.md | Replaces [metrics] scrape config with [log.telemetry] OTLP push config and environment variable examples. |
| docker-compose.yaml | Removes app metrics port, adds otel-collector service, and configures app OTLP env vars. |
| config/prometheus.yaml | Switches Prometheus scrape target to otel-collector:9898 with honor_labels. |
| config/otel-collector.yaml | Adds collector config for OTLP ingest (4317) and Prometheus exporter (9898). |
| config/docker-configuration.toml | Removes [metrics], adds [log.telemetry] defaults for containerized runs. |
| config/development.toml | Removes [metrics], adds [log.telemetry] defaults for local runs. |
| config.example.toml | Adds example [log.telemetry] section for OTLP metrics push. |
| Cargo.toml | Removes Prometheus deps and adds opentelemetry, opentelemetry_sdk, opentelemetry-otlp (plus testing feature for dev). |
| Cargo.lock | Updates lockfile for OpenTelemetry dependency graph and removes Prometheus crates. |
Review details
- Files reviewed: 16/17 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…witch Review fixes: - oneclick.sh: preflight the collector (4317/9898) and Prometheus (9090) ports. Containers from this Compose project are reused; foreign holders are treated like any other conflict. The force-kill pass now skips the container runtime's port forwarder for every port, since killing it takes down all containers. - metrics: enforce label/value arity with assert_eq! in every build instead of debug_assert!, matching the prometheus crate's behaviour; a silent zip truncation would corrupt series. Test added. Interval: - Drop the metrics_export_interval_secs setting; Hyperswitch hardcodes a 3s export interval and 10s timeout, so match it instead of exposing a knob it does not have. This also removes the interval=0 hazard the review raised. The OTLP timeout stays configurable, as it is in Hyperswitch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Decision Engine now ships its metrics the way Hyperswitch does: pushed over OTLP/gRPC to
the cluster's OpenTelemetry collector, which vmagent already scrapes into VictoriaMetrics.
The separate Prometheus
/metricsserver and its[metrics]host/port config are removed.src/metrics.rsis rebuilt on OpenTelemetry behind a Prometheus-shaped facade(
with_label_values(..).inc(),start_timer(),observe(),set()), so none of thecall sites change. Metric names, labels, and histogram buckets are unchanged.
[log.telemetry]config (metrics_enabled,ignore_errors,otel_exporter_otlp_endpoint,otel_exporter_otlp_timeout), overridable asDECISION_ENGINE__LOG__TELEMETRY__*. Off by default; when off every instrument is ano-op. The push cadence is fixed at 3s with a 10s timeout, the same as Hyperswitch.
metrics::initruns right after logger setup, before anything records a metric.opentelemetry,opentelemetry_sdk,opentelemetry-otlpat 0.27 (matchesHyperswitch and the tonic 0.12 already in the lockfile).
prometheus,lazy_static, andthe metrics listener are gone.
internal-logsis enabled so export failures reach tracing.monitoringprofile gains an otel-collector(
config/otel-collector.yaml); Prometheus scrapes the collector instead of the app.oneclick.shstarts the collector and Prometheus and runs the API with metrics enabled.Flow
flowchart LR subgraph app["Decision Engine pod"] DE["Instruments in src/metrics.rs<br/>counters · histograms · gauge"] RD["PeriodicReader<br/>every 3s, cumulative"] DE --> RD end subgraph col["otel-collector · monitoring namespace"] IN["OTLP/gRPC receiver<br/>:4317"] K8S["k8sattributes<br/>adds source_namespace, source_pod"] OUT["Prometheus exporter<br/>:9898 /metrics"] IN --> K8S --> OUT end subgraph store["VictoriaMetrics"] VA["vmagent<br/>scrapes :9898 every 15s<br/>via the collector's ServiceMonitor"] VS["vmstorage<br/>persistent volumes, 1y prod / 3M sandbox"] VA --> VS end G["Grafana<br/>VictoriaMetrics datasource<br/>exported_job="decision-engine""] RD -- "push (outbound only, no port on DE)" --> IN OUT -- "pull" --> VA VS --> G HS["Hyperswitch router"] -. "same push, same collector" .-> INLocally the
monitoringCompose profile plays the same roles with an otel-collector containerin place of the cluster collector and Prometheus in place of vmagent + VictoriaMetrics:
flowchart LR DE["Decision Engine<br/>(oneclick or Compose)"] -- "OTLP push :4317" --> C["otel-collector container"] C -- ":9898 /metrics, scraped every 15s" --> P["Prometheus :9090"] P --> GF["Grafana"]Why
Nothing in the clusters scraped the old metrics port: no ServiceMonitor, no vmagent job, no
annotation. The metrics existed but never reached Grafana. Hyperswitch avoids the scrape
problem entirely by pushing to the collector, so every link after that (collector →
vmagent → VictoriaMetrics → the Grafana
VictoriaMetricsdatasource) already exists and DEinherits it with no monitoring-stack changes. Push also means no port to keep reachable,
no per-pod discovery, and nothing to reconfigure when pods move or scale.
Metrics exported
api_requests_total{endpoint},api_requests_by_status{endpoint,status},api_latency_seconds{endpoint},routing_decisions_total{approach,status},routing_rule_hits_total{rule_name},analytics_events_total{flow_type},analytics_events_dropped_total{stream,reason},analytics_sink_queue_depth{stream},analytics_kafka_produce_total{stream,result},analytics_kafka_delivery_latency_seconds{stream}, plus the two declared-but-unrecordedanalytics_sink_writes_totalandanalytics_sink_write_latency_seconds.Notes for dashboards: the collector appends
_totalto counters that lack it(
api_requests_by_status→api_requests_by_status_total); the app'sservice.namearrives as
job="decision-engine", which vmagent renames toexported_jobbecause thecollector's ServiceMonitor does not honour labels.
Rollout
Per environment, add to the DE deployment's
extraEnvVarsin hyperswitch-infra:The old
DECISION_ENGINE__METRICS__*variables and the chart'smetricsservice port becomeunused but harmless. The old us-east-1 cluster has no collector; do not enable there.
Verification
cargo check,cargo clippy --tests,cargo fmtclean on the postgres backend.in-memory exporter; the OTLP reader builds and shuts down cleanly with no collector
reachable.
oneclick.sh→ decide-gateway and update-gateway-score traffic →series visible on the collector, in Prometheus, and on a Grafana dashboard.