feat: add OpenTelemetry metrics for credential issuance and verification - #495
Conversation
Wire OTel Metrics SDK alongside existing tracing infrastructure: - pkg/metric: MeterProvider with OTLP push + Prometheus scrape exporter - pkg/metric/vci.go: VCI counters (offers, tokens, credentials, notifications) and histograms (token latency, issuance latency) - pkg/metric/vp.go: VP counters (requests, presentations, failures) and histogram (verification latency) Instrument key handlers: - apigw: OAuthToken, VCICredential, VCIDeferredCredential, VCINotification, OIDC RP credential offer creation - verifier: CreateRequestObject, HandleDirectPost All metric labels use protocol-level metadata only (format, grant_type, credential_config_id, source, error_class) — no PII is ever recorded. Adds /metrics Prometheus scrape endpoint to apigw HTTP server. Adds common.metrics config section (mirrors common.tracing).
There was a problem hiding this comment.
Pull request overview
This PR introduces OpenTelemetry metrics instrumentation for the issuer (OpenID4VCI) and verifier (OpenID4VP) flows, adds a shared common.metrics configuration block, and exposes a Prometheus /metrics scrape endpoint in the apigw HTTP server.
Changes:
- Add a new
pkg/metricpackage providing MeterProvider setup (OTLP + Prometheus) and typed instruments for VCI/VP. - Wire metrics into apigw and verifier binaries and add handler-level metrics recording for key endpoints.
- Extend config (
common.metrics) and update dependencies to include OTel metric SDK/exporters.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/model/config.go | Adds common.metrics configuration alongside tracing. |
| pkg/metric/otel.go | Implements MeterProvider wiring (OTLP push + Prometheus reader) and exposes a scrape handler. |
| pkg/metric/handler.go | Adds Prometheus exporter/handler wrapper for scraping. |
| pkg/metric/vci.go | Defines VCI instruments (counters + histograms). |
| pkg/metric/vp.go | Defines VP instruments (counters + histogram). |
| pkg/metric/otel_test.go | Adds unit tests covering no-op mode, instrument construction, and exporter wiring. |
| internal/apigw/apiv1/client.go | Injects VCI metrics into the apigw API client. |
| internal/apigw/apiv1/handlers_oauth.go | Instruments token issuance counter + duration histogram. |
| internal/apigw/apiv1/handlers_issuer.go | Instruments credential issuance/failure counters + duration histogram; adds deferred/notification counters. |
| internal/apigw/apiv1/handlers_oidcrp.go | Instruments credential-offer creation counter. |
| internal/apigw/httpserver/service.go | Adds /metrics endpoint (Prometheus scrape) when metrics are enabled. |
| cmd/apigw/main.go | Wires MeterProvider + VCI instruments + /metrics handler into apigw. |
| internal/verifier/apiv1/client.go | Injects VP metrics into the verifier API client. |
| internal/verifier/apiv1/handler_openid4vp.go | Instruments VP request creation, verification failures, and direct_post processing. |
| cmd/verifier/main.go | Wires MeterProvider + VP instruments into verifier. |
| go.mod | Adds OTel metric SDK/exporters and related transitive deps. |
| go.sum | Updates dependency checksums for the new/updated modules. |
Comments suppressed due to low confidence (1)
internal/verifier/apiv1/handler_openid4vp.go:103
vp.presentations.receivedandvp.verification.durationare only recorded on the success path at the end of the handler, so failed direct_post requests are not counted as “received” and their latency is missing. This makes the metric names misleading and hides failure-side performance characteristics.
func (c *Client) HandleDirectPost(ctx context.Context, sessionID string, vpToken string, presentationSubmission any) error {
start := time.Now()
ctx, span := c.tracer.Start(ctx, "apiv1:handle_direct_post")
defer span.End()
// Get the session
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…vendor - Call meter.Shutdown() in cmd/apigw and cmd/verifier for clean metric flush on graceful shutdown - Record IssuanceLatency on the failure path too so latency stats include failed requests - Run go mod tidy + go mod vendor to fix indirect annotations
# Conflicts: # go.mod # go.sum # vendor/modules.txt
- Move metric.NewVCI/NewVP construction out of cmd/apigw/main.go and cmd/verifier/main.go and into apiv1.New() itself, in both services. main.go now just constructs the shared *metric.Meter and passes it through; each apiv1.New() builds its own domain-specific metrics from it. Keeps metrics wiring next to the code that uses it instead of scattered across main(). - Rename the ambiguous `vci`/`vp` Client fields to `vciMetrics`/ `vpMetrics` in both apigw and verifier for clarity, and update all call sites.
|
Resolved conflicts against main (only go.mod/go.sum/vendor/modules.txt conflicted - pure dependency-version bumps, no app-code conflicts - resolved by taking main's versions and re-running `go mod tidy && go mod vendor`). Addressed all 3 comments (commit 4c3066f):
`go build ./...` and `go test ./...` pass repo-wide. |
|



Closes #494
What
Adds OTel Metrics SDK instrumentation alongside existing tracing, covering the full OpenID4VCI and OpenID4VP flows.
Changes
New:
pkg/metric/MeterProviderwith OTLP push + Prometheus scrape readerModified
common.metricsconfig section (OTEL struct, same as tracing)/metricshandlervci *metric.VCIfield, update constructorOAuthToken(counter + latency)VCICredential(issued/failed counters + latency),VCIDeferredCredential,VCINotification/metricsPrometheus scrape endpointvp *metric.VPfield, update constructorCreateRequestObject,HandleDirectPostPII Safety
All labels are protocol-level only:
format,grant_type,credential_config_id,source,error_class,event,status. No identifiers, session IDs, or claim values are ever recorded.Cardinality
Worst case ~60 series for VCI + ~9 for VP. Well within Prometheus comfort zone.
Testing
pkg/metrichas 6 tests covering construction, no-op, recording, and Prometheus exportervci/vpis not injected)go build ./...andgo vet ./...clean