6 releases

0.1.5 Oct 1, 2024
0.1.4 Aug 27, 2024

#199 in Debugging

24 downloads per month
Used in 2 crates

MIT license

41KB
752 lines

logform

Crates.io Rust

A flexible log format library designed for chaining and composing log transformations in Rust.

use logform::{align, colorize, combine, printf, timestamp, LogInfo};

pub fn initialize_and_test_formats() {
    let aligned_with_colors_and_time = combine(vec![
        colorize(),
        timestamp(),
        align(),
        printf(|info| {
            format!(
                "{} {}: {}",
                info.meta["timestamp"], info.level, info.message
            )
        }),
    ]);

    let mut info = LogInfo::new("info", "hi");
    info = aligned_with_colors_and_time.transform(info, None).unwrap();
    println!("{}", info.message);
}

LogInfo Objects

The LogInfo struct represents a single log message.

 pub struct LogInfo {
    pub level: String,
    pub message: String,
    pub meta: HashMap<String, Value>,
}

let info = LogInfo {
    level: "info".into(),                  // Level of the logging message
    message: "Hey! Log something?".into(), // Descriptive message being logged
    meta: HashMap::new(),                   // Other properties
};

//OR
let info = LogInfo::new("info", "Hey! Log something?");

//add meta
let info = LogInfo::new("info", "Hey! Log something?").add_meta("key", "value");//you can chain more

//remove meta
info.remove_meta("key");

//get meta
info.get_meta("key");

Several of the formats in logform itself add to the meta:

Property Format added by Description
timestamp timestamp() Timestamp the message was received.
ms ms() Number of milliseconds since the previous log message.

As a consumer, you may add whatever meta you wish

Understanding Formats

Formats in logform are structs that implement a transform method with the signature transform(info: LogInfo, opts: FormatOptions) -> Option<LogInfo>.

  • info: The LogInfo struct representing the log message.
  • opts: Settings(Options) specific to the current instance of the format.

They are expected to return one of two things:

  • A LogInfo Object representing a new transformed version of the info argument. The LogInfo struct is treated as immutable, meaning a new instance is created and returned with the desired modifications.
  • A None value indicating that the info argument should be ignored by the caller. (See: Filtering LogInfo Objects) below.

Creating formats is designed to be as simple as possible. To define a new format, use Format::new() and pass a closure that implements the transformation logic:transform(info: LogInfo, opts: FormatOptions).

The named Format returned can be used to create as many copies of the given Format as desired:

use logform::Format;

fn test_custom_format() {
    let volume = Format::new(|mut info: LogInfo, opts: FormatOptions| {
        if let Some(opts) = opts {
            if opts.get("yell").is_some() {
                info.message = info.message.to_uppercase();
            } else if opts.get("whisper").is_some() {
                info.message = info.message.to_lowercase();
            }
        }
        Some(info)
    });

    // `volume` is now a Format instance that can be used for transformations
    let mut scream_opts = HashMap::new();
    scream_opts.insert("yell".to_string(), "true".to_string());
    let scream = volume.clone();

    let info = LogInfo::new("info", "sorry for making you YELL in your head!");

    let result = scream.transform(info, Some(scream_opts)).unwrap();
    println!("{}", result.message);
    //SORRY FOR MAKING YOU YELL IN YOUR HEAD!

    // `volume` can be used multiple times with different options
    let mut whisper_opts = HashMap::new();
    whisper_opts.insert("whisper".to_string(), "true".to_string());
    let whisper = volume;

    let info2 = LogInfo::new("info", "WHY ARE THEY MAKING US YELL SO MUCH!");

    let result2 = whisper.transform(info2, Some(whisper_opts)).unwrap();
    println!("{}", result2.message);
    //why are they making us yell so much!
}

Combining Formats

Any number of formats may be combined into a single format using logform::combine. Since logform::combine takes no options, it returns a pre-created instance of the combined format.

use logform::{combine, simple, timestamp};

fn test_combine_formatters() {
    // Combine timestamp and simple
    let combined_formatter = combine(vec![timestamp(), simple()]);

    let info = LogInfo::new("info", "Test message").add_meta("key", "value");

    let result = combined_formatter.transform(info, None).unwrap();
    println!("{}", result.message);
}
//info: Test message {"key":"value","timestamp":"2024-08-27 02:39:15"}

Filtering LogInfo Objects

If you wish to filter out a given LogInfo Object completely, simply return None.

use logform::Format;

fn test_ignore_private() {
    let ignore_private = Format::new(|info: LogInfo, _opts: FormatOptions| {
        if let Some(private) = info.meta.get("private") {
            if private == "true" {
                return None;
            }
        }
        Some(info)
    });

    let format = ignore_private;

    let public_info = LogInfo::new("error", "Public error to share").add_meta("private", "false");

    let result = format.transform(public_info, None).unwrap();
    println!("{}", result.message);
    //Public error to share

    let private_info =
        LogInfo::new("error", "This is super secret - hide it.").add_meta("private", "true");

    let result = format.transform(private_info, None);
    println!("{:?}", result);
    // None
}

The use of logform::combine will respect any None values returned and stop the evaluation of later formats in the series. For example:

use logform::{combine, Format};

let will_never_panic = combine(vec![
    Format::new(|_info, _opts| None), // Ignores everything
    Format::new(|_info, _opts| {
        panic!("Never reached");
    }),
]);

let info = LogInfo::new("info", "wow such testing");

println!("{:?}", will_never_panic.transform(info, None));
// None

Formats

Align

The align format adds a tab character before the message.

let aligned_format = align();

Colorize

The colorize format adds colors to log levels and messages.

let colorizer = colorize()
    .with_option("colors", r#"{"info": ["blue"], "error": ["red", "bold"]}"#)
    .with_option("all", "true");

Combine

The combine format allows you to chain multiple formats together.

let combined_format = combine(vec![
    timestamp(),
    json(),
    colorize().with_option("colors", r#"{"info": ["blue"]}"#),
]);

JSON

The json format converts the log info into a JSON string.

let json_format = json();

Ms

The ms format adds the time in milliseconds since the last log message.

let ms_format = ms();

PrettyPrint

The pretty_print format provides a more readable output of the log info.

let pretty_format = pretty_print().with_option("colorize", "true");

Printf

The printf format allows you to define a custom formatting function.

let printf_format = printf(|info| {
    format!("{} - {}: {}", info.meta["timestamp"], info.level, info.message)
});

Simple

The simple format provides a basic string representation of the log info.

let simple_format = simple();

Timestamp

The timestamp format adds a timestamp to the log info.

let timestamp_format = timestamp()
    .with_option("format", "%Y-%m-%d %H:%M:%S")
    .with_option("alias", "log_time");

Uncolorize

The uncolorize format removes ANSI color codes from the log info.

let uncolorize_format = uncolorize();

Usage

To use logform in your project, add it to your Cargo.toml:

[dependencies]
logform = "0.1"

or with

cargo add logform

Then, in your Rust code:

use logform::{LogInfo, combine, timestamp, json};

let format = combine(vec![
    timestamp(),
    json(),
]);

let info = LogInfo::new("info", "Test message");
let formatted_info = format.transform(info, None).unwrap();
println!("{}", formatted_info.message);

Testing

Run the tests using:

cargo test

License

This project is licensed under the MIT License.

Acknowledgements

This library is inspired by the logform package for Node.js.

Dependencies

~4–15MB
~134K SLoC