Codex CLI can export traces, metrics, OTel log records with event names and attributes. With that telemetry, you can track API requests, tool invocations, token usage, MCP calls and run latency.
This article walks through a local OpenTelemetry stack and Codex CLI configuration needed to send telemetry to it. The setup should help you explore the traces, logs and metrics, build dashboards and answer questions such as:
- Which models were used?
- How many tool calls were made?
- Was MCP used?
- How long did the run take?
Traces
While Codex CLI can export traces, its trace spans are not fully documented, and there is no official list of span names.
In captured traces, I saw these broad span categories:
- session lifecycle related (useful for knowing when turn begins and ends)
- network and streaming related (useful to troubleshoot network vs model latency issues)
- tool use (useful to see how often and which tools are used, as well as how long it takes)
- startup related (useful to troubleshoot first turn overhead that comes from auth, environment setup or model catalog lookups)
- low-level internal spans (useful to Codex CLI developers debugging runtime behavior)
These traces help identify the largest contributors to end-to-end latency.
Logs
Codex CLI log telemetry is emitted as structured OTel log records. As with traces, the event payloads are not fully documented, and there is no stable per-event schema.
From captured logs, I’ve noted some useful log events:
codex.user_promptexposes fields such asmodelandpromptcodex.tool_resultexposes fieldstool_name,success,duration_msand otherscodex.sse_eventwith kindresponse.completedexposesinput_token_count,cached_token_count,output_token_count,reasoning_token_countandtool_token_count
There are many use cases for collecting this data:
- use
codex.user_promptto reproduce a run or check for accidental disclosure of sensitive data - use
codex.tool_resultto investigate failed or slow tool calls - use
codex.sse_eventandresponse.completedto inspect token usage - use
codex.conversation_startsandcodex.tool_decisionto audit safety and approval behavior
# Loki query examples
{service_name=~"codex_cli_rs|codex_exec"} | event_name="codex.user_prompt"
{service_name=~"codex_cli_rs|codex_exec"} | event_name="codex.tool_result"
{service_name=~"codex_cli_rs|codex_exec"} | event_name="codex.sse_event" | event_kind="response.completed"
Metrics
For metrics, Codex exposes counters and histograms for runtime activity such as API requests, streaming events, WebSocket request, tool calls, turn latency, token usage, and MCP activity.
The most useful metrics I’ve found from my captures are:
- token related metrics, which can be used to track cost
- tool related metrics, which can be used to explain agent behavior
- turn latency and MCP related metrics, which can be used to identify performance issues
# PromQL examples
sum(max_over_time(codex_turn_token_usage_sum{token_type="total"}[$__range]))
sum by (tool) (max_over_time(codex_tool_call_total[$__range]))
Configuration
To try this locally, you need an OTel stack. If you do not already have one, the following Compose file starts Grafana LGTM with Prometheus, Loki, Tempo, Pyroscope, and an OTel Collector.
name: otel
services:
lgtm:
image: docker.io/grafana/otel-lgtm:0.28.0
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000" # Grafana UI
- "127.0.0.1:3100:3100" # Loki API
- "127.0.0.1:3200:3200" # Tempo API
- "127.0.0.1:4040:4040" # Pyroscope API
- "127.0.0.1:4317:4317" # OTLP gRPC ingest for Codex CLI
- "127.0.0.1:4318:4318" # OTLP HTTP ingest for Copilot CLI
- "127.0.0.1:9090:9090" # Prometheus UI and API
volumes:
- ./otel/collector-config.yaml:/otel-lgtm/otelcol-config.yaml:ro
- ./prometheus/prometheus.yml:/otel-lgtm/prometheus.yaml:ro
- ./grafana/provisioning/dashboards/dashboards.yaml:/otel-lgtm/grafana/conf/provisioning/dashboards/agents.yaml:ro
- ./grafana/provisioning/dashboards-json:/otel-lgtm/grafana/conf/provisioning/dashboards-json:ro
- lgtm-data:/data
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
GF_USERS_ALLOW_SIGN_UP: "false"
GF_PATHS_DATA: /data/grafana
healthcheck:
test:
- CMD-SHELL
- test -f /tmp/ready && /otel-lgtm/docker/healthcheck.sh
interval: 5s
timeout: 5s
retries: 24
start_period: 10s
volumes:
lgtm-data: {}
The stack uses OTel collector which receives OTLP traffic, Prometheus for metrics, Loki for logs, Tempo for traces and Grafana for exploring and dashboards.
OTel collector configuration:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- http://*
prometheus/collector:
config:
scrape_configs:
- job_name: opentelemetry-collector
scrape_interval: 1s
static_configs:
- targets:
- 127.0.0.1:8888
extensions:
health_check:
endpoint: 0.0.0.0:13133
path: /ready
processors:
batch:
connectors:
spanmetrics:
exporters:
prometheus:
endpoint: 0.0.0.0:9464
resource_to_telemetry_conversion:
enabled: true
otlphttp/tempo:
endpoint: http://127.0.0.1:4418
otlphttp/loki:
endpoint: http://127.0.0.1:3100/otlp
otlp/profiles:
endpoint: 127.0.0.1:4040
tls:
insecure: true
service:
extensions:
- health_check
pipelines:
traces:
receivers:
- otlp
processors:
- batch
exporters:
- otlphttp/tempo
- spanmetrics
metrics:
receivers:
- otlp
- prometheus/collector
- spanmetrics
processors:
- batch
exporters:
- prometheus
logs:
receivers:
- otlp
processors:
- batch
exporters:
- otlphttp/loki
profiles:
receivers:
- otlp
exporters:
- otlp/profiles
Prometheus configuration:
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_native_histograms: true
otlp:
keep_identifying_resource_attributes: true
promote_resource_attributes:
- service.instance.id
- service.name
- service.namespace
- service.version
- deployment.environment
- deployment.environment.name
- host.name
storage:
tsdb:
out_of_order_time_window: 10m
scrape_configs:
- job_name: otel-collector
static_configs:
- targets:
- 127.0.0.1:9464
Now configure Codex CLI by editing configuration file ~/.codex/config.toml:
[otel]
environment = "local-podman"
log_user_prompt = true
exporter = { otlp-grpc = { endpoint = "http://127.0.0.1:4317" } }
metrics_exporter = { otlp-grpc = { endpoint = "http://127.0.0.1:4317" } }
trace_exporter = { otlp-grpc = { endpoint = "http://127.0.0.1:4317" } }
Alternatively, you can use CLI flags to pass OTel configuration:
#!/usr/bin/env bash
set -euo pipefail
exec codex \
--config 'otel.environment="local-podman"' \
--config 'otel.log_user_prompt=true' \
--config 'otel.exporter={ "otlp-grpc" = { endpoint = "http://127.0.0.1:4317" } }' \
--config 'otel.metrics_exporter={ "otlp-grpc" = { endpoint = "http://127.0.0.1:4317" } }' \
--config 'otel.trace_exporter={ "otlp-grpc" = { endpoint = "http://127.0.0.1:4317" } }' \
"$@"
Conclusion
Codex CLI’s OTel support is already useful for local debugging. Expect the details to evolve, especially while OTel semantic conventions for generative AI continue to mature. For production use, remember that prompts may contain sensitive information, so keep log_user_prompt = false unless you explicitly need raw prompt text.