diff --git a/src/config.rs b/src/config.rs index 76e9ac8..52a3dc5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,10 +34,10 @@ enum OutputConfigOptions<'a> { Child(DispatchConfig<'a>), /// File logger - all messages sent to this will be output into the specified path. Note that /// the file will be opened appending, so nothing in the file will be overwritten. - File(&'a path::Path), + File { path: &'a path::Path, line_sep: &'a str }, /// File logger with OpenOptions - all messages will be sent to the specified file. The file /// will be opened using the specified OpenOptions. - FileOptions(&'a path::Path, &'a fs::OpenOptions), + FileOptions { path: &'a path::Path, options: &'a fs::OpenOptions, line_sep: &'a str }, /// Stdout logger - all messages sent to this will be printed to stdout. Stdout, /// Stderr logger - all messages sent to this will be printed to stderr. @@ -63,18 +63,53 @@ impl <'a> OutputConfig<'a> { /// Returns a file logger. All messages sent to this will be outputted to the specified path. /// Note that the file will be opened with write(true), append(true) and create(true). If you - /// need to open with other options, use `OutputConfig::file_with_options()` + /// need to open with other options, use `OutputConfig::file_with_options()`. + /// + /// Log files created using this function will use `\n` as the line separator. To specify a + /// different separator, use `file_with_line_sep`. `file(p)` behaves exactly the same as + /// `file_with_line_sep(p, "\n")` pub fn file>(path: &'a P) -> OutputConfig<'a> { - return OutputConfig(OutputConfigOptions::File(path.as_ref())); + return OutputConfig(OutputConfigOptions::File { path: path.as_ref(), line_sep: "\n" }); + } + + /// Returns a file logger. All messages sent to this will be outputted to the specified path. + /// Note that the file will be opened with write(true), append(true) and create(true). If you + /// need to open with other options, use `OutputConfig::file_with_options()`. + /// + /// Log files created using this function will use the specified separator as the newline + /// separator character. + pub fn file_with_line_sep>(path: &'a P, line_sep: &'a str) + -> OutputConfig<'a> { + return OutputConfig(OutputConfigOptions::File { path: path.as_ref(), line_sep: line_sep }); } /// Returns a file logger with OpenOptions. All messages will be sent to the specified file. /// The file will be opened using the specified OpenOptions. + /// + /// Log files created using this function will use `\n` as the line separator. To specify a + /// different separator, use `file_with_options_and_line_sep`. `file_with_options(p, o)` + /// behaves exactly the same as `file_with_options_and_line_sep(p, o, "\n")` pub fn file_with_options(path: &'a P, options: &'a fs::OpenOptions) -> OutputConfig<'a> where P: AsRef { - return OutputConfig(OutputConfigOptions::FileOptions( - path.as_ref(), options - )); + return OutputConfig(OutputConfigOptions::FileOptions { + path: path.as_ref(), + options: options, + line_sep: "\n", + }); + } + + /// Returns a file logger with OpenOptions. All messages will be sent to the specified file. + /// The file will be opened using the specified OpenOptions. + /// + /// Log files created using this function will use the specified separator as the newline + /// separator character. + pub fn file_with_options_and_line_sep(path: &'a P, options: &'a fs::OpenOptions, + line_sep: &'a str) -> OutputConfig<'a> where P: AsRef { + return OutputConfig(OutputConfigOptions::FileOptions { + path: path.as_ref(), + options: options, + line_sep: line_sep, + }); } /// Returns an stdout logger. All messages sent to this will be printed to stdout. @@ -104,10 +139,11 @@ impl <'a> IntoLog for OutputConfig<'a> { fn into_fern_logger(self) -> io::Result> { return Ok(match self.0 { OutputConfigOptions::Child(config) => try!(config.into_fern_logger()), - OutputConfigOptions::File(path) => Box::new(try!( - loggers::WriterLogger::::with_file(path))), - OutputConfigOptions::FileOptions(path, options) => Box::new(try!( - loggers::WriterLogger::::with_file_with_options(path, options))), + OutputConfigOptions::File{path, line_sep} => Box::new(try!( + loggers::WriterLogger::::with_file(path, line_sep))), + OutputConfigOptions::FileOptions{path, options, line_sep} => Box::new(try!( + loggers::WriterLogger::::with_file_with_options( + path, options, line_sep))), OutputConfigOptions::Stdout => Box::new( loggers::WriterLogger::::with_stdout()), OutputConfigOptions::Stderr => Box::new( @@ -120,10 +156,11 @@ impl <'a> IntoLog for OutputConfig<'a> { fn into_log(self) -> io::Result> { return Ok(match self.0 { OutputConfigOptions::Child(config) => try!(config.into_log()), - OutputConfigOptions::File(path) => Box::new(try!( - loggers::WriterLogger::::with_file(path))), - OutputConfigOptions::FileOptions(path, options) => Box::new(try!( - loggers::WriterLogger::::with_file_with_options(path, options))), + OutputConfigOptions::File{path, line_sep} => Box::new(try!( + loggers::WriterLogger::::with_file(path, line_sep))), + OutputConfigOptions::FileOptions{path, options, line_sep} => Box::new(try!( + loggers::WriterLogger::::with_file_with_options( + path, options, line_sep))), OutputConfigOptions::Stdout => Box::new( loggers::WriterLogger::::with_stdout()), OutputConfigOptions::Stderr => Box::new( diff --git a/src/loggers.rs b/src/loggers.rs index 1123622..8da3cba 100644 --- a/src/loggers.rs +++ b/src/loggers.rs @@ -75,38 +75,40 @@ impl log::Log for DispatchLogger { pub struct WriterLogger { writer: sync::Arc>, + line_sep: String, } impl WriterLogger { - pub fn new(writer: T) -> WriterLogger { + pub fn new(writer: T, line_sep: &str) -> WriterLogger { return WriterLogger { writer: sync::Arc::new(sync::Mutex::new(writer)), + line_sep: line_sep.to_string(), }; } pub fn with_stdout() -> WriterLogger { - return WriterLogger::new(io::stdout()); + return WriterLogger::new(io::stdout(), "\n"); } pub fn with_stderr() -> WriterLogger { - return WriterLogger::new(io::stderr()); + return WriterLogger::new(io::stderr(), "\n"); } - pub fn with_file(path: &path::Path) -> io::Result> { + pub fn with_file(path: &path::Path, line_sep: &str) -> io::Result> { return Ok(WriterLogger::new(try!(fs::OpenOptions::new().write(true).append(true) - .create(true).open(path)))); + .create(true).open(path)), line_sep)); } - pub fn with_file_with_options(path: &path::Path, options: &fs::OpenOptions) + pub fn with_file_with_options(path: &path::Path, options: &fs::OpenOptions, line_sep: &str) -> io::Result> { - return Ok(WriterLogger::new(try!(options.open(path)))); + return Ok(WriterLogger::new(try!(options.open(path)), line_sep)); } } impl api::Logger for WriterLogger { fn log(&self, msg: &str, _level: &log::LogLevel, _location: &log::LogLocation) -> Result<(), LogError> { - try!(write!(try!(self.writer.lock()), "{}\n", msg)); + try!(write!(try!(self.writer.lock()), "{}{}", msg, self.line_sep)); return Ok(()); } } diff --git a/tests/lib.rs b/tests/lib.rs index 8e80fb1..72a3042 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -54,3 +54,60 @@ fn basic_usage_test() { // manually, but it will ignore any errors when doing it automatically. temp_log_dir.close().ok().expect("Failed to clean up temporary directory"); } + +#[test] +fn custom_line_sep_test() { + // Create a temporary directory to put a log file into for testing + let temp_log_dir = tempdir::TempDir::new("fern").ok() + .expect("Failed to set up temporary directory"); + let log_file = temp_log_dir.path().join("test_custom_line_sep.log"); + + // This is done in a new scope, because why not. + { + // Create a basic logger configuration + let logger_config = fern::DispatchConfig { + format: Box::new(|msg, _level, _location| { + // This format just displays {message} + msg.to_string() + }), + // Output to stdout and the log file in the temporary directory we made above to test + output: vec![fern::OutputConfig::file_with_line_sep(&log_file, "\r\n")], + // Log all messages + level: log::LogLevelFilter::Trace, + }; + // we can't init global logger for this test as it is already initialized in the general + // usage test. + let fern_logger = fern::IntoLog::into_fern_logger(logger_config).unwrap(); + + let location = construct_fake_log_location(); + let level = log::LogLevel::Info; + + fern_logger.log("message1", &level, &location).unwrap(); + fern_logger.log("message2", &level, &location).unwrap(); + } + { + let result = { + let mut log_read = fs::File::open( + &temp_log_dir.path().join("test_custom_line_sep.log")).unwrap(); + let mut buf = String::new(); + log_read.read_to_string(&mut buf).unwrap(); + buf + }; + assert_eq!(&result, "message1\r\nmessage2\r\n"); + } + + // Just to make sure this goes smoothly - it dose this automatically if we don't .close() + // manually, but it will ignore any errors when doing it automatically. + temp_log_dir.close().ok().expect("Failed to clean up temporary directory"); +} + +/// This may be a bad idea, but it seems necessary for using fern::Logger. Maybe this should be +/// fixed. This may break with upstream changes from log, but since this is only in tests, not in +/// the actual code, it should be fine. +fn construct_fake_log_location() -> log::LogLocation { + return log::LogLocation { + __module_path: "test", + __file: "tests.rs", + __line: 0, + }; +}