mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
## What changed Add `otel.log_guardian_assessments`, disabled by default and requiring an OTLP HTTP or gRPC log exporter. Emit `codex.guardian_assessment` when a synchronous Guardian review finishes, including review identifiers, status, risk level, user authorization, and rationale. Include `allow` or `deny` outcomes only for completed model assessments, not review errors, timeouts, or cancellations. Cap exported rationales at 64 KiB on a UTF-8 character boundary and record their original byte length and truncation status. Use the log-only route to exclude assessment payloads from trace export, local state logs, and bug-report log buffers. Cached V2 approvals are not included. ## Testing Add coverage for configuration gating, enabled and disabled denial logging, terminal statuses and outcomes, UTF-8 rationale truncation, export routing, and bug-report log buffer exclusion. GitOrigin-RevId: dcbb5b429c8726a5a5c80ff82f9d625e81796ede
121 lines
4.3 KiB
Rust
121 lines
4.3 KiB
Rust
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 {
|
|
tool_result: config.tool_result,
|
|
log_user_prompt,
|
|
log_agent_responses: config.log_agent_responses.unwrap_or(false),
|
|
log_guardian_assessments: config.log_guardian_assessments.unwrap_or(false),
|
|
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);
|
|
}
|