use crate::flexi_error::FlexiLoggerError;
use crate::formats::default_format;
use crate::{Cleanup, Criterion, FileSpec, FormatFunction, Naming, WriteMode};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use super::{FileLogWriter, FileLogWriterConfig, LogWriter, RotationConfig, State};
/// Builder for [`FileLogWriter`].
#[allow(clippy::struct_excessive_bools, clippy::module_name_repetitions)]
pub struct FileLogWriterBuilder {
cfg_print_message: bool,
cfg_append: bool,
cfg_write_mode: WriteMode,
file_spec: FileSpec,
cfg_o_create_symlink: Option<PathBuf>,
cfg_line_ending: &'static [u8],
format: FormatFunction,
o_rotation_config: Option<RotationConfig>,
max_log_level: log::LevelFilter,
cleanup_in_background_thread: bool,
#[cfg(feature = "external_rotation")]
external_rotate_watcher: bool,
use_utc: bool,
}
/// Methods for influencing the behavior of the [`FileLogWriter`].
impl FileLogWriterBuilder {
pub(crate) fn new(file_spec: FileSpec) -> Self {
Self {
o_rotation_config: None,
cfg_print_message: false,
file_spec,
cfg_append: false,
cfg_write_mode: WriteMode::Direct,
cfg_o_create_symlink: None,
cfg_line_ending: super::UNIX_LINE_ENDING,
format: default_format,
max_log_level: log::LevelFilter::Trace,
cleanup_in_background_thread: true,
#[cfg(feature = "external_rotation")]
external_rotate_watcher: false,
use_utc: false,
}
}
/// Makes the [`FileLogWriter`] print an info message to stdout
/// when a new file is used for log-output.
#[must_use]
pub fn print_message(mut self) -> Self {
self.cfg_print_message = true;
self
}
/// Makes the [`FileLogWriter`] use the provided format function for the log entries,
/// rather than [`default_format`].
#[must_use]
pub fn format(mut self, format: FormatFunction) -> Self {
self.format = format;
self
}
/// Influences how the cleanup activities
/// (finding files, deleting files, optionally compressing files) are done
/// when rotation is used with some [`Cleanup`] variant.
///
/// With the synchronous [write modes](crate::WriteMode),
/// the cleanup activities are done by default in a dedicated background thread, to
/// minimize the blocking impact on your application.
/// You can avoid this extra thread by calling this method with
/// `use_background_thread = false`; the cleanup is then done synchronously
/// by the thread that is currently logging and - by chance - causing a file rotation.
///
/// With [`WriteMode::AsyncWith`](crate::WriteMode::AsyncWith),
/// the cleanup activities are always done by the same background thread
/// that also does the file I/O, this method then has no effect.
#[must_use]
pub fn cleanup_in_background_thread(mut self, use_background_thread: bool) -> Self {
self.cleanup_in_background_thread = use_background_thread;
self
}
/// Makes the `FileLogWriter` react cooperatively if external tools rename or delete
/// the log file.
///
/// The `FileLogWriter` expects that nobody interacts with the log file,
/// and it offers capabilities to rotate, compress, and clean up log files.
///
/// Alternatively, tools like linux' `logrotate` can be used to rotate, compress or remove
/// log files. But renaming or deleting the current output file e.g. will not stop
/// `FileLogWriter` from writing to the now renamed or even deleted file!
/// You should use this method to make it watch for OS events that affect its outputfile
/// and react with closing its current output stream and recreating its configured output file.
#[must_use]
#[cfg(feature = "external_rotation")]
pub fn watch_external_rotations(mut self) -> Self {
self.external_rotate_watcher = true;
self
}
/// Use rotation to prevent indefinite growth of log files.
///
/// By default, the log file is fixed while your program is running and will grow indefinitely.
/// With this option being used, when the log file reaches the specified criterion,
/// the file will be closed and a new file will be opened.
///
/// Note that also the filename pattern changes:
///
/// - by default, no timestamp is added to the filename
/// - the logs are always written to a file with infix `_rCURRENT`
/// - when the rotation criterion is fulfilled, it is closed and renamed to a file
/// with another infix (see `Naming`),
/// and then the logging continues again to the (fresh) file with infix `_rCURRENT`.
///
/// Example:
///
/// After some logging with your program `my_prog` and rotation with `Naming::Numbers`,
/// you will find files like
///
/// ```text
/// my_prog_r00000.log
/// my_prog_r00001.log
/// my_prog_r00002.log
/// my_prog_rCURRENT.log
/// ```
///
/// The cleanup parameter allows defining the strategy for dealing with older files.
/// See [`Cleanup`] for details.
#[must_use]
pub fn rotate(mut self, criterion: Criterion, naming: Naming, cleanup: Cleanup) -> Self {
self.o_rotation_config = Some(RotationConfig {
criterion,
naming,
cleanup,
});
self.file_spec.if_default_use_timestamp(false);
self
}
/// Set the file spec.
#[must_use]
pub(crate) fn file_spec(mut self, mut file_spec: FileSpec) -> Self {
if self.o_rotation_config.is_some() {
file_spec.if_default_use_timestamp(false);
}
self.file_spec = file_spec;
self
}
/// Makes the logger append to the given file, if it exists; by default, the file would be
/// truncated.
#[must_use]
pub fn append(mut self) -> Self {
self.cfg_append = true;
self
}
/// Set the maximum log level.
///
/// The default is `log::LevelFilter::Trace`, i.e., all log levels are written.
#[must_use]
pub fn max_level(mut self, max_log_level: log::LevelFilter) -> Self {
self.max_log_level = max_log_level;
self
}
/// Enforces the use of UTC, rather than local time.
#[must_use]
pub fn use_utc(mut self) -> Self {
self.file_spec.use_utc = true;
self.use_utc = true;
self
}
/// The specified String will be used on unix systems to create in the current folder
/// a symbolic link to the current log file.
#[must_use]
pub fn create_symlink<P: Into<PathBuf>>(mut self, symlink: P) -> Self {
self.cfg_o_create_symlink = Some(symlink.into());
self
}
/// Use Windows line endings, rather than just `\n`.
#[must_use]
pub fn use_windows_line_ending(mut self) -> Self {
self.cfg_line_ending = super::WINDOWS_LINE_ENDING;
self
}
/// Sets the write mode for the `FileLogWriter`.
///
/// See [`WriteMode`] for more (important!) details.
#[must_use]
pub fn write_mode(mut self, write_mode: WriteMode) -> Self {
self.cfg_write_mode = write_mode;
self
}
pub(crate) fn assert_write_mode(&self, write_mode: WriteMode) -> Result<(), FlexiLoggerError> {
if self.cfg_write_mode == write_mode {
Ok(())
} else {
Err(FlexiLoggerError::Reset)
}
}
#[must_use]
pub(crate) fn get_write_mode(&self) -> &WriteMode {
&self.cfg_write_mode
}
/// Produces the `FileLogWriter`.
///
/// # Errors
///
/// `FlexiLoggerError::Io` if the specified path doesn't work.
pub fn try_build(self) -> Result<FileLogWriter, FlexiLoggerError> {
Ok(FileLogWriter::new(
self.try_build_state()?,
self.max_log_level,
self.format,
))
}
/// Produces the `FileLogWriter` and a handle that is connected with it.
///
/// This allows handing out the `FileLogWriter` instance to methods that consume it, and still
/// be able to influence it via the handle.
///
/// # Errors
///
/// `FlexiLoggerError::Io` if the specified path doesn't work.
pub fn try_build_with_handle(
self,
) -> Result<(ArcFileLogWriter, FileLogWriterHandle), FlexiLoggerError> {
Ok(ArcFileLogWriter::new_with_handle(FileLogWriter::new(
self.try_build_state()?,
self.max_log_level,
self.format,
)))
}
pub(super) fn try_build_state(&self) -> Result<State, FlexiLoggerError> {
// make sure the folder exists or create it
let dir = self.file_spec.get_directory();
let p_directory = Path::new(&dir);
std::fs::create_dir_all(p_directory)?;
if !std::fs::metadata(p_directory)?.is_dir() {
return Err(FlexiLoggerError::OutputBadDirectory);
};
#[cfg(feature = "async")]
let cleanup_in_background_thread = if let WriteMode::AsyncWith {
bufsize: _,
pool_capa: _,
message_capa: _,
flush_interval: _,
} = self.cfg_write_mode
{
false
} else {
self.cleanup_in_background_thread
};
#[cfg(not(feature = "async"))]
let cleanup_in_background_thread = self.cleanup_in_background_thread;
#[cfg(feature = "external_rotation")]
let external_rotate_watcher = self.external_rotate_watcher;
Ok(State::new(
FileLogWriterConfig {
print_message: self.cfg_print_message,
append: self.cfg_append,
line_ending: self.cfg_line_ending,
write_mode: self.cfg_write_mode,
file_spec: self.file_spec.clone(),
o_create_symlink: self.cfg_o_create_symlink.as_ref().map(Clone::clone),
use_utc: self.use_utc,
},
self.o_rotation_config.as_ref().map(Clone::clone),
cleanup_in_background_thread,
#[cfg(feature = "external_rotation")]
external_rotate_watcher,
))
}
}
/// Alternative set of methods to control the behavior of the `FileLogWriterBuilder`.
/// Use these methods when you want to control the settings flexibly,
/// e.g. with commandline arguments via `docopts` or `clap`.
impl FileLogWriterBuilder {
/// With true, makes the `FileLogWriterBuilder` print an info message to stdout, each time
/// when a new file is used for log-output.
#[must_use]
pub fn o_print_message(mut self, print_message: bool) -> Self {
self.cfg_print_message = print_message;
self
}
/// By default, and with None, the log file will grow indefinitely.
/// If a `rotate_config` is set, when the log file reaches or exceeds the specified size,
/// the file will be closed and a new file will be opened.
/// Also the filename pattern changes: instead of the timestamp, a serial number
/// is included into the filename.
///
/// The size is given in bytes, e.g. `o_rotate_over_size(Some(1_000))` will rotate
/// files once they reach a size of 1 kB.
///
/// The cleanup strategy allows delimiting the used space on disk.
#[must_use]
pub fn o_rotate(mut self, rotate_config: Option<(Criterion, Naming, Cleanup)>) -> Self {
if let Some((criterion, naming, cleanup)) = rotate_config {
self.o_rotation_config = Some(RotationConfig {
criterion,
naming,
cleanup,
});
self.file_spec.if_default_use_timestamp(false);
} else {
self.o_rotation_config = None;
self.file_spec.if_default_use_timestamp(true);
}
self
}
/// If append is set to true, makes the logger append to the given file, if it exists.
/// By default, or with false, the file would be truncated.
#[must_use]
pub fn o_append(mut self, append: bool) -> Self {
self.cfg_append = append;
self
}
/// If a String is specified, it will be used on unix systems to create in the current folder
/// a symbolic link with this name to the current log file.
#[must_use]
pub fn o_create_symlink<S: Into<PathBuf>>(mut self, symlink: Option<S>) -> Self {
self.cfg_o_create_symlink = symlink.map(Into::into);
self
}
}
/// A shareable `FileLogWriter` with a handle.
pub struct ArcFileLogWriter(Arc<FileLogWriter>);
impl ArcFileLogWriter {
pub(crate) fn new_with_handle(flw: FileLogWriter) -> (Self, FileLogWriterHandle) {
let a_flw = Arc::new(flw);
(Self(Arc::clone(&a_flw)), FileLogWriterHandle(a_flw))
}
}
impl Clone for ArcFileLogWriter {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl std::io::Write for ArcFileLogWriter {
fn write(&mut self, buffer: &[u8]) -> std::result::Result<usize, std::io::Error> {
(*self.0).plain_write(buffer)
}
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
LogWriter::flush(&*self.0)
}
}
/// Handle to the `FileLogWriter` in an `ArcFileLogWriter`
/// that shuts down the `FileLogWriter` in its `Drop` implementation.
pub struct FileLogWriterHandle(Arc<FileLogWriter>);
impl Drop for FileLogWriterHandle {
fn drop(&mut self) {
self.0.shutdown();
}
}