Files
codex/codex-rs/core/src/otel_init.rs
T
jackz 2c3156adf4 Honor custom OTLP metrics exporters when analytics is disabled (#52278)
## Why

Disabling OpenAI analytics also disabled explicitly configured OTLP metrics collectors. Users should be able to export metrics to their own collectors independently of `analytics.enabled`.

## What changed

Apply the analytics preference only to the built-in `statsig` metrics exporter. Keep custom OTLP HTTP and gRPC exporters enabled regardless of that preference, and use `otel.metrics_exporter = "none"` to disable metrics export. Clarify these settings in config documentation and the schema.

## Testing

Cover exporter selection across explicit and default analytics preferences. Update app-server and daemon telemetry tests to verify custom metrics export with analytics disabled, and worktree tests to disable metrics through the exporter setting.

GitOrigin-RevId: 0c61f0f01e2b694db642ac7e7fd1dac9965c7d96
2026-10-08 21:18:24 +00:00

114 lines
4.1 KiB
Rust

use crate::config::Config;
use codex_config::types::OtelExporterKind as Kind;
use codex_config::types::OtelHttpProtocol as Protocol;
use codex_features::Feature;
use codex_login::default_client::originator;
use codex_otel::OtelExporter;
use codex_otel::OtelHttpProtocol;
use codex_otel::OtelProvider;
use codex_otel::OtelSettings;
use codex_otel::OtelTlsConfig as OtelTlsSettings;
use std::error::Error;
/// Build an OpenTelemetry provider from the app Config.
///
/// Returns `None` when OTEL export is disabled.
pub fn build_provider(
config: &Config,
service_version: &str,
service_name_override: Option<&str>,
default_analytics_enabled: bool,
) -> Result<Option<OtelProvider>, Box<dyn Error>> {
let to_otel_exporter = |kind: &Kind| match kind {
Kind::None => OtelExporter::None,
Kind::Statsig => OtelExporter::Statsig,
Kind::OtlpHttp {
endpoint,
headers,
protocol,
tls,
} => {
let protocol = match protocol {
Protocol::Json => OtelHttpProtocol::Json,
Protocol::Binary => OtelHttpProtocol::Binary,
};
OtelExporter::OtlpHttp {
endpoint: endpoint.clone(),
headers: headers
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
protocol,
tls: tls.as_ref().map(|config| OtelTlsSettings {
ca_certificate: config.ca_certificate.clone(),
client_certificate: config.client_certificate.clone(),
client_private_key: config.client_private_key.clone(),
}),
}
}
Kind::OtlpGrpc {
endpoint,
headers,
tls,
} => OtelExporter::OtlpGrpc {
endpoint: endpoint.clone(),
headers: headers
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
tls: tls.as_ref().map(|config| OtelTlsSettings {
ca_certificate: config.ca_certificate.clone(),
client_certificate: config.client_certificate.clone(),
client_private_key: config.client_private_key.clone(),
}),
},
};
let exporter = to_otel_exporter(&config.otel.exporter);
let trace_exporter = to_otel_exporter(&config.otel.trace_exporter);
// Analytics consent controls the built-in exporter, not explicit OTLP collectors.
let metrics_exporter = if matches!(config.otel.metrics_exporter, Kind::Statsig)
&& !config
.analytics_enabled
.unwrap_or(default_analytics_enabled)
{
OtelExporter::None
} else {
to_otel_exporter(&config.otel.metrics_exporter)
};
let originator = originator();
let service_name = service_name_override.unwrap_or(originator.value.as_str());
let runtime_metrics = config.features.enabled(Feature::RuntimeMetrics);
OtelProvider::try_new(&OtelSettings {
http_client_factory: config.http_client_factory(),
service_name: service_name.to_string(),
service_version: service_version.to_string(),
codex_home: config.codex_home.to_path_buf(),
environment: config.otel.environment.to_string(),
exporter,
trace_exporter,
metrics_exporter,
runtime_metrics,
span_attributes: config.otel.span_attributes.clone(),
tracestate: config.otel.tracestate.clone(),
})
}
pub fn record_process_start(otel: Option<&OtelProvider>, originator: &str) {
let Some(metrics) = otel.and_then(OtelProvider::metrics) else {
return;
};
let _ = codex_otel::record_process_start_once(metrics, originator);
}
pub fn install_sqlite_telemetry(otel: Option<&OtelProvider>, originator: &str) {
let Some(metrics) = otel.and_then(OtelProvider::metrics) else {
return;
};
let telemetry = codex_rollout::sqlite_telemetry_recorder(metrics.clone(), originator);
let _ = codex_state::install_process_db_telemetry(telemetry);
}