Codex Handbook
core/src/config/otel.rs 117 lines
use std::collections::BTreeMap;use std::fmt::Display;use codex_config::types::DEFAULT_OTEL_ENVIRONMENT;use codex_config::types::OtelConfig;use codex_config::types::OtelConfigToml;use codex_config::types::OtelExporterKind;pub(crate) fn resolve_config(    config: OtelConfigToml,    startup_warnings: &mut Vec<String>,) -> OtelConfig {    let log_user_prompt = config.log_user_prompt.unwrap_or(false);    let environment = config        .environment        .unwrap_or_else(|| DEFAULT_OTEL_ENVIRONMENT.to_string());    let exporter = config.exporter.unwrap_or(OtelExporterKind::None);    // OTLP HTTP endpoints are signal-specific in our config, so enabling log    // export must not implicitly send spans to a /v1/logs endpoint.    let trace_exporter = config.trace_exporter.unwrap_or(OtelExporterKind::None);    let metrics_exporter = config.metrics_exporter.unwrap_or(OtelExporterKind::Statsig);    // Provider initialization installs process-global OTEL state. Sanitize    // user-editable trace metadata here so malformed config is reported as a    // startup warning instead of making startup fail.    let span_attributes = resolve_span_attributes(config.span_attributes, startup_warnings);    let tracestate = resolve_tracestate(config.tracestate, startup_warnings);    OtelConfig {        log_user_prompt,        environment,        exporter,        trace_exporter,        metrics_exporter,        span_attributes,        tracestate,    }}fn resolve_span_attributes(    span_attributes: Option<BTreeMap<String, String>>,    startup_warnings: &mut Vec<String>,) -> BTreeMap<String, String> {    let Some(span_attributes) = span_attributes else {        return BTreeMap::new();    };    let mut valid_attributes = BTreeMap::new();    for (key, value) in span_attributes {        let attribute = BTreeMap::from([(key.clone(), value.clone())]);        if let Err(err) = codex_otel::validate_span_attributes(&attribute) {            push_invalid_config_warning("otel.span_attributes", err, startup_warnings);            continue;        }        valid_attributes.insert(key, value);    }    valid_attributes}fn resolve_tracestate(    tracestate: Option<BTreeMap<String, BTreeMap<String, String>>>,    startup_warnings: &mut Vec<String>,) -> BTreeMap<String, BTreeMap<String, String>> {    let Some(tracestate) = tracestate else {        return BTreeMap::new();    };    let mut valid_entries = BTreeMap::new();    for (member_key, fields) in tracestate {        let fields = resolve_tracestate_member_fields(&member_key, fields, startup_warnings);        if fields.is_empty() {            continue;        }        if let Err(err) = codex_otel::validate_tracestate_member(&member_key, &fields) {            push_invalid_config_warning("otel.tracestate", err, startup_warnings);            continue;        }        valid_entries.insert(member_key, fields);    }    // Tracestate members can be valid individually while the combined W3C    // tracestate header is not, so validate the filtered set before handing it    // to provider initialization.    if let Err(err) = codex_otel::validate_tracestate_entries(&valid_entries) {        push_invalid_config_warning("otel.tracestate", err, startup_warnings);        return BTreeMap::new();    }    valid_entries}fn resolve_tracestate_member_fields(    member_key: &str,    fields: BTreeMap<String, String>,    startup_warnings: &mut Vec<String>,) -> BTreeMap<String, String> {    let mut valid_fields = BTreeMap::new();    for (field_key, value) in fields {        let field = BTreeMap::from([(field_key.clone(), value.clone())]);        if let Err(err) = codex_otel::validate_tracestate_member(member_key, &field) {            push_invalid_config_warning("otel.tracestate", err, startup_warnings);            continue;        }        valid_fields.insert(field_key, value);    }    valid_fields}fn push_invalid_config_warning(    config_key: &str,    err: impl Display,    startup_warnings: &mut Vec<String>,) {    let message = format!("Ignoring invalid `{config_key}` config: {err}");    tracing::warn!("{message}");    startup_warnings.push(message);}