use std::{fs::OpenOptions, io::Write, ops::Add, path::Path};
use tracing::dispatcher;
use crate::{ok, DisplayPreference, TracingConfig, WriterConfig};
pub mod tracing_config_options {
use super::{Add, DisplayPreference, TracingConfig, WriterConfig};
pub const DEFAULT_LOG_FILE_NAME: &str = "log.txt";
impl From<tracing::Level> for TracingConfig {
fn from(level: tracing::Level) -> Self {
Self {
level_filter: level.into(),
writer_config: WriterConfig::File(DEFAULT_LOG_FILE_NAME.to_string()),
}
}
}
impl From<tracing_core::LevelFilter> for TracingConfig {
fn from(level_filter: tracing_core::LevelFilter) -> Self {
Self {
level_filter,
writer_config: WriterConfig::File(DEFAULT_LOG_FILE_NAME.to_string()),
}
}
}
impl From<DisplayPreference> for TracingConfig {
fn from(preferred_display: DisplayPreference) -> Self {
Self {
level_filter: tracing_core::LevelFilter::DEBUG,
writer_config: WriterConfig::Display(preferred_display),
}
}
}
impl From<WriterConfig> for TracingConfig {
fn from(writer_config: WriterConfig) -> Self {
Self {
level_filter: tracing_core::LevelFilter::DEBUG,
writer_config,
}
}
}
impl Add<TracingConfig> for TracingConfig {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
level_filter: self.level_filter.max(rhs.level_filter),
writer_config: match self.writer_config {
WriterConfig::None => rhs.writer_config,
_ => self.writer_config + rhs.writer_config,
},
}
}
}
impl Add<WriterConfig> for WriterConfig {
type Output = Self;
fn add(self, rhs: WriterConfig) -> Self::Output {
use WriterConfig::{Display, DisplayAndFile, File, None};
match (self, rhs) {
(None, wc_rhs) => wc_rhs,
(wc_lhs, None) => wc_lhs,
(Display(dp_lhs), File(f_rhs)) => DisplayAndFile(dp_lhs, f_rhs),
(File(f_lhs), Display(dp_rhs)) => DisplayAndFile(dp_rhs, f_lhs),
(Display(_dp_lhs), DisplayAndFile(dp_rhs, f_rhs)) => {
DisplayAndFile(dp_rhs, f_rhs)
}
(Display(_dp_lhs), Display(dp_rhs)) => Display(dp_rhs),
(File(_f_lhs), File(f_rhs)) => File(f_rhs),
(File(_f_lhs), DisplayAndFile(dp_rhs, f_rhs)) => {
DisplayAndFile(dp_rhs, f_rhs)
}
(DisplayAndFile(_dp_lhs, f_rhs), Display(dp_rhs)) => {
DisplayAndFile(dp_rhs, f_rhs)
}
(DisplayAndFile(dp_lhs, _f_lhs), File(f_rhs)) => {
DisplayAndFile(dp_lhs, f_rhs)
}
(DisplayAndFile(_dp_lhs, _f_lhs), DisplayAndFile(dp_rhs, f_rhs)) => {
DisplayAndFile(dp_rhs, f_rhs)
}
}
}
}
#[cfg(test)]
mod tests_add_writer_configs {
use crate::SharedWriter;
#[test]
fn test_add_writer_configs() {
use super::*;
let (line_sender, _) = tokio::sync::mpsc::channel(1_000);
let shared_writer = SharedWriter::new(line_sender);
let dp_shared = DisplayPreference::SharedWriter(shared_writer);
let dp_stdout = DisplayPreference::Stdout;
let dp_stderr = DisplayPreference::Stderr;
let fname = "log.txt".to_string();
let none = WriterConfig::None;
let display_stdout = WriterConfig::Display(dp_stdout.clone());
let display_stderr = WriterConfig::Display(dp_stderr.clone());
let display_sharedwriter = WriterConfig::Display(dp_shared.clone());
let file = WriterConfig::File(fname.clone());
let display_stdout_and_file =
WriterConfig::DisplayAndFile(dp_stdout, fname.clone());
let display_stderr_and_file =
WriterConfig::DisplayAndFile(dp_stderr, fname.clone());
let display_sharedwriter_and_file =
WriterConfig::DisplayAndFile(dp_shared, fname.clone());
assert_eq!(none.clone() + none.clone(), none);
assert_eq!(display_stdout.clone() + none.clone(), display_stdout);
assert_eq!(none.clone() + display_stdout.clone(), display_stdout);
assert_eq!(
display_stdout.clone() + display_stderr.clone(),
display_stderr
);
assert_eq!(
display_stderr.clone() + display_stdout.clone(),
display_stdout
);
assert_eq!(file.clone() + none.clone(), file);
assert_eq!(none.clone() + file.clone(), file);
assert_eq!(
display_stdout_and_file.clone() + none.clone(),
display_stdout_and_file
);
assert_eq!(
none.clone() + display_stdout_and_file.clone(),
display_stdout_and_file
);
assert_eq!(
display_stdout_and_file.clone() + display_stderr.clone(),
display_stderr_and_file
);
assert_eq!(
display_stderr.clone() + display_stdout_and_file.clone(),
display_stdout_and_file
);
assert_eq!(
display_sharedwriter.clone() + none.clone(),
display_sharedwriter
);
assert_eq!(
none.clone() + display_sharedwriter.clone(),
display_sharedwriter
);
assert_eq!(
display_sharedwriter.clone() + display_stdout.clone(),
display_stdout
);
assert_eq!(
display_stdout.clone() + display_sharedwriter.clone(),
display_sharedwriter
);
assert_eq!(
display_sharedwriter.clone() + file.clone(),
display_sharedwriter_and_file.clone()
);
assert_eq!(
file.clone() + display_sharedwriter.clone(),
display_sharedwriter_and_file.clone()
);
assert_eq!(
display_stdout.clone() + display_stderr_and_file.clone(),
display_stderr_and_file
);
assert_eq!(
display_stderr_and_file.clone() + display_stdout.clone(),
display_stdout_and_file
);
assert_eq!(
display_stdout.clone() + display_stderr.clone(),
display_stderr
);
assert_eq!(
display_stderr.clone() + display_stdout.clone(),
display_stdout
);
assert_eq!(
display_sharedwriter.clone() + display_stdout_and_file.clone(),
display_stdout_and_file
);
assert_eq!(
display_stdout_and_file.clone() + display_sharedwriter.clone(),
display_sharedwriter_and_file
);
assert_eq!(
file.clone() + display_stderr_and_file.clone(),
display_stderr_and_file
);
assert_eq!(
display_stderr_and_file.clone() + file.clone(),
display_stderr_and_file
);
assert_eq!(
file.clone() + display_stdout.clone(),
display_stdout_and_file
);
assert_eq!(
display_stdout_and_file.clone() + file.clone(),
display_stdout_and_file
);
assert_eq!(
display_sharedwriter_and_file.clone() + file.clone(),
display_sharedwriter_and_file
);
assert_eq!(
file.clone() + display_sharedwriter_and_file.clone(),
display_sharedwriter_and_file
);
assert_eq!(
display_stdout_and_file.clone() + display_stderr_and_file.clone(),
display_stderr_and_file
);
assert_eq!(
display_sharedwriter_and_file.clone() + display_stdout_and_file.clone(),
display_stdout_and_file
);
}
}
}
pub fn try_initialize_logging_global(
arg_options: impl Into<TracingConfig>,
) -> miette::Result<()> {
let config: TracingConfig = arg_options.into();
if matches!(config.get_level_filter(), tracing_core::LevelFilter::OFF) {
return ok!();
}
config.install_global()
}
pub fn try_initialize_logging_thread_local(
arg_options: impl Into<TracingConfig>,
) -> miette::Result<Option<dispatcher::DefaultGuard>> {
let config: TracingConfig = arg_options.into();
if matches!(config.get_level_filter(), tracing_core::LevelFilter::OFF) {
return Ok(None);
}
config.install_thread_local().map(Some)
}
pub fn file_log(file_path: Option<&Path>, message: &str) {
let file_path =
file_path.unwrap_or(Path::new(tracing_config_options::DEFAULT_LOG_FILE_NAME));
let message = if message.ends_with('\n') {
message.to_string()
} else {
format!("{message}\n")
};
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(file_path)
.unwrap();
file.write_all(message.as_bytes()).unwrap();
}