pylogging 0.1.2

A small, ergonomic logging library inspired by Python's logging module.
Documentation
  • Coverage
  • 100%
    19 out of 19 items documented1 out of 1 items with examples
  • Size
  • Source code size: 73.46 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 914.95 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 6s Average build duration of successful builds.
  • all releases: 6s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • dt-ss/pylogging
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • dt-ss

pylogging

Crates.io Documentation License

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

[dependencies]
pylogging = "0.1"

Import as logging:

use logging::{Formatter, Level, Logger, StreamHandler};

Quick start

use logging::{Logger, StreamHandler};

let logger = Logger::get("example");
logger
    .add_handler(StreamHandler::with_pattern(std::io::stdout(), "%(level): %(message)"))
    .unwrap();
logger.info("hello");
// prints: INFO: hello

Python → Rust side by side

# Python
import logging
log = logging.getLogger("my_app")
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)-8s] %(message)s"))
log.addHandler(handler)
log.info("server started on port %d", 8080)
// Rust — same idea, zero mental overhead
use logging::{Formatter, Logger, StreamHandler};

let log = Logger::get("my_app");
log.set_level(Level::Debug);
let mut fmt = Formatter::new("%(timestamp) [%(level)-8] %(message)");
fmt.set_time_format("%Y-%m-%d %H:%M:%S");
log.add_handler(StreamHandler::new(fmt, std::io::stdout())).unwrap();
log.info(format!("server started on port {}", 8080));

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 Handler trait; StreamHandler writes to any std::io::Write sink (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 want log.info("...").)

Advanced: configure root logger once

Every logger inherits from the root. Set it up once and all your modules get it:

use logging::{Formatter, Level, Logger, StreamHandler};

let mut formatter = Formatter::new("%(timestamp) [%(level)-8] %(name)-12 | %(message)");
formatter.set_time_format("%Y-%m-%d %H:%M:%S");

let root = Logger::root();
root.add_handler(StreamHandler::new(formatter, std::io::stdout())).unwrap();
root.set_level(Level::Debug);

// Any logger created after this inherits the root's handler and level
let logger = Logger::get("my::module");
logger.info("inherits root's handler and level");
// 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:

cargo run --example quickstart

License

Licensed under either of

at your option.