Files
codex/codex-rs/app-server/src/app_server_tracing.rs
Ruslan Nigmatullin a124ddb854 app-server: remove TCP websocket listener (#21843)
## Why

The app-server no longer needs to expose a TCP websocket listener.
Keeping that transport also kept around a separate listener/auth surface
that is unnecessary now that local clients can use stdio or the
Unix-domain control socket, while remote connectivity is handled by
`remote_control`.

## What Changed

- Removed `ws://IP:PORT` parsing and the `AppServerTransport::WebSocket`
startup path.
- Deleted the app-server websocket listener auth module and removed
related CLI flags/dependencies.
- Kept websocket framing only where it is still needed: over the
Unix-domain control socket and in the outbound `remote_control`
connection.
- Updated app-server CLI/help text and `app-server/README.md` to
document only `stdio://`, `unix://`, `unix://PATH`, and `off` for local
transports.
- Converted affected app-server integration coverage from TCP websocket
listeners to UDS-backed websocket connections, and added a parse test
that rejects `ws://` listen URLs.
- Removed the now-unused workspace `constant_time_eq` dependency and
refreshed `Cargo.lock` after `cargo shear` caught the drift.
- Moved test app-server UDS socket paths to short Unix temp paths so
macOS Bazel test sandboxes do not exceed Unix socket path limits.

## Verification

- Added/updated tests around UDS websocket transport behavior and
`ws://` listen URL rejection.
- `cargo shear`
- `cargo metadata --no-deps --format-version 1`
- `cargo test -p codex-app-server unix_socket_transport`
- `cargo test -p codex-app-server unix_socket_disconnect`
- `just fix -p codex-app-server`
- `git diff --check`

Local full Rust test execution was blocked before compilation by an
external fetch failure for the pinned `nornagon/crossterm` git
dependency. `just bazel-lock-update` and `just bazel-lock-check` were
retried after the manifest cleanup but remain blocked by external
BuildBuddy/V8 fetch timeouts.
2026-05-11 10:17:26 -07:00

180 lines
5.8 KiB
Rust

//! Tracing helpers shared by socket and in-process app-server entry points.
//!
//! The in-process path intentionally reuses the same span shape as JSON-RPC
//! transports so request telemetry stays comparable across stdio, unix socket,
//! and embedded callers. [`typed_request_span`] is the in-process counterpart
//! of [`request_span`] and stamps `rpc.transport` as `"in-process"` while
//! deriving client identity from the typed [`ClientRequest`] rather than
//! from a parsed JSON envelope.
use crate::message_processor::ConnectionSessionState;
use crate::outgoing_message::ConnectionId;
use crate::transport::AppServerTransport;
use codex_app_server_protocol::ClientRequest;
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 initialize_client_info = initialize_client_info(request);
let method = request.method.as_str();
let span = app_server_request_span_template(
method,
transport_name(transport),
&request.id,
connection_id,
);
record_client_info(
&span,
client_name(initialize_client_info.as_ref(), session),
client_version(initialize_client_info.as_ref(), session),
);
let parent_trace = request.trace.as_ref().and_then(|trace| {
trace.traceparent.as_ref()?;
Some(W3cTraceContext {
traceparent: trace.traceparent.clone(),
tracestate: trace.tracestate.clone(),
})
});
attach_parent_context(&span, method, &request.id, parent_trace.as_ref());
span
}
/// Builds tracing span metadata for typed in-process requests.
///
/// This mirrors `request_span` semantics while stamping transport as
/// `in-process` and deriving client info either from initialize params or
/// from existing connection session state.
pub(crate) fn typed_request_span(
request: &ClientRequest,
connection_id: ConnectionId,
session: &ConnectionSessionState,
) -> Span {
let method = request.method();
let span = app_server_request_span_template(&method, "in-process", request.id(), connection_id);
let client_info = initialize_client_info_from_typed_request(request);
record_client_info(
&span,
client_info
.map(|(client_name, _)| client_name)
.or(session.app_server_client_name()),
client_info
.map(|(_, client_version)| client_version)
.or(session.client_version()),
);
attach_parent_context(&span, &method, request.id(), /*parent_trace*/ None);
span
}
fn transport_name(transport: &AppServerTransport) -> &'static str {
match transport {
AppServerTransport::Stdio => "stdio",
AppServerTransport::UnixSocket { .. } => "unix_socket",
AppServerTransport::Off => "off",
}
}
fn app_server_request_span_template(
method: &str,
transport: &'static str,
request_id: &impl std::fmt::Display,
connection_id: ConnectionId,
) -> Span {
info_span!(
"app_server.request",
otel.kind = "server",
otel.name = method,
rpc.system = "jsonrpc",
rpc.method = method,
rpc.transport = 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,
turn.id = field::Empty,
)
}
fn record_client_info(span: &Span, client_name: Option<&str>, client_version: Option<&str>) {
if let Some(client_name) = client_name {
span.record("app_server.client_name", client_name);
}
if let Some(client_version) = client_version {
span.record("app_server.client_version", client_version);
}
}
fn attach_parent_context(
span: &Span,
method: &str,
request_id: &impl std::fmt::Display,
parent_trace: Option<&W3cTraceContext>,
) {
if let Some(trace) = parent_trace {
if !set_parent_from_w3c_trace_context(span, trace) {
tracing::warn!(
rpc_method = method,
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);
}
}
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()
}
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()
}
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()
}
fn initialize_client_info_from_typed_request(request: &ClientRequest) -> Option<(&str, &str)> {
match request {
ClientRequest::Initialize { params, .. } => Some((
params.client_info.name.as_str(),
params.client_info.version.as_str(),
)),
_ => None,
}
}