mirror of
https://github.com/openai/codex.git
synced 2026-04-28 08:34:54 +00:00
### Overview This PR adds the first piece of tracing for app-server JSON-RPC requests. There are two main changes: - JSON-RPC requests can now take an optional W3C trace context at the top level via a `trace` field (`traceparent` / `tracestate`). - app-server now creates a dedicated request span for every inbound JSON-RPC request in `MessageProcessor`, and uses the request-level trace context as the parent when present. For compatibility with existing flows, app-server still falls back to the TRACEPARENT env var when there is no request-level traceparent. This PR is intentionally scoped to the app-server boundary. In a followup, we'll actually propagate trace context through the async handoff into core execution spans like run_turn, which will make app-server traces much more useful. ### Spans A few details on the app-server span shape: - each inbound request gets its own server span - span/resource names are based on the JSON-RPC method (`initialize`, `thread/start`, `turn/start`, etc.) - spans record transport (stdio vs websocket), request id, connection id, and client name/version when available - `initialize` stores client metadata in session state so later requests on the same connection can reuse it
102 lines
3.3 KiB
Rust
102 lines
3.3 KiB
Rust
use crate::message_processor::ConnectionSessionState;
|
|
use crate::outgoing_message::ConnectionId;
|
|
use crate::transport::AppServerTransport;
|
|
use codex_app_server_protocol::InitializeParams;
|
|
use codex_app_server_protocol::JSONRPCRequest;
|
|
use codex_otel::set_parent_from_context;
|
|
use codex_otel::set_parent_from_w3c_trace_context;
|
|
use codex_otel::traceparent_context_from_env;
|
|
use codex_protocol::protocol::W3cTraceContext;
|
|
use tracing::Span;
|
|
use tracing::field;
|
|
use tracing::info_span;
|
|
|
|
pub(crate) fn request_span(
|
|
request: &JSONRPCRequest,
|
|
transport: AppServerTransport,
|
|
connection_id: ConnectionId,
|
|
session: &ConnectionSessionState,
|
|
) -> Span {
|
|
let span = info_span!(
|
|
"app_server.request",
|
|
otel.kind = "server",
|
|
otel.name = request.method.as_str(),
|
|
rpc.system = "jsonrpc",
|
|
rpc.method = request.method.as_str(),
|
|
rpc.transport = transport_name(transport),
|
|
rpc.request_id = ?request.id,
|
|
app_server.connection_id = ?connection_id,
|
|
app_server.api_version = "v2",
|
|
app_server.client_name = field::Empty,
|
|
app_server.client_version = field::Empty,
|
|
);
|
|
|
|
let initialize_client_info = initialize_client_info(request);
|
|
if let Some(client_name) = client_name(initialize_client_info.as_ref(), session) {
|
|
span.record("app_server.client_name", client_name);
|
|
}
|
|
if let Some(client_version) = client_version(initialize_client_info.as_ref(), session) {
|
|
span.record("app_server.client_version", client_version);
|
|
}
|
|
|
|
if let Some(traceparent) = request
|
|
.trace
|
|
.as_ref()
|
|
.and_then(|trace| trace.traceparent.as_deref())
|
|
{
|
|
let trace = W3cTraceContext {
|
|
traceparent: Some(traceparent.to_string()),
|
|
tracestate: request
|
|
.trace
|
|
.as_ref()
|
|
.and_then(|value| value.tracestate.clone()),
|
|
};
|
|
if !set_parent_from_w3c_trace_context(&span, &trace) {
|
|
tracing::warn!(
|
|
rpc_method = request.method.as_str(),
|
|
rpc_request_id = ?request.id,
|
|
"ignoring invalid inbound request trace carrier"
|
|
);
|
|
}
|
|
} else if let Some(context) = traceparent_context_from_env() {
|
|
set_parent_from_context(&span, context);
|
|
}
|
|
|
|
span
|
|
}
|
|
|
|
fn transport_name(transport: AppServerTransport) -> &'static str {
|
|
match transport {
|
|
AppServerTransport::Stdio => "stdio",
|
|
AppServerTransport::WebSocket { .. } => "websocket",
|
|
}
|
|
}
|
|
|
|
fn client_name<'a>(
|
|
initialize_client_info: Option<&'a InitializeParams>,
|
|
session: &'a ConnectionSessionState,
|
|
) -> Option<&'a str> {
|
|
if let Some(params) = initialize_client_info {
|
|
return Some(params.client_info.name.as_str());
|
|
}
|
|
session.app_server_client_name.as_deref()
|
|
}
|
|
|
|
fn client_version<'a>(
|
|
initialize_client_info: Option<&'a InitializeParams>,
|
|
session: &'a ConnectionSessionState,
|
|
) -> Option<&'a str> {
|
|
if let Some(params) = initialize_client_info {
|
|
return Some(params.client_info.version.as_str());
|
|
}
|
|
session.client_version.as_deref()
|
|
}
|
|
|
|
fn initialize_client_info(request: &JSONRPCRequest) -> Option<InitializeParams> {
|
|
if request.method != "initialize" {
|
|
return None;
|
|
}
|
|
let params = request.params.clone()?;
|
|
serde_json::from_value(params).ok()
|
|
}
|