Skip to content

Commit

Permalink
Remove dependency to glob
Browse files Browse the repository at this point in the history
  • Loading branch information
emabee committed Oct 11, 2024
1 parent b4c434e commit caa4331
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 145 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.29.3] - unpublished

Removed dependency to `glob`, to fix issue-173.

## [0.29.2] - 2024-10-07

Fix a regression ([issue #178](https://github.com/emabee/flexi_logger/issues/178))
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
crossbeam-channel = { version = "0.5", optional = true }
crossbeam-queue = { version = "0.3", optional = true }
flate2 = { version = "1.0", optional = true, features = ["rust_backend"] }
glob = "0.3"
hostname = { version = "0.4", optional = true }
log = { version = "0.4", features = ["std"] }
notify-debouncer-mini = { version = "0.4.1", optional = true, default-features = false }
Expand All @@ -68,7 +67,9 @@ libc = { version = "^0.2.50", optional = true }
cond_sync = "0.2"
either = "1.9"
flate2 = "1.0"
glob = "0.3"
serde_derive = "1.0"
version-sync = "0.9"
temp-dir = "0.1"
tracing = "0.1.36"
#env_logger = '*' # optionally needed for the performance example
252 changes: 207 additions & 45 deletions src/parameters/file_spec.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{DeferredNow, FlexiLoggerError};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::{
ffi::OsStr,
ops::Add,
path::{Path, PathBuf},
};

/// Builder object for specifying the name and path of the log output file.
///
Expand Down Expand Up @@ -240,20 +243,32 @@ impl FileSpec {
self.o_suffix.clone()
}

/// Derives a `PathBuf` from the spec and the given infix.
#[must_use]
pub fn as_pathbuf(&self, o_infix: Option<&str>) -> PathBuf {
let mut filename = self.basename.clone();
filename.reserve(50);
pub(crate) fn fixed_name_part(&self) -> String {
let mut fixed_name_part = self.basename.clone();
fixed_name_part.reserve(50);

if let Some(discriminant) = &self.o_discriminant {
FileSpec::separate_with_underscore(&mut filename);
filename.push_str(discriminant);
FileSpec::separate_with_underscore(&mut fixed_name_part);
fixed_name_part.push_str(discriminant);
}
if let Some(timestamp) = &self.timestamp_cfg.get_timestamp() {
FileSpec::separate_with_underscore(&mut filename);
filename.push_str(timestamp);
FileSpec::separate_with_underscore(&mut fixed_name_part);
fixed_name_part.push_str(timestamp);
}
fixed_name_part
}

fn separate_with_underscore(filename: &mut String) {
if !filename.is_empty() {
filename.push('_');
}
}

/// Derives a `PathBuf` from the spec and the given infix.
#[must_use]
pub fn as_pathbuf(&self, o_infix: Option<&str>) -> PathBuf {
let mut filename = self.fixed_name_part();

if let Some(infix) = o_infix {
if !infix.is_empty() {
FileSpec::separate_with_underscore(&mut filename);
Expand All @@ -270,45 +285,135 @@ impl FileSpec {
p_path
}

fn separate_with_underscore(filename: &mut String) {
if !filename.is_empty() {
filename.push('_');
}
}
// handles collisions by appending ".restart-<number>" to the infix, if necessary
pub(crate) fn collision_free_infix_for_rotated_file(&self, infix: &str) -> String {
// Some("log") -> ["log", "log.gz"], None -> [".gz"]:
let suffices = self
.o_suffix
.clone()
.into_iter()
.chain(
self.o_suffix
.as_deref()
.or(Some(""))
.map(|s| [s, ".gz"].concat()),
)
.collect::<Vec<String>>();

let mut restart_siblings = self
.list_related_files()
.into_iter()
.filter(|pb| {
// ignore files with irrelevant suffixes:
// TODO this does not work correctly if o_suffix = None, because we ignore all
// non-compressed files
pb.file_name()
.map(OsStr::to_string_lossy)
.filter(|file_name| {
file_name.ends_with(&suffices[0])
|| suffices.len() > 1 && file_name.ends_with(&suffices[1])
})
.is_some()
})
.filter(|pb| {
pb.file_name()
.unwrap()
.to_string_lossy()
.contains(".restart-")
})
.collect::<Vec<PathBuf>>();

let new_path = self.as_pathbuf(Some(infix));
let new_path_with_gz = {
let mut new_path_with_gz = new_path.clone();
new_path_with_gz
.set_extension([self.o_suffix.as_deref().unwrap_or(""), ".gz"].concat());
new_path_with_gz
};

// <directory>/[<basename>][_][<discriminant>][_][<starttime>][_][<infix_pattern>]
pub(crate) fn as_glob_pattern(&self, infix_pattern: &str, o_suffix: Option<&str>) -> String {
let mut filename = self.basename.clone();
filename.reserve(50);
// if collision would occur (new_path or compressed new_path exists already),
// find highest restart and add 1, else continue without restart
if new_path.exists() || new_path_with_gz.exists() || !restart_siblings.is_empty() {
let next_number = if restart_siblings.is_empty() {
0
} else {
restart_siblings.sort_unstable();
let new_path = restart_siblings.pop().unwrap(/*ok*/);
let file_stem_string = if self.o_suffix.is_some() {
new_path
.file_stem().unwrap(/*ok*/)
.to_string_lossy().to_string()
} else {
new_path.to_string_lossy().to_string()
};
let index = file_stem_string.find(".restart-").unwrap(/*ok*/);
file_stem_string[(index + 9)..(index + 13)].parse::<usize>().unwrap(/*ok*/) + 1
};

if let Some(discriminant) = &self.o_discriminant {
FileSpec::separate_with_underscore(&mut filename);
filename.push_str(discriminant);
}
if let Some(timestamp) = &self.timestamp_cfg.get_timestamp() {
FileSpec::separate_with_underscore(&mut filename);
filename.push_str(timestamp);
infix.to_string().add(&format!(".restart-{next_number:04}"))
} else {
infix.to_string()
}
}

FileSpec::separate_with_underscore(&mut filename);
filename.push_str(infix_pattern);

match o_suffix {
Some(s) => {
filename.push('.');
filename.push_str(s);
}
None => {
if let Some(suffix) = &self.o_suffix {
filename.push('.');
filename.push_str(suffix);
pub(crate) fn list_of_files(
&self,
infix_filter: fn(&str) -> bool,
o_suffix: Option<&str>,
) -> Vec<PathBuf> {
let fixed_name_part = self.fixed_name_part();
self.list_related_files()
.into_iter()
.filter(|path| {
// if suffix is specified, it must match
if let Some(suffix) = o_suffix {
path.extension().is_some_and(|ext| {
let s = ext.to_string_lossy();
s == suffix
})
} else {
true
}
}
}
})
.filter(|path| {
// infix filter must pass
let stem = path.file_stem().unwrap(/* CANNOT FAIL*/).to_string_lossy();
let infix_start = if fixed_name_part.is_empty() {
0
} else {
fixed_name_part.len() + 1 // underscore at the end
};
let maybe_infix = &stem[infix_start..];
infix_filter(maybe_infix)
})
.collect::<Vec<PathBuf>>()
}

// returns an ordered list of all files in the right directory that start with the fixed_name_part
fn list_related_files(&self) -> Vec<PathBuf> {
let fixed_name_part = self.fixed_name_part();
let mut log_files = std::fs::read_dir(&self.directory)
.unwrap(/*ignore errors from reading the directory*/)
.flatten(/*ignore errors from reading entries in the directory*/)
.filter(|entry| entry.path().is_file())
.map(|de| de.path())
.filter(|path| {
// fixed name part must match
if let Some(fln) = path.file_name() {
fln.to_string_lossy(/*good enough*/).starts_with(&fixed_name_part)
} else {
false
}
})
.collect::<Vec<PathBuf>>();
log_files.sort_unstable();
log_files.reverse();
log_files
}

let mut p_path = self.directory.clone();
p_path.push(filename);
p_path.to_str().unwrap(/* can hardly fail*/).to_string()
#[cfg(test)]
pub(crate) fn get_timestamp(&self) -> Option<String> {
self.timestamp_cfg.get_timestamp()
}
}

Expand Down Expand Up @@ -336,7 +441,10 @@ impl TimestampCfg {
#[cfg(test)]
mod test {
use super::{FileSpec, TimestampCfg};
use std::path::{Path, PathBuf};
use std::{
fs::File,
path::{Path, PathBuf},
};

#[test]
fn test_timstamp_cfg() {
Expand Down Expand Up @@ -562,4 +670,58 @@ mod test {
"BASENAME_1.log"
);
}

#[test]
fn test_list_of_files() {
let dir = temp_dir::TempDir::new().unwrap();
let pd = dir.path();
let filespec: FileSpec = FileSpec::default()
.directory(pd)
.basename("Base")
.discriminant("Discr")
.use_timestamp(true);
println!("Filespec: {}", filespec.as_pathbuf(Some("Infix")).display());

let mut fn1 = String::new();
fn1.push_str("Base_Discr_");
fn1.push_str(&filespec.get_timestamp().unwrap());
fn1.push_str("_Infix");
fn1.push_str(".log");
assert_eq!(
filespec
.as_pathbuf(Some("Infix"))
.file_name()
.unwrap()
.to_string_lossy(),
fn1
);
// create typical set of files, and noise
create_file(pd, "test1.txt");
create_file(pd, &build_filename(&filespec, "Infix1"));
create_file(pd, &build_filename(&filespec, "Infix2"));

println!("\nFolder content:");
for entry in std::fs::read_dir(pd).unwrap() {
println!(" {}", entry.unwrap().path().display());
}

println!("\nRelevant subset:");
for pb in filespec.list_of_files(|s: &str| s.starts_with("Infix"), Some("log")) {
println!(" {}", pb.display());
}
}

fn build_filename(file_spec: &FileSpec, infix: &str) -> String {
let mut fn1 = String::new();
fn1.push_str("Base_Discr_");
fn1.push_str(&file_spec.get_timestamp().unwrap());
fn1.push('_');
fn1.push_str(infix);
fn1.push_str(".log");
fn1
}

fn create_file(dir: &Path, filename: &str) {
File::create(dir.join(filename)).unwrap();
}
}
11 changes: 3 additions & 8 deletions src/writers/file_log_writer/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ use std::{
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use timestamps::{
collision_free_infix_for_rotated_file, creation_timestamp_of_currentfile, infix_from_timestamp,
latest_timestamp_file,
};
use timestamps::{creation_timestamp_of_currentfile, infix_from_timestamp, latest_timestamp_file};

#[cfg(feature = "async")]
const ASYNC_FLUSHER: &str = "flexi_logger-fs-async_flusher";
Expand All @@ -39,8 +36,7 @@ const CURRENT_INFIX: &str = "rCURRENT";
#[derive(Debug)]
enum NamingState {
// Contains the timestamp of the current output file (read from its name),
// plus the optional current infix (_with_ underscore),
// and the format of the timestamp infix (_with_ underscore)
// plus the optional current infix and the format of the timestamp infix
Timestamps(DateTime<Local>, Option<String>, InfixFormat),

// contains the index to which we will rotate
Expand Down Expand Up @@ -434,8 +430,7 @@ impl State {
current_infix.clone()
} else {
*ts = Local::now();
collision_free_infix_for_rotated_file(
&self.config.file_spec,
self.config.file_spec.collision_free_infix_for_rotated_file(
&infix_from_timestamp(ts, self.config.use_utc, &InfixFormat::Std),
)
}
Expand Down
Loading

0 comments on commit caa4331

Please sign in to comment.