See Gateway requests in your app's trace
See each model call as one step in the trace of the request that triggered it. Your app’s spans and the Gateway’s span share one trace, and one query can filter both by the same attributes.
A span is one unit of work, with a name, a start, and a duration. A trace is the full journey of one request, made of nested spans. With Gateway telemetry enabled, the Gateway records every request as a span in your Logfire project, with the model, token counts, cost, and timing. This page shows how to make that span a child of a span in your app, and how to copy your own attributes onto it.
Skip this page if you only need the Gateway’s own spans. Gateway telemetry works with no change in your app. Without trace headers, each Gateway span starts a trace of its own.
- Gateway telemetry enabled for a project. Open Gateway → Settings, find the Telemetry card, and select the project. The recommended setup in Send your first Gateway request does this for you.
- A Gateway API key for that project. Open Gateway → Connect to copy one. The Gateway writes its spans to the project that the key belongs to.
- A write token (the credential your app uses to send data to a Logfire project) for the same project, so your app’s spans reach the project that holds the Gateway’s spans. Create one under Project → Settings → Write tokens. See Create write tokens.
The Gateway reads traceparent, baggage, and, when present, tracestate. The traceparent header names the span in your app that the Gateway’s span continues. The optional tracestate header carries extra vendor data alongside it. All three use the format that the World Wide Web Consortium (W3C) defines for OpenTelemetry, the open standard for collecting traces, metrics, and logs. Your app needs to send traceparent and baggage. How depends on the SDK that calls the Gateway.
The gateway/ model provider in Pydantic AI adds both headers for you whenever your app has an active trace. Install the packages, and export your write token:
pip install logfire pydantic-ai
export LOGFIRE_TOKEN='<YOUR_WRITE_TOKEN>'
Then run this script. It sends the model call to the Gateway: the gateway/ prefix in the model name selects the Gateway provider, and the two PYDANTIC_AI_GATEWAY_* variables hold your API key and the Gateway URL. Use the base URL that Gateway → Connect shows for your region.
import os
from pydantic_ai import Agent
import logfire
os.environ['PYDANTIC_AI_GATEWAY_API_KEY'] = '<YOUR_GATEWAY_API_KEY>'
os.environ['PYDANTIC_AI_GATEWAY_BASE_URL'] = 'https://gateway-us.pydantic.dev/proxy'
logfire.configure(service_name='gateway-demo')
logfire.instrument_pydantic_ai()
agent = Agent('gateway/openai:gpt-5.2', instructions='say only hi')
with logfire.set_baggage(user_tier='pro', experiment='ranking-v2'):
result = agent.run_sync('hi')
print(repr(result.output))
Pydantic AI also puts its own entries in baggage: gen_ai.agent.name, gen_ai.agent.call.id, and gen_ai.conversation.id. The Gateway copies them onto its span next to user_tier and experiment.
Other SDKs, such as the OpenAI SDK, send no trace headers on their own. Instrument the HTTP client that the SDK uses, and the Logfire SDK adds both headers to every request. The OpenAI SDK uses httpx:
pip install "logfire[httpx]" openai
from openai import OpenAI
import logfire
logfire.configure(service_name='gateway-demo')
logfire.instrument_httpx() # adds the traceparent and baggage headers
client = OpenAI(
api_key='<YOUR_GATEWAY_API_KEY>',
base_url='https://gateway-us.pydantic.dev/proxy/openai',
)
with logfire.span('handle request'), logfire.set_baggage(user_tier='pro'):
response = client.chat.completions.create(
model='gpt-5.2',
messages=[{'role': 'user', 'content': 'Say only hi'}],
)
print(response.choices[0].message.content)
Without the instrument_httpx line, the Gateway receives no trace context and starts a trace of its own.
Any HTTP client that sends the two headers works the same way, including one that you instrument with another OpenTelemetry SDK or one where you set the headers by hand:
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
baggage: user_tier=pro,experiment=ranking-v2
See Baggage for how set_baggage works, and Distributed tracing for context propagation across services.
Open the project in Live View. You should see one trace in which the Gateway’s span is nested under a span from your app. Span names depend on the SDK versions, but the nesting looks like this. With Pydantic AI:
invoke_agent agent
└─ chat gpt-5.2 (Pydantic AI: the model request)
└─ chat gpt-5.2 (Gateway: model, token counts, cost)
With the OpenAI SDK and instrument_httpx:
handle request
└─ POST (httpx: the HTTP call)
└─ chat gpt-5.2 (Gateway: model, token counts, cost)
Then query for the baggage. The same filter returns your app’s spans and the Gateway’s span:
SELECT start_timestamp, span_name, trace_id, attributes
FROM records
WHERE attributes->>'user_tier' = 'pro'
| Header | Gateway telemetry (your project) | Model provider |
|---|---|---|
traceparent, tracestate | The Gateway’s span becomes a child of your span, in the same trace. | The provider receives one valid traceparent that continues your trace. |
baggage | Each entry becomes an attribute on the Gateway’s span, under the key you sent. The whole header is also recorded as http.request.header.baggage. | Not sent. |
These rules apply to baggage entries:
- Gateway attributes win. If an entry has the same key as an attribute that the Gateway sets itself, such as
gen_ai.request.model, the Gateway keeps its own value. - Size limits. The Gateway reads at most 8,192 bytes of baggage, and at most 4,096 bytes for one entry. It ignores entries beyond those limits.
- Message capture. When Include messages is off for the project, the Gateway leaves prompts and responses out of its span. It also does not copy baggage entries whose keys match those message attributes. The setting filters by key. It does not inspect values, and it does not remove the
http.request.header.baggageattribute. Keep prompt text out of baggage.
- The Gateway span is in a separate trace. The request carried no
traceparent. With the OpenAI SDK or another SDK, check thatlogfire.instrument_httpx()runs before the first model call. With Pydantic AI, check that you calllogfire.configure()andlogfire.instrument_pydantic_ai()first, so an active trace exists. In both cases, check that no proxy between your app and the Gateway removes headers. - The Gateway span has no baggage attributes. The request carried no
baggageheader. Make the model call inside theset_baggageblock, and check that the entry is under the size limits above. - There is no Gateway span. Gateway telemetry is not enabled for the project that owns your API key. See “No trace” in Send your first Gateway request.
- Your app’s spans are in another project. The
LOGFIRE_TOKENbelongs to a different project than the Gateway API key. Use a write token from the same project.
- Choose what the Gateway records: Protect request data.
- Cap what a key or a project can spend: Control AI Gateway spending.
- Learn how baggage works in the SDK: Baggage.