mirror of
https://github.com/openai/codex.git
synced 2026-05-17 17:53:06 +00:00
Add Codex config for static trace span attributes and structured W3C tracestate field upserts. The config flows through OtelSettings so callers can attach trace metadata without touching every span call site. Apply span attributes with an SDK span processor so every exported trace span carries the configured metadata. Model tracestate as nested member fields so configured keys can be upserted while unrelated propagated state in the same member is preserved. Validate configured tracestate before installing provider-global state, including header-unsafe values the SDK does not reject by itself. This keeps Codex from propagating malformed trace context from config. Update the config schema, public docs, and OTLP loopback coverage for config parsing, span export, propagation, and invalid-header rejection.
164 lines
5.0 KiB
Markdown
164 lines
5.0 KiB
Markdown
# codex-otel
|
|
|
|
`codex-otel` is the OpenTelemetry integration crate for Codex. It provides:
|
|
|
|
- Provider wiring for log/trace/metric exporters (`codex_otel::OtelProvider`
|
|
and `codex_otel::provider`).
|
|
- Session-scoped business event emission via `codex_otel::SessionTelemetry`.
|
|
- Low-level metrics APIs via `codex_otel::metrics`.
|
|
- Trace-context helpers via `codex_otel::trace_context` and crate-root re-exports.
|
|
|
|
## Tracing and logs
|
|
|
|
Create an OTEL provider from `OtelSettings`. The provider also configures
|
|
metrics (when enabled), then attach its layers to your `tracing_subscriber`
|
|
registry:
|
|
|
|
```rust
|
|
use codex_otel::config::OtelExporter;
|
|
use codex_otel::config::OtelHttpProtocol;
|
|
use codex_otel::config::OtelSettings;
|
|
use codex_otel::OtelProvider;
|
|
use tracing_subscriber::prelude::*;
|
|
|
|
let settings = OtelSettings {
|
|
environment: "dev".to_string(),
|
|
service_name: "codex-cli".to_string(),
|
|
service_version: env!("CARGO_PKG_VERSION").to_string(),
|
|
codex_home: std::path::PathBuf::from("/tmp"),
|
|
exporter: OtelExporter::OtlpHttp {
|
|
endpoint: "https://otlp.example.com".to_string(),
|
|
headers: std::collections::HashMap::new(),
|
|
protocol: OtelHttpProtocol::Binary,
|
|
tls: None,
|
|
},
|
|
trace_exporter: OtelExporter::OtlpHttp {
|
|
endpoint: "https://otlp.example.com".to_string(),
|
|
headers: std::collections::HashMap::new(),
|
|
protocol: OtelHttpProtocol::Binary,
|
|
tls: None,
|
|
},
|
|
metrics_exporter: OtelExporter::None,
|
|
span_attributes: std::collections::BTreeMap::new(),
|
|
tracestate: std::collections::BTreeMap::new(),
|
|
};
|
|
|
|
if let Some(provider) = OtelProvider::from(&settings)? {
|
|
let registry = tracing_subscriber::registry()
|
|
.with(provider.logger_layer())
|
|
.with(provider.tracing_layer());
|
|
registry.init();
|
|
}
|
|
```
|
|
|
|
Configured span attributes and W3C tracestate member fields are applied to
|
|
exported trace spans and propagated trace context:
|
|
|
|
```toml
|
|
[otel.span_attributes]
|
|
"example.trace_attr" = "enabled"
|
|
|
|
[otel.tracestate.example]
|
|
alpha = "one"
|
|
beta = "two"
|
|
```
|
|
|
|
Configured tracestate members and encoded values must be valid W3C tracestate.
|
|
Each nested table is encoded as semicolon-separated `key:value` fields inside
|
|
that member. If propagated trace context already has the named member, Codex
|
|
upserts configured fields and preserves other fields in that member. This
|
|
config shape does not support setting opaque tracestate member values. Invalid
|
|
trace metadata entries are ignored during config load and reported as startup
|
|
warnings.
|
|
|
|
## SessionTelemetry (events)
|
|
|
|
`SessionTelemetry` adds consistent metadata to tracing events and helps record
|
|
Codex-specific session events. Rich session/business events should go through
|
|
`SessionTelemetry`; subsystem-owned audit events can stay with the owning subsystem.
|
|
|
|
```rust
|
|
use codex_otel::SessionTelemetry;
|
|
|
|
let manager = SessionTelemetry::new(
|
|
conversation_id,
|
|
model,
|
|
slug,
|
|
account_id,
|
|
account_email,
|
|
auth_mode,
|
|
originator,
|
|
log_user_prompts,
|
|
terminal_type,
|
|
session_source,
|
|
);
|
|
|
|
manager.user_prompt(&prompt_items);
|
|
```
|
|
|
|
## Metrics (OTLP or in-memory)
|
|
|
|
Modes:
|
|
|
|
- OTLP: exports metrics via the OpenTelemetry OTLP exporter (HTTP or gRPC).
|
|
- In-memory: records via `opentelemetry_sdk::metrics::InMemoryMetricExporter` for tests/assertions; call `shutdown()` to flush.
|
|
|
|
`codex-otel` also provides `OtelExporter::Statsig`, a shorthand for exporting OTLP/HTTP JSON metrics
|
|
to Statsig using Codex-internal defaults.
|
|
|
|
Statsig ingestion (OTLP/HTTP JSON) example:
|
|
|
|
```rust
|
|
use codex_otel::config::{OtelExporter, OtelHttpProtocol};
|
|
|
|
let metrics = MetricsClient::new(MetricsConfig::otlp(
|
|
"dev",
|
|
"codex-cli",
|
|
env!("CARGO_PKG_VERSION"),
|
|
OtelExporter::OtlpHttp {
|
|
endpoint: "https://api.statsig.com/otlp".to_string(),
|
|
headers: std::collections::HashMap::from([(
|
|
"statsig-api-key".to_string(),
|
|
std::env::var("STATSIG_SERVER_SDK_SECRET")?,
|
|
)]),
|
|
protocol: OtelHttpProtocol::Json,
|
|
tls: None,
|
|
},
|
|
))?;
|
|
|
|
metrics.counter("codex.session_started", 1, &[("source", "tui")])?;
|
|
metrics.histogram("codex.request_latency", 83, &[("route", "chat")])?;
|
|
```
|
|
|
|
In-memory (tests):
|
|
|
|
```rust
|
|
let exporter = InMemoryMetricExporter::default();
|
|
let metrics = MetricsClient::new(MetricsConfig::in_memory(
|
|
"test",
|
|
"codex-cli",
|
|
env!("CARGO_PKG_VERSION"),
|
|
exporter.clone(),
|
|
))?;
|
|
metrics.counter("codex.turns", 1, &[("model", "gpt-5.1")])?;
|
|
metrics.shutdown()?; // flushes in-memory exporter
|
|
```
|
|
|
|
## Trace context
|
|
|
|
Trace propagation helpers remain separate from the session event emitter:
|
|
|
|
```rust
|
|
use codex_otel::current_span_w3c_trace_context;
|
|
use codex_otel::set_parent_from_w3c_trace_context;
|
|
```
|
|
|
|
## Shutdown
|
|
|
|
- `OtelProvider::shutdown()` stops the OTEL exporter.
|
|
- `SessionTelemetry::shutdown_metrics()` flushes and shuts down the metrics provider.
|
|
|
|
Both are optional because drop performs best-effort shutdown, but calling them
|
|
explicitly gives deterministic flushing (or a shutdown error if flushing does
|
|
not complete in time).
|