Watch · API gateways

API gateways

One of these four gateways can feed RepoOps today. That is the useful answer, so it is the first one. Reaching the local OTLP receiver takes two separate things, and most gateways have one of them.

The two things a gateway needs

RepoOps runs a local OpenTelemetry receiver at POST /v1/traces. For a gateway's traffic to become a session there, two independent conditions both have to hold.

  1. A transport. The gateway has to POST OTLP over HTTP to an endpoint you name. Some gateways can only reach their own vendor's sink.
  2. GenAI attributes. RepoOps keeps a span only when it carries gen_ai.* or OpenInference keys. A gateway's default tracing describes HTTP requests, and those spans are dropped.

A gateway with the first and not the second connects perfectly and contributes nothing. Its spans arrive, and every one of them is discarded. The Signal tab names that state directly, so you are never left guessing which half is missing.

Where each one stands

GatewayTransportAttributesResult
Kong Gateway 3.13+DirectStandard gen_ai.*Becomes sessions
Gravitee APIM 4.6+Directgravitee.* onlyArrives, then dropped
Azure API ManagementNone on any tierNone on a spanNo path today
Apigee X and hybridCollector onlyNone on a spanArrives, then dropped

Kong: the one that works

Kong AI Gateway's ai-proxy plugin emits the standard semantic-convention keys, and the opentelemetry plugin posts them straight at the receiver. Replace the endpoint with the one shown on your own Signal tab.

# 1. node level. Without both of these Kong emits no spans at all.
export KONG_TRACING_INSTRUMENTATIONS=all   # default: off
export KONG_TRACING_SAMPLING_RATE=1.0      # default: 0.01

# 2. the exporter (decK / kong.yml). traces_endpoint is a full URL.
plugins:
  - name: opentelemetry
    config:
      traces_endpoint: http://localhost:4000/v1/traces

# 3. token usage rides on log_statistics.
  - name: ai-proxy
    config:
      route_type: llm/v1/chat
      logging:
        log_statistics: true
        log_payloads: false

Three things that catch people out:

  • Node-level tracing is off by default and sampling defaults to 1%. Both settings above are required.
  • Kong emits binary protobuf and never JSON. The receiver decodes protobuf, so that is fine, but setting a content type of application/json mislabels a protobuf body rather than changing it.
  • log_payloads puts your prompt and answer text on the span. RepoOps never stores span content, so leaving it off costs you nothing here and narrows what you expose.

Gravitee: connected, and empty

Gravitee exports OTLP wherever you point it and has a real LLM Proxy, but its spans carry gravitee.* attributes rather than gen_ai.*, and its AI token tracking emits analytics metrics rather than span attributes. Everything it sends here is dropped. The configuration below is correct and worth keeping if you want the connection ready.

# gravitee.yml. endpoint is the BASE: Gravitee appends /v1/traces.
services:
  opentelemetry:
    enabled: true
    exporter:
      endpoint: http://localhost:4000
      protocol: http/protobuf   # default is grpc, not accepted here

# and per v4 API: analytics.tracing.enabled = true
  • The endpoint is the bare base. Gravitee appends the signal path itself, so a base ending in /v1/traces posts to /v1/traces/v1/traces.
  • The default protocol is grpc, which this receiver does not accept. The OpenTelemetry spelling http/json is not a value Gravitee knows, and it falls back to grpc without saying so.
  • Tracing is enabled twice: once on the gateway, and again per v4 API.

Azure API Management: no path today

This is not a version or tier limitation, and no configuration change fixes it. If you are evaluating RepoOps on the strength of Azure APIM data, this is the answer you need before you start.

  • The classic, v2, consumption and workspace gateways cannot emit OpenTelemetry to an endpoint you name. Azure Monitor, Application Insights and Event Hubs are the only sinks.
  • The self-hosted gateway does speak OTLP, but Microsoft documents it as metrics. This receiver takes traces and logs, and there is no metrics endpoint.
  • The AI Gateway tier, in public preview in two regions, does export to an OTLP endpoint you name, but only a token usage metric. Its own documentation says traces are not exported yet.
  • The GenAI policies send Application Insights custom metrics and put no gen_ai.* attributes on a span.

The nearest real path routes through Event Hubs into an OpenTelemetry Collector, which delivers HTTP request records rather than model calls. Those would arrive here and be dropped, which is why it is described rather than offered as a recipe.

Apigee: two hops, and still dropped

Apigee's trace exporter is a closed set of Jaeger and Cloud Trace, documented as unchangeable once set, so a collector has to translate. The Jaeger exporter speaks Zipkin on port 9411, so the collector needs a zipkin receiver rather than an otlp one.

# 1. collector: Zipkin in, OTLP/HTTP out.
receivers:
  zipkin:
    endpoint: 0.0.0.0:9411
exporters:
  otlphttp:
    endpoint: http://localhost:4000
service:
  pipelines:
    traces:
      receivers: [zipkin]
      exporters: [otlphttp]

# 2. point Apigee at the collector. The sampler is OFF by default.
PATCH .../environments/$ENV/traceConfig
{ "exporter": "JAEGER",
  "endpoint": "http://COLLECTOR:9411/api/v2/spans",
  "samplingConfig": { "sampler": "PROBABILITY", "samplingRate": 0.5 } }

What arrives is HTTP and proxy data, so it is dropped. The only route to GenAI attributes is a TraceCapture policy where you hand-declare flow variables under those names, capped at 15 variables for Cloud Trace and 25 for Jaeger. Apigee Edge has no distributed tracing at all.

How to tell which problem you have

Open the Signal tab. It reports three states rather than two, because "nothing is arriving" and "everything arriving is discarded" need opposite fixes and used to look identical.

  • No spans received yet. Nothing is reaching the receiver. Check the gateway's endpoint, its sampling rate, and whether tracing is switched on at all.
  • Spans are arriving, and none of them are AI calls. The connection works. The gateway is sending traces that carry no GenAI attributes, which the table above will usually explain.
  • Last session received. Working, with the model and time of the most recent one.

You can also read it straight off the response to any POST:

{"partialSuccess":{},"repoops":{"accepted":0,"total":0,"received":3}}
#                                          kept ^        arrived ^

What is not claimed

These recipes were written from vendor documentation and have not been run against a live gateway. Kong's row says its spans become sessions because its documented attribute names are the ones RepoOps reads, which is a claim about two documents agreeing rather than about watched traffic. The cards in the app carry the same label.

Last updated