Skip to content

Implement configurable native size/time log rotation & deletion#189

Open
Anyitechs wants to merge 2 commits intolightningdevkit:mainfrom
Anyitechs:log-rotation
Open

Implement configurable native size/time log rotation & deletion#189
Anyitechs wants to merge 2 commits intolightningdevkit:mainfrom
Anyitechs:log-rotation

Conversation

@Anyitechs
Copy link
Copy Markdown
Contributor

@Anyitechs Anyitechs commented Apr 15, 2026

This updates the ServerLogger to track file size and age internally, triggering rotation at 50MB or 24 hours. Old logs are now compressed in the background using native OS gzip via std::thread::spawn, eliminating the need for the user to configure logrotate.

Disk writes are also now buffered for better runtime performance.

@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Apr 15, 2026

🎉 This PR is now ready for review!
Please choose at least one reviewer by assigning them on the right bar.
If no reviewers are assigned within 10 minutes, I'll automatically assign one.
Once the first reviewer has submitted a review, a second will be assigned if required.

@Anyitechs Anyitechs marked this pull request as draft April 15, 2026 19:35
@benthecarman
Copy link
Copy Markdown
Collaborator

I would rather keep the old version. I like all the handling for the different signals in main.rs, makes things easier to reason about imo.

@Anyitechs Anyitechs changed the title refactor: encapsulate log rotation logic Implement native size/time log rotation with gzip compression Apr 17, 2026
@Anyitechs Anyitechs marked this pull request as ready for review April 17, 2026 19:58
@Anyitechs
Copy link
Copy Markdown
Contributor Author

I would rather keep the old version. I like all the handling for the different signals in main.rs, makes things easier to reason about imo.

As we discussed elsewhere, this is now dropped entirely as we're moving to doing the rotation ourselves rather than relying on the user to configure logrotate.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 1st Reminder

Hey @benthecarman! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment thread ldk-server/src/util/logger.rs Outdated
Comment on lines +20 to +23
/// Maximum size of the log file before it gets rotated (50 MB)
const MAX_LOG_SIZE_BYTES: usize = 50 * 1024 * 1024;
/// Maximum age of the log file before it gets rotated (24 hours)
const ROTATION_INTERVAL_SECS: u64 = 24 * 60 * 60;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be configurable

Comment thread ldk-server/src/util/logger.rs Outdated
state.created_at = SystemTime::now();

// Spawn independent OS thread to compress the old file using native gzip
thread::spawn(move || match Command::new("gzip").arg("-f").arg(&rotated_path).status() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is assuming they have gzip installed and we have permissions to run it. I would rather just have the rust code do this

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really seem worth taking a gzip dependency just to do log rotation. We can just rotate and delete as-is and tell people to use the system log (which handles this properly) otherwise. Really we shouldn't be doing logging ourselves anyway - log should go to stdout/err and systemd or some other logic should figure out what to do with it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to drop the compression so we do a rotate and delete. If that works for everyone, I'll update this to introduce a configurable max_rotated_files that will default to the last 5 uncompressed files. Does that work?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of just doing deletion instead of compression but I would really rather keep the log file and not rely on just stdout/stderr. If we don't have an easy log file and rely on the user's systemd/docker/whatever to do and persist the logs, we will likely not have logs for lots of users and make it much harder to do support.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of just doing deletion instead of compression but I would really rather keep the log file and not rely on just stdout/stderr. If we don't have an easy log file and rely on the user's systemd/docker/whatever to do and persist the logs, we will likely not have logs for lots of users and make it much harder to do support.

I'm also leaning towards just not logging to file. However I see your point that it might lead to nothing being logged at all. In that case we should however at the very least add the option to disable logging to file, and also update our docs and checked-in systemd ldk-server.service to use that mode, so that everybody who runs it that way will just use journald rather than our custom filesystem logger.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added an option to disable logging to file by default and a configurable native rotate and delete here b54e0a5

Comment thread contrib/ldk-server-config.toml Outdated
Comment thread docs/configuration.md Outdated
Comment thread ldk-server/src/main.rs
}

runtime.block_on(async {
// Register SIGHUP handler for log rotation
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like you actually removed the SIGHUP handling even though in the docs you say we have it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. Thanks!

Anyitechs added 2 commits May 2, 2026 02:08
Update the ServerLogger to track file size and age internally,
triggering rotation at 50MB or 24 hours. Old logs are now compressed
in the background using native OS gzip via `std::thread::spawn`,
eliminating the need for the user to configure `logrotate`.
Disk writes are also now buffered for better runtime performance.

Co-Authored-By: Gemini 3.0 Pro
@Anyitechs Anyitechs changed the title Implement native size/time log rotation with gzip compression Implement configurable native size/time log rotation & deletion May 2, 2026
@Anyitechs
Copy link
Copy Markdown
Contributor Author

Addressed review comment here 42cd66a and rebased to fix conflict.

[log]
level = "Debug" # Log level (Error, Warn, Info, Debug, Trace)
#file = "/tmp/ldk-server/ldk-server.log" # Log file path
log_to_file = true # Enable logging to a file (default: true, also logs to both stdout and stderr)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Alignment of comments is off.

Comment thread docs/operations.md
> **Important:** LDK Server does not rotate or truncate its own log file. Without log rotation
> configured, the log file will grow indefinitely and can eventually fill your disk. A full
> disk can prevent the node from persisting channel state, risking fund loss.
By default, LDK Server logs to `stdout`/`stderr`. When running under `systemd` or Docker,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, here we say that by default we log to stdout/stderr, but above we say log_to_file defaults to true? Probably both somewhat true, but is a bit confusing. Might be good to clarify.

}

fn cleanup_old_logs(log_file_path: &Path, max_files: usize) -> io::Result<()> {
let parent = log_file_path.parent().unwrap_or_else(|| Path::new("."));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why are we falling back to .? In general, maybe it would be better to just error out if something is unexpected instead of doing the unwrap_or fallbacks here and below, just to be sure we never end up removing some unrelated files?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex has the same concern:

  • P3 Retention cleanup matches every file whose name starts with the active log basename (ldk-server/src/util/logger.rs:227). That can delete unrelated files such as ldk-server.log.backup or config artifacts once the count exceeds max_files; it should match the exact rotation filename
    pattern.

Comment thread docs/configuration.md

If `log_to_file` is enabled, the server performs internal rotation and retention
based on `max_size_mb`, `rotation_interval_hours`, and `max_files`. The server will
also reopen the log file on `SIGHUP` for compatibility with external tools like `logrotate`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it seems we're now no longer compatible with logrotate, but rather rotate ourselves when log_to_file is configured? I guess that's fine, but we might want to update docs here and elsewhere?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems we're now no longer compatible with logrotate

We still are, I added that back in 42cd66a.

but rather rotate ourselves when log_to_file is configured?

Yes, we rotate ourselves when log_to_file is configured, but also have the SIGHUP signal handler for users relying on logrotate

Copy link
Copy Markdown
Collaborator

@tnull tnull May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how would the user disable our internal log rotation if they use logrotate themselves?

}
}

fn flush(&self) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex:

  • P1 File logs are now buffered with BufWriter (ldk-server/src/util/logger.rs:83), but normal writes do not flush (ldk-server/src/util/logger.rs:184) and shutdown does not call log::logger().flush() after the final log line (ldk-server/src/main.rs:527). Since the logger is installed
    globally, recent file logs from short runs or shutdown can remain in memory and never reach disk.

@benthecarman benthecarman mentioned this pull request May 7, 2026
25 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants