pylogging
Ergonomic logging for Rust, inspired by Python's logging module.
If you know Python's logging, you already know how to use this. Same API
patterns, same feel — no need to learn a new mental model for something as
fundamental as logging.
Published as pylogging, imported as logging, so your code reads
like Python but runs like Rust.
Why pylogging?
| You want… | With log + env_logger |
With pylogging ✅ |
|---|---|---|
| A named logger per module | log::info!(target: "my::module", "msg") — target is an annotation |
let log = Logger::get("my::module"); log.info("msg") — a first-class object |
| Custom formatting | Compile-time via env_logger::Builder::format() or a whole new crate |
Formatter::new("%(timestamp) [%(level)-8] %(name) | %(message)") — runtime pattern string |
| Runtime level control | RUST_LOG=warn env var or log::set_max_level(...) |
logger.set_level(Level::Warning) — any logger, any time |
| File logging | Bring in log4rs (heavy) or roll your own |
StreamHandler::new(formatter, File::create("app.log")?) — one-liner |
| ANSI colors / transforms | Doesn't exist out of the box | Transformer trait — add colors, redact, anything post-format |
| Pythonic inheritance | Not built-in | Child loggers inherit from root — just like Python |
In short: if you're a Python dev learning Rust (and there are a lot of you),
pylogging makes logging feel like home — without sacrificing performance.
Installation
[]
= "0.1"
Import as logging:
use ;
Quick start
use ;
let logger = get;
logger
.add_handler
.unwrap;
logger.info;
// prints: INFO: hello
Python → Rust side by side
# Python
=
=
// Rust — same idea, zero mental overhead
use ;
let log = get;
log.set_level;
let mut fmt = new;
fmt.set_time_format;
log.add_handler.unwrap;
log.info;
Features
- Named loggers in a process-global registry (
Logger::get("my::module")). - Inheritance from a configurable root logger (handlers + level propagate to child loggers automatically).
- Level filtering (
Debug,Info,Warning,Error,Critical) — messages below the threshold are dropped allocation-free. - Pattern-based formatting —
"%(timestamp) [%(level)-8] %(name)-12 | %(message)"with width, alignment, truncation specs. - Pluggable handlers via the
Handlertrait;StreamHandlerwrites to anystd::io::Writesink (stdout, files,Vec<u8>, ...). - Transformers for post-processing — sprinkle ANSI colors per level, redact secrets, add timestamps, anything you can write as a function.
- No macros required — call methods on real objects. (
info!("...")is cute, but sometimes you just wantlog.info("...").)
Advanced: configure root logger once
Every logger inherits from the root. Set it up once and all your modules get it:
use ;
let mut formatter = new;
formatter.set_time_format;
let root = root;
root.add_handler.unwrap;
root.set_level;
// Any logger created after this inherits the root's handler and level
let logger = get;
logger.info;
// 2026-06-01 14:30:00 [INFO ] my::module | inherits root's handler and level
Pattern syntax
A pattern is literal text with %(field) placeholders. Optional width/alignment
follows the printf convention:
| Spec | Meaning |
|---|---|
%(name) |
The value of name, or "" if absent. |
%(name)8 |
Right-align to a minimum width of 8. |
%(name)-8 |
Left-align to a minimum width of 8. |
%(name).4 |
Truncate to at most 4 characters. |
%(name)-8.4 |
Truncate to 4, then left-pad to width 8. |
Common fields per record: message, level, name, timestamp, thread.
See examples/quickstart.rs for a full demo with
per-level ANSI colors:
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.