fix(lsp): size-rotate per-server LSP logs (#10877)#10975
fix(lsp): size-rotate per-server LSP logs (#10877)#10975david-engelmann wants to merge 5 commits into
Conversation
…dev#7723) Warp captures every MCP server's stderr/stdout to a per-server log file under the `mcp/` namespace. Once a logger was registered, the file was opened with `truncate(true)` and then grew without bound for the lifetime of the app session — a verbose server like iMCP could reach 20 GB in five days (warpdotdev#7723). Adds size-based rotation to the `simple_logger` crate that backs MCP log capture: - `RotationConfig { max_file_size_bytes, max_rotation }` (in crates/simple_logger/src/lib.rs). `RotationConfig::new` returns `Option<Self>`, mapping any zero to `None` so callers reading from config can statically express "rotation disabled". - `SimpleLogger::new` now takes `rotation: Option<RotationConfig>`. When `None`, behavior is unchanged (one file per logger lifetime, truncate-on-create). When `Some`, the writer tracks bytes written and, after any write that brings the counter to or above the threshold, drops the file handle, shifts existing `.N` rotations up by one (discarding `.{max_rotation}` first), renames the active file to `.1`, and reopens a fresh active file. Log lines are never split across files; the active file may briefly overshoot the threshold by one line. - `LogManager::register_with_rotation` is a sibling of `register` that threads the config through. The existing `register` becomes a thin wrapper that passes `None`, so all current callers other than MCP are unaffected. - The MCP call site in `templatable_manager/native.rs` now uses `register_with_rotation` with a policy of 10 MiB × 5 rotations = 60 MiB cap per server (1 active + 5 rotated). Generous enough to keep a useful debugging window; small enough that even a chatty server can't fill a multi-day session. Rotation operates on `path.1` … `path.{max_rotation}` suffixes rather than rewriting extensions, so compound names like `server.stderr.log` get rotated to `server.stderr.log.1` rather than `server.stderr.1` (which is what `Path::set_extension` would produce). Rename failures for intermediate `.N` files are tolerated — the file may not exist yet if fewer than `max_rotation` rotations have happened. Only the rename of the active file surfaces an error, and even then the writer reopens the active path truncated and keeps going, so a transient rotation failure (full disk, permission flip) degrades to "oldest rotation discarded, current session continues" rather than "logging stops". Tests ===== $ cargo nextest run -p simple_logger 19 tests run: 19 passed $ cargo nextest run -p warp -E 'test(mcp::) or test(file_based_manager) or test(file_mcp_watcher) or test(simple_logger)' 74 tests run: 74 passed 15 new file-level unit tests in `crates/simple_logger/src/lib_tests.rs` cover `RotationConfig::new`, `path_with_suffix`, and `perform_rotation` across the boundary cases (empty file, missing intermediates, cap, max rotation = 1, three consecutive rotations to verify shift order). 2 new end-to-end tests in `crates/simple_logger/src/manager_tests.rs` drive a live `SimpleLogger` on a real background executor: one asserts that high-volume writes with rotation enabled produce a `.1` rotated file; the other pins the backward-compatibility guarantee that the same write volume *without* rotation produces no `.1` file. $ cargo clippy -p simple_logger --tests -- -D warnings # clean $ cargo clippy -p warp --tests -- -D warnings # clean $ cargo fmt -p simple_logger -p warp -- --check # clean Fixes warpdotdev#7723 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the LSP namespace into the size-based rotation primitives shipped for MCP in warpdotdev#10874 (warpdotdev#7723). Cap is identical: 10 MiB per file × 5 rotated copies = 60 MiB per LSP server per workspace. Replaces the unbounded LogManager::register call at crates/lsp/src/model.rs:272 with register_with_rotation, mirroring the MCP wire-up in app/src/ai/mcp/templatable_manager/native.rs:810. A new crates/lsp/src/logs.rs hosts lsp_log_rotation_config(), parallel to app/src/ai/mcp/logs.rs::mcp_log_rotation_config(). Two unit tests pin the cap constants and document that they're intentionally kept in sync with the MCP policy. Depends on warpdotdev#10874 landing first — register_with_rotation is introduced there. Fixes warpdotdev#10877.
|
I'm starting a first review of this pull request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds size-based rotation support to simple_logger, wires MCP and LSP server logs to use the new rotation policy, and adds unit/integration coverage for the rotation helpers.
Concerns
- The rotation failure path can still truncate the active log after a failed rename, which loses the log data the rotation was supposed to preserve.
Verdict
Found: 0 critical, 1 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| &log_path, | ||
| ); | ||
| } | ||
| log_file = match open_truncated(&log_path).await { |
There was a problem hiding this comment.
perform_rotation fails after the active handle is dropped, this reopens log_path with truncation and erases the current log even though it was never preserved. Reopen without truncation or keep appending/retry rotation when the rename fails.
`perform_rotation`'s only Err path is step 3 — the rename of the active file into its `.1` slot. When that rename fails, the original log content is still on disk at `log_path`. The callback in `SimpleLogger` was nevertheless calling `open_truncated` unconditionally, which destroyed the data the rotation was meant to preserve. Fix: on rotation failure, open the file in append mode and seed `written_bytes` from its current size so the next threshold check is accurate. On success, behavior is unchanged (truncate + reset counter). A new manager test pre-stages `server.log.1` as a non-empty directory with `max_rotation = 1` (so step 2's shift loop is a no-op and doesn't quietly move the blocker), drives enough log volume to trigger rotation, and asserts both that the active log keeps the pre-rotation content and that the blocking directory is untouched. Addresses Oz's "rotation failure path can still truncate the active log after a failed rename" concern on warpdotdev#10975.
|
Addressed in the latest push, which lands on the underlying The fix is in New test
/oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I reviewed this pull request and requested human review from: Comment Powered by Oz |
Oz no longer requests changes for this pull request after the latest automated review.
There was a problem hiding this comment.
Overview
This PR adds size-based rotation support to simple_logger, wires the rotation config into MCP and LSP per-server logs, and adds coverage for rotation behavior and namespace-specific caps.
Concerns
- No blocking correctness, security, or error-handling concerns found in the annotated diff.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
|
/oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
Reviewed the size-based log rotation primitives and the MCP/LSP wire-ups. The rotation implementation and tests look scoped to the log-growth issue, and no supplemental security findings were identified.
Concerns
- The new LSP
logsmodule is compiled unconditionally even though it importssimple_logger, which is only a non-wasm dependency ofcrates/lsp; this will break wasm builds unless the module is gated like the call site.
Verdict
Found: 0 critical, 1 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| mod language_server_candidate; | ||
| pub use language_server_candidate::LanguageServerCandidate; | ||
| pub mod install; | ||
| mod logs; |
There was a problem hiding this comment.
simple_logger, which crates/lsp only depends on under cfg(not(target_arch = "wasm32")); gate it like the call site or wasm builds will fail.
| mod logs; | |
| #[cfg(not(target_arch = "wasm32"))] | |
| mod logs; |
…dotdev#10877) Address Oz reviewer feedback on warpdotdev#10975: the new `logs` module imports `simple_logger`, which is only a dependency of `crates/lsp` for non-wasm targets. The module was compiled unconditionally, breaking `cargo check -p lsp --target wasm32-unknown-unknown`. Gate the module declaration with `#[cfg(not(target_arch = "wasm32"))]` matching the call site at model.rs:269 and the existing pattern on `mod transport;`. Verified locally: cargo check -p lsp # clean cargo check -p lsp --target wasm32-unknown-unknown # clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed the wasm-gate concern in Verified locally: /oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I reviewed this pull request and requested human review from: Comment Powered by Oz |
Oz no longer requests changes for this pull request after the latest automated review.
There was a problem hiding this comment.
Overview
This PR adds size-based rotation support to simple_logger, wires the rotation policy into MCP and LSP server logs, and adds focused unit coverage for the rotation helper behavior and configured caps.
Concerns
- No blocking correctness, security, or spec-alignment concerns found in the annotated diff.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
kevinyang372
left a comment
There was a problem hiding this comment.
@david-engelmann Can you re-request review once you land #10874? Currently there are a lot of overlap between the two PRs
|
@kevinyang372 for sure! |
Wires the LSP namespace into the size-based rotation primitives shipped for MCP in #10874 (#7723). Same cap as MCP: 10 MiB per file × 5 rotated copies = ~60 MiB per LSP server per workspace.
Fixes #10877. Depends on #10874 landing first —
LogManager::register_with_rotationis introduced there.What changes
crates/lsp/src/model.rs:272— replaces the unboundedmanager.register("lsp", ...)withmanager.register_with_rotation("lsp", ..., logs::lsp_log_rotation_config()). Identical pattern to the MCP wire-up atapp/src/ai/mcp/templatable_manager/native.rs:810.crates/lsp/src/logs.rs(new) —lsp_log_rotation_config()mirroringapp/src/ai/mcp/logs.rs::mcp_log_rotation_config().crates/lsp/src/lib.rs— registers the newmod logs.Net diff: 49 lines added, 1 removed.
Why mirror MCP's policy
simple_logger's rotation primitives are namespace-agnostic — the cap constants are the only knobs. Choosing the same 10 MiB × 5 cap as MCP keeps both namespaces well below the multi-GB unbounded growth observed for verbose servers (rust-analyzer in #10877, MCP servers in #7723) while preserving a useful multi-day debugging window. If a future change wants per-namespace caps the constants are already isolated incrates/lsp/src/logs.rs.Tests
Two unit tests pin the cap constants and document the intentional sync with MCP:
Full lsp suite still green (18/18), rustfmt + clippy -D warnings clean.
Why no integration / live-rotation evidence
The rotation logic itself lives in
simple_loggerand is fully covered by the existingmanager_tests.rscases shipped in #10874. This PR is strictly the wire-up: changing oneregistercall site and adding the config helper. The unit tests pin that the helper produces the policy the wire-up depends on; runtime correctness of rotation is already proven for the MCP namespace and is identical here.A demonstration of the on-disk rotation shape (active log +
.log.1...log.N+.rotations.jsonlsidecar) is available in the warp-taper sample tape — the LSP path produces the same artifacts.