mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## Why Windows sandbox diagnostics are currently hard to recover from `/feedback` even though they are often the most useful artifact when debugging sandbox behavior. Now that sandbox logging uses daily rolling files, feedback can safely include the current day's sandbox log without uploading the old ever-growing legacy `sandbox.log`. ## What changed - Add a `codex-windows-sandbox` helper that resolves the current daily sandbox log from `codex_home`. - When feedback is submitted with logs enabled on Windows, app-server attaches today's sandbox log if it exists. - Upload the attachment under the stable filename `windows-sandbox.log`, independent of the dated on-disk filename. - Keep existing raw `extra_log_files` behavior unchanged for rollout and desktop log attachments. ## Verification - `cargo fmt -p codex-app-server -p codex-windows-sandbox` - `cargo test -p codex-windows-sandbox current_log_file_path_for_codex_home_uses_sandbox_dir` - `cargo test -p codex-app-server windows_sandbox_log_attachment_uses_current_log` - Manual CLI/TUI `/feedback` test confirmed Sentry received `windows-sandbox.log`.
160 lines
4.8 KiB
Rust
160 lines
4.8 KiB
Rust
use std::io::Write;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::sync::OnceLock;
|
|
|
|
use codex_utils_string::take_bytes_at_char_boundary;
|
|
use tracing_appender::rolling::RollingFileAppender;
|
|
use tracing_appender::rolling::Rotation;
|
|
|
|
const LOG_COMMAND_PREVIEW_LIMIT: usize = 200;
|
|
pub const LOG_FILE_PREFIX: &str = "sandbox";
|
|
pub const LOG_FILE_SUFFIX: &str = "log";
|
|
pub const MAX_LOG_FILES: usize = 90;
|
|
|
|
fn exe_label() -> &'static str {
|
|
static LABEL: OnceLock<String> = OnceLock::new();
|
|
LABEL.get_or_init(|| {
|
|
std::env::current_exe()
|
|
.ok()
|
|
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
|
|
.unwrap_or_else(|| "proc".to_string())
|
|
})
|
|
}
|
|
|
|
fn preview(command: &[String]) -> String {
|
|
let joined = command.join(" ");
|
|
if joined.len() <= LOG_COMMAND_PREVIEW_LIMIT {
|
|
joined
|
|
} else {
|
|
take_bytes_at_char_boundary(&joined, LOG_COMMAND_PREVIEW_LIMIT).to_string()
|
|
}
|
|
}
|
|
|
|
pub fn log_file_path_for_utc_date(base_dir: &Path, date: chrono::NaiveDate) -> PathBuf {
|
|
base_dir.join(format!(
|
|
"{LOG_FILE_PREFIX}.{}.{}",
|
|
date.format("%Y-%m-%d"),
|
|
LOG_FILE_SUFFIX
|
|
))
|
|
}
|
|
|
|
pub fn current_log_file_path(base_dir: &Path) -> PathBuf {
|
|
log_file_path_for_utc_date(base_dir, chrono::Utc::now().date_naive())
|
|
}
|
|
|
|
pub fn current_log_file_path_for_codex_home(codex_home: &Path) -> PathBuf {
|
|
current_log_file_path(&crate::sandbox_dir(codex_home))
|
|
}
|
|
|
|
pub fn log_writer(base_dir: &Path) -> Option<RollingFileAppender> {
|
|
if !base_dir.is_dir() {
|
|
return None;
|
|
}
|
|
|
|
RollingFileAppender::builder()
|
|
.rotation(Rotation::DAILY)
|
|
.filename_prefix(LOG_FILE_PREFIX)
|
|
.filename_suffix(LOG_FILE_SUFFIX)
|
|
.max_log_files(MAX_LOG_FILES)
|
|
.build(base_dir)
|
|
.ok()
|
|
}
|
|
|
|
fn append_line(line: &str, base_dir: Option<&Path>) {
|
|
if let Some(dir) = base_dir
|
|
&& let Some(mut f) = log_writer(dir)
|
|
{
|
|
let _ = writeln!(f, "{line}");
|
|
}
|
|
}
|
|
|
|
pub fn log_start(command: &[String], base_dir: Option<&Path>) {
|
|
let p = preview(command);
|
|
log_note(&format!("START: {p}"), base_dir);
|
|
}
|
|
|
|
pub fn log_success(command: &[String], base_dir: Option<&Path>) {
|
|
let p = preview(command);
|
|
log_note(&format!("SUCCESS: {p}"), base_dir);
|
|
}
|
|
|
|
pub fn log_failure(command: &[String], detail: &str, base_dir: Option<&Path>) {
|
|
let p = preview(command);
|
|
log_note(&format!("FAILURE: {p} ({detail})"), base_dir);
|
|
}
|
|
|
|
// Debug logging helper. Emits only when SBX_DEBUG=1 to avoid noisy logs.
|
|
pub fn debug_log(msg: &str, base_dir: Option<&Path>) {
|
|
if std::env::var("SBX_DEBUG").ok().as_deref() == Some("1") {
|
|
append_line(&format!("DEBUG: {msg}"), base_dir);
|
|
eprintln!("{msg}");
|
|
}
|
|
}
|
|
|
|
// Unconditional note logging to the daily sandbox log.
|
|
pub fn log_note(msg: &str, base_dir: Option<&Path>) {
|
|
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
|
append_line(&format!("[{ts} {}] {}", exe_label(), msg), base_dir);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn preview_does_not_panic_on_utf8_boundary() {
|
|
// Place a 4-byte emoji such that naive (byte-based) truncation would split it.
|
|
let prefix = "x".repeat(LOG_COMMAND_PREVIEW_LIMIT - 1);
|
|
let command = vec![format!("{prefix}😀")];
|
|
let result = std::panic::catch_unwind(|| preview(&command));
|
|
assert!(result.is_ok());
|
|
let previewed = result.unwrap();
|
|
assert!(previewed.len() <= LOG_COMMAND_PREVIEW_LIMIT);
|
|
}
|
|
|
|
#[test]
|
|
fn log_note_writes_to_daily_rolling_log() {
|
|
let tempdir = tempfile::tempdir().expect("tempdir");
|
|
|
|
log_note("hello daily log", Some(tempdir.path()));
|
|
|
|
let entries = std::fs::read_dir(tempdir.path())
|
|
.expect("read log dir")
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.expect("read entries");
|
|
assert_eq!(entries.len(), 1);
|
|
|
|
let log_path = entries[0].path();
|
|
let filename = log_path
|
|
.file_name()
|
|
.and_then(|name| name.to_str())
|
|
.expect("utf-8 filename");
|
|
assert!(filename.starts_with("sandbox."));
|
|
assert!(filename.ends_with(".log"));
|
|
|
|
let log = std::fs::read_to_string(log_path).expect("read log");
|
|
assert!(log.contains("hello daily log"));
|
|
}
|
|
|
|
#[test]
|
|
fn log_file_path_for_utc_date_matches_rolling_appender_name() {
|
|
let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 21).expect("valid date");
|
|
|
|
assert_eq!(
|
|
log_file_path_for_utc_date(Path::new("logs"), date),
|
|
PathBuf::from("logs").join("sandbox.2026-05-21.log")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn current_log_file_path_for_codex_home_uses_sandbox_dir() {
|
|
let codex_home = Path::new("codex-home");
|
|
|
|
assert_eq!(
|
|
current_log_file_path_for_codex_home(codex_home),
|
|
current_log_file_path(&codex_home.join(".sandbox"))
|
|
);
|
|
}
|
|
}
|