Files
codex/codex-rs/feedback/src/daemon_logs.rs
T
Eric Traut 08b07eef12 Preserve daemon diagnostics and include updater logs in reports (#49843)
## Why

Detached launches truncate stderr logs, losing details from previous failures. Updater handoffs can also leave existing writers with stale offsets after truncation.

## What changed

- Record daemon lifecycle and update stages with timings, outcomes, and typed OS errors, omitting error messages and context.
- Preserve the last 256 KiB of nonempty stderr logs before truncation and use append handles to prevent gaps during handoff. Preservation failures do not block launch.
- Attach current and previous updater log tails when log inclusion is enabled for a report, with a 512 KiB total limit and legacy filename fallback. Skip missing, empty, and nonregular files.

## Testing

Add regression coverage for diagnostic error redaction, bounded log preservation, append behavior during handoff, attachment selection, and updater logs in captured report uploads.

GitOrigin-RevId: 918317022ab9dcfd1b13d676872706c77e9ffbb2
2026-10-01 03:24:18 +00:00

65 lines
2.5 KiB
Rust

//! Collects bounded current and previous updater log tails after feedback log consent.
//! Read failures omit individual files; only fixed regular-file names are considered.
use std::fs;
use std::io;
use std::io::Read;
use std::io::Seek;
use std::path::Path;
use crate::FeedbackAttachment;
const MAX_LOG_BYTES: u64 = 256 * 1024;
const LOG_NAMES: [&str; 2] = ["daemon-updater.stderr.log", "app-server-updater.stderr.log"];
/// Collect at most two 256 KiB updater tails (512 KiB total) from the app-server host.
/// Call only after feedback consent includes logs, on a blocking worker.
pub fn daemon_log_attachments(codex_home: &Path) -> Vec<FeedbackAttachment> {
let directory = codex_home.join("app-server-daemon");
let mut attachments = Vec::new();
for suffix in ["", ".previous"] {
for name in LOG_NAMES {
let filename = format!("{name}{suffix}");
let path = directory.join(&filename);
let result = (|| -> io::Result<Vec<u8>> {
// Reject symlinks and nonregular files before opening (e.g. FIFOs).
if !fs::symlink_metadata(&path)?.is_file() {
return Ok(Vec::new());
}
let mut file = fs::File::open(&path)?;
let metadata = file.metadata()?;
if !metadata.is_file() {
return Ok(Vec::new());
}
file.seek(io::SeekFrom::Start(
metadata.len().saturating_sub(MAX_LOG_BYTES),
))?;
let mut bytes = Vec::new();
file.take(MAX_LOG_BYTES).read_to_end(&mut bytes)?;
Ok(bytes)
})();
match result {
Ok(buffer) if !buffer.is_empty() => {
attachments.push(FeedbackAttachment {
filename,
content_type: Some("text/plain".to_string()),
buffer,
});
// Legacy filenames are a fallback, not additional attachments.
break;
}
Ok(_) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => {
tracing::warn!(filename, error_kind = ?error.kind(), "failed to read daemon feedback log; skipping attachment")
}
}
}
}
attachments
}
#[cfg(test)]
#[path = "daemon_logs_tests.rs"]
mod tests;