A flexible log formatting library designed for chaining and composing log transformations in Rust.
logform provides a powerful, extensible system to transform structured log messages via composable formatters called Formats. Each format implements a common Format trait, enabling flexible composition and transformation pipelines.
use logform::{timestamp, colorize, align, Format, LogInfo};
fn main() {
// Compose multiple formats using chaining
let formatter = timestamp()
.chain(colorize())
.chain(align());
let info = LogInfo::new("info", "Hello, logform!");
// Apply the composed formatter
if let Some(transformed) = formatter.transform(info) {
println!("{}", transformed.message);
}
}At the core is the LogInfo struct representing a single log message:
pub struct LogInfo {
pub level: String,
pub message: String,
pub meta: std::collections::HashMap<String, serde_json::Value>,
}-
Create a new
LogInfo:let info = LogInfo::new("info", "User logged in");
-
Add metadata fields:
let info = info.with_meta("user_id", 12345) .with_meta("session_id", "abcde12345");
-
Remove metadata:
let info = info.without_meta("session_id");
-
Access metadata:
if let Some(serde_json::Value::Number(id)) = info.meta.get("user_id") { // use id... }
Formats implement the Format trait to transform log messages:
pub trait Format {
type Input;
/// Transforms the input log message, returning:
/// - `Some(LogInfo)` for transformed logs
/// - `None` to filter out the log (skip it)
fn transform(&self, input: Self::Input) -> Option<LogInfo>;
/// Chain two formats, applying one after another
fn chain<F>(self, next: F) -> ChainedFormat<Self, F>
where
Self: Sized,
F: Format<Input = Self::Input>,
{
ChainedFormat { first: self, next }
}
}- Transform: Modify or produce a new log from input.
- Filter (return
None): Skip processing or output for certain logs. - Chaining: Compose formats easily in sequence.
You can chain multiple formats using the chain method:
let combined = timestamp().chain(json()).chain(colorize());Or use the chain! macro for succinct chaining of multiple formats:
use logform::chain;
let combined = chain!(timestamp(), json(), colorize());Chaining stops early when any format returns None (useful for filtering logs).
Adds a timestamp to the log metadata.
Builder methods:
.with_format(&str)— Customize timestamp display format (uses chrono formatting)..with_alias(&str)— Add an alias field for the timestamp.
let ts = timestamp()
.with_format("%Y-%m-%d %H:%M:%S")
.with_alias("time");A minimal text formatter producing output like:
level: message { ...metadata... }
Respects padding stored in meta under "padding" to align levels nicely.
Serializes the log info into a JSON string:
{ "level": "info", "message": "User logged in", "user_id": 12345 }Adds a tab character before the message, useful for aligned output.
Combines colorizing and padding:
- Colors the level and/or message.
- Pads messages for neat CLI output.
- Configurable via builder methods like
.with_levels(),.with_colors(),.with_filler(), and.with_all().
Example:
let cli_format = cli()
.with_filler("*")
.with_all(true);
let out = cli_format.transform(info).unwrap();Provides colorization for levels and messages via colored crate.
Configurable options include:
.with_all(bool).with_level(bool).with_message(bool).with_colors(...)to specify colors for levels.
Strips ANSI color codes from level and/or message.
Adds a label either as a prefix to the message or into metadata.
Builder:
.with_label("MY_LABEL").with_message(true|false)— if true, prefix message; else add to meta.
Transforms the log info into a Logstash-compatible JSON string with fields like @timestamp, @message, and @fields.
Collects metadata keys into a single key.
Builder methods:
.with_key(&str)— metadata container key (default:"metadata")..with_fill_except(Vec<&str>)— exclude keys..with_fill_with(Vec<&str>)— include only these keys.
Adds time elapsed since the previous log message in milliseconds in the meta key "ms".
Pads the message to align levels uniformly.
Configurable:
.with_levels(...).with_filler(...)
Prettifies log output in a human-friendly format, optionally colorized.
Builder:
.with_colorize(bool)
Customize the output with any formatting closure:
let printf_format = printf(|info| {
format!("{} - {}: {}",
info.level,
info.message,
serde_json::to_string(&info.meta).unwrap()
)
});A format can filter out unwanted logs by returning None from transform.
Example:
struct IgnorePrivate;
impl Format for IgnorePrivate {
type Input = LogInfo;
fn transform(&self, info: LogInfo) -> Option<LogInfo> {
if let Some(private) = info.meta.get("private") {
use serde_json::Value;
if matches!(private, Value::Bool(true)) || private == "true" {
return None;
}
}
Some(info)
}
}When chained, subsequent formats will not run if any upstream returns None.
Implement Format for custom transformations over any input type:
struct UpperCase;
impl Format for UpperCase {
type Input = String;
fn transform(&self, input: String) -> Option<String> {
if input.is_empty() {
None
} else {
Some(input.to_uppercase())
}
}
}Then chain with other formats for composable log processing.
Add to your Cargo.toml:
[dependencies]
logform = "0.5"Or use cargo:
cargo add logformThis project is licensed under the MIT License.