use crate::formats::default_format;
use crate::writers::log_writer::LogWriter;
use crate::FlexiLoggerError;
use crate::FormatFunction;
use log::Record;
use chrono::Local;
use glob::glob;
use std::cell::RefCell;
use std::cmp::max;
use std::env;
use std::fs::{self, File, OpenOptions};
use std::io::{self, BufRead, BufReader, LineWriter, Write};
use std::ops::{Add, DerefMut};
use std::path::Path;
use std::sync::Mutex;
const CURRENT_INFIX: &str = "_rCURRENT";
fn number_infix(idx: u32) -> String {
format!("_r{:0>5}", idx)
}
struct FileLogWriterConfig {
format: FormatFunction,
print_message: bool,
path_base: String,
suffix: String,
use_timestamp: bool,
append: bool,
rotate_over_size: Option<u64>,
create_symlink: Option<String>,
}
impl FileLogWriterConfig {
pub fn default() -> FileLogWriterConfig {
FileLogWriterConfig {
format: default_format,
print_message: false,
path_base: String::new(),
suffix: "log".to_string(),
use_timestamp: true,
append: false,
rotate_over_size: None,
create_symlink: None,
}
}
}
pub struct FileLogWriterBuilder {
directory: Option<String>,
discriminant: Option<String>,
config: FileLogWriterConfig,
}
impl FileLogWriterBuilder {
pub fn print_message(mut self) -> FileLogWriterBuilder {
self.config.print_message = true;
self
}
pub fn format(mut self, format: FormatFunction) -> FileLogWriterBuilder {
self.config.format = format;
self
}
pub fn directory<S: Into<String>>(mut self, directory: S) -> FileLogWriterBuilder {
self.directory = Some(directory.into());
self
}
pub fn suffix<S: Into<String>>(mut self, suffix: S) -> FileLogWriterBuilder {
self.config.suffix = suffix.into();
self
}
pub fn suppress_timestamp(mut self) -> FileLogWriterBuilder {
self.config.use_timestamp = false;
self
}
pub fn rotate_over_size(mut self, rotate_over_size: usize) -> FileLogWriterBuilder {
self.config.rotate_over_size = Some(rotate_over_size as u64);
self.config.use_timestamp = false;
self
}
pub fn append(mut self) -> FileLogWriterBuilder {
self.config.append = true;
self
}
pub fn discriminant<S: Into<String>>(mut self, discriminant: S) -> FileLogWriterBuilder {
self.discriminant = Some(discriminant.into());
self
}
pub fn create_symlink<S: Into<String>>(mut self, symlink: S) -> FileLogWriterBuilder {
self.config.create_symlink = Some(symlink.into());
self
}
pub fn instantiate(mut self) -> Result<FileLogWriter, FlexiLoggerError> {
let s_directory: String = self.directory.unwrap_or_else(|| ".".to_string());
let p_directory = Path::new(&s_directory);
fs::create_dir_all(&p_directory)?;
if !fs::metadata(&p_directory)?.is_dir() {
return Err(FlexiLoggerError::BadDirectory);
};
let arg0 = env::args().nth(0).unwrap_or_else(|| "rs".to_owned());
let progname = Path::new(&arg0).file_stem().unwrap().to_string_lossy();
self.config.path_base.clear();
self.config.path_base.reserve(180);
self.config.path_base += &s_directory;
self.config.path_base += "/";
self.config.path_base += &progname;
if let Some(discriminant) = self.discriminant {
self.config.path_base += &format!("_{}", discriminant);
}
if self.config.use_timestamp {
self.config.path_base += &Local::now().format("_%Y-%m-%d_%H-%M-%S").to_string();
};
Ok(FileLogWriter {
state: Mutex::new(RefCell::new(FileLogWriterState::try_new(&self.config)?)),
config: self.config,
})
}
}
impl FileLogWriterBuilder {
pub fn o_print_message(mut self, print_message: bool) -> FileLogWriterBuilder {
self.config.print_message = print_message;
self
}
pub fn o_directory<S: Into<String>>(mut self, directory: Option<S>) -> FileLogWriterBuilder {
self.directory = directory.map(|d| d.into());
self
}
pub fn o_timestamp(mut self, use_timestamp: bool) -> FileLogWriterBuilder {
self.config.use_timestamp = use_timestamp;
self
}
pub fn o_rotate_over_size(mut self, rotate_over_size: Option<usize>) -> FileLogWriterBuilder {
self.config.rotate_over_size = rotate_over_size.map(|r| r as u64);
self.config.use_timestamp = false;
self
}
pub fn o_append(mut self, append: bool) -> FileLogWriterBuilder {
self.config.append = append;
self
}
pub fn o_discriminant<S: Into<String>>(
mut self,
discriminant: Option<S>,
) -> FileLogWriterBuilder {
self.discriminant = discriminant.map(|d| d.into());
self
}
pub fn o_create_symlink<S: Into<String>>(mut self, symlink: Option<S>) -> FileLogWriterBuilder {
self.config.create_symlink = symlink.map(|s| s.into());
self
}
}
struct FileLogWriterState {
line_writer: Option<LineWriter<File>>,
written_bytes: u64,
rotate_idx: Option<u32>,
path: String,
}
impl FileLogWriterState {
fn try_new(config: &FileLogWriterConfig) -> Result<FileLogWriterState, FlexiLoggerError> {
let rotate_idx = match config.rotate_over_size {
None => None,
Some(_) => Some({
let mut rotate_idx = get_highest_rotate_idx(&config.path_base, &config.suffix);
if !config.append {
rotate_idx = rotate_output_file(rotate_idx, config)?;
}
rotate_idx
}),
};
let (line_writer, written_bytes, path) = get_linewriter(config)?;
Ok(FileLogWriterState {
line_writer: Some(line_writer),
path,
written_bytes,
rotate_idx,
})
}
fn line_writer(&mut self) -> &mut LineWriter<File> {
self.line_writer
.as_mut()
.expect("FlexiLogger: line_writer unexpectedly not available")
}
fn mount_next_linewriter(
&mut self,
config: &FileLogWriterConfig,
) -> Result<(), FlexiLoggerError> {
self.line_writer = None;
self.rotate_idx = Some(rotate_output_file(self.rotate_idx.take().unwrap(), config)?);
self.written_bytes = 0;
let (line_writer, written_bytes, path) = get_linewriter(config)?;
self.line_writer = Some(line_writer);
self.written_bytes = written_bytes;
self.path = path;
Ok(())
}
}
impl Write for FileLogWriterState {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.line_writer().write_all(buf)?;
if self.rotate_idx.is_some() {
self.written_bytes += buf.len() as u64;
};
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.line_writer().flush()
}
}
fn get_infix_path(infix: &str, config: &FileLogWriterConfig) -> String {
let mut s_path = String::with_capacity(180) + &config.path_base;
if config.rotate_over_size.is_some() {
s_path += infix;
};
s_path += ".";
s_path + &config.suffix
}
fn get_linewriter(
config: &FileLogWriterConfig,
) -> Result<(LineWriter<File>, u64, String), FlexiLoggerError> {
let s_path = get_infix_path(CURRENT_INFIX, &config);
let (line_writer, file_size) = {
let p_path = Path::new(&s_path);
if config.print_message {
println!("Log is written to {}", &p_path.display());
}
if let Some(ref link) = config.create_symlink {
self::platform::create_symlink_if_possible(link, p_path);
}
(
LineWriter::new(
OpenOptions::new()
.write(true)
.create(true)
.append(config.append)
.truncate(!config.append)
.open(&p_path)?,
),
if config.append {
let metadata = fs::metadata(&s_path)?;
metadata.len()
} else {
0
},
)
};
Ok((line_writer, file_size, s_path))
}
fn get_highest_rotate_idx(path_base: &str, suffix: &str) -> u32 {
let mut rotate_idx = 0;
let fn_pattern = String::with_capacity(180)
.add(path_base)
.add("_r*")
.add(".")
.add(suffix);
match glob(&fn_pattern) {
Err(e) => {
eprintln!("Listing files with ({}) failed with {}", fn_pattern, e);
}
Ok(globresults) => {
for globresult in globresults {
match globresult {
Err(e) => eprintln!(
"Error occured when reading directory for log files: {:?}",
e
),
Ok(pathbuf) => {
let filename = pathbuf.file_stem().unwrap().to_string_lossy();
let mut it = filename.rsplit("_r");
let idx: u32 = it.next().unwrap().parse().unwrap_or(0);
rotate_idx = max(rotate_idx, idx);
}
}
}
}
}
rotate_idx
}
fn rotate_output_file(
rotate_idx: u32,
config: &FileLogWriterConfig,
) -> Result<u32, FlexiLoggerError> {
match std::fs::rename(
get_infix_path(CURRENT_INFIX, config),
get_infix_path(&number_infix(rotate_idx), config),
) {
Ok(()) => Ok(rotate_idx + 1),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(rotate_idx)
} else {
Err(FlexiLoggerError::Io(e))
}
}
}
}
pub struct FileLogWriter {
config: FileLogWriterConfig,
state: Mutex<RefCell<FileLogWriterState>>,
}
impl FileLogWriter {
pub fn builder() -> FileLogWriterBuilder {
FileLogWriterBuilder {
directory: None,
discriminant: None,
config: FileLogWriterConfig::default(),
}
}
#[inline]
pub fn format(&self) -> FormatFunction {
self.config.format
}
#[doc(hidden)]
pub fn validate_logs(&self, expected: &[(&'static str, &'static str, &'static str)]) -> bool {
let guard = self.state.lock().unwrap();
let state = guard.borrow();
let path = Path::new(&state.path);
let f = File::open(path).unwrap();
let mut reader = BufReader::new(f);
let mut line = String::new();
for tuple in expected {
line.clear();
reader.read_line(&mut line).unwrap();
assert!(
line.contains(&tuple.0),
"Did not find tuple.0 = {}",
tuple.0
);
assert!(
line.contains(&tuple.1),
"Did not find tuple.1 = {}",
tuple.1
);
assert!(
line.contains(&tuple.2),
"Did not find tuple.2 = {}",
tuple.2
);
}
false
}
}
impl LogWriter for FileLogWriter {
#[inline]
fn write(&self, record: &Record) -> io::Result<()> {
let guard = self.state.lock().unwrap();
let mut state = guard.borrow_mut();
let state = state.deref_mut();
if let Some(rotate_over_size) = self.config.rotate_over_size {
if state.written_bytes > rotate_over_size {
state
.mount_next_linewriter(&self.config)
.unwrap_or_else(|e| {
eprintln!("FlexiLogger: opening file failed with {}", e);
});
}
}
(self.config.format)(state, record)?;
state.write_all(b"\n")
}
#[inline]
fn flush(&self) -> io::Result<()> {
let guard = self.state.lock().unwrap();
let mut state = guard.borrow_mut();
state.line_writer().flush()
}
}
mod platform {
use std::path::Path;
pub fn create_symlink_if_possible(link: &str, path: &Path) {
linux_create_symlink(link, path);
}
#[cfg(target_os = "linux")]
fn linux_create_symlink(link: &str, path: &Path) {
use std::fs;
use std::os::unix::fs as unix_fs;
if fs::metadata(link).is_ok() {
let _ = fs::remove_file(link);
}
if let Err(e) = unix_fs::symlink(&path, link) {
eprintln!(
"Can not create symlink \"{}\" for path \"{}\": {}",
link,
&path.display(),
e
);
}
}
#[cfg(not(target_os = "linux"))]
fn linux_create_symlink(_: &str, _: &Path) {}
}