Skip to content

Commit 83177ed

Browse files
Enable analytics in codex exec and codex mcp-server (#13083)
Addresses #12913 `codex exec` was not correctly defaulting to Otel metrics to enabled `codex mcp-server` completely lacked an Otel collector Summary: - default to enabling analytics when `codex exec` initializes OpenTelemetry so the CLI actually reports metrics again - add a regression test that proves the flag remains enabled by default - added Otel collector to `codex mcp-server`
1 parent e2fef7a commit 83177ed

File tree

2 files changed

+105
-20
lines changed

2 files changed

+105
-20
lines changed

codex-rs/exec/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ use codex_core::default_client::set_default_originator;
7474
use codex_core::find_thread_path_by_id_str;
7575
use codex_core::find_thread_path_by_name_str;
7676

77+
const DEFAULT_ANALYTICS_ENABLED: bool = true;
78+
7779
enum InitialOperation {
7880
UserTurn {
7981
items: Vec<UserInput>,
@@ -316,7 +318,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
316318
}
317319

318320
let otel = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
319-
codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION"), None, false)
321+
codex_core::otel_init::build_provider(
322+
&config,
323+
env!("CARGO_PKG_VERSION"),
324+
None,
325+
DEFAULT_ANALYTICS_ENABLED,
326+
)
320327
})) {
321328
Ok(Ok(otel)) => otel,
322329
Ok(Err(e)) => {
@@ -927,6 +934,11 @@ mod tests {
927934
use super::*;
928935
use pretty_assertions::assert_eq;
929936

937+
#[test]
938+
fn exec_defaults_analytics_to_enabled() {
939+
assert_eq!(DEFAULT_ANALYTICS_ENABLED, true);
940+
}
941+
930942
#[test]
931943
fn builds_uncommitted_review_request() {
932944
let request = build_review_request(ReviewArgs {

codex-rs/mcp-server/src/lib.rs

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use tracing::debug;
2121
use tracing::error;
2222
use tracing::info;
2323
use tracing_subscriber::EnvFilter;
24+
use tracing_subscriber::prelude::*;
2425

2526
mod codex_tool_config;
2627
mod codex_tool_runner;
@@ -45,19 +46,53 @@ pub use crate::patch_approval::PatchApprovalResponse;
4546
/// is a balance between throughput and memory usage – 128 messages should be
4647
/// plenty for an interactive CLI.
4748
const CHANNEL_CAPACITY: usize = 128;
49+
const DEFAULT_ANALYTICS_ENABLED: bool = true;
50+
const OTEL_SERVICE_NAME: &str = "codex_mcp_server";
4851

4952
type IncomingMessage = JsonRpcMessage<ClientRequest, Value, ClientNotification>;
5053

5154
pub async fn run_main(
5255
arg0_paths: Arg0DispatchPaths,
5356
cli_config_overrides: CliConfigOverrides,
5457
) -> IoResult<()> {
55-
// Install a simple subscriber so `tracing` output is visible. Users can
56-
// control the log level with `RUST_LOG`.
57-
tracing_subscriber::fmt()
58+
// Parse CLI overrides once and derive the base Config eagerly so later
59+
// components do not need to work with raw TOML values.
60+
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {
61+
std::io::Error::new(
62+
ErrorKind::InvalidInput,
63+
format!("error parsing -c overrides: {e}"),
64+
)
65+
})?;
66+
let config = Config::load_with_cli_overrides(cli_kv_overrides)
67+
.await
68+
.map_err(|e| {
69+
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
70+
})?;
71+
72+
let otel = codex_core::otel_init::build_provider(
73+
&config,
74+
env!("CARGO_PKG_VERSION"),
75+
Some(OTEL_SERVICE_NAME),
76+
DEFAULT_ANALYTICS_ENABLED,
77+
)
78+
.map_err(|e| {
79+
std::io::Error::new(
80+
ErrorKind::InvalidData,
81+
format!("error loading otel config: {e}"),
82+
)
83+
})?;
84+
85+
let fmt_layer = tracing_subscriber::fmt::layer()
5886
.with_writer(std::io::stderr)
59-
.with_env_filter(EnvFilter::from_default_env())
60-
.init();
87+
.with_filter(EnvFilter::from_default_env());
88+
let otel_logger_layer = otel.as_ref().and_then(|provider| provider.logger_layer());
89+
let otel_tracing_layer = otel.as_ref().and_then(|provider| provider.tracing_layer());
90+
91+
let _ = tracing_subscriber::registry()
92+
.with(fmt_layer)
93+
.with(otel_logger_layer)
94+
.with(otel_tracing_layer)
95+
.try_init();
6196

6297
// Set up channels.
6398
let (incoming_tx, mut incoming_rx) = mpsc::channel::<IncomingMessage>(CHANNEL_CAPACITY);
@@ -86,20 +121,6 @@ pub async fn run_main(
86121
}
87122
});
88123

89-
// Parse CLI overrides once and derive the base Config eagerly so later
90-
// components do not need to work with raw TOML values.
91-
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {
92-
std::io::Error::new(
93-
ErrorKind::InvalidInput,
94-
format!("error parsing -c overrides: {e}"),
95-
)
96-
})?;
97-
let config = Config::load_with_cli_overrides(cli_kv_overrides)
98-
.await
99-
.map_err(|e| {
100-
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
101-
})?;
102-
103124
// Task: process incoming messages.
104125
let processor_handle = tokio::spawn({
105126
let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx);
@@ -152,3 +173,55 @@ pub async fn run_main(
152173

153174
Ok(())
154175
}
176+
177+
#[cfg(test)]
178+
mod tests {
179+
use super::*;
180+
use codex_core::config::ConfigBuilder;
181+
use codex_core::config::types::OtelExporterKind;
182+
use pretty_assertions::assert_eq;
183+
use std::collections::HashMap;
184+
use tempfile::TempDir;
185+
186+
#[test]
187+
fn mcp_server_defaults_analytics_to_enabled() {
188+
assert_eq!(DEFAULT_ANALYTICS_ENABLED, true);
189+
}
190+
191+
#[tokio::test]
192+
async fn mcp_server_builds_otel_provider_with_logs_traces_and_metrics() -> anyhow::Result<()> {
193+
let codex_home = TempDir::new()?;
194+
let mut config = ConfigBuilder::default()
195+
.codex_home(codex_home.path().to_path_buf())
196+
.build()
197+
.await?;
198+
let exporter = OtelExporterKind::OtlpGrpc {
199+
endpoint: "http://localhost:4317".to_string(),
200+
headers: HashMap::new(),
201+
tls: None,
202+
};
203+
config.otel.exporter = exporter.clone();
204+
config.otel.trace_exporter = exporter.clone();
205+
config.otel.metrics_exporter = exporter;
206+
config.analytics_enabled = None;
207+
208+
let provider = codex_core::otel_init::build_provider(
209+
&config,
210+
"0.0.0-test",
211+
Some(OTEL_SERVICE_NAME),
212+
DEFAULT_ANALYTICS_ENABLED,
213+
)
214+
.map_err(|err| anyhow::anyhow!(err.to_string()))?
215+
.expect("otel provider");
216+
217+
assert!(provider.logger.is_some(), "expected log exporter");
218+
assert!(
219+
provider.tracer_provider.is_some(),
220+
"expected trace exporter"
221+
);
222+
assert!(provider.metrics().is_some(), "expected metrics exporter");
223+
provider.shutdown();
224+
225+
Ok(())
226+
}
227+
}

0 commit comments

Comments
 (0)