Reputation: 433
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
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