Skip to content

feat(router): add opt-in OpenTelemetry traces and metrics - #10381

Open
rabeckett wants to merge 3 commits into
mainfrom
rabeckett-router-go-otel
Open

rabeckett wants to merge 3 commits into
mainfrom
rabeckett-router-go-otel

Conversation

@rabeckett

@rabeckett rabeckett commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #10380

Part of #10261 (step 6 of 7).

Summary

This PR adds opt-in OpenTelemetry traces and metrics to the Go router:

  • They're configured with the standard OTEL_* environment variables and exported over OTLP/HTTP with protobuf encoding.
  • They're off by default. With no endpoint, the router creates no exporter, starts no telemetry goroutines and makes no network calls. Its HTTP responses and CLI stdout don't change.
  • request log records gain trace_id and span_id, and startup logs one telemetry configured record.
  • Telemetry takes attributes only from a fixed allowlist. It never records conversation content, caller-supplied model or choice ids, or raw headers.

AWF doesn't pass any telemetry settings to the router yet, so AWF runs behave as before. Step 7 adds the AWF wiring and trace context on the API proxy's router calls.

Changes

  • telemetry.go (new):
    • Reads the standard variables: endpoints, protocol, headers, timeout, CA certificate, sampler, exporters, metric interval and timeout, service name and resource attributes, with the usual per-signal precedence.
    • An invalid or unsupported setting never stops the router. It logs telemetry setting invalid, naming the variable but not its value, and turns off the signals it applies to.
    • Builds the resource: service.name, service.version (always the binary's), a random service.instance.id for each process, and only service.namespace and deployment.environment.name from OTEL_RESOURCE_ATTRIBUTES.
    • Registers an error handler that logs at most one telemetry error per signal per minute, with a short reason (the HTTP status, timeout, connection refused or other) and never the exporter's error text.
    • Flushes both providers in parallel at shutdown, within a fixed budget.
  • server.go:
    • Reads W3C traceparent and tracestate, and ignores baggage.
    • Starts a SERVER span for each request, except GET /healthz and a bare 503 from the 64-request limit.
    • Starts an INTERNAL child span for /classify and /route once the body decodes.
    • Records the metrics and adds the trace ids to the request record.
  • route.go:
    • Splits out resolvedProfile, so spans and metrics can record the mode a route was served with.
    • Adds cataloguePair, so selected.model and selected.effort always come from the loaded catalogue, never from the request.
  • serve.go, cli.go:
    • Telemetry is set up before listening.
    • With telemetry on, the drain is shorter, so drain plus flush fits AWF's 1 s stop timeout.
    • Every exit path flushes telemetry.
  • Tests, in three new files:
    • telemetry_config_test.go: configuration, precedence, warnings that never contain the value, the resource and sampling.
    • telemetry_server_test.go: spans, metrics, log fields, a cardinality bound (including redirected paths), and privacy canaries.
    • telemetry_export_test.go: end-to-end export to a fake collector that decodes the protobuf, disabled telemetry, an HTTPS collector with and without its CA configured, and unavailable, failing or stalled collectors. It also checks that shutdown stays within 1 s.
  • go.mod, go.sum:
    • OTel Go v1.47.0 (otel, sdk, sdk/metric, otlptracehttp, otlpmetrichttp) and semconv v1.43.0.
    • golang.org/x/net v0.60.0 (see the decisions).
  • CI:
    • test-router.yml turns on setup-go's module cache, keyed on containers/router/go.sum, now that the module has dependencies.
    • dependabot.yml gets a weekly grouped gomod entry for /containers/router.
  • README: a Telemetry section covering the variables, trace context, spans, metrics, privacy and the shutdown budget. It also updates the log table and shutdown text.

What's emitted

Spans.

  • One SERVER span per request, named {method} {route}. Its attributes are http.request.method, http.route, url.scheme, network.protocol.version, http.response.status_code and, for an error, error.type (the error envelope's code).
  • One INTERNAL child span per decoded /classify or /route request, with attributes prefixed gh_aw_router.:
    • outcome;
    • the message and candidate counts;
    • the goal, mode and resolved mode;
    • whether a classification was sent, and its labels;
    • the selected model and effort, taken from the catalogue.

Metrics.

Instrument Type, unit Attributes
http.server.request.duration histogram, s method, route, status, error.type
gh_aw_router.requests.rejected counter gh_aw_router.reason: overload or payload_too_large
gh_aw_router.planning.outcomes counter operation, outcome, and for route the goal, mode and resolved mode

Every metric attribute comes from a fixed set. Metrics never carry model ids or classifier labels.

Logs.

  • request records get trace_id and span_id when the router started a span. With tracing off, they get only the trace_id of a valid incoming traceparent.
  • The new records are telemetry configured, telemetry setting invalid, telemetry error and telemetry flush incomplete.
  • No record contains a setting's value, an endpoint's path, query or user info, or tracestate.

Decisions for maintainers

  • Official OTLP exporters.
    • The OTLP/HTTP exporters link gRPC, grpc-gateway, genproto and golang.org/x/net/http2, even though they only export over HTTP. They make up most of the size increase below.
    • A hand-written OTLP encoder on top of the SDK would keep the binary at about 8.2 MB, but it would reimplement an official library.
    • Instead, the new Dependabot entry, govulncheck in CI and the Grype gate keep these dependencies current.
    • The cost is that Grype's blocking gate can fail a PR on a High finding in any linked module until Dependabot bumps it.
  • golang.org/x/net v0.60.0. With v0.59.0, which the exporters require, govulncheck reports five reachable HTTP/2 advisories: GO-2026-6603, -6610, -6611, -6612 and -6617. v0.60.0 fixes them.
  • OTLP/HTTP with protobuf only. grpc and http/json log a warning and turn the signal off.
  • Router-specific metric attributes are prefixed gh_aw_router., like the span attributes, so they can't collide with semconv names. A bare 503 has no error envelope, so it's recorded with error.type 503.
  • tracestate stays on the router's spans, as W3C propagation requires, but is never logged. baggage is ignored.
  • service.instance.id is a random UUID for each process. Metrics are cumulative, so without it two routers exporting to one collector would look like one counter resetting. The host, process and container detectors stay off.
  • Three metric instruments.
    • There's no http.server.active_requests. It's a Development-status semconv instrument, and a router that serves a few requests per run gets nothing from a concurrency gauge.
    • gh_aw_router.requests.rejected repeats what the histogram records for those requests, so that load shedding has one name to find and alert on.
  • Shutdown budget.
    • With telemetry on, the router drains HTTP for up to 0.25 s and then flushes for up to 0.5 s.
    • Without telemetry, the existing 0.5 s drain is unchanged.
    • AWF's teardown sends SIGKILL 1 s after SIGTERM.
  • No shorter metric interval.
    • The default stays 60 s.
    • Metrics are cumulative and shutdown exports them, so a run shorter than the interval still reports its totals.
    • Step 7 will forward an explicit setting without setting a default.
  • Router version stays 0.2.0. No release has shipped 0.2.0 yet: v0.28.70 was published before feat(router): ship the Go router image and remove the Python implementation #10359 merged.

Validation

  • Go checks.

    • gofmt -l, go vet and go test ./... pass in containers/router in golang:1.27.3-alpine.
    • go test -race ./... passes in golang:1.27.3-bookworm.
    • The test binary passes natively on Windows ARM64.
  • Shutdown timing.

    • With a collector that never answers, SIGTERM to exit took 0.75–0.76 s.
    • When the in-flight request drains, it took 0.64–0.65 s.
    • The shutdown tests passed 5 runs in a row.
  • Vulnerability scans.

    • govulncheck v1.9.0 reports no vulnerabilities.
    • Grype v0.116.0 (the digest supply-chain-scan.yml pins) with the repo's .grype.yaml and --fail-on high finds none in the image.
  • Container tests. The image builds from this branch, and the container tests pass against it, including the routing-contract corpus.

  • Licenses. All 24 modules linked into the binary are Apache-2.0, MIT or BSD-3-Clause.

  • Size.

    Before After
    Stripped binary, linux/amd64 7,479,456 bytes 16,298,144 bytes
    Stripped binary, linux/arm64 6,946,976 bytes 15,204,512 bytes
    Image, uncompressed (linux/arm64) 12.4 MB 23.5 MB
    Image, compressed (linux/arm64) 3.1 MB 5.9 MB
  • Not tested here. Export from a router running inside AWF. AWF doesn't pass telemetry settings to the router until step 7, which adds an integration test with a collector on awf-routing. With telemetry off, the router behaves as in feat(router): ship the Go router image and remove the Python implementation #10359, and test-model-routing runs on the merge commit.

Rollback

Telemetry is off unless the OTEL_* variables are set, and AWF doesn't set them. Reverting this PR restores #10359's router with no other changes.

Comment thread containers/router/go.mod
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 93.39% 93.39% ➡️ +0.00%
Statements 91.83% 91.83% ➡️ +0.00%
Functions 90.98% 90.98% ➡️ +0.00%
Branches 85.87% 85.87% ➡️ +0.00%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/enclave/host-executor-server.ts 91.0% → 91.0% (+0.00%) 90.0% → 89.7% (-0.29%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Privacy-sensitive exports, new dependencies and the strict one-second shutdown budget require final human validation.

3 open issues
What changed in this PR

Adds opt-in OpenTelemetry to the Go router while preserving its HTTP and CLI contracts.

Changes:

  • Adds OTLP/HTTP configuration, sanitized diagnostics and bounded shutdown flushing.
  • Instruments HTTP and planning operations, metrics and request-log trace correlation.
  • Adds telemetry tests, documentation and dependency maintenance.
File Description
containers/​router/​telemetry.go Configures telemetry providers, exporters and flushing.
containers/​router/​telemetry_server_test.go Tests instrumentation, propagation and privacy.
containers/​router/​telemetry_export_test.go Tests collector exports, failures and shutdown timing.
containers/​router/​telemetry_config_test.go Tests settings, resources and sanitized diagnostics.
containers/​router/​server.go Adds request and planning instrumentation.
containers/​router/​server_test.go Validates telemetry-related log fields.
containers/​router/​serve.go Initializes telemetry and flushes on exit.
containers/​router/​serve_test.go Extends environment helpers and startup expectations.
containers/​router/​route.go Extracts resolved profiles and catalogue lookup.
containers/​router/​README.md Documents telemetry configuration and behavior.
containers/​router/​go.sum Records dependency checksums.
containers/​router/​go.mod Adds OpenTelemetry dependencies.
containers/​router/​cli.go Passes environment access into serving.
.github/​workflows/​test-router.yml Enables Go module caching.
.github/​dependabot.yml Adds grouped router module updates.

Review effort: Max

Comment thread containers/router/telemetry.go
Comment thread containers/router/telemetry.go Outdated
Comment thread containers/router/telemetry_server_test.go Outdated
rabeckett and others added 3 commits October 20, 2026 21:23
The router exports traces and metrics over OTLP/HTTP when the standard
OTEL_* variables configure an endpoint, and stays silent otherwise.
Incoming W3C trace context is honoured, request logs gain validated
trace_id and span_id fields, spans and metrics carry only allowlisted
attributes, and shutdown flushes within the 1 s AWF allows.

Raise the indirect golang.org/x/net requirement to v0.60.0 so that
govulncheck passes, add a gomod Dependabot entry, and cache Go modules
in the router workflow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add a random service.instance.id to the resource, so that the cumulative
  metric streams of concurrent routers stay apart at a collector.
- Drop http.server.active_requests. It's a Development-status semconv
  instrument, and a router serving a handful of requests a run has no
  useful concurrency gauge. Keep gh_aw_router.requests.rejected as a named
  load-shedding signal.
- Shorten the shutdown budget with telemetry on to a 0.25 s drain and a
  0.5 s flush. The worst case measured with a stalled collector drops from
  about 0.92 s to 0.76 s, leaving margin under AWF's 1 s before SIGKILL.
- Stop recommending a 15 s metric interval for AWF. Shutdown exports the
  cumulative totals, so short runs keep them with the default interval.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Don't report an export that shutdown cancels; the final flush reports
  its own result. Classify a failure with its export context's error too,
  because the exporters return the SDK context's cause, so a metric reader
  timeout was reported as "other" instead of "timeout".
- Test that OTEL_EXPORTER_OTLP_CERTIFICATE reaches the exporters' TLS
  client, and that an untrusted collector is reported without detail.
- Make the unavailable-collector test observe real export failures before
  shutdown, and add redirected paths to the cardinality test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rabeckett
rabeckett force-pushed the rabeckett-router-go-otel branch from 6de90dc to 3e0f64a Compare October 21, 2026 04:23
@github-actions

Copy link
Copy Markdown
Contributor

✅ Copilot review passed with no inline comments.

@rabeckett Add the ready-for-aw label to this PR to trigger agentic CI smoke tests.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot BYOK AOAI (Entra) completed. Copilot AOAI BYOK (Entra) mode operational. 🔓

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Security Guard has started processing this pull request

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

📰 BREAKING: Report filed by Smoke Copilot

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot BYOK completed. Copilot BYOK mode operational. 🔓

🔑 BYOK report filed by Smoke Copilot BYOK

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

🔌 Smoke Services — All services reachable! ✅

🔌 Service connectivity validated by Smoke Services

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Smoke Cloud Hypervisor completed. Cloud Hypervisor + Copilot passed.

Cloud Hypervisor + Copilot smoke test by Smoke Cloud Hypervisor

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Claude passed

Generated by Smoke Claude for #10381

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

🛡️ Smoke Copilot Network Isolation confirmed the egress allowlist is enforced. ✅

🛡️ Egress verdict from Smoke Copilot Network Isolation

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Gemini completed. All facets verified. 💎

💎 Faceted by Smoke Gemini

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

📡 Smoke OTel Tracing completed. All tracing scenarios validated. ✅

📡 OTel tracing validated by Smoke OTel Tracing

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

Smoke Codex

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Smoke Codex · gpt54 · 14 AIC · ⊞ 20.9K · ◷

🔮 The oracle has spoken through Smoke Codex

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot BYOK AOAI (api-key) completed. Copilot AOAI BYOK (api-key) mode operational. 🔓

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

Tested by Smoke Chroot

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

✅ Build Test Suite completed successfully!

Generated by Build Test Suite for #10381

@github-actions

Copy link
Copy Markdown
Contributor

Cloud Hypervisor + Copilot smoke test

  1. Merged PR lookup: PASS
  2. github.com connectivity: PASS (HTTP 200)
  3. File write/read: PASS
  4. example.com blocked: PASS (HTTP 000)
    Overall: PASS

Cloud Hypervisor + Copilot smoke test by Smoke Cloud Hypervisor
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Claude Engine Validation

  • API status: ✅ PASS
  • GH check: ✅ PASS
  • File status: ✅ PASS

Overall result: PASS

Generated by Smoke Claude for #10381 · claude · haiku45 · 33.9 AIC · ⊞ 6.1K · ◷
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke test (Copilot) — @rabeckett

  • ✅ GitHub MCP: "[docs] docs: sync unified enclave features and capped tool descriptions"
  • ✅ GitHub.com connectivity (200)
  • ✅ File write/read
    Overall: PASS

📰 BREAKING: Report filed by Smoke Copilot
Add label ready-for-aw to run again

@github-actions github-actions Bot added smoke-copilot smoke-copilot-network-isolation Copilot network-isolation egress smoke test labels Oct 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

EGRESS_RESULT allow=pass deny=pass

  • ✅ Allowed domain: api.github.com → HTTP 200
  • ✅ Blocked domain: example.com → blocked (curl failed with SSL cert error, no HTTP response)

Overall: PASS. cc @rabeckett

🛡️ Egress verdict from Smoke Copilot Network Isolation
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke services: ✅ Redis PONG · ✅ pg_isready · ✅ SELECT 1 → PASS

🔌 Service connectivity validated by Smoke Services
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

OTEL smoke test

  • ✅ S1: otel.js loads and exports its API (including the _parseEndpoints, _parseOtlpHeaders, _buildResourceSpans and _createOtlpWorkloadIdentity helpers).
  • ✅ S2: Jest otel tests: 3 suites and 68 tests passed.
  • ✅ S3: env-passthrough.ts and api-proxy-env-config.ts both reference trace/OTEL vars.
  • ✅ S4: The onUsage hook exists in token-tracker-http.js.
  • ⚠️ S5: I did not find the diagnostics post-step output, so I can't say whether spans were exported. This is expected-pending.

📡 OTel tracing validated by Smoke OTel Tracing
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test: Copilot BYOK (Direct) Mode ✅ PASS

Direct BYOK Mode (COPILOT_PROVIDER_API_KEY via api-proxy → api.githubcopilot.com)

Status: All tests passed. Inference path operational.

🔑 BYOK report filed by Smoke Copilot BYOK
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke test:

  • fix: match capped enclave tool descriptions during readiness ✅
  • fix: preserve enclave gateway notification acknowledgments ✅
  • PR detail lookup ✅
  • Playwright title check ❌
  • File write/read ✅
  • Build (npm ci && npm run build) ✅
    Overall: FAIL

🔮 The oracle has spoken through Smoke Codex
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Smoke Test Results

Overall status: FAIL

💎 Faceted by Smoke Gemini
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

Chroot Version Comparison

Runtime Host Version Chroot Version Match?
Python Python 3.12.15 Python 3.12.15 ✅
Node.js v24.21.0 v22.23.2 ❌
Go go1.22.12 go1.22.12 ✅

Result: Not all tests passed (Node.js version mismatch), so the smoke-chroot label was not added.

Tested by Smoke Chroot
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

@rabeckett
fix: match capped enclave tool descriptions during readiness — ✅
fix: preserve enclave gateway notification acknowledgments — ✅
HTTP connectivity — ✅
File write/read — ✅
BYOK inference — ✅
Running in direct BYOK mode (AWF_AUTH_TYPE=github-oidc + AWF_AUTH_AZURE_* + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw)
Overall: PASS

🪪 BYOK (AOAI Entra) report filed by Smoke Copilot BYOK AOAI (Entra)
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

@rabeckett Smoke test under direct BYOK (COPILOT_PROVIDER_API_KEY + COPILOT_PROVIDER_BASE_URL) via api-proxy → Azure OpenAI (Foundry, o4-mini-aw):

  • GitHub MCP connectivity ❌ validating "arguments": validating root: validating /properties/state: enum: merged does not equal any of: [open closed all]
  • GitHub.com connectivity ✅
  • File I/O sandbox ✅
  • BYOK inference path ✅

Overall: FAIL

🔑 BYOK (AOAI api-key) report filed by Smoke Copilot BYOK AOAI (api-key)
Add label ready-for-aw to run again

@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia ❌ not run ❌ FAIL
Bun hono ❌ not run ❌ FAIL
C++ fmt ✅ N/A ✅ PASS
C++ json ✅ N/A ✅ PASS
Deno oak N/A not run ❌ FAIL
Deno std N/A not run ❌ FAIL
.NET hello-world ✅ N/A ✅ PASS
.NET json-parse ✅ N/A ✅ PASS
Go color ✅ pass (1 pkg ok) ✅ PASS
Go env ✅ pass (1 pkg ok) ✅ PASS
Go uuid ✅ pass (1 pkg ok) ✅ PASS
Java gson ❌ not run ❌ FAIL
Java caffeine ❌ not run ❌ FAIL
Node.js clsx ✅ all passed ✅ PASS
Node.js execa ✅ all passed ✅ PASS
Node.js p-limit ✅ all passed ✅ PASS
Rust fd ✅ 1/1 passed ✅ PASS
Rust zoxide ✅ 1/1 passed ✅ PASS

Overall: 5/8 ecosystems passed — FAIL

Failure details

  • Bun / Deno: all repos cloned, but the curl | bash / curl | sh installers were denied by the sandbox permission policy, so Bun and Deno were never installed and no tests ran.
  • Java: mvn compile failed on plugin resolution: InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty. This is a Java truststore problem in the runner environment, not a project failure. ~/.m2 is root-owned, so I also had to use -Dmaven.repo.local.
  • .NET: needed XDG_RUNTIME_DIR and DOTNET_CLI_HOME set to writable paths before it would run.

The build-test label was not added because not all ecosystems passed.

Generated by Build Test Suite for #10381 · copilot · auto · 26.1 AIC · ⊞ 12K · ◷
Add label ready-for-aw to run again

This branch was successfully deployed

1 active deployment
aoai-model — 3e0f64ab Deployed Oct 21, 2026 by rabeckett via conclusion #2095
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Router: add opt-in OpenTelemetry traces and metrics

2 participants