rustsyslog

How can I "template" or "functionalise" repeated behaviour?


In Rust I have the following syslog code:

use syslog::{Facility, Formatter3164};

fn main() {
    let formatter = Formatter3164 {
        facility: Facility::LOG_USER,
        hostname: None,
        process: "syslog_test".into(),
        pid: 0,
    };

    let mut logger = syslog::unix(formatter).expect("Could not connect to syslog");

    let sum1 = 2 + 3;
    logger.info(&format!("Sum 1: 2 + 3 = {}", sum1)).expect("Failed to log");

    let sum2 = 10 - 4;
    logger.warning(&format!("Sum 2: 10 - 4 = {}", sum2)).expect("Failed to log");

    let sum3 = 6 * 7;
    logger.err(&format!("Sum 3: 6 * 7 = {}", sum3)).expect("Failed to log");
}

In C++ I would usually base class or wrap this up in a function so I don't need to keep repeating the .expect("failed to log") and use macros to provide filename and line number etc.

How would I go about this in Rust? I attempted a few things, like functionalising it, but passing the logger around as a parameter proved tricky (for me).

I wanted to have a function that takes: logger, log level, message text, or such.


Solution

  • To access the metadata of file!, line! and/or column! with the correct source location you'll have to use a macro:

    macro_rules! log {
        ($logger: expr, $level: ident, $message: literal $(, $args: tt)* $(,)?) => {
            $logger.$level(format!($message $(, $args)*)).unwrap_or_else(|e| {
                panic!(
                    "{file}:{line}:{column}: Failed to log: {e}",
                    file = file!(),
                    line = line!(),
                    column = column!(),
                )
            })
        };
    }
    

    Note: I use unwrap_or_else(|e| panic!(…)) instead of expect to avoid an allocation in the happy path see clippy::expect_fun_call.

    You can add convenience macros to directly invoke a method for example an info!:

    macro_rules! info {
        ($logger: expr, $message: literal $(, $args: tt)* $(,)?) => {
            log!($logger, info, $message $(, $args)*)
        };
    }
    

    Usage:

    let sum1 = 2 + 3;
    log!(logger, info, "Sum 1: 2 + 3 = {sum1}");
    info!(logger, "Foobar");