mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## Why Managed stderr logs previously retained a tail only before a new launch, allowing logs to grow during a long-running process. Direct stderr writes could also block lifecycle operations on disk I/O. ## What changed - Route managed app-server and updater logs through a shared nonblocking worker with a bounded queue. Drop records when the queue is full or a record exceeds 16 KiB, and avoid waiting for the worker at shutdown. - Prune live logs when they exceed 1 MiB, preserving the latest 256 KiB in `.log.previous` before truncating the current file. Keep inherited stderr handles usable after truncation. - Pause predecessor pruning during Windows updater handoff and resume it after a failed handoff only when ownership has been restored. - Keep ordinary stderr output when managed logging has not been initialized. ## Testing Add regression tests for stalled workers and queue overflow, tail retention, inherited append and nonappend handles, and pruning exclusion during updater handoff. GitOrigin-RevId: b6301962b1180fa6cdb231a56edce2429dc20b54
125 lines
3.2 KiB
Rust
125 lines
3.2 KiB
Rust
//! Default-on local lifecycle records contain only fixed events and typed OS errors.
|
|
//! Error messages and chains can contain raw child stderr or protocol data; never print them.
|
|
|
|
use std::io::IsTerminal;
|
|
use std::io::Write;
|
|
use std::time::Instant;
|
|
use std::time::SystemTime;
|
|
use std::time::UNIX_EPOCH;
|
|
|
|
use anyhow::Result;
|
|
use serde::Serialize;
|
|
|
|
#[derive(Debug, Serialize, PartialEq, Eq)]
|
|
struct Failure {
|
|
kind: String,
|
|
os_code: Option<i32>,
|
|
}
|
|
|
|
impl From<&anyhow::Error> for Failure {
|
|
fn from(error: &anyhow::Error) -> Self {
|
|
match error.downcast_ref::<std::io::Error>() {
|
|
Some(error) => Self {
|
|
kind: format!("{:?}", error.kind()),
|
|
os_code: error.raw_os_error(),
|
|
},
|
|
None => Self {
|
|
kind: "other".to_string(),
|
|
os_code: None,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct Record<D> {
|
|
timestamp_ms: u128,
|
|
process_pid: u32,
|
|
#[serde(flatten)]
|
|
payload: Payload<D>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(untagged)]
|
|
enum Payload<D> {
|
|
Event {
|
|
event: &'static str,
|
|
details: D,
|
|
},
|
|
Result {
|
|
stage: &'static str,
|
|
outcome: Outcome,
|
|
#[serde(rename = "elapsedMs")]
|
|
elapsed_ms: u128,
|
|
failure: Option<Failure>,
|
|
},
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
enum Outcome {
|
|
Succeeded,
|
|
Failed,
|
|
}
|
|
|
|
pub(crate) fn event(name: &'static str, details: impl Serialize) {
|
|
emit(Payload::Event {
|
|
event: name,
|
|
details,
|
|
});
|
|
}
|
|
|
|
pub(crate) fn result<T>(stage: &'static str, started: Instant, result: Result<T>) -> Result<T> {
|
|
emit(Payload::<()>::Result {
|
|
stage,
|
|
outcome: if result.is_ok() {
|
|
Outcome::Succeeded
|
|
} else {
|
|
Outcome::Failed
|
|
},
|
|
elapsed_ms: started.elapsed().as_millis(),
|
|
failure: result.as_ref().err().map(Failure::from),
|
|
});
|
|
result
|
|
}
|
|
|
|
fn emit(payload: Payload<impl Serialize>) {
|
|
let failed = matches!(
|
|
&payload,
|
|
Payload::Result {
|
|
outcome: Outcome::Failed,
|
|
..
|
|
}
|
|
);
|
|
let record = Record {
|
|
timestamp_ms: SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_millis(),
|
|
process_pid: std::process::id(),
|
|
payload,
|
|
};
|
|
let Ok(mut record) = serde_json::to_string(&record) else {
|
|
return;
|
|
};
|
|
if tracing::dispatcher::has_been_set() {
|
|
// Use the configured logger whenever one is available.
|
|
if failed {
|
|
tracing::warn!(target: "codex_app_server_daemon", "{record}");
|
|
} else {
|
|
tracing::info!(target: "codex_app_server_daemon", "{record}");
|
|
}
|
|
} else if !std::io::stderr().is_terminal() {
|
|
// The detached updater has redirected stderr and no tracing subscriber.
|
|
// A TUI can own a live terminal before installing its subscriber.
|
|
// Never let a log write failure interrupt the lifecycle operation.
|
|
record.push('\n');
|
|
let _ = crate::stderr_writer().write_all(record.as_bytes());
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "diagnostics_tests.rs"]
|
|
mod tests;
|