Skip to content

Commit

Permalink
Add configuration options to allow setting a custom line separator fo…
Browse files Browse the repository at this point in the history
…r file output.
  • Loading branch information
daboross committed May 6, 2015
1 parent 7efaa42 commit 7d1f14c
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 23 deletions.
67 changes: 52 additions & 15 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<P: ?Sized + AsRef<path::Path>>(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<P: ?Sized + AsRef<path::Path>>(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<P: ?Sized>(path: &'a P, options: &'a fs::OpenOptions)
-> OutputConfig<'a> where P: AsRef<path::Path> {
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<P: ?Sized>(path: &'a P, options: &'a fs::OpenOptions,
line_sep: &'a str) -> OutputConfig<'a> where P: AsRef<path::Path> {
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.
Expand Down Expand Up @@ -104,10 +139,11 @@ impl <'a> IntoLog for OutputConfig<'a> {
fn into_fern_logger(self) -> io::Result<Box<api::Logger>> {
return Ok(match self.0 {
OutputConfigOptions::Child(config) => try!(config.into_fern_logger()),
OutputConfigOptions::File(path) => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file(path))),
OutputConfigOptions::FileOptions(path, options) => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file_with_options(path, options))),
OutputConfigOptions::File{path, line_sep} => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file(path, line_sep))),
OutputConfigOptions::FileOptions{path, options, line_sep} => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file_with_options(
path, options, line_sep))),
OutputConfigOptions::Stdout => Box::new(
loggers::WriterLogger::<io::Stdout>::with_stdout()),
OutputConfigOptions::Stderr => Box::new(
Expand All @@ -120,10 +156,11 @@ impl <'a> IntoLog for OutputConfig<'a> {
fn into_log(self) -> io::Result<Box<log::Log>> {
return Ok(match self.0 {
OutputConfigOptions::Child(config) => try!(config.into_log()),
OutputConfigOptions::File(path) => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file(path))),
OutputConfigOptions::FileOptions(path, options) => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file_with_options(path, options))),
OutputConfigOptions::File{path, line_sep} => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file(path, line_sep))),
OutputConfigOptions::FileOptions{path, options, line_sep} => Box::new(try!(
loggers::WriterLogger::<fs::File>::with_file_with_options(
path, options, line_sep))),
OutputConfigOptions::Stdout => Box::new(
loggers::WriterLogger::<io::Stdout>::with_stdout()),
OutputConfigOptions::Stderr => Box::new(
Expand Down
18 changes: 10 additions & 8 deletions src/loggers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,38 +75,40 @@ impl log::Log for DispatchLogger {

pub struct WriterLogger<T: io::Write + Send> {
writer: sync::Arc<sync::Mutex<T>>,
line_sep: String,
}

impl <T: io::Write + Send> WriterLogger<T> {
pub fn new(writer: T) -> WriterLogger<T> {
pub fn new(writer: T, line_sep: &str) -> WriterLogger<T> {
return WriterLogger {
writer: sync::Arc::new(sync::Mutex::new(writer)),
line_sep: line_sep.to_string(),
};
}

pub fn with_stdout() -> WriterLogger<io::Stdout> {
return WriterLogger::new(io::stdout());
return WriterLogger::new(io::stdout(), "\n");
}

pub fn with_stderr() -> WriterLogger<io::Stderr> {
return WriterLogger::new(io::stderr());
return WriterLogger::new(io::stderr(), "\n");
}

pub fn with_file(path: &path::Path) -> io::Result<WriterLogger<fs::File>> {
pub fn with_file(path: &path::Path, line_sep: &str) -> io::Result<WriterLogger<fs::File>> {
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<WriterLogger<fs::File>> {
return Ok(WriterLogger::new(try!(options.open(path))));
return Ok(WriterLogger::new(try!(options.open(path)), line_sep));
}
}

impl <T: io::Write + Send> api::Logger for WriterLogger<T> {
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(());
}
}
Expand Down
57 changes: 57 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}

0 comments on commit 7d1f14c

Please sign in to comment.