11 releases
new 0.3.1 | Jan 12, 2025 |
---|---|
0.3.0 | Jan 5, 2025 |
0.2.0 | Jan 5, 2025 |
0.1.7 | Dec 10, 2024 |
0.1.4 | Aug 27, 2024 |
#158 in Debugging
247 downloads per month
Used in 2 crates
77KB
1.5K
SLoC
logform
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_as_str("timestamp").unwrap_or(""), 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?").with_meta("key", "value");//you can chain more
//remove meta
info.without_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 theinfo
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: FilteringLogInfo
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").with_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").with_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.").with_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();
CLI
The cli
format is a combination of the colorize
and pad_levels
formats and accepts both of their options. It pads, colorize, and then formats the log message as level:message
.
use std::collections::HashMap;
use crate::{Format, LogInfo, cli};
let cli_format = cli()
.with_option("colors", &serde_json::to_string(&HashMap::from([("info", "blue")])).unwrap())
.with_option("filler", "*")
.with_option("all", "true");
let info = LogInfo::new("info", "my message");
let transformed_info = cli_format.transform(info, None);
println!("{:?}", transformed_info);
// Output: LogInfo { level: "\x1b[34minfo\x1b[39m", message: "\x1b[34m**my message\x1b[39m", meta: {} }
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();
Label
The label
format adds a specified label to the log message or metadata. It accepts the following options:
- label: The label to prepend to the message or store in the metadata.
- message (optional): Determines where the label is added.
- true (default): Adds the label before the message.
- false: Adds the label to the
meta
field instead of the message.
use std::collections::HashMap;
use crate::{Format, LogInfo, label};
let label_format = label();
let info = LogInfo::new("info", "Test message");
let mut opts = HashMap::new();
opts.insert("label".to_string(), "MY_LABEL".to_string());
opts.insert("message".to_string(), "true".to_string());
let result = label_format.transform(info, Some(opts)).unwrap();
println!("{:?}", result);
// Output: LogInfo { level: "info", message: "[MY_LABEL] Test message", meta: {} }
opts.insert("message".to_string(), "false".to_string());
let result_meta = label_format.transform(info, Some(opts)).unwrap();
println!("{:?}", result_meta);
// Output: LogInfo { level: "info", message: "Test message", meta: {"label": "MY_LABEL"} }
Logstash
The logstash
format converts the log info into a Logstash-compatible JSON string.
use logform::{ combine, logstash, timestamp };
let logstash_format = combine(vec![timestamp(), logstash()]);
let mut info = LogInfo::new("info", "my message");
let formatted_info = logstash_format.transform(info, None).unwrap();
println!("{}", formatted_info.message);
// {"@message":"my message","@timestamp":"2025-01-12T13:10:05.202213+00:00","@fields":{"level":"info"}}
Metadata
The metadata
format collects metadata from the log and adds it to the specified key. It defaults to using the key "metadata"
, and includes all the keys in info.meta
unless exclusions are specified.
It accepts the following options:
- key (optional): Name of the key used for the metadata. Default is
"metadata"
. - fillExcept (optional): Comma-separated list of keys to exclude from the metadata object.
- fillWith (optional): Comma-separated list of keys to include in the metadata object.
By default, all keys in info.meta
are collected into the metadata, except those specified in fillExcept
.
use logform::{metadata, LogInfo};
use serde_json::json;
use std::collections::HashMap;
let metadata_format = metadata();
let mut info = LogInfo::new("info", "Test message");
info.meta.insert("key1".to_string(), "value1".into());
info.meta.insert("key2".to_string(), "value2".into());
// Example 1: Default behavior (no options given)
let result = metadata_format.transform(info.clone(), None).unwrap();
println!("{:?}", result);
// Output: LogInfo { level: "info", message: "Test message", meta: {"metadata": Object {"key1": String("value1"), "key2": String("value2")}} }
// Example 2: Only include `key1` in metadata
let mut opts = HashMap::new();
opts.insert("fillWith".to_string(), "key1".to_string());
let result = metadata_format.transform(info.clone(), Some(opts)).unwrap();
println!("{:?}", result);
// Output: LogInfo { level: "info", message: "Test message", meta: {"key2": String("value2"), "metadata": Object {"key1": String("value1")}} }
// Example 3: Exclude only `key1` from metadata
let mut opts = HashMap::new();
opts.insert("fillExcept".to_string(), "key1".to_string());
let result = metadata_format.transform(info, Some(opts)).unwrap();
println!("{:?}", result.meta);
// Output: LogInfo { level: "info", message: "Test message", meta: {"metadata": Object {"key2": String("value2")}, "key1": String("value1")} }
PadLevels
The pad_levels
format pads levels to be the same length.
use std::collections::HashMap;
use crate::{Format, LogInfo, pad_levels};
let pad_levels_format = pad_levels();
let info = LogInfo::new("info", "my message");
let transformed_info = pad_levels_format.transform(info, None);
println!("{:?}", transformed_info);
// Output: LogInfo { level: "info", message: " my message", meta: {} }
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_as_str("timestamp").unwrap_or(""), 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.3"
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–12MB
~126K SLoC