Repository navigation
feat(diagnostics): list and retrieve retained log files #2185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eb5ac9c
8e155a1
5d1f75b
9cfee51
0024bba
5c820e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,14 @@ pub trait StaticLogConfig { | |
| const LOG_FILE_PREFIX: &'static str; | ||
| } | ||
|
|
||
| /// Metadata for a retained log file in the configured log directory. | ||
| pub struct LogFile { | ||
| pub path: std::path::PathBuf, | ||
| pub name: String, | ||
| pub size: u64, | ||
| pub modified: SystemTime, | ||
| } | ||
|
|
||
| pub struct LoggerGuard { | ||
| _file_guard: WorkerGuard, | ||
| _stdio_guard: WorkerGuard, | ||
|
|
@@ -91,38 +99,90 @@ pub fn init<C: StaticLogConfig>( | |
| }) | ||
| } | ||
|
|
||
| /// Find latest log file (by age) | ||
| /// Finds the newest retained regular log file by modification time. | ||
| /// | ||
| /// Given path is used to filter out by file name prefix. | ||
| /// Uses the same file filter and order as [`list_log_files`]. | ||
| #[instrument] | ||
| pub async fn find_latest_log_file<C: StaticLogConfig>(prefix: &Utf8Path) -> anyhow::Result<std::path::PathBuf> { | ||
| let cfg = LogPathCfg::<C>::from_path(prefix)?; | ||
| list_log_files::<C>(prefix) | ||
| .await? | ||
| .into_iter() | ||
| .next() | ||
| .map(|file| file.path) | ||
| .context("no file found") | ||
| } | ||
|
|
||
| /// Lists the regular log files retained under the configured log path. | ||
| pub async fn list_log_files<C: StaticLogConfig>(path: &Utf8Path) -> anyhow::Result<Vec<LogFile>> { | ||
| let cfg = LogPathCfg::<C>::from_path(path)?; | ||
| let mut read_dir = fs::read_dir(cfg.folder).await.context("couldn't read log directory")?; | ||
| let mut files = Vec::new(); | ||
|
|
||
| let mut read_dir = fs::read_dir(cfg.folder).await.context("couldn't read directory")?; | ||
| while let Some(entry) = read_dir.next_entry().await.context("couldn't read log entry")? { | ||
| let Some(name) = entry.file_name().to_str().map(str::to_owned) else { | ||
| continue; | ||
| }; | ||
|
|
||
| let mut most_recent_time = SystemTime::UNIX_EPOCH; | ||
| let mut most_recent = None; | ||
| if !name.starts_with(cfg.prefix) || !name.ends_with(".log") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question:
|
||
| continue; | ||
| } | ||
|
|
||
| while let Ok(Some(entry)) = read_dir.next_entry().await { | ||
| match entry.file_name().to_str() { | ||
| Some(file_name) if file_name.starts_with(cfg.prefix) && file_name.contains("log") => { | ||
| debug!(file_name, "Found a log file"); | ||
| match entry.metadata().await.and_then(|metadata| metadata.modified()) { | ||
| Ok(modified) if modified > most_recent_time => { | ||
| most_recent_time = modified; | ||
| most_recent = Some(entry.path()); | ||
| } | ||
| Ok(_) => {} | ||
| Err(error) => { | ||
| warn!(%error, file_name, "Couldn't retrieve metadata for file"); | ||
| } | ||
| } | ||
| let file_type = match entry.file_type().await { | ||
| Ok(file_type) => file_type, | ||
| Err(error) => { | ||
| warn!(%error, name, "Couldn't read log file type"); | ||
| continue; | ||
| } | ||
| _ => continue, | ||
| }; | ||
| if !file_type.is_file() { | ||
| continue; | ||
| } | ||
|
|
||
| let metadata = match entry.metadata().await { | ||
| Ok(metadata) => metadata, | ||
| Err(error) => { | ||
| warn!(%error, name, "Couldn't read log metadata"); | ||
| continue; | ||
| } | ||
| }; | ||
| let modified = match metadata.modified() { | ||
| Ok(modified) => modified, | ||
| Err(error) => { | ||
| warn!(%error, name, "Couldn't read log modification time"); | ||
| continue; | ||
| } | ||
| }; | ||
| files.push(LogFile { | ||
| path: entry.path(), | ||
| name, | ||
| size: metadata.len(), | ||
| modified, | ||
| }); | ||
| } | ||
|
|
||
| files.sort_by(|left, right| { | ||
| right | ||
| .modified | ||
| .cmp(&left.modified) | ||
| .then_with(|| compare_log_file_names(&right.name, &left.name)) | ||
| }); | ||
| Ok(files) | ||
| } | ||
|
|
||
| fn compare_log_file_names(left: &str, right: &str) -> std::cmp::Ordering { | ||
| let rotations = left | ||
| .strip_suffix(".log") | ||
| .and_then(|name| name.rsplit_once('.')) | ||
| .zip(right.strip_suffix(".log").and_then(|name| name.rsplit_once('.'))); | ||
|
|
||
| if let Some(((left_base, left_rotation), (right_base, right_rotation))) = rotations | ||
| && left_base == right_base | ||
| && let (Ok(left_rotation), Ok(right_rotation)) = (left_rotation.parse::<u64>(), right_rotation.parse::<u64>()) | ||
| { | ||
| return left_rotation.cmp(&right_rotation).then_with(|| left.cmp(right)); | ||
| } | ||
|
|
||
| most_recent.context("no file found") | ||
| left.cmp(right) | ||
| } | ||
|
|
||
| /// File deletion task (by age) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing a doc comment on this new public type. Also the rest of this crate's API speaks
Utf8Path, whilepathhere is astd::path::PathBuf. Not a blocker.