mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
tui: make codex-tui.log opt-in (#24081)
## Why The TUI currently creates a shared plaintext `codex-tui.log` under the default log directory. That append-only file can keep growing across runs even though the TUI already records diagnostics in bounded local stores. Make the plaintext file log an explicit troubleshooting choice instead of a default side effect. This is possible because logs are also stored in the DB with proper rotation ## What changed - Only install the TUI file logging layer when `log_dir` is explicitly set. - Remove the prior `codex-tui.log` at startup before an opt-in file layer is created. - Clarify the `log_dir` config/schema text and `docs/install.md` example so users opt in with `codex -c log_dir=...` when they need a plaintext log.
This commit is contained in:
committed by
GitHub
Unverified
parent
dac98cb635
commit
f55f864b9f
@@ -319,7 +319,8 @@ pub struct ConfigToml {
|
||||
/// Defaults to `$CODEX_SQLITE_HOME` when set. Otherwise uses `$CODEX_HOME`.
|
||||
pub sqlite_home: Option<AbsolutePathBuf>,
|
||||
|
||||
/// Directory where Codex writes log files, for example `codex-tui.log`.
|
||||
/// Directory where Codex writes log files. Setting this value explicitly
|
||||
/// also enables the TUI text log in this directory.
|
||||
/// Defaults to `$CODEX_HOME/log`.
|
||||
pub log_dir: Option<AbsolutePathBuf>,
|
||||
|
||||
|
||||
@@ -4654,7 +4654,7 @@
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Directory where Codex writes log files, for example `codex-tui.log`. Defaults to `$CODEX_HOME/log`."
|
||||
"description": "Directory where Codex writes log files. Setting this value explicitly also enables the TUI text log in this directory. Defaults to `$CODEX_HOME/log`."
|
||||
},
|
||||
"marketplaces": {
|
||||
"additionalProperties": {
|
||||
|
||||
+56
-38
@@ -278,6 +278,8 @@ pub use public_widgets::composer_input::ComposerAction;
|
||||
pub use public_widgets::composer_input::ComposerInput;
|
||||
// (tests access modules directly within the crate)
|
||||
|
||||
const TUI_LOG_FILE_NAME: &str = "codex-tui.log";
|
||||
|
||||
#[cfg(unix)]
|
||||
const AUTO_CONNECT_DAEMON_CONNECT_TIMEOUT: std::time::Duration =
|
||||
std::time::Duration::from_millis(50);
|
||||
@@ -349,6 +351,13 @@ async fn init_state_db_for_app_server_target(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jif) delete after 22/11/2026.
|
||||
fn remove_legacy_tui_log_file(codex_home: &Path) {
|
||||
// Shared append-only TUI logs could grow without bound. Existing processes
|
||||
// may still hold the file open, so startup cleanup is best effort.
|
||||
let _ = std::fs::remove_file(codex_home.join("log").join(TUI_LOG_FILE_NAME));
|
||||
}
|
||||
|
||||
fn remote_addr_has_explicit_port(addr: &str, parsed: &Url) -> bool {
|
||||
let Some(host) = parsed.host_str() else {
|
||||
return false;
|
||||
@@ -1055,6 +1064,8 @@ pub async fn run_main(
|
||||
)
|
||||
.await;
|
||||
|
||||
remove_legacy_tui_log_file(config.codex_home.as_path());
|
||||
|
||||
let otel_originator = originator().value;
|
||||
let otel = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
crate::legacy_core::otel_init::build_provider(
|
||||
@@ -1166,47 +1177,40 @@ pub async fn run_main(
|
||||
}
|
||||
}
|
||||
|
||||
let log_dir = config.log_dir.clone();
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
// Open (or create) your log file, appending to it.
|
||||
let mut log_file_opts = OpenOptions::new();
|
||||
log_file_opts.create(true).append(true);
|
||||
let (tui_file_layer, _tui_file_log_guard) = if config_toml.log_dir.is_some() {
|
||||
let log_dir = config.log_dir.clone();
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
let mut log_file_opts = OpenOptions::new();
|
||||
log_file_opts.create(true).append(true);
|
||||
|
||||
// Ensure the file is only readable and writable by the current user.
|
||||
// Doing the equivalent to `chmod 600` on Windows is quite a bit more code
|
||||
// and requires the Windows API crates, so we can reconsider that when
|
||||
// Codex CLI is officially supported on Windows.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
log_file_opts.mode(0o600);
|
||||
}
|
||||
// Ensure the file is only readable and writable by the current user.
|
||||
// Doing the equivalent to `chmod 600` on Windows is quite a bit more
|
||||
// code and requires the Windows API crates.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
log_file_opts.mode(0o600);
|
||||
}
|
||||
|
||||
let log_file = log_file_opts.open(log_dir.join("codex-tui.log"))?;
|
||||
|
||||
// Wrap file in non‑blocking writer.
|
||||
let (non_blocking, _guard) = non_blocking(log_file);
|
||||
|
||||
// use RUST_LOG env var, default to info for codex crates.
|
||||
let env_filter = || {
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
let log_file = log_file_opts.open(log_dir.join(TUI_LOG_FILE_NAME))?;
|
||||
let (non_blocking, guard) = non_blocking(log_file);
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
EnvFilter::new("codex_core=info,codex_tui=info,codex_rmcp_client=info")
|
||||
})
|
||||
});
|
||||
let file_layer = tracing_subscriber::fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
.with_target(true)
|
||||
.with_ansi(false)
|
||||
.with_span_events(
|
||||
tracing_subscriber::fmt::format::FmtSpan::NEW
|
||||
| tracing_subscriber::fmt::format::FmtSpan::CLOSE,
|
||||
)
|
||||
.with_filter(env_filter);
|
||||
(Some(file_layer), Some(guard))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let file_layer = tracing_subscriber::fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
// `with_target(true)` is the default, but we previously disabled it for file output.
|
||||
// Keep it enabled so we can selectively enable targets via `RUST_LOG=...` and then
|
||||
// grep for a specific module/target while troubleshooting.
|
||||
.with_target(true)
|
||||
.with_ansi(false)
|
||||
.with_span_events(
|
||||
tracing_subscriber::fmt::format::FmtSpan::NEW
|
||||
| tracing_subscriber::fmt::format::FmtSpan::CLOSE,
|
||||
)
|
||||
.with_filter(env_filter());
|
||||
|
||||
let feedback = codex_feedback::CodexFeedback::new();
|
||||
let feedback_layer = feedback.logger_layer();
|
||||
let feedback_metadata_layer = feedback.metadata_layer();
|
||||
@@ -1236,7 +1240,7 @@ pub async fn run_main(
|
||||
.map(|layer| layer.with_filter(Targets::new().with_default(Level::TRACE)));
|
||||
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(file_layer)
|
||||
.with(tui_file_layer)
|
||||
.with(feedback_layer)
|
||||
.with(feedback_metadata_layer)
|
||||
.with(log_db_layer)
|
||||
@@ -1915,6 +1919,20 @@ mod tests {
|
||||
.await
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_removes_legacy_tui_log_file() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let legacy_log_dir = temp_dir.path().join("log");
|
||||
std::fs::create_dir_all(&legacy_log_dir)?;
|
||||
let legacy_log = legacy_log_dir.join(TUI_LOG_FILE_NAME);
|
||||
std::fs::write(&legacy_log, "legacy log")?;
|
||||
|
||||
remove_legacy_tui_log_file(temp_dir.path());
|
||||
|
||||
assert!(!legacy_log.exists());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_test_embedded_app_server(
|
||||
config: Config,
|
||||
) -> color_eyre::Result<InProcessAppServerClient> {
|
||||
@@ -2364,7 +2382,7 @@ mod tests {
|
||||
let updated_at =
|
||||
chrono::DateTime::parse_from_rfc3339(meta_rfc3339)?.with_timezone(&chrono::Utc);
|
||||
let times = std::fs::FileTimes::new().set_modified(updated_at.into());
|
||||
OpenOptions::new()
|
||||
std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(rollout_path)?
|
||||
.set_times(times)?;
|
||||
|
||||
@@ -30,6 +30,10 @@ impl SessionLogger {
|
||||
let mut opts = OpenOptions::new();
|
||||
opts.create(true).truncate(true).write(true);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
+4
-3
@@ -51,12 +51,13 @@ just test
|
||||
|
||||
Codex is written in Rust, so it honors the `RUST_LOG` environment variable to configure its logging behavior.
|
||||
|
||||
The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info,codex_rmcp_client=info` and log messages are written to `~/.codex/log/codex-tui.log` by default. For a single run, you can override the log directory with `-c log_dir=...` (for example, `-c log_dir=./.codex-log`).
|
||||
The TUI records diagnostics in bounded local stores by default. Set `log_dir` explicitly to enable a plaintext TUI log for a run:
|
||||
|
||||
```bash
|
||||
tail -F ~/.codex/log/codex-tui.log
|
||||
codex -c log_dir=./.codex-log
|
||||
tail -F ./.codex-log/codex-tui.log
|
||||
```
|
||||
|
||||
By comparison, the non-interactive mode (`codex exec`) defaults to `RUST_LOG=error`, but messages are printed inline, so there is no need to monitor a separate file.
|
||||
The non-interactive mode (`codex exec`) defaults to `RUST_LOG=error`, but messages are printed inline, so there is no need to monitor a separate file.
|
||||
|
||||
See the Rust documentation on [`RUST_LOG`](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) for more information on the configuration options.
|
||||
|
||||
Reference in New Issue
Block a user