Skip to content

Commit b54e0a5

Browse files
committed
Implement configurable native log rotate and delete
1 parent 795b6a1 commit b54e0a5

6 files changed

Lines changed: 219 additions & 66 deletions

File tree

contrib/ldk-server-config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ dir_path = "/tmp/ldk-server/" # Path for LDK and BDK data persis
1515
[log]
1616
level = "Debug" # Log level (Error, Warn, Info, Debug, Trace)
1717
#file = "/tmp/ldk-server/ldk-server.log" # Log file path
18+
log_to_file = false # Enable logging to a file (default: false, logs to stderr only)
19+
#max_size_mb = 50 # Max size of log file before rotation (default: 50MB)
20+
#rotation_interval_hours = 24 # Max age of log file before rotation (default: 24h)
21+
#max_files = 5 # Number of rotated log files to keep (default: 5)
1822

1923
[tls]
2024
#cert_path = "/path/to/tls.crt" # Path to TLS certificate, by default uses dir_path/tls.crt

docs/configuration.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,12 @@ Where persistent data is stored. Defaults to `~/.ldk-server/` on Linux and
6161

6262
### `[log]`
6363

64-
Log level and file path. The server reopens the log file on `SIGHUP`, which integrates with
65-
standard `logrotate` setups.
64+
Controls logging behavior. By default, `log_to_file` is `false` and logs are written
65+
to `stdout`/`stderr`.
66+
67+
If `log_to_file` is enabled, the server performs internal rotation and retention
68+
based on `max_size_mb`, `rotation_interval_hours`, and `max_files`. The server still
69+
reopens the log file on `SIGHUP` for compatibility with external tools.
6670

6771
### `[tls]`
6872

docs/operations.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ The server handles `SIGTERM` and `CTRL-C` (SIGINT). On receipt, it:
2121

2222
### Log Rotation
2323

24-
> **Important:** LDK Server does not rotate or truncate its own log file. Without log rotation
25-
> configured, the log file will grow indefinitely and can eventually fill your disk. A full
26-
> disk can prevent the node from persisting channel state, risking fund loss.
24+
By default, LDK Server logs to `stdout`/`stderr`. When running under `systemd` or Docker,
25+
this allows the environment (e.g., `journald`) to handle persistence, rotation, and
26+
compression automatically.
2727

28-
The server reopens its log file on `SIGHUP`. This integrates with standard `logrotate`. Save
29-
the following config to `/etc/logrotate.d/ldk-server` (adjust the log path to match your
30-
setup):
28+
If you enable `log_to_file` in the configuration, LDK Server will automatically rotate
29+
logs when they exceed 50MB or 24 hours (configurable) and keep the last 5 uncompressed
30+
log files.
31+
32+
If you prefer to use system `logrotate` for file logs, the server still reopens its log
33+
file on `SIGHUP`. Save the following config to `/etc/logrotate.d/ldk-server`
34+
(adjust the log path to match your setup):
3135

3236
```
3337
/var/lib/ldk-server/regtest/ldk-server.log {

ldk-server/src/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use crate::io::persist::{
4848
};
4949
use crate::service::NodeService;
5050
use crate::util::config::{load_config, ArgsConfig, ChainSource};
51-
use crate::util::logger::ServerLogger;
51+
use crate::util::logger::{LogConfig, ServerLogger};
5252
use crate::util::metrics::Metrics;
5353
use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto};
5454
use crate::util::systemd;
@@ -121,7 +121,14 @@ fn main() {
121121
std::process::exit(-1);
122122
}
123123

124-
if let Err(e) = ServerLogger::init(config_file.log_level, &log_file_path) {
124+
let log_config = LogConfig {
125+
log_to_file: config_file.log_to_file,
126+
log_max_files: config_file.log_max_files,
127+
log_max_size_bytes: config_file.log_max_size_bytes,
128+
log_rotation_interval_secs: config_file.log_rotation_interval_secs,
129+
};
130+
131+
if let Err(e) = ServerLogger::init(config_file.log_level, &log_file_path, log_config) {
125132
eprintln!("Failed to initialize logger: {e}");
126133
std::process::exit(-1);
127134
}

ldk-server/src/util/config.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ pub struct Config {
5555
pub lsps2_service_config: Option<LSPS2ServiceConfig>,
5656
pub log_level: LevelFilter,
5757
pub log_file_path: Option<String>,
58+
pub log_max_size_bytes: usize,
59+
pub log_rotation_interval_secs: u64,
60+
pub log_max_files: usize,
61+
pub log_to_file: bool,
5862
pub pathfinding_scores_source_url: Option<String>,
5963
pub metrics_enabled: bool,
6064
pub poll_metrics_interval: Option<u64>,
@@ -108,6 +112,10 @@ struct ConfigBuilder {
108112
lsps2: Option<LiquidityConfig>,
109113
log_level: Option<String>,
110114
log_file_path: Option<String>,
115+
log_max_size_mb: Option<u64>,
116+
log_rotation_interval_hours: Option<u64>,
117+
log_max_files: Option<usize>,
118+
log_to_file: Option<bool>,
111119
pathfinding_scores_source_url: Option<String>,
112120
metrics_enabled: Option<bool>,
113121
poll_metrics_interval: Option<u64>,
@@ -155,6 +163,11 @@ impl ConfigBuilder {
155163
if let Some(log) = toml.log {
156164
self.log_level = log.level.or(self.log_level.clone());
157165
self.log_file_path = log.file.or(self.log_file_path.clone());
166+
self.log_max_size_mb = log.max_size_mb.or(self.log_max_size_mb);
167+
self.log_rotation_interval_hours =
168+
log.rotation_interval_hours.or(self.log_rotation_interval_hours);
169+
self.log_max_files = log.max_files.or(self.log_max_files);
170+
self.log_to_file = log.log_to_file.or(self.log_to_file);
158171
}
159172

160173
if let Some(liquidity) = toml.liquidity {
@@ -242,6 +255,22 @@ impl ConfigBuilder {
242255
if let Some(tor_proxy_address) = &args.tor_proxy_address {
243256
self.tor_proxy_address = Some(tor_proxy_address.clone());
244257
}
258+
259+
if let Some(log_max_size_mb) = args.log_max_size_mb {
260+
self.log_max_size_mb = Some(log_max_size_mb);
261+
}
262+
263+
if let Some(log_rotation_interval_hours) = args.log_rotation_interval_hours {
264+
self.log_rotation_interval_hours = Some(log_rotation_interval_hours);
265+
}
266+
267+
if let Some(log_max_files) = args.log_max_files {
268+
self.log_max_files = Some(log_max_files);
269+
}
270+
271+
if args.log_to_file {
272+
self.log_to_file = Some(true);
273+
}
245274
}
246275

247276
fn build(self) -> io::Result<Config> {
@@ -351,6 +380,11 @@ impl ConfigBuilder {
351380
.transpose()?
352381
.unwrap_or(LevelFilter::Debug);
353382

383+
let log_max_size_bytes = self.log_max_size_mb.unwrap_or(50) * 1024 * 1024;
384+
let log_rotation_interval_secs = self.log_rotation_interval_hours.unwrap_or(24) * 60 * 60;
385+
let log_max_files = self.log_max_files.unwrap_or(5);
386+
let log_to_file = self.log_to_file.unwrap_or(false);
387+
354388
let lsps2_client_config = self
355389
.lsps2
356390
.as_ref()
@@ -416,6 +450,10 @@ impl ConfigBuilder {
416450
lsps2_service_config,
417451
log_level,
418452
log_file_path: self.log_file_path,
453+
log_max_size_bytes: log_max_size_bytes as usize,
454+
log_rotation_interval_secs,
455+
log_max_files,
456+
log_to_file,
419457
pathfinding_scores_source_url,
420458
metrics_enabled,
421459
poll_metrics_interval,
@@ -483,6 +521,10 @@ struct EsploraConfig {
483521
struct LogConfig {
484522
level: Option<String>,
485523
file: Option<String>,
524+
max_size_mb: Option<u64>,
525+
rotation_interval_hours: Option<u64>,
526+
max_files: Option<usize>,
527+
log_to_file: Option<bool>,
486528
}
487529

488530
#[derive(Deserialize, Serialize)]
@@ -632,6 +674,34 @@ pub struct ArgsConfig {
632674
)]
633675
node_alias: Option<String>,
634676

677+
#[arg(
678+
long,
679+
env = "LDK_SERVER_LOG_MAX_SIZE_MB",
680+
help = "The maximum size of the log file in MB before rotation. Defaults to 50MB."
681+
)]
682+
log_max_size_mb: Option<u64>,
683+
684+
#[arg(
685+
long,
686+
env = "LDK_SERVER_LOG_ROTATION_INTERVAL_HOURS",
687+
help = "The maximum age of the log file in hours before rotation. Defaults to 24h."
688+
)]
689+
log_rotation_interval_hours: Option<u64>,
690+
691+
#[arg(
692+
long,
693+
env = "LDK_SERVER_LOG_MAX_FILES",
694+
help = "The maximum number of rotated log files to keep. Defaults to 5."
695+
)]
696+
log_max_files: Option<usize>,
697+
698+
#[arg(
699+
long,
700+
env = "LDK_SERVER_LOG_TO_FILE",
701+
help = "The option to enable logging to a file. If not set, logging to file is disabled."
702+
)]
703+
log_to_file: bool,
704+
635705
#[arg(
636706
long,
637707
env = "LDK_SERVER_BITCOIND_RPC_ADDRESS",
@@ -795,6 +865,10 @@ mod tests {
795865
[log]
796866
level = "Trace"
797867
file = "/var/log/ldk-server.log"
868+
max_size_mb = 50
869+
rotation_interval_hours = 24
870+
max_files = 5
871+
log_to_file = false
798872
799873
[bitcoind]
800874
rpc_address = "127.0.0.1:8332"
@@ -840,6 +914,10 @@ mod tests {
840914
metrics_username: None,
841915
metrics_password: None,
842916
tor_proxy_address: None,
917+
log_to_file: false,
918+
log_max_size_mb: Some(50),
919+
log_rotation_interval_hours: Some(24),
920+
log_max_files: Some(5),
843921
}
844922
}
845923

@@ -861,6 +939,10 @@ mod tests {
861939
metrics_username: None,
862940
metrics_password: None,
863941
tor_proxy_address: None,
942+
log_to_file: false,
943+
log_max_size_mb: None,
944+
log_rotation_interval_hours: None,
945+
log_max_files: None,
864946
}
865947
}
866948

@@ -928,6 +1010,10 @@ mod tests {
9281010
}),
9291011
log_level: LevelFilter::Trace,
9301012
log_file_path: Some("/var/log/ldk-server.log".to_string()),
1013+
log_max_size_bytes: 50 * 1024 * 1024,
1014+
log_rotation_interval_secs: 24 * 60 * 60,
1015+
log_max_files: 5,
1016+
log_to_file: false,
9311017
pathfinding_scores_source_url: None,
9321018
metrics_enabled: false,
9331019
poll_metrics_interval: None,
@@ -1241,6 +1327,10 @@ mod tests {
12411327
metrics_username: None,
12421328
metrics_password: None,
12431329
tor_config: None,
1330+
log_max_size_bytes: 50 * 1024 * 1024,
1331+
log_rotation_interval_secs: 24 * 60 * 60,
1332+
log_max_files: 5,
1333+
log_to_file: false,
12441334
};
12451335

12461336
assert_eq!(config.listening_addrs, expected.listening_addrs);
@@ -1254,6 +1344,10 @@ mod tests {
12541344
assert_eq!(config.pathfinding_scores_source_url, expected.pathfinding_scores_source_url);
12551345
assert_eq!(config.metrics_enabled, expected.metrics_enabled);
12561346
assert_eq!(config.tor_config, expected.tor_config);
1347+
assert_eq!(config.log_max_size_bytes, expected.log_max_size_bytes);
1348+
assert_eq!(config.log_rotation_interval_secs, expected.log_rotation_interval_secs);
1349+
assert_eq!(config.log_max_files, expected.log_max_files);
1350+
assert_eq!(config.log_to_file, expected.log_to_file);
12571351
}
12581352

12591353
#[test]
@@ -1350,6 +1444,10 @@ mod tests {
13501444
tor_config: Some(TorConfig {
13511445
proxy_address: SocketAddress::from_str("127.0.0.1:9050").unwrap(),
13521446
}),
1447+
log_max_size_bytes: 50 * 1024 * 1024,
1448+
log_rotation_interval_secs: 24 * 60 * 60,
1449+
log_max_files: 5,
1450+
log_to_file: false,
13531451
};
13541452

13551453
assert_eq!(config.listening_addrs, expected.listening_addrs);

0 commit comments

Comments
 (0)