From 9932c0861335fca8e6f4b67acad2f01db0f8f845 Mon Sep 17 00:00:00 2001 From: emabee Date: Fri, 22 Mar 2019 17:07:19 +0100 Subject: [PATCH] [0.11.2] Change API to more idiomatic parameter types, in a compatible way - Logger::directory() and o_directory(), and Logger::create_symlink() and o_create_symlink(): take Into rather than Into - same for the respective methods of FileLogWriter - implement SyslogWriter (not yet exposed, since hardly tested) --- .gitignore | 1 + CHANGELOG.md | 6 + Cargo.toml | 16 +- README.md | 4 +- benches/bench_reconfigurable.rs | 2 +- src/logger.rs | 14 +- src/writers/file_log_writer.rs | 192 +++++++++---------- src/writers/mod.rs | 10 + src/writers/syslog_writer.rs | 270 +++++++++++++++++++++++++++ tests/test_opt_files_dir_dscr_rot.rs | 8 +- tests/test_syslog.rs | 61 ++++++ 11 files changed, 469 insertions(+), 115 deletions(-) create mode 100644 src/writers/syslog_writer.rs create mode 100644 tests/test_syslog.rs diff --git a/.gitignore b/.gitignore index 7ed1074..2603ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ todo tests/logspec.toml *~ .*~ +.vscode \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 386c840..2338879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.11.2] - 2019-03-22 + +Change API to more idiomatic parameter types, in a compatible way. + +Add first implementation of a SyslogWriter. + ## [0.11.1] - 2019-03-06 Add option to write windows line endings, rather than a plain `\n`. diff --git a/Cargo.toml b/Cargo.toml index 69b3ab5..b1665aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flexi_logger" -version = "0.11.1" +version = "0.11.2" authors = ["emabee "] edition = "2018" license = "MIT/Apache-2.0" @@ -22,19 +22,25 @@ all-features = true [features] default = [] specfile = ["serde","toml","notify", "serde_derive"] +syslog_writer = ["libc", "hostname", "procinfo"] ziplogs = ["zip"] [dependencies] chrono = "0.4" -glob = "0.2" -regex = "1.0" +glob = "0.3" +hostname = {version = "0.1", optional = true} log = { version = "0.4", features = ["std"] } -serde = { version = "1.0", optional = true } -toml = { version = "0.4", optional = true } notify = { version = "4.0", optional = true } +regex = "1.1" +serde = { version = "1.0", optional = true } serde_derive = {version = "1.0", optional = true} +toml = { version = "0.4", optional = true } zip = {version = "0.5", optional = true} +[target.'cfg(unix)'.dependencies] +libc = {version = "^0.2.50", optional = true} +procinfo = {version = "0.4", optional = true} + [dev-dependencies] serde_derive = "1.0" version-sync = "0.7" diff --git a/README.md b/README.md index 4c703ee..118bdbc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Add flexi_logger to the dependencies section in your project's `Cargo.toml`, wit ```toml [dependencies] -flexi_logger = "^0.11.1" +flexi_logger = "^0.11.2" log = "0.4" ``` @@ -17,7 +17,7 @@ or, if you want to use the optional features, with ```toml [dependencies] -flexi_logger = { version = "^0.11.1", features = ["specfile", "ziplogs"] } +flexi_logger = { version = "^0.11.2", features = ["specfile", "ziplogs"] } log = "0.4" ``` diff --git a/benches/bench_reconfigurable.rs b/benches/bench_reconfigurable.rs index b397da3..98ebb58 100644 --- a/benches/bench_reconfigurable.rs +++ b/benches/bench_reconfigurable.rs @@ -17,7 +17,7 @@ fn b20_initialize_logger(_: &mut Bencher) { Logger::with_str("info") .log_to_file() .directory("log_files") - .start_reconfigurable() + .start() .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e)); } diff --git a/src/logger.rs b/src/logger.rs index 7cb5367..e7a039b 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -5,6 +5,7 @@ use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use std::collections::HashMap; #[cfg(feature = "specfile")] use std::path::Path; +use std::path::PathBuf; #[cfg(feature = "specfile")] use std::sync::mpsc::channel; use std::sync::{Arc, RwLock}; @@ -353,7 +354,7 @@ impl Logger { /// This parameter only has an effect if `log_to_file()` is used, too. /// If the specified folder does not exist, the initialization will fail. /// By default, the log files are created in the folder where the program was started. - pub fn directory>(mut self, directory: S) -> Logger { + pub fn directory>(mut self, directory: S) -> Logger { self.flwb = self.flwb.directory(directory); self } @@ -440,11 +441,12 @@ impl Logger { self } - /// The specified String will be used on linux systems to create in the current folder - /// a symbolic link to the current log file. + /// The specified path will be used on linux systems to create a symbolic link + /// to the current log file. /// + /// This method has no effect on filesystems where symlinks are not supported. /// This option only has an effect if `log_to_file()` is used, too. - pub fn create_symlink>(mut self, symlink: S) -> Logger { + pub fn create_symlink>(mut self, symlink: P) -> Logger { self.flwb = self.flwb.create_symlink(symlink); self } @@ -517,7 +519,7 @@ impl Logger { /// This parameter only has an effect if `log_to_file` is set to true. /// If the specified folder does not exist, the initialization will fail. /// With None, the log files are created in the folder where the program was started. - pub fn o_directory>(mut self, directory: Option) -> Logger { + pub fn o_directory>(mut self, directory: Option

) -> Logger { self.flwb = self.flwb.o_directory(directory); self } @@ -590,7 +592,7 @@ impl Logger { /// /// If a String is specified, it will be used on linux systems to create in the current folder /// a symbolic link with this name to the current log file. - pub fn o_create_symlink>(mut self, symlink: Option) -> Logger { + pub fn o_create_symlink>(mut self, symlink: Option

) -> Logger { self.flwb = self.flwb.o_create_symlink(symlink); self } diff --git a/src/writers/file_log_writer.rs b/src/writers/file_log_writer.rs index 955edef..6801825 100644 --- a/src/writers/file_log_writer.rs +++ b/src/writers/file_log_writer.rs @@ -3,9 +3,8 @@ use crate::logger::Cleanup; use crate::writers::log_writer::LogWriter; use crate::FlexiLoggerError; use crate::FormatFunction; -use log::Record; - use chrono::Local; +use log::Record; use std::cell::RefCell; use std::cmp::max; use std::env; @@ -14,7 +13,7 @@ use std::fs::{File, OpenOptions}; use std::io::Read; use std::io::{BufRead, BufReader, LineWriter, Write}; use std::ops::{Add, DerefMut}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Mutex; const CURRENT_INFIX: &str = "_rCURRENT"; @@ -26,13 +25,14 @@ fn number_infix(idx: u32) -> String { struct FileLogWriterConfig { format: FormatFunction, print_message: bool, - path_base: String, + directory: PathBuf, + file_basename: String, suffix: String, use_timestamp: bool, append: bool, rotate_over_size: Option, cleanup: Cleanup, - create_symlink: Option, + create_symlink: Option, use_windows_line_ending: bool, } impl FileLogWriterConfig { @@ -41,7 +41,8 @@ impl FileLogWriterConfig { FileLogWriterConfig { format: default_format, print_message: false, - path_base: String::new(), + directory: PathBuf::from("."), + file_basename: String::new(), suffix: "log".to_string(), use_timestamp: true, append: false, @@ -55,7 +56,6 @@ impl FileLogWriterConfig { /// Builder for `FileLogWriter`. pub struct FileLogWriterBuilder { - directory: Option, discriminant: Option, config: FileLogWriterConfig, } @@ -80,8 +80,8 @@ impl FileLogWriterBuilder { /// /// If the specified folder does not exist, the initialization will fail. /// By default, the log files are created in the folder where the program was started. - pub fn directory>(mut self, directory: S) -> FileLogWriterBuilder { - self.directory = Some(directory.into()); + pub fn directory>(mut self, directory: P) -> FileLogWriterBuilder { + self.config.directory = directory.into(); self } @@ -158,7 +158,7 @@ impl FileLogWriterBuilder { /// The specified String will be used on linux systems to create in the current folder /// a symbolic link to the current log file. - pub fn create_symlink>(mut self, symlink: S) -> FileLogWriterBuilder { + pub fn create_symlink>(mut self, symlink: P) -> FileLogWriterBuilder { self.config.create_symlink = Some(symlink.into()); self } @@ -172,27 +172,21 @@ impl FileLogWriterBuilder { /// Produces the FileLogWriter. pub fn instantiate(mut self) -> Result { // make sure the folder exists or create it - let s_directory: String = self.directory.unwrap_or_else(|| ".".to_string()); - let p_directory = Path::new(&s_directory); + let p_directory = Path::new(&self.config.directory); std::fs::create_dir_all(&p_directory)?; if !std::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(/*cannot fail*/).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; + self.config.file_basename = + Path::new(&arg0).file_stem().unwrap(/*cannot fail*/).to_string_lossy().to_string(); if let Some(discriminant) = self.discriminant { - self.config.path_base += &format!("_{}", discriminant); + self.config.file_basename += &format!("_{}", discriminant); } if self.config.use_timestamp { - self.config.path_base += &Local::now().format("_%Y-%m-%d_%H-%M-%S").to_string(); + self.config.file_basename += &Local::now().format("_%Y-%m-%d_%H-%M-%S").to_string(); }; Ok(FileLogWriter { @@ -217,8 +211,10 @@ impl FileLogWriterBuilder { /// /// If the specified folder does not exist, the initialization will fail. /// With None, the log files are created in the folder where the program was started. - pub fn o_directory>(mut self, directory: Option) -> FileLogWriterBuilder { - self.directory = directory.map(|d| d.into()); + pub fn o_directory>(mut self, directory: Option

) -> FileLogWriterBuilder { + self.config.directory = directory + .map(|d| d.into()) + .unwrap_or_else(|| PathBuf::from(".")); self } @@ -287,7 +283,10 @@ impl FileLogWriterBuilder { /// If a String is specified, it will be used on linux systems to create in the current folder /// a symbolic link with this name to the current log file. - pub fn o_create_symlink>(mut self, symlink: Option) -> FileLogWriterBuilder { + pub fn o_create_symlink>( + mut self, + symlink: Option, + ) -> FileLogWriterBuilder { self.config.create_symlink = symlink.map(|s| s.into()); self } @@ -299,7 +298,6 @@ struct FileLogWriterState { written_bytes: u64, // None if no rotation is desired, or else Some(idx) where idx is the highest existing rotate_idx rotate_idx: Option, - path: String, line_ending: &'static [u8], } impl FileLogWriterState { @@ -308,7 +306,7 @@ impl FileLogWriterState { 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); + let mut rotate_idx = get_highest_rotate_idx(&config); if !config.append { rotate_idx = rotate_output_file(rotate_idx, config)?; } @@ -316,10 +314,9 @@ impl FileLogWriterState { }), }; - let (line_writer, written_bytes, path) = get_linewriter(config)?; + let (line_writer, written_bytes) = get_linewriter(config)?; Ok(FileLogWriterState { line_writer: Some(line_writer), - path, written_bytes, rotate_idx, line_ending: if config.use_windows_line_ending { @@ -346,10 +343,9 @@ impl FileLogWriterState { self.line_writer = None; // close the output file self.rotate_idx = Some(rotate_output_file(self.rotate_idx.take().unwrap(), config)?); - let (line_writer, written_bytes, path) = get_linewriter(config)?; + let (line_writer, written_bytes) = get_linewriter(config)?; self.line_writer = Some(line_writer); self.written_bytes = written_bytes; - self.path = path; remove_or_zip_too_old_logfiles(&config)?; @@ -373,52 +369,52 @@ impl Write for FileLogWriterState { } } -fn get_infix_path(infix: &str, config: &FileLogWriterConfig) -> String { - let mut s_path = String::with_capacity(180) + &config.path_base; +fn get_filepath(infix: &str, config: &FileLogWriterConfig) -> PathBuf { + let mut s_filename = + String::with_capacity(config.file_basename.len() + infix.len() + 1 + config.suffix.len()) + + &config.file_basename; if config.rotate_over_size.is_some() { - s_path += infix; + s_filename += infix; }; - s_path += "."; - s_path + &config.suffix + s_filename += "."; + s_filename += &config.suffix; + let mut p_path = config.directory.to_path_buf(); + p_path.push(s_filename); + p_path } // Returns line_writer, written_bytes, path. fn get_linewriter( config: &FileLogWriterConfig, -) -> Result<(LineWriter, 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 = std::fs::metadata(&s_path)?; - metadata.len() - } else { - 0 - }, - ) - }; - Ok((line_writer, file_size, s_path)) +) -> Result<(LineWriter, u64), FlexiLoggerError> { + let p_path = get_filepath(CURRENT_INFIX, &config); + 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); + } + + Ok(( + LineWriter::new( + OpenOptions::new() + .write(true) + .create(true) + .append(config.append) + .truncate(!config.append) + .open(&p_path)?, + ), + if config.append { + let metadata = std::fs::metadata(&p_path)?; + metadata.len() + } else { + 0 + }, + )) } -fn get_highest_rotate_idx(path_base: &str, suffix: &str) -> u32 { - match list_of_log_and_zip_files(path_base, suffix) { +fn get_highest_rotate_idx(config: &FileLogWriterConfig) -> u32 { + match list_of_log_and_zip_files(config) { Err(e) => { eprintln!("Listing files failed with {}", e); 0 @@ -442,15 +438,15 @@ fn get_highest_rotate_idx(path_base: &str, suffix: &str) -> u32 { } fn list_of_log_and_zip_files( - path_base: &str, - suffix: &str, + config: &FileLogWriterConfig, ) -> Result, FlexiLoggerError> { let fn_pattern = String::with_capacity(180) - .add(path_base) + .add(&std::ffi::OsString::from(&config.directory).to_string_lossy()) + .add(&config.file_basename) .add("_r[0-9][0-9][0-9][0-9][0-9]*") .add("."); - let log_pattern = fn_pattern.clone().add(suffix); + let log_pattern = fn_pattern.clone().add(&config.suffix); let zip_pattern = fn_pattern.add("zip"); Ok(glob::glob(&log_pattern)?.chain(glob::glob(&zip_pattern)?)) } @@ -467,7 +463,7 @@ fn remove_or_zip_too_old_logfiles(config: &FileLogWriterConfig) -> Result<(), Fl Cleanup::KeepLogAndZipFiles(log_limit, zip_limit) => (log_limit, zip_limit), }; // list files by name, in ascending order - let mut file_list: Vec<_> = list_of_log_and_zip_files(&config.path_base, &config.suffix)? + let mut file_list: Vec<_> = list_of_log_and_zip_files(&config)? .filter_map(|gr| gr.ok()) .collect(); file_list.sort_unstable(); @@ -517,8 +513,8 @@ fn rotate_output_file( // current-file must be closed already // move it to the name with the next rotate_idx match std::fs::rename( - get_infix_path(CURRENT_INFIX, config), - get_infix_path(&number_infix(rotate_idx), config), + get_filepath(CURRENT_INFIX, config), + get_filepath(&number_infix(rotate_idx), config), ) { Ok(()) => Ok(rotate_idx + 1), Err(e) => { @@ -544,7 +540,6 @@ impl FileLogWriter { /// Instantiates a builder for `FileLogWriter`. pub fn builder() -> FileLogWriterBuilder { FileLogWriterBuilder { - directory: None, discriminant: None, config: FileLogWriterConfig::default(), } @@ -559,9 +554,7 @@ impl FileLogWriter { // don't use this function in productive code - it exists only for flexi_loggers own tests #[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 path = get_filepath(CURRENT_INFIX, &self.config); let f = File::open(path).unwrap(); let mut reader = BufReader::new(f); @@ -592,9 +585,9 @@ impl FileLogWriter { impl LogWriter for FileLogWriter { #[inline] fn write(&self, record: &Record) -> std::io::Result<()> { - let guard = self.state.lock().unwrap(); // : MutexGuard> - let mut state = guard.borrow_mut(); // : RefMut - let state = state.deref_mut(); // : &mut FileLogWriterState + let mr_state = self.state.lock().unwrap(); // : MutexGuard> + let mut refmut_state = mr_state.borrow_mut(); // : RefMut + let state = refmut_state.deref_mut(); // : &mut FileLogWriterState // switch to next file if necessary if let Some(rotate_over_size) = self.config.rotate_over_size { @@ -613,39 +606,40 @@ impl LogWriter for FileLogWriter { #[inline] fn flush(&self) -> std::io::Result<()> { - let guard = self.state.lock().unwrap(); - let mut state = guard.borrow_mut(); + let mr_state = self.state.lock().unwrap(); + let mut state = mr_state.borrow_mut(); state.line_writer().flush() } } mod platform { - use std::path::Path; + use std::path::{Path, PathBuf}; - pub fn create_symlink_if_possible(link: &str, path: &Path) { + pub fn create_symlink_if_possible(link: &PathBuf, 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() { + fn linux_create_symlink(link: &PathBuf, logfile: &Path) { + if std::fs::metadata(link).is_ok() { // old symlink must be removed before creating a new one - let _ = fs::remove_file(link); + let _ = std::fs::remove_file(link); } - if let Err(e) = unix_fs::symlink(&path, link) { - eprintln!( - "Can not create symlink \"{}\" for path \"{}\": {}", - link, - &path.display(), - e - ); + if let Err(e) = std::os::unix::fs::symlink(&logfile, link) { + if !e.to_string().contains("Operation not supported") { + eprintln!( + "Cannot create symlink {:?} for logfile \"{}\": {:?}", + link, + &logfile.display(), + e + ); + } + // no error output if e.g. writing from a linux VM to a + // windows host's filesystem... } } #[cfg(not(target_os = "linux"))] - fn linux_create_symlink(_: &str, _: &Path) {} + fn linux_create_symlink(_: &PathBuf, _: &Path) {} } diff --git a/src/writers/mod.rs b/src/writers/mod.rs index ea01a42..4b39186 100644 --- a/src/writers/mod.rs +++ b/src/writers/mod.rs @@ -73,5 +73,15 @@ mod file_log_writer; mod log_writer; +#[cfg(feature = "syslog_writer")] +#[cfg(target_os = "linux")] +mod syslog_writer; + +#[cfg(feature = "syslog_writer")] +#[cfg(target_os = "linux")] +pub use self::syslog_writer::{ + LevelToSyslogSeverity, SyslogFacility, SyslogSeverity, SyslogWriter, +}; + pub use self::file_log_writer::{FileLogWriter, FileLogWriterBuilder}; pub use self::log_writer::LogWriter; diff --git a/src/writers/syslog_writer.rs b/src/writers/syslog_writer.rs new file mode 100644 index 0000000..b75118c --- /dev/null +++ b/src/writers/syslog_writer.rs @@ -0,0 +1,270 @@ +use crate::writers::log_writer::LogWriter; +use std::cell::RefCell; +use std::io::Error as IoError; +use std::io::Result as IoResult; +use std::io::{BufWriter, ErrorKind, Write}; +use std::net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket}; +use std::os::unix::net::{UnixDatagram, UnixStream}; +use std::path::Path; +use std::sync::Mutex; + +/// Syslog Severity. +/// +/// See [RFC 5424](https://datatracker.ietf.org/doc/rfc5424). +#[doc(hidden)] +pub enum SyslogSeverity { + /// System is unusable. + Emergency = 0, + /// Action must be taken immediately. + Alert = 1, + /// Critical conditions. + Critical = 2, + /// Error conditions. + Error = 3, + /// Warning conditions + Warning = 4, + /// Normal but significant condition + Notice = 5, + /// Informational messages. + Info = 6, + /// Debug-level messages. + Debug = 7, +} + +/// Syslog Facility. +/// +/// See [RFC 5424](https://datatracker.ietf.org/doc/rfc5424). +#[derive(Copy, Clone)] +#[doc(hidden)] +pub enum SyslogFacility { + /// kernel messages. + Kernel = 0 << 3, + /// user-level messages. + UserLevel = 1 << 3, + /// mail system. + MailSystem = 2 << 3, + /// system daemons. + SystemDaemons = 3 << 3, + /// security/authorization messages. + Authorization = 4 << 3, + /// messages generated internally by syslogd. + SyslogD = 5 << 3, + /// line printer subsystem. + LinePrinter = 6 << 3, + /// network news subsystem. + News = 7 << 3, + /// UUCP subsystem. + Uucp = 8 << 3, + /// clock daemon. + Clock = 9 << 3, + /// security/authorization messages. + Authorization2 = 10 << 3, + /// FTP daemon. + Ftp = 11 << 3, + /// NTP subsystem. + Ntp = 12 << 3, + /// log audit. + LogAudit = 13 << 3, + /// log alert. + LogAlert = 14 << 3, + /// clock daemon (note 2). + Clock2 = 15 << 3, + /// local use 0 (local0). + LocalUse0 = 16 << 3, + /// local use 1 (local1). + LocalUse1 = 17 << 3, + /// local use 2 (local2). + LocalUse2 = 18 << 3, + /// local use 3 (local3). + LocalUse3 = 19 << 3, + /// local use 4 (local4). + LocalUse4 = 20 << 3, + /// local use 5 (local5). + LocalUse5 = 21 << 3, + /// local use 6 (local6). + LocalUse6 = 22 << 3, + /// local use 7 (local7). + LocalUse7 = 23 << 3, +} + +/// Signature for a custom mapping function that maps the rust log levels to +/// values of the syslog Severity. +#[doc(hidden)] +pub type LevelToSyslogSeverity = fn(log::Level) -> SyslogSeverity; + +fn default_mapping(level: log::Level) -> SyslogSeverity { + match level { + log::Level::Error => SyslogSeverity::Error, + log::Level::Warn => SyslogSeverity::Error, + log::Level::Info => SyslogSeverity::Error, + log::Level::Debug => SyslogSeverity::Error, + log::Level::Trace => SyslogSeverity::Error, + } +} + +/// Writes log messages to the syslog. +/// +/// See [RFC 5424](https://datatracker.ietf.org/doc/rfc5424). +#[doc(hidden)] +pub struct SyslogWriter { + hostname: String, + process: String, + pid: i32, + facility: SyslogFacility, + message_id: String, + map_loglevel_to_severity: LevelToSyslogSeverity, + syslog: Mutex>, +} +impl SyslogWriter { + pub fn try_path>( + path: P, + facility: SyslogFacility, + message_id: String, + map_loglevel_to_severity: Option, + ) -> IoResult> { + Ok(Box::new(SyslogWriter::try_new( + Syslog::try_path(path)?, + facility, + message_id, + map_loglevel_to_severity, + )?)) + } + + pub fn try_udp( + local: T, + server: T, + facility: SyslogFacility, + message_id: String, + map_loglevel_to_severity: Option, + ) -> IoResult> { + Ok(Box::new(SyslogWriter::try_new( + Syslog::try_udp(local, server)?, + facility, + message_id, + map_loglevel_to_severity, + )?)) + } + + pub fn try_tcp( + server: T, + facility: SyslogFacility, + message_id: String, + map_loglevel_to_severity: Option, + ) -> IoResult> { + Ok(Box::new(SyslogWriter::try_new( + Syslog::try_tcp(server)?, + facility, + message_id, + map_loglevel_to_severity, + )?)) + } + + // Factory method. + // + // Regarding the parameters `facility` and `message_id`, + // see [RFC 5424](https://datatracker.ietf.org/doc/rfc5424). + fn try_new( + syslog: Syslog, + facility: SyslogFacility, + message_id: String, + map_loglevel_to_severity: Option, + ) -> IoResult { + Ok(SyslogWriter { + hostname: hostname::get_hostname().unwrap_or_else(|| "".to_string()), + process: std::env::args() + .next() + .ok_or_else(|| IoError::new(ErrorKind::Other, "".to_owned()))?, + pid: procinfo::pid::stat_self()?.pid, + facility, + message_id, + map_loglevel_to_severity: map_loglevel_to_severity.unwrap_or_else(|| default_mapping), + syslog: Mutex::new(RefCell::new(syslog)), + }) + } +} + +impl LogWriter for SyslogWriter { + fn write(&self, record: &log::Record) -> IoResult<()> { + let mr_syslog = self.syslog.lock().unwrap(); + let mut syslog = mr_syslog.borrow_mut(); + + let severity = (self.map_loglevel_to_severity)(record.level()); + write!( + syslog, + "<{}> {} 1 {} {} {} {} - {}", + self.facility as u8 | severity as u8, + chrono::Utc::now().to_rfc3339(), // or Local? + self.hostname, + self.process, + self.pid, + self.message_id, + &record.args() + ) + } + + fn flush(&self) -> IoResult<()> { + let mr_syslog = self.syslog.lock().unwrap(); + let mut syslog = mr_syslog.borrow_mut(); + syslog.flush()?; + Ok(()) + } +} + +pub(crate) enum Syslog { + Local(UnixDatagram), + UnixStream(BufWriter), + Udp(UdpSocket, SocketAddr), + Tcp(BufWriter), +} +impl Syslog { + pub fn try_path>(path: P) -> IoResult { + let ud = UnixDatagram::unbound()?; + match ud.connect(&path) { + Ok(()) => Ok(Syslog::Local(ud)), + Err(ref e) if e.raw_os_error() == Some(libc::EPROTOTYPE) => Ok(Syslog::UnixStream( + BufWriter::new(UnixStream::connect(path)?), + )), + Err(e) => Err(e), + } + } + + pub fn try_udp(local: T, server: T) -> IoResult { + server + .to_socket_addrs() + .and_then(|mut addrs_iter| { + addrs_iter.next().ok_or_else(|| { + IoError::new(ErrorKind::Other, "Server address resolution failed") + }) + }) + .and_then(|server_addr| { + UdpSocket::bind(local) + .and_then(|local_socket| Ok(Syslog::Udp(local_socket, server_addr))) + }) + } + + pub fn try_tcp(server: T) -> IoResult { + TcpStream::connect(server).and_then(|tcpstream| Ok(Syslog::Tcp(BufWriter::new(tcpstream)))) + } +} + +impl Write for Syslog { + fn write(&mut self, message: &[u8]) -> IoResult { + match *self { + Syslog::Local(ref ud) => ud.send(&message[..]), + Syslog::UnixStream(ref mut w) => w + .write(&message[..]) + .and_then(|sz| w.write_all(&[0; 1]).map(|_| sz)), + Syslog::Udp(ref socket, ref addr) => socket.send_to(&message[..], addr), + Syslog::Tcp(ref mut w) => w.write(&message[..]), + } + } + + fn flush(&mut self) -> IoResult<()> { + match *self { + Syslog::Local(_) => Ok(()), + Syslog::UnixStream(ref mut w) => w.flush(), + Syslog::Udp(_, _) => Ok(()), + Syslog::Tcp(ref mut w) => w.flush(), + } + } +} diff --git a/tests/test_opt_files_dir_dscr_rot.rs b/tests/test_opt_files_dir_dscr_rot.rs index 3d76d7b..9e0dc1d 100644 --- a/tests/test_opt_files_dir_dscr_rot.rs +++ b/tests/test_opt_files_dir_dscr_rot.rs @@ -30,8 +30,12 @@ fn test_opt_files_dir_dscr_rot() { mod platform { #[cfg(target_os = "linux")] pub fn check_link(link_name: &str) { - use std::fs; - assert!(fs::metadata(link_name).is_ok()); + match std::fs::metadata(link_name) { + Err(e) => { + eprintln!("error with symlink: {}",e) + } + Ok(_md) => {}, + } } #[cfg(not(target_os = "linux"))] diff --git a/tests/test_syslog.rs b/tests/test_syslog.rs new file mode 100644 index 0000000..a3f4376 --- /dev/null +++ b/tests/test_syslog.rs @@ -0,0 +1,61 @@ +#[cfg(target_os = "linux")] +#[cfg(feature = "syslog_writer")] +mod test { + use flexi_logger::writers::{SyslogFacility, SyslogWriter}; + use flexi_logger::{detailed_format, Logger}; + use log::*; + + #[macro_use] + mod macros { + #[macro_export] + macro_rules! syslog_error { + ($($arg:tt)*) => ( + error!(target: "{Syslog,_Default}", $($arg)*); + ) + } + } + + #[test] + fn test_syslog() { + // more complex just to support validation: + let syslog_writer = SyslogWriter::try_udp( + "localhost:0", + "localhost:514", + SyslogFacility::LocalUse0, + "JustForTest".to_owned(), + None, + ) + .unwrap(); + let log_handle = Logger::with_str("info") + .format(detailed_format) + .print_message() + .log_to_file() + .add_writer("Syslog", syslog_writer) + .start() + .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e)); + + // Explicitly send logs to different loggers + error!(target : "{Syslog}", "This is a syslog-relevant error message"); + error!(target : "{Syslog,_Default}", "This is a syslog- and log-relevant error message"); + + // Nicer: use explicit macros + syslog_error!("This is another syslog- and log-relevant error message"); + debug!("This is a warning message"); + debug!("This is a debug message - you must not see it!"); + trace!("This is a trace message - you must not see it!"); + + // Verification: + #[cfg_attr(rustfmt, rustfmt_skip)] + log_handle.validate_logs(&[ + ("ERROR", "syslog", "a syslog- and log-relevant error message"), + ("ERROR", "syslog", "another syslog- and log-relevant error message"), + ]); + // #[cfg_attr(rustfmt, rustfmt_skip)] + // sec_handle.validate_logs(&[ + // ("ERROR", "multi_logger", "security-relevant error"), + // ("ERROR", "multi_logger", "a security-relevant alert"), + // ("ERROR", "multi_logger", "security-relevant alert and log message"), + // ("ERROR", "multi_logger", "another security-relevant alert"), + // ]); + } +}