Skip to content

Metrics with OpenTelemetry#770

Open
kamilkisiela wants to merge 1 commit intomainfrom
kamil-metrics
Open

Metrics with OpenTelemetry#770
kamilkisiela wants to merge 1 commit intomainfrom
kamil-metrics

Conversation

@kamilkisiela
Copy link
Contributor

@kamilkisiela kamilkisiela commented Feb 12, 2026

This PR introduces a full metrics pipeline based on OpenTelemetry. It adds support for emitting histograms, counters and gauges for HTTP and GraphQL operations, exports them through OTLP or Prometheus, and allows fine‑grained control over metrics.

New dependencies
The router now depends on opentelemetry‑prometheus and prometheus crates, and humantime for duration parsing.

Performance considerations
Observed metrics are collected asynchronously and should not block request processing.

Metrics struct
aggregates metrics for five domains - HTTP server, HTTP client, GraphQL pipeline, the supergraph loader and the internal caches. Each sub‑component exposes counters and histograms and can be disabled via configuration.

Meter provider setup
The Telemetry struct now holds separate traces_provider, metrics_provider and an optional PrometheusRuntime. During initialisation the router builds an OpenTelemetry meter provider based on telemetry.metrics.* configuration and sets it as global via opentelemetry::global::set_meter_provider. If a Prometheus exporter is configured, a PrometheusRuntime is created, either attached to the existing HTTP server (reusing the router port) or detached on a dedicated port.

Prometheus runtime
The PrometheusRuntime enum encapsulates two modes:

  • Attached - metrics are served from the same HTTP server on a configurable path (default /metrics).
  • Detached - if a distinct port is configured, the runtime spawns a dedicated ntex::HttpServer with its own lifecycle. I limited the server to a single worker.

Endpoint conflict detection
Because metrics may live under /metrics or another path, the router now encapsulates paths into a RouterPaths struct. On start‑up it verifies that GraphQL, health, readiness and Prometheus endpoints do not collide and returns a RouterInitError::EndpointConflict if they do.

Request context storage
A new pipeline::request_extensions module defines small structs stored in HttpRequest extensions. These hold the raw body size, the GraphQL operation name and type, and the final response status. It also exposes helper functions that are used by different pipeline stages to read and write data.

Configuration

Metrics are disabled by default. They are enabled when at least one exporter is present and marked as enabled.
Each exporter entry has a kind field (e.g. otlp or prometheus) and optional settings such as interval, temporality and protocol. A Prometheus exporter can specify a path and port ("empty" means reuse the router port).

The telemetry.metrics.instrumentation section allows tuning histogram aggregation.

A common histogram configuration defines explicit buckets for bytes (request/response body sizes) and seconds (durations). The default buckets are covered in the docs. We do two buckets, one per unit, as their values and their ranges are completely different.

A per-instrument override lets you disable individual metrics or strip certain attributes.

Noteworthy changes

  • Caches are now stored in CacheState. This centralisation makes it easier to manage cache invalidation on supergraph reloads and to register metrics observers.
  • Cache reads and writes are now standardised. I used .entry().or_try_insert_with() API of moka everywhere, to guarantee that concurrent calls on the same not-existing entry are coalesced into one evaluation. We used to do get().await and then insert().await in some cases, but now we have minimum cache writes and maximum reusability of just inserted entries.
  • I also created EntryResultHitMissExt trait that extends Moka's Entry API, to give us insights into cache hits, misses and errors. Useful for metrics and spans attempting to record cache hits/misses.

Example config

telemetry:
  metrics:
    exporters:
      - kind: prometheus       # enable the Prometheus exporter
        enabled: true
        path: /metrics         # override the default path
        port: 6969             # spins up a dedicated http server, running
      - kind: otlp
        endpoint: http://otel‑collector:4318
        protocol: http    # or grpc
        interval: 30s     # push every 30s
        temporality: cumulative
    instrumentation:
      common:
        histogram:
          aggregation: explicit # or exponential
          bytes:
            buckets: [128, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 3145728, 4194304, 5242880]
            record_min_max: false
          seconds:
            buckets: [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]
            record_min_max: false
      instruments:
        http_server_request_duration_seconds:
          attributes:
            graphql.operation.name: false    # drop operation name label, as it has high cardinality

@gemini-code-assist

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

@github-actions
Copy link

github-actions bot commented Feb 12, 2026

k6-benchmark results

     ✓ response code was 200
     ✓ no graphql errors
     ✓ valid response structure

     █ setup

     checks.........................: 100.00% ✓ 218745      ✗ 0    
     data_received..................: 6.4 GB  212 MB/s
     data_sent......................: 86 MB   2.8 MB/s
     http_req_blocked...............: avg=3.43µs   min=661ns   med=1.73µs  max=10.91ms  p(90)=2.49µs   p(95)=2.92µs  
     http_req_connecting............: avg=470ns    min=0s      med=0s      max=1.77ms   p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=20.06ms  min=2.11ms  med=19.12ms max=114.03ms p(90)=27.53ms  p(95)=30.84ms 
       { expected_response:true }...: avg=20.06ms  min=2.11ms  med=19.12ms max=114.03ms p(90)=27.53ms  p(95)=30.84ms 
     http_req_failed................: 0.00%   ✓ 0           ✗ 72935
     http_req_receiving.............: avg=154.58µs min=25.98µs med=41.33µs max=78.26ms  p(90)=123.22µs p(95)=467.18µs
     http_req_sending...............: avg=25.9µs   min=5.45µs  med=11.04µs max=20.39ms  p(90)=17.6µs   p(95)=31.43µs 
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s      max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=19.88ms  min=2.04ms  med=18.98ms max=111.52ms p(90)=27.28ms  p(95)=30.5ms  
     http_reqs......................: 72935   2424.951425/s
     iteration_duration.............: avg=20.56ms  min=6.2ms   med=19.5ms  max=252.58ms p(90)=28.02ms  p(95)=31.38ms 
     iterations.....................: 72915   2424.286463/s
     vus............................: 50      min=50        max=50 
     vus_max........................: 50      min=50        max=50 

@github-actions
Copy link

github-actions bot commented Feb 12, 2026

🐋 This PR was built and pushed to the following Docker images:

Image Names: ghcr.io/graphql-hive/router

Platforms: linux/amd64,linux/arm64

Image Tags: ghcr.io/graphql-hive/router:pr-770 ghcr.io/graphql-hive/router:sha-26afa62

Docker metadata
{
"buildx.build.provenance/linux/amd64": {
  "builder": {
    "id": "https://github.com/graphql-hive/router/actions/runs/22412420378/attempts/1"
  },
  "buildType": "https://mobyproject.org/buildkit@v1",
  "materials": [
    {
      "uri": "pkg:docker/docker/dockerfile@1.21",
      "digest": {
        "sha256": "27f9262d43452075f3c410287a2c43f5ef1bf7ec2bb06e8c9eeb1b8d453087bc"
      }
    },
    {
      "uri": "pkg:docker/gcr.io/distroless/cc-debian12@latest?platform=linux%2Famd64",
      "digest": {
        "sha256": "329e54034ce498f9c6b345044e8f530c6691f99e94a92446f68c0adf9baa8464"
      }
    }
  ],
  "invocation": {
    "configSource": {
      "entryPoint": "router.Dockerfile"
    },
    "parameters": {
      "frontend": "gateway.v0",
      "args": {
        "cmdline": "docker/dockerfile:1.21",
        "label:org.opencontainers.image.created": "2026-02-25T19:44:04.917Z",
        "label:org.opencontainers.image.description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
        "label:org.opencontainers.image.licenses": "MIT",
        "label:org.opencontainers.image.revision": "26afa620ca6acab9bf81a449fb93f603c21f757d",
        "label:org.opencontainers.image.source": "https://github.com/graphql-hive/router",
        "label:org.opencontainers.image.title": "router",
        "label:org.opencontainers.image.url": "https://github.com/graphql-hive/router",
        "label:org.opencontainers.image.vendor": "theguild",
        "label:org.opencontainers.image.version": "pr-770",
        "source": "docker/dockerfile:1.21"
      },
      "locals": [
        {
          "name": "context"
        },
        {
          "name": "dockerfile"
        }
      ]
    },
    "environment": {
      "github_actor": "kamilkisiela",
      "github_actor_id": "8167190",
      "github_event_name": "pull_request",
      "github_event_payload": {
        "action": "synchronize",
        "after": "28cde6770c54bbd00a30f3b144176ada3c58a633",
        "before": "1ec8fa5fc27a605fd0f8acee3ae939b3d553a6e3",
        "enterprise": {
          "avatar_url": "https://avatars.githubusercontent.com/b/187753?v=4",
          "created_at": "2024-07-02T08:52:28Z",
          "description": "",
          "html_url": "https://github.com/enterprises/the-guild",
          "id": 187753,
          "name": "The Guild",
          "node_id": "E_kgDOAALdaQ",
          "slug": "the-guild",
          "updated_at": "2026-02-11T08:01:14Z",
          "website_url": "https://the-guild.dev/"
        },
        "number": 770,
        "organization": {
          "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
          "description": "Schema registry, analytics and gateway for GraphQL federation and other GraphQL APIs.",
          "events_url": "https://api.github.com/orgs/graphql-hive/events",
          "hooks_url": "https://api.github.com/orgs/graphql-hive/hooks",
          "id": 182742256,
          "issues_url": "https://api.github.com/orgs/graphql-hive/issues",
          "login": "graphql-hive",
          "members_url": "https://api.github.com/orgs/graphql-hive/members{/member}",
          "node_id": "O_kgDOCuRs8A",
          "public_members_url": "https://api.github.com/orgs/graphql-hive/public_members{/member}",
          "repos_url": "https://api.github.com/orgs/graphql-hive/repos",
          "url": "https://api.github.com/orgs/graphql-hive"
        },
        "pull_request": {
          "_links": {
            "comments": {
              "href": "https://api.github.com/repos/graphql-hive/router/issues/770/comments"
            },
            "commits": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/770/commits"
            },
            "html": {
              "href": "https://github.com/graphql-hive/router/pull/770"
            },
            "issue": {
              "href": "https://api.github.com/repos/graphql-hive/router/issues/770"
            },
            "review_comment": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}"
            },
            "review_comments": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/770/comments"
            },
            "self": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/770"
            },
            "statuses": {
              "href": "https://api.github.com/repos/graphql-hive/router/statuses/28cde6770c54bbd00a30f3b144176ada3c58a633"
            }
          },
          "active_lock_reason": null,
          "additions": 5579,
          "assignee": null,
          "assignees": [],
          "author_association": "CONTRIBUTOR",
          "auto_merge": null,
          "base": {
            "label": "graphql-hive:main",
            "ref": "main",
            "repo": {
              "allow_auto_merge": false,
              "allow_forking": true,
              "allow_merge_commit": false,
              "allow_rebase_merge": false,
              "allow_squash_merge": true,
              "allow_update_branch": true,
              "archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
              "archived": false,
              "assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
              "blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
              "branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
              "clone_url": "https://github.com/graphql-hive/router.git",
              "collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
              "comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
              "commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
              "compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
              "contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
              "contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
              "created_at": "2024-11-20T16:16:12Z",
              "default_branch": "main",
              "delete_branch_on_merge": true,
              "deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
              "description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
              "disabled": false,
              "downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
              "events_url": "https://api.github.com/repos/graphql-hive/router/events",
              "fork": false,
              "forks": 8,
              "forks_count": 8,
              "forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
              "full_name": "graphql-hive/router",
              "git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
              "git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
              "git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
              "git_url": "git://github.com/graphql-hive/router.git",
              "has_discussions": false,
              "has_downloads": true,
              "has_issues": true,
              "has_pages": false,
              "has_projects": false,
              "has_pull_requests": true,
              "has_wiki": false,
              "homepage": "https://the-guild.dev/graphql/hive/docs/router",
              "hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
              "html_url": "https://github.com/graphql-hive/router",
              "id": 891604244,
              "is_template": false,
              "issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
              "issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
              "issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
              "keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
              "labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
              "language": "Rust",
              "languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
              "license": {
                "key": "mit",
                "name": "MIT License",
                "node_id": "MDc6TGljZW5zZTEz",
                "spdx_id": "MIT",
                "url": "https://api.github.com/licenses/mit"
              },
              "merge_commit_message": "PR_TITLE",
              "merge_commit_title": "MERGE_MESSAGE",
              "merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
              "milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
              "mirror_url": null,
              "name": "router",
              "node_id": "R_kgDONSTNFA",
              "notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
              "open_issues": 54,
              "open_issues_count": 54,
              "owner": {
                "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
                "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
                "followers_url": "https://api.github.com/users/graphql-hive/followers",
                "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
                "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
                "gravatar_id": "",
                "html_url": "https://github.com/graphql-hive",
                "id": 182742256,
                "login": "graphql-hive",
                "node_id": "O_kgDOCuRs8A",
                "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
                "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
                "repos_url": "https://api.github.com/users/graphql-hive/repos",
                "site_admin": false,
                "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
                "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
                "type": "Organization",
                "url": "https://api.github.com/users/graphql-hive",
                "user_view_type": "public"
              },
              "private": false,
              "pull_request_creation_policy": "all",
              "pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
              "pushed_at": "2026-02-25T19:27:14Z",
              "releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
              "size": 5337,
              "squash_merge_commit_message": "PR_BODY",
              "squash_merge_commit_title": "PR_TITLE",
              "ssh_url": "git@github.com:graphql-hive/router.git",
              "stargazers_count": 73,
              "stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
              "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
              "subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
              "subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
              "svn_url": "https://github.com/graphql-hive/router",
              "tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
              "teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
              "topics": [
                "apollo-federation",
                "federation",
                "federation-gateway",
                "graphql",
                "graphql-federation",
                "router"
              ],
              "trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
              "updated_at": "2026-02-25T12:54:29Z",
              "url": "https://api.github.com/repos/graphql-hive/router",
              "use_squash_pr_title_as_default": true,
              "visibility": "public",
              "watchers": 73,
              "watchers_count": 73,
              "web_commit_signoff_required": false
            },
            "sha": "da189c6f34379e3620d77247cb4071b3d2b59fff",
            "user": {
              "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
              "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
              "followers_url": "https://api.github.com/users/graphql-hive/followers",
              "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
              "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
              "gravatar_id": "",
              "html_url": "https://github.com/graphql-hive",
              "id": 182742256,
              "login": "graphql-hive",
              "node_id": "O_kgDOCuRs8A",
              "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
              "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
              "repos_url": "https://api.github.com/users/graphql-hive/repos",
              "site_admin": false,
              "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
              "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
              "type": "Organization",
              "url": "https://api.github.com/users/graphql-hive",
              "user_view_type": "public"
            }
          },
          "body": "This PR introduces a full metrics pipeline based on OpenTelemetry. It adds support for emitting histograms, counters and gauges for HTTP and GraphQL operations, exports them through OTLP or Prometheus, and allows fine‑grained control over metrics.\r\n\r\n- [Documentation PR](https://github.com/graphql-hive/console/pull/7696)\r\n- [Documentation preview](https://pr-7696.hive-landing-page.pages.dev/docs/router/observability/metrics)\r\n\r\n**New dependencies**\r\nThe router now depends on `opentelemetry‑prometheus` and `prometheus` crates, and `humantime` for duration parsing.\r\n\r\n**Performance considerations**\r\nObserved metrics are collected asynchronously and should not block request processing.\r\n\r\n**`Metrics` struct**\r\naggregates metrics for five domains - HTTP server, HTTP client, GraphQL pipeline, the supergraph loader and the internal caches. Each sub‑component exposes counters and histograms and can be disabled via configuration.\r\n\r\n**Meter provider setup**\r\nThe Telemetry struct now holds separate `traces_provider`, `metrics_provider` and an optional `PrometheusRuntime`. During initialisation the router builds an OpenTelemetry meter provider based on `telemetry.metrics.*` configuration and sets it as global via `opentelemetry::global::set_meter_provider`. If a Prometheus exporter is configured, a `PrometheusRuntime` is created, either attached to the existing HTTP server (reusing the router port) or detached on a dedicated port.\r\n\r\n**Prometheus runtime**\r\nThe PrometheusRuntime enum encapsulates two modes:\r\n- `Attached` - metrics are served from the same HTTP server on a configurable path (default `/metrics`).\r\n- `Detached` - if a distinct port is configured, the runtime spawns a dedicated `ntex::HttpServer` with its own lifecycle. I limited the server to a **single worker**.\r\n\r\n**Endpoint conflict detection**\r\nBecause metrics may live under `/metrics` or another path, the router now encapsulates paths into a `RouterPaths` struct. On start‑up it verifies that GraphQL, health, readiness and Prometheus endpoints do not collide and returns a `RouterInitError::EndpointConflict` if they do.\r\n\r\n**Request context storage**\r\nA new `pipeline::request_extensions` module defines small structs stored in `HttpRequest` extensions. These hold the raw body size, the GraphQL operation name and type, and the final response status. It also exposes helper functions that are used by different pipeline stages to read and write data.\r\n\r\n\r\n## Configuration\r\n\r\nMetrics are disabled by default. They are enabled when at least one exporter is present and marked as enabled.\r\nEach exporter entry has a `kind` field (e.g. `otlp` or `prometheus`) and optional settings such as `interval`, `temporality` and `protocol`. A Prometheus exporter can specify a `path` and `port` (\"empty\" means reuse the router port).\r\n\r\nThe `telemetry.metrics.instrumentation` section allows tuning histogram aggregation.\r\n\r\nA common histogram configuration defines explicit buckets for `bytes` (request/response body sizes) and `seconds` (durations). The default buckets are covered in the docs. We do two buckets, one per unit, as their values and their ranges are completely different.\r\n\r\nA per-instrument override lets you disable individual metrics or strip certain attributes.\r\n\r\n\r\n## Noteworthy changes\r\n\r\n- Caches are now stored in `CacheState`. This centralisation makes it easier to manage cache invalidation on supergraph reloads and to register metrics observers.\r\n- Cache reads and writes are now standardised. I used `.entry().or_try_insert_with()` API of moka everywhere, to guarantee that concurrent calls on the same not-existing entry are coalesced into one evaluation. We used to do `get().await` and then `insert().await` in some cases, but now we have minimum cache writes and maximum reusability of just inserted entries.\r\n- I also created `EntryResultHitMissExt` trait that extends Moka's Entry API, to give us insights into cache hits, misses and errors. Useful for metrics and spans attempting to record cache hits/misses.\r\n\r\n## Example config\r\n\r\n```yaml\r\ntelemetry:\r\n  metrics:\r\n    exporters:\r\n      - kind: prometheus       # enable the Prometheus exporter\r\n        enabled: true\r\n        path: /metrics         # override the default path\r\n        port: 6969             # spins up a dedicated http server, running\r\n      - kind: otlp\r\n        endpoint: http://otel‑collector:4318\r\n        protocol: http    # or grpc\r\n        interval: 30s     # push every 30s\r\n        temporality: cumulative\r\n    instrumentation:\r\n      common:\r\n        histogram:\r\n          aggregation: explicit # or exponential\r\n          bytes:\r\n            buckets: [128, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 3145728, 4194304, 5242880]\r\n            record_min_max: false\r\n          seconds:\r\n            buckets: [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]\r\n            record_min_max: false\r\n      instruments:\r\n        http_server_request_duration_seconds:\r\n          attributes:\r\n            graphql.operation.name: false    # drop operation name label, as it has high cardinality\r\n```",
          "changed_files": 61,
          "closed_at": null,
          "comments": 4,
          "comments_url": "https://api.github.com/repos/graphql-hive/router/issues/770/comments",
          "commits": 1,
          "commits_url": "https://api.github.com/repos/graphql-hive/router/pulls/770/commits",
          "created_at": "2026-02-12T10:16:36Z",
          "deletions": 422,
          "diff_url": "https://github.com/graphql-hive/router/pull/770.diff",
          "draft": false,
          "head": {
            "label": "graphql-hive:kamil-metrics",
            "ref": "kamil-metrics",
            "repo": {
              "allow_auto_merge": false,
              "allow_forking": true,
              "allow_merge_commit": false,
              "allow_rebase_merge": false,
              "allow_squash_merge": true,
              "allow_update_branch": true,
              "archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
              "archived": false,
              "assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
              "blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
              "branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
              "clone_url": "https://github.com/graphql-hive/router.git",
              "collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
              "comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
              "commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
              "compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
              "contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
              "contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
              "created_at": "2024-11-20T16:16:12Z",
              "default_branch": "main",
              "delete_branch_on_merge": true,
              "deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
              "description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
              "disabled": false,
              "downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
              "events_url": "https://api.github.com/repos/graphql-hive/router/events",
              "fork": false,
              "forks": 8,
              "forks_count": 8,
              "forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
              "full_name": "graphql-hive/router",
              "git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
              "git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
              "git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
              "git_url": "git://github.com/graphql-hive/router.git",
              "has_discussions": false,
              "has_downloads": true,
              "has_issues": true,
              "has_pages": false,
              "has_projects": false,
              "has_pull_requests": true,
              "has_wiki": false,
              "homepage": "https://the-guild.dev/graphql/hive/docs/router",
              "hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
              "html_url": "https://github.com/graphql-hive/router",
              "id": 891604244,
              "is_template": false,
              "issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
              "issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
              "issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
              "keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
              "labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
              "language": "Rust",
              "languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
              "license": {
                "key": "mit",
                "name": "MIT License",
                "node_id": "MDc6TGljZW5zZTEz",
                "spdx_id": "MIT",
                "url": "https://api.github.com/licenses/mit"
              },
              "merge_commit_message": "PR_TITLE",
              "merge_commit_title": "MERGE_MESSAGE",
              "merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
              "milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
              "mirror_url": null,
              "name": "router",
              "node_id": "R_kgDONSTNFA",
              "notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
              "open_issues": 54,
              "open_issues_count": 54,
              "owner": {
                "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
                "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
                "followers_url": "https://api.github.com/users/graphql-hive/followers",
                "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
                "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
                "gravatar_id": "",
                "html_url": "https://github.com/graphql-hive",
                "id": 182742256,
                "login": "graphql-hive",
                "node_id": "O_kgDOCuRs8A",
                "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
                "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
                "repos_url": "https://api.github.com/users/graphql-hive/repos",
                "site_admin": false,
                "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
                "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
                "type": "Organization",
                "url": "https://api.github.com/users/graphql-hive",
                "user_view_type": "public"
              },
              "private": false,
              "pull_request_creation_policy": "all",
              "pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
              "pushed_at": "2026-02-25T19:27:14Z",
              "releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
              "size": 5337,
              "squash_merge_commit_message": "PR_BODY",
              "squash_merge_commit_title": "PR_TITLE",
              "ssh_url": "git@github.com:graphql-hive/router.git",
              "stargazers_count": 73,
              "stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
              "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
              "subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
              "subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
              "svn_url": "https://github.com/graphql-hive/router",
              "tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
              "teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
              "topics": [
                "apollo-federation",
                "federation",
                "federation-gateway",
                "graphql",
                "graphql-federation",
                "router"
              ],
              "trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
              "updated_at": "2026-02-25T12:54:29Z",
              "url": "https://api.github.com/repos/graphql-hive/router",
              "use_squash_pr_title_as_default": true,
              "visibility": "public",
              "watchers": 73,
              "watchers_count": 73,
              "web_commit_signoff_required": false
            },
            "sha": "28cde6770c54bbd00a30f3b144176ada3c58a633",
            "user": {
              "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
              "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
              "followers_url": "https://api.github.com/users/graphql-hive/followers",
              "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
              "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
              "gravatar_id": "",
              "html_url": "https://github.com/graphql-hive",
              "id": 182742256,
              "login": "graphql-hive",
              "node_id": "O_kgDOCuRs8A",
              "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
              "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
              "repos_url": "https://api.github.com/users/graphql-hive/repos",
              "site_admin": false,
              "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
              "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
              "type": "Organization",
              "url": "https://api.github.com/users/graphql-hive",
              "user_view_type": "public"
            }
          },
          "html_url": "https://github.com/graphql-hive/router/pull/770",
          "id": 3275474501,
          "issue_url": "https://api.github.com/repos/graphql-hive/router/issues/770",
          "labels": [],
          "locked": false,
          "maintainer_can_modify": false,
          "merge_commit_sha": null,
          "mergeable": null,
          "mergeable_state": "unknown",
          "merged": false,
          "merged_at": null,
          "merged_by": null,
          "milestone": null,
          "node_id": "PR_kwDONSTNFM7DO8ZF",
          "number": 770,
          "patch_url": "https://github.com/graphql-hive/router/pull/770.patch",
          "rebaseable": null,
          "requested_reviewers": [],
          "requested_teams": [],
          "review_comment_url": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}",
          "review_comments": 7,
          "review_comments_url": "https://api.github.com/repos/graphql-hive/router/pulls/770/comments",
          "state": "open",
          "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/28cde6770c54bbd00a30f3b144176ada3c58a633",
          "title": "Metrics with OpenTelemetry",
          "updated_at": "2026-02-25T19:27:15Z",
          "url": "https://api.github.com/repos/graphql-hive/router/pulls/770",
          "user": {
            "avatar_url": "https://avatars.githubusercontent.com/u/8167190?v=4",
            "events_url": "https://api.github.com/users/kamilkisiela/events{/privacy}",
            "followers_url": "https://api.github.com/users/kamilkisiela/followers",
            "following_url": "https://api.github.com/users/kamilkisiela/following{/other_user}",
            "gists_url": "https://api.github.com/users/kamilkisiela/gists{/gist_id}",
            "gravatar_id": "",
            "html_url": "https://github.com/kamilkisiela",
            "id": 8167190,
            "login": "kamilkisiela",
            "node_id": "MDQ6VXNlcjgxNjcxOTA=",
            "organizations_url": "https://api.github.com/users/kamilkisiela/orgs",
            "received_events_url": "https://api.github.com/users/kamilkisiela/received_events",
            "repos_url": "https://api.github.com/users/kamilkisiela/repos",
            "site_admin": false,
            "starred_url": "https://api.github.com/users/kamilkisiela/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/kamilkisiela/subscriptions",
            "type": "User",
            "url": "https://api.github.com/users/kamilkisiela",
            "user_view_type": "public"
          }
        },
        "repository": {
          "allow_forking": true,
          "archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
          "archived": false,
          "assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
          "blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
          "branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
          "clone_url": "https://github.com/graphql-hive/router.git",
          "collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
          "comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
          "commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
          "compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
          "contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
          "contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
          "created_at": "2024-11-20T16:16:12Z",
          "custom_properties": {
            "vanta_production_branch_name": "main"
          },
          "default_branch": "main",
          "deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
          "description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
          "disabled": false,
          "downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
          "events_url": "https://api.github.com/repos/graphql-hive/router/events",
          "fork": false,
          "forks": 8,
          "forks_count": 8,
          "forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
          "full_name": "graphql-hive/router",
          "git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
          "git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
          "git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
          "git_url": "git://github.com/graphql-hive/router.git",
          "has_discussions": false,
          "has_downloads": true,
          "has_issues": true,
          "has_pages": false,
          "has_projects": false,
          "has_pull_requests": true,
          "has_wiki": false,
          "homepage": "https://the-guild.dev/graphql/hive/docs/router",
          "hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
          "html_url": "https://github.com/graphql-hive/router",
          "id": 891604244,
          "is_template": false,
          "issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
          "issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
          "issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
          "keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
          "labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
          "language": "Rust",
          "languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
          "license": {
            "key": "mit",
            "name": "MIT License",
            "node_id": "MDc6TGljZW5zZTEz",
            "spdx_id": "MIT",
            "url": "https://api.github.com/licenses/mit"
          },
          "merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
          "milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
          "mirror_url": null,
          "name": "router",
          "node_id": "R_kgDONSTNFA",
          "notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
          "open_issues": 54,
          "open_issues_count": 54,
          "owner": {
            "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
            "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
            "followers_url": "https://api.github.com/users/graphql-hive/followers",
            "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
            "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
            "gravatar_id": "",
            "html_url": "https://github.com/graphql-hive",
            "id": 182742256,
            "login": "graphql-hive",
            "node_id": "O_kgDOCuRs8A",
            "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
            "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
            "repos_url": "https://api.github.com/users/graphql-hive/repos",
            "site_admin": false,
            "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
            "type": "Organization",
            "url": "https://api.github.com/users/graphql-hive",
            "user_view_type": "public"
          },
          "private": false,
          "pull_request_creation_policy": "all",
          "pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
          "pushed_at": "2026-02-25T19:27:14Z",
          "releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
          "size": 5337,
          "ssh_url": "git@github.com:graphql-hive/router.git",
          "stargazers_count": 73,
          "stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
          "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
          "subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
          "subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
          "svn_url": "https://github.com/graphql-hive/router",
          "tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
          "teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
          "topics": [
            "apollo-federation",
            "federation",
            "federation-gateway",
            "graphql",
            "graphql-federation",
            "router"
          ],
          "trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
          "updated_at": "2026-02-25T12:54:29Z",
          "url": "https://api.github.com/repos/graphql-hive/router",
          "visibility": "public",
          "watchers": 73,
          "watchers_count": 73,
          "web_commit_signoff_required": false
        },
        "sender": {
          "avatar_url": "https://avatars.githubusercontent.com/u/8167190?v=4",
          "events_url": "https://api.github.com/users/kamilkisiela/events{/privacy}",
          "followers_url": "https://api.github.com/users/kamilkisiela/followers",
          "following_url": "https://api.github.com/users/kamilkisiela/following{/other_user}",
          "gists_url": "https://api.github.com/users/kamilkisiela/gists{/gist_id}",
          "gravatar_id": "",
          "html_url": "https://github.com/kamilkisiela",
          "id": 8167190,
          "login": "kamilkisiela",
          "node_id": "MDQ6VXNlcjgxNjcxOTA=",
          "organizations_url": "https://api.github.com/users/kamilkisiela/orgs",
          "received_events_url": "https://api.github.com/users/kamilkisiela/received_events",
          "repos_url": "https://api.github.com/users/kamilkisiela/repos",
          "site_admin": false,
          "starred_url": "https://api.github.com/users/kamilkisiela/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/kamilkisiela/subscriptions",
          "type": "User",
          "url": "https://api.github.com/users/kamilkisiela",
          "user_view_type": "public"
        }
      },
      "github_job": "docker",
      "github_ref": "refs/pull/770/merge",
      "github_ref_name": "770/merge",
      "github_ref_protected": "false",
      "github_ref_type": "branch",
      "github_repository": "graphql-hive/router",
      "github_repository_id": "891604244",
      "github_repository_owner": "graphql-hive",
      "github_repository_owner_id": "182742256",
      "github_run_attempt": "1",
      "github_run_id": "22412420378",
      "github_run_number": "1619",
      "github_runner_arch": "X64",
      "github_runner_environment": "github-hosted",
      "github_runner_image_os": "ubuntu24",
      "github_runner_image_version": "20260224.36.1",
      "github_runner_name": "GitHub Actions 1000593292",
      "github_runner_os": "Linux",
      "github_runner_tracking_id": "github_46054210-3c2f-41c0-a684-ba8cf3a16151",
      "github_server_url": "https://github.com",
      "github_triggering_actor": "kamilkisiela",
      "github_workflow": "build-router",
      "github_workflow_ref": "graphql-hive/router/.github/workflows/build-router.yaml@refs/pull/770/merge",
      "github_workflow_sha": "26afa620ca6acab9bf81a449fb93f603c21f757d",
      "platform": "linux/amd64"
    }
  }
},
"buildx.build.provenance/linux/arm64": {
  "builder": {
    "id": "https://github.com/graphql-hive/router/actions/runs/22412420378/attempts/1"
  },
  "buildType": "https://mobyproject.org/buildkit@v1",
  "materials": [
    {
      "uri": "pkg:docker/docker/dockerfile@1.21",
      "digest": {
        "sha256": "27f9262d43452075f3c410287a2c43f5ef1bf7ec2bb06e8c9eeb1b8d453087bc"
      }
    },
    {
      "uri": "pkg:docker/gcr.io/distroless/cc-debian12@latest?platform=linux%2Farm64",
      "digest": {
        "sha256": "329e54034ce498f9c6b345044e8f530c6691f99e94a92446f68c0adf9baa8464"
      }
    }
  ],
  "invocation": {
    "configSource": {
      "entryPoint": "router.Dockerfile"
    },
    "parameters": {
      "frontend": "gateway.v0",
      "args": {
        "cmdline": "docker/dockerfile:1.21",
        "label:org.opencontainers.image.created": "2026-02-25T19:44:04.917Z",
        "label:org.opencontainers.image.description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
        "label:org.opencontainers.image.licenses": "MIT",
        "label:org.opencontainers.image.revision": "26afa620ca6acab9bf81a449fb93f603c21f757d",
        "label:org.opencontainers.image.source": "https://github.com/graphql-hive/router",
        "label:org.opencontainers.image.title": "router",
        "label:org.opencontainers.image.url": "https://github.com/graphql-hive/router",
        "label:org.opencontainers.image.vendor": "theguild",
        "label:org.opencontainers.image.version": "pr-770",
        "source": "docker/dockerfile:1.21"
      },
      "locals": [
        {
          "name": "context"
        },
        {
          "name": "dockerfile"
        }
      ]
    },
    "environment": {
      "github_actor": "kamilkisiela",
      "github_actor_id": "8167190",
      "github_event_name": "pull_request",
      "github_event_payload": {
        "action": "synchronize",
        "after": "28cde6770c54bbd00a30f3b144176ada3c58a633",
        "before": "1ec8fa5fc27a605fd0f8acee3ae939b3d553a6e3",
        "enterprise": {
          "avatar_url": "https://avatars.githubusercontent.com/b/187753?v=4",
          "created_at": "2024-07-02T08:52:28Z",
          "description": "",
          "html_url": "https://github.com/enterprises/the-guild",
          "id": 187753,
          "name": "The Guild",
          "node_id": "E_kgDOAALdaQ",
          "slug": "the-guild",
          "updated_at": "2026-02-11T08:01:14Z",
          "website_url": "https://the-guild.dev/"
        },
        "number": 770,
        "organization": {
          "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
          "description": "Schema registry, analytics and gateway for GraphQL federation and other GraphQL APIs.",
          "events_url": "https://api.github.com/orgs/graphql-hive/events",
          "hooks_url": "https://api.github.com/orgs/graphql-hive/hooks",
          "id": 182742256,
          "issues_url": "https://api.github.com/orgs/graphql-hive/issues",
          "login": "graphql-hive",
          "members_url": "https://api.github.com/orgs/graphql-hive/members{/member}",
          "node_id": "O_kgDOCuRs8A",
          "public_members_url": "https://api.github.com/orgs/graphql-hive/public_members{/member}",
          "repos_url": "https://api.github.com/orgs/graphql-hive/repos",
          "url": "https://api.github.com/orgs/graphql-hive"
        },
        "pull_request": {
          "_links": {
            "comments": {
              "href": "https://api.github.com/repos/graphql-hive/router/issues/770/comments"
            },
            "commits": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/770/commits"
            },
            "html": {
              "href": "https://github.com/graphql-hive/router/pull/770"
            },
            "issue": {
              "href": "https://api.github.com/repos/graphql-hive/router/issues/770"
            },
            "review_comment": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}"
            },
            "review_comments": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/770/comments"
            },
            "self": {
              "href": "https://api.github.com/repos/graphql-hive/router/pulls/770"
            },
            "statuses": {
              "href": "https://api.github.com/repos/graphql-hive/router/statuses/28cde6770c54bbd00a30f3b144176ada3c58a633"
            }
          },
          "active_lock_reason": null,
          "additions": 5579,
          "assignee": null,
          "assignees": [],
          "author_association": "CONTRIBUTOR",
          "auto_merge": null,
          "base": {
            "label": "graphql-hive:main",
            "ref": "main",
            "repo": {
              "allow_auto_merge": false,
              "allow_forking": true,
              "allow_merge_commit": false,
              "allow_rebase_merge": false,
              "allow_squash_merge": true,
              "allow_update_branch": true,
              "archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
              "archived": false,
              "assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
              "blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
              "branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
              "clone_url": "https://github.com/graphql-hive/router.git",
              "collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
              "comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
              "commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
              "compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
              "contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
              "contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
              "created_at": "2024-11-20T16:16:12Z",
              "default_branch": "main",
              "delete_branch_on_merge": true,
              "deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
              "description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
              "disabled": false,
              "downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
              "events_url": "https://api.github.com/repos/graphql-hive/router/events",
              "fork": false,
              "forks": 8,
              "forks_count": 8,
              "forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
              "full_name": "graphql-hive/router",
              "git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
              "git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
              "git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
              "git_url": "git://github.com/graphql-hive/router.git",
              "has_discussions": false,
              "has_downloads": true,
              "has_issues": true,
              "has_pages": false,
              "has_projects": false,
              "has_pull_requests": true,
              "has_wiki": false,
              "homepage": "https://the-guild.dev/graphql/hive/docs/router",
              "hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
              "html_url": "https://github.com/graphql-hive/router",
              "id": 891604244,
              "is_template": false,
              "issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
              "issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
              "issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
              "keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
              "labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
              "language": "Rust",
              "languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
              "license": {
                "key": "mit",
                "name": "MIT License",
                "node_id": "MDc6TGljZW5zZTEz",
                "spdx_id": "MIT",
                "url": "https://api.github.com/licenses/mit"
              },
              "merge_commit_message": "PR_TITLE",
              "merge_commit_title": "MERGE_MESSAGE",
              "merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
              "milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
              "mirror_url": null,
              "name": "router",
              "node_id": "R_kgDONSTNFA",
              "notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
              "open_issues": 54,
              "open_issues_count": 54,
              "owner": {
                "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
                "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
                "followers_url": "https://api.github.com/users/graphql-hive/followers",
                "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
                "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
                "gravatar_id": "",
                "html_url": "https://github.com/graphql-hive",
                "id": 182742256,
                "login": "graphql-hive",
                "node_id": "O_kgDOCuRs8A",
                "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
                "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
                "repos_url": "https://api.github.com/users/graphql-hive/repos",
                "site_admin": false,
                "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
                "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
                "type": "Organization",
                "url": "https://api.github.com/users/graphql-hive",
                "user_view_type": "public"
              },
              "private": false,
              "pull_request_creation_policy": "all",
              "pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
              "pushed_at": "2026-02-25T19:27:14Z",
              "releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
              "size": 5337,
              "squash_merge_commit_message": "PR_BODY",
              "squash_merge_commit_title": "PR_TITLE",
              "ssh_url": "git@github.com:graphql-hive/router.git",
              "stargazers_count": 73,
              "stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
              "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
              "subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
              "subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
              "svn_url": "https://github.com/graphql-hive/router",
              "tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
              "teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
              "topics": [
                "apollo-federation",
                "federation",
                "federation-gateway",
                "graphql",
                "graphql-federation",
                "router"
              ],
              "trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
              "updated_at": "2026-02-25T12:54:29Z",
              "url": "https://api.github.com/repos/graphql-hive/router",
              "use_squash_pr_title_as_default": true,
              "visibility": "public",
              "watchers": 73,
              "watchers_count": 73,
              "web_commit_signoff_required": false
            },
            "sha": "da189c6f34379e3620d77247cb4071b3d2b59fff",
            "user": {
              "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
              "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
              "followers_url": "https://api.github.com/users/graphql-hive/followers",
              "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
              "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
              "gravatar_id": "",
              "html_url": "https://github.com/graphql-hive",
              "id": 182742256,
              "login": "graphql-hive",
              "node_id": "O_kgDOCuRs8A",
              "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
              "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
              "repos_url": "https://api.github.com/users/graphql-hive/repos",
              "site_admin": false,
              "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
              "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
              "type": "Organization",
              "url": "https://api.github.com/users/graphql-hive",
              "user_view_type": "public"
            }
          },
          "body": "This PR introduces a full metrics pipeline based on OpenTelemetry. It adds support for emitting histograms, counters and gauges for HTTP and GraphQL operations, exports them through OTLP or Prometheus, and allows fine‑grained control over metrics.\r\n\r\n- [Documentation PR](https://github.com/graphql-hive/console/pull/7696)\r\n- [Documentation preview](https://pr-7696.hive-landing-page.pages.dev/docs/router/observability/metrics)\r\n\r\n**New dependencies**\r\nThe router now depends on `opentelemetry‑prometheus` and `prometheus` crates, and `humantime` for duration parsing.\r\n\r\n**Performance considerations**\r\nObserved metrics are collected asynchronously and should not block request processing.\r\n\r\n**`Metrics` struct**\r\naggregates metrics for five domains - HTTP server, HTTP client, GraphQL pipeline, the supergraph loader and the internal caches. Each sub‑component exposes counters and histograms and can be disabled via configuration.\r\n\r\n**Meter provider setup**\r\nThe Telemetry struct now holds separate `traces_provider`, `metrics_provider` and an optional `PrometheusRuntime`. During initialisation the router builds an OpenTelemetry meter provider based on `telemetry.metrics.*` configuration and sets it as global via `opentelemetry::global::set_meter_provider`. If a Prometheus exporter is configured, a `PrometheusRuntime` is created, either attached to the existing HTTP server (reusing the router port) or detached on a dedicated port.\r\n\r\n**Prometheus runtime**\r\nThe PrometheusRuntime enum encapsulates two modes:\r\n- `Attached` - metrics are served from the same HTTP server on a configurable path (default `/metrics`).\r\n- `Detached` - if a distinct port is configured, the runtime spawns a dedicated `ntex::HttpServer` with its own lifecycle. I limited the server to a **single worker**.\r\n\r\n**Endpoint conflict detection**\r\nBecause metrics may live under `/metrics` or another path, the router now encapsulates paths into a `RouterPaths` struct. On start‑up it verifies that GraphQL, health, readiness and Prometheus endpoints do not collide and returns a `RouterInitError::EndpointConflict` if they do.\r\n\r\n**Request context storage**\r\nA new `pipeline::request_extensions` module defines small structs stored in `HttpRequest` extensions. These hold the raw body size, the GraphQL operation name and type, and the final response status. It also exposes helper functions that are used by different pipeline stages to read and write data.\r\n\r\n\r\n## Configuration\r\n\r\nMetrics are disabled by default. They are enabled when at least one exporter is present and marked as enabled.\r\nEach exporter entry has a `kind` field (e.g. `otlp` or `prometheus`) and optional settings such as `interval`, `temporality` and `protocol`. A Prometheus exporter can specify a `path` and `port` (\"empty\" means reuse the router port).\r\n\r\nThe `telemetry.metrics.instrumentation` section allows tuning histogram aggregation.\r\n\r\nA common histogram configuration defines explicit buckets for `bytes` (request/response body sizes) and `seconds` (durations). The default buckets are covered in the docs. We do two buckets, one per unit, as their values and their ranges are completely different.\r\n\r\nA per-instrument override lets you disable individual metrics or strip certain attributes.\r\n\r\n\r\n## Noteworthy changes\r\n\r\n- Caches are now stored in `CacheState`. This centralisation makes it easier to manage cache invalidation on supergraph reloads and to register metrics observers.\r\n- Cache reads and writes are now standardised. I used `.entry().or_try_insert_with()` API of moka everywhere, to guarantee that concurrent calls on the same not-existing entry are coalesced into one evaluation. We used to do `get().await` and then `insert().await` in some cases, but now we have minimum cache writes and maximum reusability of just inserted entries.\r\n- I also created `EntryResultHitMissExt` trait that extends Moka's Entry API, to give us insights into cache hits, misses and errors. Useful for metrics and spans attempting to record cache hits/misses.\r\n\r\n## Example config\r\n\r\n```yaml\r\ntelemetry:\r\n  metrics:\r\n    exporters:\r\n      - kind: prometheus       # enable the Prometheus exporter\r\n        enabled: true\r\n        path: /metrics         # override the default path\r\n        port: 6969             # spins up a dedicated http server, running\r\n      - kind: otlp\r\n        endpoint: http://otel‑collector:4318\r\n        protocol: http    # or grpc\r\n        interval: 30s     # push every 30s\r\n        temporality: cumulative\r\n    instrumentation:\r\n      common:\r\n        histogram:\r\n          aggregation: explicit # or exponential\r\n          bytes:\r\n            buckets: [128, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 3145728, 4194304, 5242880]\r\n            record_min_max: false\r\n          seconds:\r\n            buckets: [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]\r\n            record_min_max: false\r\n      instruments:\r\n        http_server_request_duration_seconds:\r\n          attributes:\r\n            graphql.operation.name: false    # drop operation name label, as it has high cardinality\r\n```",
          "changed_files": 61,
          "closed_at": null,
          "comments": 4,
          "comments_url": "https://api.github.com/repos/graphql-hive/router/issues/770/comments",
          "commits": 1,
          "commits_url": "https://api.github.com/repos/graphql-hive/router/pulls/770/commits",
          "created_at": "2026-02-12T10:16:36Z",
          "deletions": 422,
          "diff_url": "https://github.com/graphql-hive/router/pull/770.diff",
          "draft": false,
          "head": {
            "label": "graphql-hive:kamil-metrics",
            "ref": "kamil-metrics",
            "repo": {
              "allow_auto_merge": false,
              "allow_forking": true,
              "allow_merge_commit": false,
              "allow_rebase_merge": false,
              "allow_squash_merge": true,
              "allow_update_branch": true,
              "archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
              "archived": false,
              "assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
              "blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
              "branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
              "clone_url": "https://github.com/graphql-hive/router.git",
              "collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
              "comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
              "commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
              "compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
              "contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
              "contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
              "created_at": "2024-11-20T16:16:12Z",
              "default_branch": "main",
              "delete_branch_on_merge": true,
              "deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
              "description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
              "disabled": false,
              "downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
              "events_url": "https://api.github.com/repos/graphql-hive/router/events",
              "fork": false,
              "forks": 8,
              "forks_count": 8,
              "forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
              "full_name": "graphql-hive/router",
              "git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
              "git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
              "git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
              "git_url": "git://github.com/graphql-hive/router.git",
              "has_discussions": false,
              "has_downloads": true,
              "has_issues": true,
              "has_pages": false,
              "has_projects": false,
              "has_pull_requests": true,
              "has_wiki": false,
              "homepage": "https://the-guild.dev/graphql/hive/docs/router",
              "hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
              "html_url": "https://github.com/graphql-hive/router",
              "id": 891604244,
              "is_template": false,
              "issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
              "issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
              "issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
              "keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
              "labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
              "language": "Rust",
              "languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
              "license": {
                "key": "mit",
                "name": "MIT License",
                "node_id": "MDc6TGljZW5zZTEz",
                "spdx_id": "MIT",
                "url": "https://api.github.com/licenses/mit"
              },
              "merge_commit_message": "PR_TITLE",
              "merge_commit_title": "MERGE_MESSAGE",
              "merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
              "milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
              "mirror_url": null,
              "name": "router",
              "node_id": "R_kgDONSTNFA",
              "notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
              "open_issues": 54,
              "open_issues_count": 54,
              "owner": {
                "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
                "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
                "followers_url": "https://api.github.com/users/graphql-hive/followers",
                "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
                "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
                "gravatar_id": "",
                "html_url": "https://github.com/graphql-hive",
                "id": 182742256,
                "login": "graphql-hive",
                "node_id": "O_kgDOCuRs8A",
                "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
                "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
                "repos_url": "https://api.github.com/users/graphql-hive/repos",
                "site_admin": false,
                "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
                "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
                "type": "Organization",
                "url": "https://api.github.com/users/graphql-hive",
                "user_view_type": "public"
              },
              "private": false,
              "pull_request_creation_policy": "all",
              "pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
              "pushed_at": "2026-02-25T19:27:14Z",
              "releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
              "size": 5337,
              "squash_merge_commit_message": "PR_BODY",
              "squash_merge_commit_title": "PR_TITLE",
              "ssh_url": "git@github.com:graphql-hive/router.git",
              "stargazers_count": 73,
              "stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
              "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
              "subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
              "subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
              "svn_url": "https://github.com/graphql-hive/router",
              "tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
              "teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
              "topics": [
                "apollo-federation",
                "federation",
                "federation-gateway",
                "graphql",
                "graphql-federation",
                "router"
              ],
              "trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
              "updated_at": "2026-02-25T12:54:29Z",
              "url": "https://api.github.com/repos/graphql-hive/router",
              "use_squash_pr_title_as_default": true,
              "visibility": "public",
              "watchers": 73,
              "watchers_count": 73,
              "web_commit_signoff_required": false
            },
            "sha": "28cde6770c54bbd00a30f3b144176ada3c58a633",
            "user": {
              "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
              "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
              "followers_url": "https://api.github.com/users/graphql-hive/followers",
              "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
              "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
              "gravatar_id": "",
              "html_url": "https://github.com/graphql-hive",
              "id": 182742256,
              "login": "graphql-hive",
              "node_id": "O_kgDOCuRs8A",
              "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
              "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
              "repos_url": "https://api.github.com/users/graphql-hive/repos",
              "site_admin": false,
              "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
              "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
              "type": "Organization",
              "url": "https://api.github.com/users/graphql-hive",
              "user_view_type": "public"
            }
          },
          "html_url": "https://github.com/graphql-hive/router/pull/770",
          "id": 3275474501,
          "issue_url": "https://api.github.com/repos/graphql-hive/router/issues/770",
          "labels": [],
          "locked": false,
          "maintainer_can_modify": false,
          "merge_commit_sha": null,
          "mergeable": null,
          "mergeable_state": "unknown",
          "merged": false,
          "merged_at": null,
          "merged_by": null,
          "milestone": null,
          "node_id": "PR_kwDONSTNFM7DO8ZF",
          "number": 770,
          "patch_url": "https://github.com/graphql-hive/router/pull/770.patch",
          "rebaseable": null,
          "requested_reviewers": [],
          "requested_teams": [],
          "review_comment_url": "https://api.github.com/repos/graphql-hive/router/pulls/comments{/number}",
          "review_comments": 7,
          "review_comments_url": "https://api.github.com/repos/graphql-hive/router/pulls/770/comments",
          "state": "open",
          "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/28cde6770c54bbd00a30f3b144176ada3c58a633",
          "title": "Metrics with OpenTelemetry",
          "updated_at": "2026-02-25T19:27:15Z",
          "url": "https://api.github.com/repos/graphql-hive/router/pulls/770",
          "user": {
            "avatar_url": "https://avatars.githubusercontent.com/u/8167190?v=4",
            "events_url": "https://api.github.com/users/kamilkisiela/events{/privacy}",
            "followers_url": "https://api.github.com/users/kamilkisiela/followers",
            "following_url": "https://api.github.com/users/kamilkisiela/following{/other_user}",
            "gists_url": "https://api.github.com/users/kamilkisiela/gists{/gist_id}",
            "gravatar_id": "",
            "html_url": "https://github.com/kamilkisiela",
            "id": 8167190,
            "login": "kamilkisiela",
            "node_id": "MDQ6VXNlcjgxNjcxOTA=",
            "organizations_url": "https://api.github.com/users/kamilkisiela/orgs",
            "received_events_url": "https://api.github.com/users/kamilkisiela/received_events",
            "repos_url": "https://api.github.com/users/kamilkisiela/repos",
            "site_admin": false,
            "starred_url": "https://api.github.com/users/kamilkisiela/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/kamilkisiela/subscriptions",
            "type": "User",
            "url": "https://api.github.com/users/kamilkisiela",
            "user_view_type": "public"
          }
        },
        "repository": {
          "allow_forking": true,
          "archive_url": "https://api.github.com/repos/graphql-hive/router/{archive_format}{/ref}",
          "archived": false,
          "assignees_url": "https://api.github.com/repos/graphql-hive/router/assignees{/user}",
          "blobs_url": "https://api.github.com/repos/graphql-hive/router/git/blobs{/sha}",
          "branches_url": "https://api.github.com/repos/graphql-hive/router/branches{/branch}",
          "clone_url": "https://github.com/graphql-hive/router.git",
          "collaborators_url": "https://api.github.com/repos/graphql-hive/router/collaborators{/collaborator}",
          "comments_url": "https://api.github.com/repos/graphql-hive/router/comments{/number}",
          "commits_url": "https://api.github.com/repos/graphql-hive/router/commits{/sha}",
          "compare_url": "https://api.github.com/repos/graphql-hive/router/compare/{base}...{head}",
          "contents_url": "https://api.github.com/repos/graphql-hive/router/contents/{+path}",
          "contributors_url": "https://api.github.com/repos/graphql-hive/router/contributors",
          "created_at": "2024-11-20T16:16:12Z",
          "custom_properties": {
            "vanta_production_branch_name": "main"
          },
          "default_branch": "main",
          "deployments_url": "https://api.github.com/repos/graphql-hive/router/deployments",
          "description": "Open-source (MIT) GraphQL Federation Router. Built with Rust for maximum performance and robustness.",
          "disabled": false,
          "downloads_url": "https://api.github.com/repos/graphql-hive/router/downloads",
          "events_url": "https://api.github.com/repos/graphql-hive/router/events",
          "fork": false,
          "forks": 8,
          "forks_count": 8,
          "forks_url": "https://api.github.com/repos/graphql-hive/router/forks",
          "full_name": "graphql-hive/router",
          "git_commits_url": "https://api.github.com/repos/graphql-hive/router/git/commits{/sha}",
          "git_refs_url": "https://api.github.com/repos/graphql-hive/router/git/refs{/sha}",
          "git_tags_url": "https://api.github.com/repos/graphql-hive/router/git/tags{/sha}",
          "git_url": "git://github.com/graphql-hive/router.git",
          "has_discussions": false,
          "has_downloads": true,
          "has_issues": true,
          "has_pages": false,
          "has_projects": false,
          "has_pull_requests": true,
          "has_wiki": false,
          "homepage": "https://the-guild.dev/graphql/hive/docs/router",
          "hooks_url": "https://api.github.com/repos/graphql-hive/router/hooks",
          "html_url": "https://github.com/graphql-hive/router",
          "id": 891604244,
          "is_template": false,
          "issue_comment_url": "https://api.github.com/repos/graphql-hive/router/issues/comments{/number}",
          "issue_events_url": "https://api.github.com/repos/graphql-hive/router/issues/events{/number}",
          "issues_url": "https://api.github.com/repos/graphql-hive/router/issues{/number}",
          "keys_url": "https://api.github.com/repos/graphql-hive/router/keys{/key_id}",
          "labels_url": "https://api.github.com/repos/graphql-hive/router/labels{/name}",
          "language": "Rust",
          "languages_url": "https://api.github.com/repos/graphql-hive/router/languages",
          "license": {
            "key": "mit",
            "name": "MIT License",
            "node_id": "MDc6TGljZW5zZTEz",
            "spdx_id": "MIT",
            "url": "https://api.github.com/licenses/mit"
          },
          "merges_url": "https://api.github.com/repos/graphql-hive/router/merges",
          "milestones_url": "https://api.github.com/repos/graphql-hive/router/milestones{/number}",
          "mirror_url": null,
          "name": "router",
          "node_id": "R_kgDONSTNFA",
          "notifications_url": "https://api.github.com/repos/graphql-hive/router/notifications{?since,all,participating}",
          "open_issues": 54,
          "open_issues_count": 54,
          "owner": {
            "avatar_url": "https://avatars.githubusercontent.com/u/182742256?v=4",
            "events_url": "https://api.github.com/users/graphql-hive/events{/privacy}",
            "followers_url": "https://api.github.com/users/graphql-hive/followers",
            "following_url": "https://api.github.com/users/graphql-hive/following{/other_user}",
            "gists_url": "https://api.github.com/users/graphql-hive/gists{/gist_id}",
            "gravatar_id": "",
            "html_url": "https://github.com/graphql-hive",
            "id": 182742256,
            "login": "graphql-hive",
            "node_id": "O_kgDOCuRs8A",
            "organizations_url": "https://api.github.com/users/graphql-hive/orgs",
            "received_events_url": "https://api.github.com/users/graphql-hive/received_events",
            "repos_url": "https://api.github.com/users/graphql-hive/repos",
            "site_admin": false,
            "starred_url": "https://api.github.com/users/graphql-hive/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/graphql-hive/subscriptions",
            "type": "Organization",
            "url": "https://api.github.com/users/graphql-hive",
            "user_view_type": "public"
          },
          "private": false,
          "pull_request_creation_policy": "all",
          "pulls_url": "https://api.github.com/repos/graphql-hive/router/pulls{/number}",
          "pushed_at": "2026-02-25T19:27:14Z",
          "releases_url": "https://api.github.com/repos/graphql-hive/router/releases{/id}",
          "size": 5337,
          "ssh_url": "git@github.com:graphql-hive/router.git",
          "stargazers_count": 73,
          "stargazers_url": "https://api.github.com/repos/graphql-hive/router/stargazers",
          "statuses_url": "https://api.github.com/repos/graphql-hive/router/statuses/{sha}",
          "subscribers_url": "https://api.github.com/repos/graphql-hive/router/subscribers",
          "subscription_url": "https://api.github.com/repos/graphql-hive/router/subscription",
          "svn_url": "https://github.com/graphql-hive/router",
          "tags_url": "https://api.github.com/repos/graphql-hive/router/tags",
          "teams_url": "https://api.github.com/repos/graphql-hive/router/teams",
          "topics": [
            "apollo-federation",
            "federation",
            "federation-gateway",
            "graphql",
            "graphql-federation",
            "router"
          ],
          "trees_url": "https://api.github.com/repos/graphql-hive/router/git/trees{/sha}",
          "updated_at": "2026-02-25T12:54:29Z",
          "url": "https://api.github.com/repos/graphql-hive/router",
          "visibility": "public",
          "watchers": 73,
          "watchers_count": 73,
          "web_commit_signoff_required": false
        },
        "sender": {
          "avatar_url": "https://avatars.githubusercontent.com/u/8167190?v=4",
          "events_url": "https://api.github.com/users/kamilkisiela/events{/privacy}",
          "followers_url": "https://api.github.com/users/kamilkisiela/followers",
          "following_url": "https://api.github.com/users/kamilkisiela/following{/other_user}",
          "gists_url": "https://api.github.com/users/kamilkisiela/gists{/gist_id}",
          "gravatar_id": "",
          "html_url": "https://github.com/kamilkisiela",
          "id": 8167190,
          "login": "kamilkisiela",
          "node_id": "MDQ6VXNlcjgxNjcxOTA=",
          "organizations_url": "https://api.github.com/users/kamilkisiela/orgs",
          "received_events_url": "https://api.github.com/users/kamilkisiela/received_events",
          "repos_url": "https://api.github.com/users/kamilkisiela/repos",
          "site_admin": false,
          "starred_url": "https://api.github.com/users/kamilkisiela/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/kamilkisiela/subscriptions",
          "type": "User",
          "url": "https://api.github.com/users/kamilkisiela",
          "user_view_type": "public"
        }
      },
      "github_job": "docker",
      "github_ref": "refs/pull/770/merge",
      "github_ref_name": "770/merge",
      "github_ref_protected": "false",
      "github_ref_type": "branch",
      "github_repository": "graphql-hive/router",
      "github_repository_id": "891604244",
      "github_repository_owner": "graphql-hive",
      "github_repository_owner_id": "182742256",
      "github_run_attempt": "1",
      "github_run_id": "22412420378",
      "github_run_number": "1619",
      "github_runner_arch": "X64",
      "github_runner_environment": "github-hosted",
      "github_runner_image_os": "ubuntu24",
      "github_runner_image_version": "20260224.36.1",
      "github_runner_name": "GitHub Actions 1000593292",
      "github_runner_os": "Linux",
      "github_runner_tracking_id": "github_46054210-3c2f-41c0-a684-ba8cf3a16151",
      "github_server_url": "https://github.com",
      "github_triggering_actor": "kamilkisiela",
      "github_workflow": "build-router",
      "github_workflow_ref": "graphql-hive/router/.github/workflows/build-router.yaml@refs/pull/770/merge",
      "github_workflow_sha": "26afa620ca6acab9bf81a449fb93f603c21f757d",
      "platform": "linux/amd64"
    }
  }
},
"buildx.build.ref": "builder-e9a13902-c5c4-40d5-b702-b50d90d55fb3/builder-e9a13902-c5c4-40d5-b702-b50d90d55fb30/4kp1nfje3cssd7ill15co8hrb",
"containerimage.descriptor": {
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "digest": "sha256:54e3c27a571838810ed91f24adf26c75a781f2ba603e635ebad27a8ce1a7094d",
  "size": 1609
},
"containerimage.digest": "sha256:54e3c27a571838810ed91f24adf26c75a781f2ba603e635ebad27a8ce1a7094d",
"image.name": "ghcr.io/graphql-hive/router:pr-770,ghcr.io/graphql-hive/router:sha-26afa62"
}

@kamilkisiela kamilkisiela force-pushed the kamil-metrics branch 2 times, most recently from a8dcb29 to 0336b97 Compare February 13, 2026 14:41
@kamilkisiela
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive metrics system using OpenTelemetry, including support for Prometheus, with extensive instrumentation across various parts of the request pipeline. A critical concern, however, is a potential Denial of Service (DoS) vulnerability identified due to high cardinality in metrics labels, specifically when using user-provided GraphQL operation names without sanitization or limiting, which could lead to resource exhaustion. Additionally, critical issues were found regarding type mismatches in error handling and lifetime issues in a helper function, which are likely to cause compilation failures. Addressing these points will ensure a robust and secure implementation of this significant and valuable addition.

@kamilkisiela kamilkisiela marked this pull request as ready for review February 17, 2026 11:20
@kamilkisiela kamilkisiela force-pushed the kamil-metrics branch 4 times, most recently from 96d96e2 to 459a7c8 Compare February 24, 2026 09:04
@kamilkisiela kamilkisiela force-pushed the kamil-metrics branch 2 times, most recently from 9853d8c to 1ec8fa5 Compare February 25, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant