Dynamic File Descriptor

Hello,
I am quite new to Rust and Programming in general.
My Goal is to create a Library for logging for practice and because none of the once I have found really have the Features I want to have.
I want to have Macros like info!(), warn!(), error!(), debug!(), trace!() and you can Configure the Logger in a Config file.
In this Config file you can define if for example the info!() macro should write to Stdout or to a File Descriptor, but I cannot get it to work how would I make this?

It should look like this but I am not sure if this is even possible

macro_rules! info {
    () => {
        println!("{}-INFO:", get_ts())
    };
    ($($arg:tt)*) => {{
        writeln!(FD, "{}-INFO: {}",get_ts(), format_args!($($arg)*));
    }};
}

EDIT:
I have found a solution which works for me, it may be not the best one but ok.
I have created a trait which impls File and Stdout

trait MyWriter: Write {}
impl MyWriter for io::Stdout {}
impl MyWriter for File {}

and then just created a function which will return my anything which impls my trait

fn get_fd() -> impl MyWriter {
    let fd = std::fs::File::create("test.txt").unwrap();
    fd
}

and I use it in the marcro like this

#[macro_export]
macro_rules! info {
    () => {
        println!("{}-INFO:", get_ts())
    };
    ($($arg:tt)*) => {{
        let mut fd = get_fd();
        let _ = writeln!(fd, "{}-INFO: {}",get_ts(), format_args!($($arg)*));
    }};
}

If anybody has a better Idea I am open but this works for me

What prevents you from just using std::io::Write?

1 Like

Thank you for pointing that out, I just have not thought about that it fits perfect.

writeln!(out, fmt, ...args) expands to out.write_fmt(format_args_nl!(fmt, ...args)) (though format_args_nl! is unstable), where write_fmt might be std::io::Write::write_fmt, std::fmt::Write::write_fmt or anything else that happens to be in scope at the use-site. You probably want to use an inherent impl method (impl Type {}, not impl Trait for Type {}), or call any other method directly on FD.

Your FD is also not looked up at the definition site, so you will need to provide the fully qualified path to it (eg with $crate::some::path::FD). Statics are not safely mutable so you can change it on the fly, so you'll need a Mutex.

If you want anything writable stored in there, you'll need it to be a dyn Write, but since it's in a static accessible from any thread, it also needs Send + Sync, and to be boxed as you don't know the size. But since it's boxed you can't initialize it statically, so it needs to be wrapped in Option.

All up, something like this:

pub static OUTPUT: Mutex<Option<Box<dyn std::io::Write + Send + Sync>>> = Mutex::new(None);

macro_rules! info {
  ($($args: tt),*) => {
    ::std::io::Write::write_fmt( // fully qualify the call to write_fmt you want
    $crate::OUTPUT
      .lock() // lock the Mutex 
      .unwrap() // remove the Result, assuming the lock won't be poisoned
      .get_or_insert_with(get_default_output),
      format_args!($($args),*)
  }
}

This uses Option::get_or_insert_with to initialize the output if it hadn't already been set.

You should probably actually wrap up that mess of an OUTPUT type in a custom type, which would make the macro less messy, and would make it simpler to fix the missing new line (due to format_args_nl! still being unstable)

1 Like

Ok, thank you for that detailed Description.
I didn't know about that all, that helps me a lot.
I will create a custom type and change the macro in the way you suggested.