Cole Reynolds
Cole Reynolds

Reputation: 433

How to implement a logger that appends to a file?

I'm trying to implement log::Log such that the call to log() appends the message to a file. This is my logger at the moment:

pub struct MyLogger {
    loglevel: LogLevelFilter,
    logfile: Option<File>,
}

And the implementation of log::Log:

impl Log for Logger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.loglevel
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            let msg = format!("{}\t| {}\t| {}", record.level(), record.target(), record.args());
            self.logfile.write_all(msg.as_bytes()).unwrap();
        }
    }
}

Which, understandably, fails because log() doesn't take a mutable reference. I can't take a mutable reference because then I wouldn't be implementing the type correctly, so what would be the idiomatic way to achieve this?

Upvotes: 1

Views: 112

Answers (1)

Shepmaster
Shepmaster

Reputation: 431769

Whenever you need to present a non-mutable interface but do mutation behind the scenes, you can use interior mutability. The common way of doing this is with something from std::cell. The docs call out this specific usecase:

because you must employ mutation to implement a trait method that was originally defined to take &self.

Specifically, I'd try to use RefCell in this case.

Unfortunately, Log requires the implementer to also be Sync + Send, but cells are not. That means we need to upgrade to something that can handle multiple threads. That something is Mutex:

extern crate log;

use std::fs::File;
use std::io::Write;
use std::sync::Mutex;
use log::{LogLevelFilter,LogMetadata,LogRecord,Log};

pub struct FileLogger {
    loglevel: LogLevelFilter,
    logfile: Option<Mutex<File>>,
}

impl Log for FileLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= self.loglevel
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            let msg = format!("{}\t| {}\t| {}", record.level(), record.target(), record.args());
            self.logfile.as_ref().map(|f| {
                f.lock().unwrap().write_all(msg.as_bytes()).unwrap()
            });
        }
    }
}

#[test]
fn it_works() {
}

Upvotes: 3

Related Questions