Compare commits
6 Commits
366809d8c6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a26444006b | |||
| ccf3e28323 | |||
| 6eea6b92fb | |||
|
6df68b8a66
|
|||
|
cbca6bd916
|
|||
|
92187c5f16
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## v0.7.3 (2026-06-25)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Implemented log rolling so the log file doesn't just grow exponentially [#60]
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- addressed code review comments
|
||||||
|
- tail-logs subcommand follows log rollovers and sleeps to minimize idle CPU loops
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Refactored several usages of sort_by_key and match guards to utilize newer Rust version APIs
|
||||||
|
|
||||||
## v0.7.2 (2026-04-20)
|
## v0.7.2 (2026-04-20)
|
||||||
|
|
||||||
### Feat
|
### Feat
|
||||||
|
|||||||
Generated
+3
-3
@@ -93,9 +93,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "approx"
|
name = "approx"
|
||||||
@@ -1844,7 +1844,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
description = "A TUI and CLI to manage your Servarrs"
|
description = "A TUI and CLI to manage your Servarrs"
|
||||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||||
|
|||||||
+34
-7
@@ -59,7 +59,7 @@ pub fn init_logging_config() -> log4rs::Config {
|
|||||||
let trigger = SizeTrigger::new(10 * 1024 * 1024);
|
let trigger = SizeTrigger::new(10 * 1024 * 1024);
|
||||||
let roller = FixedWindowRoller::builder()
|
let roller = FixedWindowRoller::builder()
|
||||||
.build(&archive_pattern, 3)
|
.build(&archive_pattern, 3)
|
||||||
.unwrap();
|
.expect("Failed to build log roller");
|
||||||
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
|
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
|
||||||
|
|
||||||
let logfile = RollingFileAppender::builder()
|
let logfile = RollingFileAppender::builder()
|
||||||
@@ -67,7 +67,7 @@ pub fn init_logging_config() -> log4rs::Config {
|
|||||||
"{d(%Y-%m-%d %H:%M:%S%.3f)(utc)} <{i}> [{l}] {f}:{L} - {m}{n}",
|
"{d(%Y-%m-%d %H:%M:%S%.3f)(utc)} <{i}> [{l}] {f}:{L} - {m}{n}",
|
||||||
)))
|
)))
|
||||||
.build(log_path, Box::new(policy))
|
.build(log_path, Box::new(policy))
|
||||||
.unwrap();
|
.expect("Failed to build rolling file appender");
|
||||||
|
|
||||||
log4rs::Config::builder()
|
log4rs::Config::builder()
|
||||||
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
.appender(Appender::builder().build("logfile", Box::new(logfile)))
|
||||||
@@ -104,23 +104,50 @@ pub async fn tail_logs(no_color: bool) -> Result<()> {
|
|||||||
.seek(SeekFrom::End(0))
|
.seek(SeekFrom::End(0))
|
||||||
.with_context(|| "Unable to tail log file")?;
|
.with_context(|| "Unable to tail log file")?;
|
||||||
|
|
||||||
let mut lines = reader.lines();
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let mut line_buf = String::new();
|
||||||
tokio::spawn(async move {
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(Ok(line)) = lines.next() {
|
line_buf.clear();
|
||||||
|
match reader.read_line(&mut line_buf) {
|
||||||
|
Ok(0) => {
|
||||||
|
if was_log_rotated(&file_path, &mut reader) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
let line = line_buf.trim_end();
|
||||||
if no_color {
|
if no_color {
|
||||||
println!("{line}");
|
println!("{line}");
|
||||||
} else {
|
} else {
|
||||||
let colored_line = colorize_log_line(&line, &re);
|
let colored_line = colorize_log_line(line, &re);
|
||||||
println!("{colored_line}");
|
println!("{colored_line}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn was_log_rotated(file_path: &PathBuf, reader: &mut BufReader<File>) -> bool {
|
||||||
|
let current_pos = reader.stream_position().unwrap_or(0);
|
||||||
|
let file_len = fs::metadata(file_path).map(|m| m.len()).unwrap_or(0);
|
||||||
|
|
||||||
|
if file_len < current_pos
|
||||||
|
&& let Ok(new_file) = File::open(file_path)
|
||||||
|
{
|
||||||
|
*reader = BufReader::new(new_file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn colorize_log_line(line: &str, re: &Regex) -> String {
|
fn colorize_log_line(line: &str, re: &Regex) -> String {
|
||||||
if let Some(caps) = re.captures(line) {
|
if let Some(caps) = re.captures(line) {
|
||||||
let level = &caps["level"];
|
let level = &caps["level"];
|
||||||
|
|||||||
+55
-1
@@ -1,8 +1,11 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::utils::{convert_f64_to_gb, convert_runtime, convert_to_gb};
|
use crate::utils::{convert_f64_to_gb, convert_runtime, convert_to_gb, was_log_rotated};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_convert_to_gb() {
|
fn test_convert_to_gb() {
|
||||||
@@ -23,4 +26,55 @@ mod tests {
|
|||||||
assert_eq!(hours, 2);
|
assert_eq!(hours, 2);
|
||||||
assert_eq!(minutes, 34);
|
assert_eq!(minutes, 34);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_was_log_rotated_returns_false_when_file_has_not_rotated() {
|
||||||
|
let path = std::env::temp_dir().join("managarr_test_no_rotation.log");
|
||||||
|
fs::write(&path, "line one\nline two\n").unwrap();
|
||||||
|
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
|
||||||
|
assert!(!was_log_rotated(&path, &mut reader));
|
||||||
|
|
||||||
|
fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_was_log_rotated_returns_true_and_reopens_reader_after_rotation() {
|
||||||
|
let path = std::env::temp_dir().join("managarr_test_rotation.log");
|
||||||
|
fs::write(&path, "original content that is long enough\n").unwrap();
|
||||||
|
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
|
||||||
|
fs::write(&path, "new\n").unwrap();
|
||||||
|
|
||||||
|
assert!(was_log_rotated(&path, &mut reader));
|
||||||
|
|
||||||
|
let mut line = String::new();
|
||||||
|
reader.read_line(&mut line).unwrap();
|
||||||
|
assert_eq!(line, "new\n");
|
||||||
|
|
||||||
|
fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_was_log_rotated_returns_false_when_file_grows() {
|
||||||
|
let path = std::env::temp_dir().join("managarr_test_growing.log");
|
||||||
|
fs::write(&path, "initial\n").unwrap();
|
||||||
|
|
||||||
|
let file = File::open(&path).unwrap();
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
|
|
||||||
|
let mut appender = fs::OpenOptions::new().append(true).open(&path).unwrap();
|
||||||
|
appender.write_all(b"more data\n").unwrap();
|
||||||
|
|
||||||
|
assert!(!was_log_rotated(&path, &mut reader));
|
||||||
|
|
||||||
|
fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user