Repository navigation
app-server: OTLP logs remain empty while traces and metrics export, then emit BatchLogProcessor.Emit.AfterShutdown on shutdown #28810
Description
What version of the Codex App are you using (From “About Codex” dialog)?
Codex Desktop internal Codex binary 0.136.0
What subscription do you have?
ChatGPT Business
What platform is your computer?
macOS arm64
What issue are you seeing?
codex app-server exports OTLP traces and OTLP metrics, but native OTLP logs remain empty even when OTLP log export is explicitly configured and the same collector is successfully receiving the other signals.
In repeated local repros, the collector output shows:
logs.json: emptytraces.json: non-emptymetrics.json: non-empty
On shutdown, codex app-server emits this warning:
BatchLogProcessor.Emit.AfterShutdown
with the message:
Logs are being emitted even after Shutdown. This indicates incorrect lifecycle management of OTelLoggerProvider in application. Logs will not be exported.
This appears to be an app-server / codex-otel lifecycle bug rather than a collector or config issue.
What steps can reproduce the bug?
- Start an OTLP collector that writes logs, traces, and metrics to local files.
- Run a long-lived
codex app-serverinstance with OTLP enabled in config:
[analytics]
enabled = true
[otel]
environment = "local-sre-agent-ws"
log_user_prompt = true
exporter = { otlp-http = { endpoint = "http://127.0.0.1:4319/v1/logs", protocol = "json" } }
trace_exporter = { otlp-http = { endpoint = "http://127.0.0.1:4319/v1/traces", protocol = "json" } }
metrics_exporter = { otlp-http = { endpoint = "http://127.0.0.1:4319/v1/metrics", protocol = "json" } }- Connect to the running app-server over WebSocket and perform a real request flow:
initializeinitializedthread/startturn/start
- Complete the turn successfully.
- Stop the server cleanly.
- Inspect collector outputs and app-server stderr.
Observed result:
- OTLP traces export successfully.
- OTLP metrics export successfully.
- OTLP logs remain empty.
- app-server prints:
BatchLogProcessor.Emit.AfterShutdownLogs are being emitted even after Shutdown. This indicates incorrect lifecycle management of OTelLoggerProvider in application. Logs will not be exported.
I also reproduced the same symptom through codex debug app-server send-message-v2, but the long-lived WebSocket repro is stronger because it removes the built-in helper client as the primary variable.
What is the expected behavior?
If [otel].exporter is configured for codex app-server, native OTLP log events should be exported successfully, just like traces and metrics.
Representative events that appear to be intended for log export include:
codex.conversation_startscodex.api_requestcodex.websocket_connectcodex.websocket_requestcodex.sse_eventcodex.user_prompt
These should reach the OTLP log exporter without the AfterShutdown failure.
Additional information
Why this looks like an upstream code bug
The open-source code appears to support log export on the app-server path:
app-serverbuilds an OTEL provider and installs both logger and tracing layers:codex-rs/app-server/src/lib.rs
- OTEL event macros emit log-only events to
codex_otel.log_only:codex-rs/otel/src/events/shared.rs
- session telemetry emits many log-capable events:
codex-rs/otel/src/events/session_telemetry.rs
However, app-server later explicitly shuts OTEL down:
codex-rs/app-server/src/lib.rs
and OtelProvider also shuts down again in Drop:
codex-rs/otel/src/provider.rs
The provider shutdown logic also force-flushes traces before shutdown, but does not explicitly force-flush logs.
Root-cause hypothesis
Most likely explanation:
app-serverstill has active drop/cleanup paths that emittracinglog events after the OTEL logger provider has already been shut down.- The global tracing subscriber remains installed, so those later log events hit
OpenTelemetryTracingBridge. - OTEL rejects them with
BatchLogProcessor.Emit.AfterShutdown, so logs are never exported.
Relevant code references
codex-rs/app-server/src/lib.rs- OTEL provider construction and layer installation
- explicit
otel.shutdown()near process shutdown
codex-rs/otel/src/provider.rsOtelProvider::shutdown()impl Drop for OtelProviderlogger_layer()build_logger()
codex-rs/core/src/otel_init.rs- analytics gating appears to affect metrics, not log export
codex-rs/otel/src/events/shared.rslog_event!target iscodex_otel.log_only
codex-rs/otel/src/events/session_telemetry.rs- events such as
codex.user_prompt,codex.api_request,codex.websocket_*,codex.sse_event
- events such as
High-level fix outline
I believe the fix is likely one or more of:
- keep the OTel provider alive until after all remaining app-server cleanup/drop paths finish emitting logs
- remove or redesign the explicit
otel.shutdown()vsDropdouble-shutdown behavior - make shutdown one-shot / idempotent
- ensure the log exporter is flushed before shutdown if the logger provider supports it
I have a local repro and can provide more exact command/output detail if useful.
Activity
Necmttn commentedon Jun 21, 2026
One downstream reason this is worth fixing: for Codex, OTLP logs can be the usage-bearing signal, not just a nice-to-have sibling of traces/metrics.
In a local receiver, the contract that made Codex telemetry useful was:
/v1/logs, because the exporter posts to the endpoint as configuredcodex.conversation_starts,codex.user_prompt, and token-bearingcodex.sse_eventconversation.idas the session keyA focused regression for this issue could assert that a long-lived
codex app-serverrun exports at least conversation start, user prompt, and one token-bearing SSE log before shutdown, then shuts down withoutBatchLogProcessor.Emit.AfterShutdown.Traces/metrics passing should not be enough for this bug. A downstream session/cost tool can otherwise read the same run as "no usage" when the real failure is "log pipeline never flushed."
Generated with ax.