diff --git a/codex-rs/exec-server/src/client/reqwest_http_client.rs b/codex-rs/exec-server/src/client/reqwest_http_client.rs index 2fcb7c7e6..667e4c49d 100644 --- a/codex-rs/exec-server/src/client/reqwest_http_client.rs +++ b/codex-rs/exec-server/src/client/reqwest_http_client.rs @@ -18,6 +18,7 @@ use reqwest::Url; use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; +use tracing::Instrument; use super::HttpResponseBodyStream; use super::response_body_stream::send_body_delta; @@ -146,15 +147,26 @@ impl ReqwestHttpRequestRunner { } } - let headers = Self::build_headers(params.headers)?; + let request_span = tracing::info_span!( + "codex.exec_server.http_request", + otel.kind = "client", + http.request.method = method.as_str(), + server.address = url.host_str().unwrap_or_default(), + server.port = u64::from(url.port_or_known_default().unwrap_or_default()), + http.response.status_code = tracing::field::Empty, + error.type = tracing::field::Empty, + ); + let mut headers = Self::build_headers(params.headers)?; + codex_otel::inject_span_w3c_trace_headers(&request_span, &mut headers); let mut request = self.client.request(method.clone(), url).headers(headers); if let Some(body) = params.body { request = request.body(body.into_inner()); } - let response = match request.send().await { + let response = match request.send().instrument(request_span.clone()).await { Ok(response) => response, Err(error) => { + request_span.record("error.type", "request"); let error_message = error.to_string(); log_send_error(&method, error); return Err(internal_error(format!( @@ -163,6 +175,7 @@ impl ReqwestHttpRequestRunner { } }; let status = response.status().as_u16(); + request_span.record("http.response.status_code", u64::from(status)); let headers = Self::response_headers(response.headers()); if params.stream_response { diff --git a/codex-rs/otel/src/lib.rs b/codex-rs/otel/src/lib.rs index 2a36fab16..87ccbbfee 100644 --- a/codex-rs/otel/src/lib.rs +++ b/codex-rs/otel/src/lib.rs @@ -29,6 +29,7 @@ pub use crate::provider::OtelProvider; pub use crate::trace_context::context_from_w3c_trace_context; pub use crate::trace_context::current_span_trace_id; pub use crate::trace_context::current_span_w3c_trace_context; +pub use crate::trace_context::inject_span_w3c_trace_headers; pub use crate::trace_context::set_parent_from_context; pub use crate::trace_context::set_parent_from_w3c_trace_context; pub use crate::trace_context::span_w3c_trace_context; diff --git a/codex-rs/otel/src/trace_context.rs b/codex-rs/otel/src/trace_context.rs index c625a416a..6ae924d1d 100644 --- a/codex-rs/otel/src/trace_context.rs +++ b/codex-rs/otel/src/trace_context.rs @@ -49,6 +49,38 @@ pub fn span_w3c_trace_context(span: &Span) -> Option { }) } +/// Injects the W3C trace context for `span` into HTTP headers. +/// +/// Existing `traceparent` and `tracestate` values are replaced so callers can +/// safely reuse a request header map while keeping the supplied span as the +/// source of truth. +pub fn inject_span_w3c_trace_headers(span: &Span, headers: &mut http::HeaderMap) -> bool { + let Some(trace) = span_w3c_trace_context(span) else { + return false; + }; + match trace.traceparent { + Some(traceparent) => { + if let Ok(value) = http::HeaderValue::from_str(&traceparent) { + headers.insert("traceparent", value); + } + } + None => { + headers.remove("traceparent"); + } + } + match trace.tracestate { + Some(tracestate) => { + if let Ok(value) = http::HeaderValue::from_str(&tracestate) { + headers.insert("tracestate", value); + } + } + None => { + headers.remove("tracestate"); + } + } + true +} + pub(crate) fn set_tracestate_entries( entries: BTreeMap>, ) -> Result<(), Box> {