diff --git a/Cargo.lock b/Cargo.lock index ed78944b..8424e2a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1317,6 +1317,7 @@ dependencies = [ "derive_more", "faccess", "ffplayout-lib", + "flexi_logger", "futures-util", "home", "jsonwebtoken", @@ -1325,6 +1326,7 @@ dependencies = [ "local-ip-address", "log", "once_cell", + "paris", "parking_lot", "path-clean", "rand", @@ -1499,6 +1501,21 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flexi_logger" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f248c29a6d4bc5d065c9e9068d858761a0dcd796759f7801cc14db35db23abd8" +dependencies = [ + "chrono", + "glob", + "is-terminal", + "log", + "nu-ansi-term", + "regex", + "thiserror", +] + [[package]] name = "flume" version = "0.11.0" @@ -2021,6 +2038,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -2380,6 +2408,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "num-bigint" version = "0.4.5" diff --git a/ffplayout/Cargo.toml b/ffplayout/Cargo.toml index f5492d7a..aeee97e9 100644 --- a/ffplayout/Cargo.toml +++ b/ffplayout/Cargo.toml @@ -26,6 +26,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock", "std" clap = { version = "4.3", features = ["derive"] } derive_more = "0.99" faccess = "0.2" +flexi_logger = { version = "0.28", features = ["kv", "colors"] } futures-util = { version = "0.3", default-features = false, features = ["std"] } home = "0.5" jsonwebtoken = "9" @@ -34,6 +35,7 @@ lexical-sort = "0.3" local-ip-address = "0.6" log = { version = "0.4", features = ["std", "serde", "kv", "kv_std", "kv_sval", "kv_serde"] } once_cell = "1.18" +paris = "1.5" parking_lot = "0.12" path-clean = "1.0" rand = "0.8" diff --git a/ffplayout/examples/flexi.rs b/ffplayout/examples/flexi.rs new file mode 100644 index 00000000..08ef904d --- /dev/null +++ b/ffplayout/examples/flexi.rs @@ -0,0 +1,62 @@ +use std::fmt; + +use flexi_logger::{ + filter::{LogLineFilter, LogLineWriter}, + DeferredNow, FlexiLoggerError, FormatFunction, Logger, +}; +use log::info; +use log::kv::Key; + +use flexi_logger::writers::LogWriter; +use std::{ + io::{Error, ErrorKind}, + sync::{Arc, Mutex}, +}; + +#[derive(Debug)] +enum Target { + Terminal, + File, +} + +impl Target { + fn as_str(&self) -> &'static str { + match *self { + Target::Terminal => "terminal", + Target::File => "file", + } + } +} + +impl fmt::Display for Target { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Target::Terminal => write!(f, "terminal"), + Target::File => write!(f, "file"), + } + } +} + +pub struct Console; +impl LogLineFilter for Console { + fn write( + &self, + now: &mut DeferredNow, + record: &log::Record, + log_line_writer: &dyn LogLineWriter, + ) -> std::io::Result<()> { + println!("{:?}", record.key_values().get(Key::from_str("target"))); + log_line_writer.write(now, record)?; + Ok(()) + } +} + +fn main() -> Result<(), FlexiLoggerError> { + Logger::try_with_str("debug")? + .filter(Box::new(Console)) + .start()?; + + info!(target = Target::Terminal.as_str(); "info logging"); + + Ok(()) +} diff --git a/ffplayout/examples/flexi2.rs b/ffplayout/examples/flexi2.rs new file mode 100644 index 00000000..2df50000 --- /dev/null +++ b/ffplayout/examples/flexi2.rs @@ -0,0 +1,145 @@ +use log::*; +use std::io::Write; + +use flexi_logger::writers::{FileLogWriter, LogWriter}; +use flexi_logger::{Age, Cleanup, Criterion, DeferredNow, FileSpec, Logger, Naming}; +use paris::formatter::colorize_string; + +pub struct LogMailer; + +impl LogWriter for LogMailer { + fn write(&self, now: &mut DeferredNow, record: &Record<'_>) -> std::io::Result<()> { + println!( + "[{}] [{:>5}] Mail logger: {:?}", + now.now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + record.args() + ); + Ok(()) + } + fn flush(&self) -> std::io::Result<()> { + Ok(()) + } +} + +pub struct LogConsole; + +impl LogWriter for LogConsole { + fn write(&self, now: &mut DeferredNow, record: &Record<'_>) -> std::io::Result<()> { + console_formatter(&mut std::io::stderr(), now, record)?; + + println!(); + Ok(()) + } + fn flush(&self) -> std::io::Result<()> { + Ok(()) + } +} + +pub fn file_logger(to_file: bool) -> Box { + if to_file { + Box::new( + FileLogWriter::builder( + FileSpec::default() + .suppress_timestamp() + // .directory("/var/log") + .basename("ffplayout"), + ) + .append() + .format(file_formatter) + .rotate( + Criterion::Age(Age::Day), + Naming::Timestamps, + Cleanup::KeepLogFiles(7), + ) + .print_message() + .try_build() + .unwrap(), + ) + } else { + Box::new(LogConsole) + } +} +// Define a macro for writing messages to the alert log and to the normal log +#[macro_use] +mod macros { + #[macro_export] + macro_rules! file_error { + ($($arg:tt)*) => ( + error!(target: "{File}", $($arg)*); + ) + } +} + +pub fn console_formatter( + w: &mut dyn Write, + now: &mut DeferredNow, + record: &Record, +) -> std::io::Result<()> { + let timestamp = colorize_string(format!( + "[{}]", + now.now().format("%Y-%m-%d %H:%M:%S%.6f") + )); + + let level = match record.level() { + Level::Debug => colorize_string("[DEBUG]"), + Level::Error => colorize_string("[ERROR]"), + Level::Info => colorize_string("[ INFO]"), + Level::Trace => colorize_string("[TRACE]"), + Level::Warn => colorize_string("[ WARN]"), + }; + + write!( + w, + "{} {} {}", + timestamp, + level, + colorize_string(record.args().to_string()), + ) +} + +pub fn file_formatter( + w: &mut dyn Write, + now: &mut DeferredNow, + record: &Record, +) -> std::io::Result<()> { + let timestamp = format!("[{}]", now.now().format("%Y-%m-%d %H:%M:%S%.6f")); + + let level = match record.level() { + Level::Debug => "[DEBUG]", + Level::Error => "[ERROR]", + Level::Info => "[ INFO]", + Level::Trace => "[TRACE]", + Level::Warn => "[ WARN]", + }; + + write!(w, "{} {} {}", timestamp, level, record.args()) +} + +fn main() { + let to_file = true; + + Logger::try_with_str("trace") + .expect("LogSpecification String has errors") + .format(console_formatter) + .print_message() + .log_to_stderr() + .add_writer("File", file_logger(to_file)) + .add_writer("Mail", Box::new(LogMailer)) + .start() + .unwrap(); + + // Explicitly send logs to different loggers + info!(target: "{Mail}", "This logs only to Mail"); + warn!(target: "{File,Mail}", "This logs to File and Mail"); + error!(target: "{File}", "This logs only to file"); + error!(target: "{_Default}", "This logs to console"); + + file_error!("This is another file log"); + + error!("This is a normal error message"); + warn!("This is a warning"); + info!("This is an info message"); + debug!("This is an debug message"); + trace!("This is an trace message"); +}