Beautiful, ergonomic logging and banners for Rust CLI applications
πͺ΅ Production-Ready Logging
- Cargo-style verbosity levels (Quiet β Normal β Verbose β Trace)
- Dual output modes: beautiful text or structured JSON
- Automatic task timing and span management
- Global singleton for ergonomic API
- Zero-cost quiet mode suppression
π― Elegant Banners
- Clean ASCII art for professional first impressions
- Smart address formatting (wildcard binds show as
:PORT) - Optional taglines and version display
- ANSI color support with graceful fallbacks
π Multiple Styles
SimpleLogger: Classic ASCII symbols for maximum compatibilityModernLogger: Beautiful unicode for modern terminals- Extensible formatter trait for custom styles
A comparison of the three builtβin output styles: SimpleLogger, ModernLogger, and JSON mode.
| Capability | SimpleLogger | ModernLogger | JSON Mode |
|---|---|---|---|
| Humanβfriendly text output | β Yes | β Yes (rich CLI style) | β No |
| Machineβreadable output | β No | β No | β Yes (structured JSON) |
| ANSI colors | β Yes (optional) | β Yes | β No |
| Unicode symbols | β Basic | β Polished (cliclackβstyle) | β Not applicable |
| Quietβmode suppression | β Yes | β Yes | β Yes |
| Verbose/trace support | β Yes | β Yes | β Yes |
| Structured fields | β Ignored | β Ignored | β Included in JSON |
| Progress API compatibility | β Yes | β Yes | β Emits JSON events |
| Task tree introspection | β Text output | β Text output | β JSON output |
| Best for | Simple CLIs, scripts | Polished CLIs, userβfacing tools | CI, log aggregation, automation |
cargo add log-rsuse log_rs::{
logging::{set_logger, log, Printer, ModernLogger, Verbosity, LogFormat},
banner::{BannerConfig, print as print_banner},
};
fn main() {
// Initialize logger once at startup
let logger = Printer::new(ModernLogger, LogFormat::Text, Verbosity::Normal);
set_logger(logger);
// Use anywhere in your app
log::intro("Deploying application");
log::step("Building assets");
log::ok("Build successful");
log::outro("Deployment complete");
// β Deploying application
// β Ώ Building assets
// β Build successful
// β Deployment complete (took 2.3s)
}let banner = BannerConfig {
name: "MyAPI",
version: "1.0.0",
tagline: Some("Fast and reliable REST API"),
addr: Some("0.0.0.0:8080"),
};
print_banner(&banner);Output:
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v1.0.0
Fast and reliable REST API
β¨ MyAPI listening on :8080
Control output detail with four levels:
| Level | Flag | Usage | Output |
|---|---|---|---|
| Quiet | -q |
Cron jobs, CI | Errors only |
| Normal | (default) | Standard CLI | Success, warnings, info |
| Verbose | -v |
Troubleshooting | + Debug logs, tracing spans |
| Trace | -vv |
Deep debugging | + Trace logs, full diagnostics |
Text Mode (Human-Friendly)
β Server started
β Ώ Processing request
β Cache miss for key: user_123
β Database connection failed
JSON Mode (Machine-Friendly)
{"level":"info","message":"β Server started","timestamp":"2026-01-15T10:30:00Z"}
{"level":"warn","message":"β Cache miss","timestamp":"2026-01-15T10:30:01Z"}
{"level":"error","message":"β Database connection failed","timestamp":"2026-01-15T10:30:02Z"}// Status messages
log().ok("Operation successful");
log().warn("Potential issue detected");
log().err("Operation failed");
log().info("Informational message");
log().dim("Muted remark");
// Task management
log().intro("Starting deployment"); // Begins a timed task
log().step("Building assets"); // Progress indicator
log().outro("Deployment complete"); // Ends task, shows duration
// Debug output (verbose mode only)
log().debug("Cache hit rate: 87%");
log().trace("SQL: SELECT * FROM users");pub struct BannerConfig<'a> {
pub name: &'a str, // Required: app name
pub version: &'a str, // Required: version string
pub tagline: Option<&'a str>, // Optional: description
pub addr: Option<&'a str>, // Optional: bind address
}Address Formatting:
127.0.0.1:8080β displays as127.0.0.1:80800.0.0.0:8080β displays as:8080(cleaner for wildcards)[::]:8080β displays as:8080- Invalid/empty β omitted from banner
+ Configuration loaded
! Cache not configured
X Database timeout
* Processing items
β Starting deployment
β Deployment complete
β Configuration loaded
β Cache not configured
β Database timeout
β Ώ Processing items
β Starting deployment
β Deployment complete
π Debug information
β¦ Trace details
Run the included examples to see the loggers in action:
# Simple ASCII logger
cargo run --example simple-logger
cargo run --example simple-logger -- -v
# Modern unicode logger
cargo run --example modern-logger
cargo run --example modern-logger -- --json
# Quiet mode (errors only)
cargo run --example modern-logger -- -qTwo-Layer Design:
-
FormatLogger β Formats messages into styled strings
SimpleLogger: ASCII symbolsModernLogger: Unicode symbols- Implement your own for custom styles
-
ScreenLogger β Prints formatted messages
Printer: Manages spans, timing, output routing- Integrates with
tracingfor structured logs
Benefits:
- Clean separation of formatting and I/O
- Easy to test formatters without side effects
- Trivial to add new output styles
- Swap backends without changing user code
The logger integrates seamlessly with the tracing ecosystem:
use tracing::info;
// In verbose mode, log() calls emit tracing events
log().intro("Processing batch"); // Creates a tracing span
log().step("Item 1"); // Nested span
log().outro("Batch complete"); // Closes span with timing
// Regular tracing works alongside
info!("Direct tracing event");use clap::Parser;
#[derive(Parser)]
struct Cli {
#[arg(short, long)]
quiet: bool,
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}
fn main() {
let cli = Cli::parse();
let verbosity = match (cli.quiet, cli.verbose) {
(true, _) => Verbosity::Quiet,
(_, 0) => Verbosity::Normal,
(_, 1) => Verbosity::Verbose,
(_, _) => Verbosity::Trace,
};
let logger = Printer::new(ModernLogger, LogFormat::Text);
set_logger(logger);
// Your app logic...
}Great CLIs are invisible until you need them.
This toolkit prioritizes:
- Ergonomics β
log().ok("done")beats dependency injection - Clarity β Visual symbols communicate status instantly
- Performance β Lazy formatting, zero-cost abstractions
- Flexibility β Start simple, scale to production
- Professionalism β Output that looks polished everywhere
Whether you're building a quick script or a production service, these tools adapt to your needs without getting in your way.
# Run all tests
cargo test
# Run specific module tests
cargo test logger_tests
cargo test banner_tests
# Run with output
cargo test -- --nocaptureA quick reference table summarizing the major enhancements planned for the logger.
| Feature | Description | Status |
|---|---|---|
| Structured Fields | Attach key/value metadata to any log call for richer JSON output and better machine parsing. | Planned |
| Progress API | Lightweight progress handle for long-running tasks with update, tick, and finish. |
Planned |
| Task Tree Visualizer | Dump active tasks and steps with timing information in verbose/trace mode. | Partial |
| QuietβButβTimed Mode | Quiet mode still prints timing summaries for tasks and steps. | Planned |
| Plugin System for Custom Formatters | Allow users to register custom formatters, themes, or output styles. | Planned |
| CompileβTime LogβLevel Stripping | Macros that compile to nothing unless enabled, keeping release builds lean. | Planned |
| Log Capture API for Tests | Capture logs programmatically for assertions in unit tests. | Planned |
| OpenTelemetry Integration | Optional feature to export spans and events to tracing backends like Jaeger or Honeycomb. | Planned |
| Sampling for HighβVolume Logs | Prevent log floods by sampling trace/debug events. | Planned |
| Emoji & Symbol Refinement | Improved glyphs for debug/trace to enhance readability. | Complete |
| DeveloperβMode Banner | Friendly banner shown when running with RUST_LOG=debug or trace. |
Complete |
This project is licensed under the MIT License - see the LICENSE file for details.
Inspired by:
- Cargo's excellent CLI output
- cliclack for modern terminal aesthetics
- Echo and Express for startup banner design
- The tracing ecosystem for structured logging
Q: Can I use both SimpleLogger and ModernLogger in the same app?
A: No, you set one logger globally at startup. Choose based on your target environment.
Q: Does quiet mode completely silence output?
A: No, errors always print. Quiet mode is for automation where you only want failures.
Q: Can I customize the banner ASCII art?
A: Currently no, but you can print your own before calling print_banner(). Open an issue if you need this feature!
Q: Is this production-ready?
A: Yes! The logger is built on the battle-tested tracing ecosystem and includes comprehensive tests.
Q: How do I capture logs in tests?
A: Use tracing-subscriber test utilities or capture stdout/stderr. See the test files for examples.
Documentation β’ Crates.io β’ Report Bug β’ Request Feature
Made with β€οΈ for the Rust CLI community