twyg

Crates.iotwyg
lib.rstwyg
version0.6.2
created_at2020-02-09 04:33:54.72025+00
updated_at2026-01-23 00:54:59.212546+00
descriptionA tiny logging setup for Rust applications
homepage
repositoryhttps://github.com/oxur/twyg
max_upload_size
id206556
size261,039
Duncan McGreggor (oubiwann)

documentation

https://docs.rs/twyg/

README

twyg

A tiny logging setup for Rust applications

I got used to logging my apps in with:

so here's something similar for Rust ;-)

Features

  • 🎨 Beautiful colored output with fine-grained color customization
  • ⏰ Multiple timestamp formats (RFC3339, Standard, Simple, TimeOnly)
  • 📍 Optional caller information (file, line, function)
  • 📏 Configurable level padding for perfect alignment
  • 🎯 Structured logging with key-value pairs
  • ⚙️ Simple configuration via builder pattern or config files
  • 🔄 Both foreground and background color support
  • 🚀 Zero-overhead when color is disabled

Installation

Add twyg to your Cargo.toml:

[dependencies]
twyg = "0.6"

Quick Start

use twyg::{LogLevel, OptsBuilder};

// Set up with default configuration
let opts = OptsBuilder::new()
    .coloured(true)
    .level(LogLevel::Debug)
    .report_caller(true)
    .build()
    .unwrap();

twyg::setup(opts).expect("Failed to set up logger");

// Now use standard Rust logging macros
log::info!("Application started");
log::debug!(user = "alice", id = 42; "User logged in");
log::warn!("Configuration file missing, using defaults");
log::error!("Failed to connect to database");

Once the setup function has been called, all subsequent calls to the standard Rust logging macros will use this configuration, providing beautifully formatted output:

The output in the screenshot above (click for a full-sized view) is from running the demos in the examples directory.

Configuration Options

Option Type Default Description
coloured bool true Enable/disable ANSI color output
output Output Stdout Output destination: Stdout, Stderr, or File(path)
level LogLevel Info Minimum log level: Trace, Debug, Info, Warn, Error
report_caller bool false Include file name and line number in output
timestamp_format TSFormat Standard Timestamp format (see below)
pad_level bool false Enable padding of log level strings for alignment
pad_amount usize 5 Number of characters to pad level strings to
pad_side PadSide Right Padding side: Left (right-align) or Right (left-align)
arrow_char String "▶" Arrow separator between metadata and message
msg_separator String ": " Separator before structured logging attributes
colors Colors See below Fine-grained color control for each component

Timestamp Formats

twyg supports multiple timestamp format presets:

use twyg::TSFormat;

// RFC3339 format
TSFormat::RFC3339        // "2026-01-15T14:30:52-08:00"

// Standard format (default)
TSFormat::Standard       // "2026-01-15 14:30:52"

// Compact format
TSFormat::Simple         // "20260115.143052"

// Time only
TSFormat::TimeOnly       // "14:30:52"

// Custom chrono format string
TSFormat::Custom("%H:%M:%S%.3f".to_string())  // "14:30:52.123"

Output Format

twyg produces clean, readable log output with optional caller information and structured key-value pairs:

Without caller information:

2026-01-15 14:30:52 INFO  [myapp] ▶ Application started
2026-01-15 14:30:52 DEBUG [myapp::auth] ▶ Processing login request
2026-01-15 14:30:52 WARN  [myapp::config] ▶ Using default configuration: file={config.yaml}
2026-01-15 14:30:52 ERROR [myapp::db] ▶ Connection failed: host={localhost}, port={5432}

With caller information:

2026-01-15 14:30:52 INFO  [main.rs:42 myapp] ▶ Application started
2026-01-15 14:30:52 DEBUG [auth.rs:127 myapp::auth] ▶ User logged in: user={alice}, id={42}

With level padding and custom formatting:

20260115.143052 INFO  [main.rs:42 myapp] → Application started
20260115.143052 WARN  [config.rs:18 myapp::config] → Missing key | key={api_token}
20260115.143052 ERROR [db.rs:93 myapp::db] → Failed to connect | error={timeout}

Fine-Grained Color Configuration

twyg allows you to customize the foreground and background colors of every formatted element. By default, twyg uses sensible color defaults, but you can override any color you want.

Simple Example - Changing a Few Colors

You don't need to configure every color. Just customize the ones you want to change:

use twyg::{Color, ColorAttribute, Colors, OptsBuilder};

let mut colors = Colors::default();

// Customize just the colors you want to change
colors.level_error = Some(Color::new(
    ColorAttribute::HiWhite,     // White text
    ColorAttribute::Red          // Red background
));
colors.message = Some(Color::fg(ColorAttribute::HiCyan));
colors.arrow = Some(Color::fg(ColorAttribute::Magenta));

let opts = OptsBuilder::new()
    .coloured(true)
    .colors(colors)
    .build()
    .unwrap();

twyg::setup(opts).unwrap();
log::error!("This error has white text on a red background!");
log::info!("This message is in high-intensity cyan");

Disabling Color for Specific Elements

To disable color for a specific element while keeping others colored, set both foreground and background to Reset:

let mut colors = Colors::default();
colors.timestamp = Some(Color::new(
    ColorAttribute::Reset,
    ColorAttribute::Reset
));
// Timestamp will now be uncolored, but everything else remains colored

Complete Color Configuration Reference

The Colors struct provides fine-grained control over every colored element:

pub struct Colors {
    // Timestamp color (default: Green)
    pub timestamp: Option<Color>,

    // Log level colors
    pub level_trace: Option<Color>,    // default: HiBlue
    pub level_debug: Option<Color>,    // default: Cyan
    pub level_info: Option<Color>,     // default: HiGreen
    pub level_warn: Option<Color>,     // default: HiYellow
    pub level_error: Option<Color>,    // default: Red

    // Message text color (default: Green)
    pub message: Option<Color>,

    // Arrow separator "▶" (default: Cyan)
    pub arrow: Option<Color>,

    // Caller information colors
    pub caller_file: Option<Color>,    // default: HiYellow
    pub caller_line: Option<Color>,    // default: HiYellow

    // Target/module name color (default: HiYellow)
    pub target: Option<Color>,

    // Structured logging attribute colors
    pub attr_key: Option<Color>,       // default: HiYellow
    pub attr_value: Option<Color>,     // default: Cyan
}

pub struct Color {
    pub fg: ColorAttribute,  // Foreground color
    pub bg: ColorAttribute,  // Background color
}

Available Colors

ColorAttribute provides these options:

Standard colors:

  • Black, Red, Green, Yellow, Blue, Magenta, Cyan, White

Bright/high-intensity colors:

  • HiBlack, HiRed, HiGreen, HiYellow, HiBlue, HiMagenta, HiCyan, HiWhite

Special:

  • Reset - No color (use for both foreground and background to disable coloring for an element)

You can create colors with just foreground:

Color::fg(ColorAttribute::Red)

Or with both foreground and background:

Color::new(ColorAttribute::White, ColorAttribute::Red)  // White text on red background

Global Color Disable

The coloured: false option continues to work and will disable ALL colors regardless of individual color settings:

let opts = OptsBuilder::new()
    .coloured(false)  // Disables all colors globally
    .build()
    .unwrap();

Complete Example

See examples/fine-grained-colors.rs for a complete working example with custom colors, padding, and formatting options.

Examples

twyg includes several examples demonstrating different features:

# Run all examples
make run-examples

# Or run individual examples
cargo run --example simple
cargo run --example fine-grained-colors
cargo run --example from-confyg-full        # Comprehensive TOML config
cargo run --example from-confyg-env         # Environment variable config

Configuration Files

twyg works seamlessly with configuration libraries thanks to serde support.

Using with the config crate

Use with the config library:

1. Set up your configuration file (YAML example):

logging:
    coloured: true
    level: debug
    output: stdout
    report_caller: true
    timestamp_format: Standard  # Or: RFC3339, Simple, TimeOnly
    pad_level: true
    pad_amount: 7
    pad_side: Right
    arrow_char: "→"
    msg_separator: " | "
    colors:
        timestamp:
            fg: HiCyan
            bg: Reset
        level_info:
            fg: HiWhite
            bg: Blue
        message:
            fg: HiWhite
            bg: Reset

2. Add twyg to your config struct:

use serde::Deserialize;
use twyg::Opts;

#[derive(Debug, Deserialize)]
pub struct AppConfig {
    pub logging: Opts,
}

3. Load and apply configuration:

let cfg: AppConfig = config::Config::builder()
    .add_source(config::File::with_name("config.yaml"))
    .build()?
    .try_deserialize()?;

twyg::setup(cfg.logging)?;

Using with confyg

For TOML configuration with the confyg library:

[logging]
coloured = true
level = "debug"
output = "stdout"
report_caller = true
timestamp_format = "Simple"
pad_level = true
pad_amount = 7
pad_side = "Right"
arrow_char = "→"
msg_separator = " | "

[logging.colors]
timestamp = { fg = "HiBlack", bg = "Reset" }
level_info = { fg = "HiGreen", bg = "Reset" }
level_error = { fg = "White", bg = "Red" }
message = { fg = "Cyan", bg = "Reset" }

See examples/config-full.toml for a comprehensive configuration example with all available options.

Using Environment Variables

Configuration via environment variables with the envy crate:

export MYAPP_LOGGING_COLOURED=true
export MYAPP_LOGGING_OUTPUT=stdout
export MYAPP_LOGGING_LEVEL=debug
export MYAPP_LOGGING_REPORT_CALLER=true
export MYAPP_LOGGING_TIMESTAMP_FORMAT=Simple
export MYAPP_LOGGING_PAD_LEVEL=true
export MYAPP_LOGGING_PAD_AMOUNT=7
export MYAPP_LOGGING_PAD_SIDE=Left
use serde::Deserialize;
use twyg::Opts;

let logging: Opts = envy::prefixed("MYAPP_LOGGING_").from_env()?;
twyg::setup(logging)?;

See examples/.env-example and examples/from-confyg-env.rs for complete examples.

Note: Configuration uses lowercase serialization for enums, so use strings like "debug", "info", "stdout", etc.

Migration Guide

Upgrading from v0.5 to v0.6

v0.6 adds fine-grained color configuration and new formatting options. All changes are backward compatible:

New Features (Optional)

All new fields have sensible defaults, so existing code works without changes:

// v0.5 code continues to work
let opts = OptsBuilder::new()
    .coloured(true)
    .level(LogLevel::Debug)
    .build()
    .unwrap();

To use new features:

use twyg::{Color, ColorAttribute, Colors, PadSide, TSFormat};

let mut colors = Colors::default();
colors.level_error = Some(Color::new(ColorAttribute::White, ColorAttribute::Red));

let opts = OptsBuilder::new()
    .coloured(true)
    .level(LogLevel::Debug)
    .timestamp_format(TSFormat::Simple)  // NEW
    .pad_level(true)                     // NEW
    .pad_amount(7)                       // NEW
    .pad_side(PadSide::Right)           // NEW
    .arrow_char("→")                     // NEW
    .msg_separator(" | ")                // NEW
    .colors(colors)                      // NEW
    .build()
    .unwrap();

Deprecated Methods

The time_format() method is deprecated in favor of timestamp_format():

// Deprecated (still works)
.time_format("%H:%M:%S")

// Preferred
.timestamp_format(TSFormat::Custom("%H:%M:%S".to_string()))

Upgrading from v0.4 to v0.5

v0.5 introduces type-safe enums and a builder pattern for better API ergonomics. Here's how to migrate:

1. Replace stringly-typed level functions with LogLevel enum

Before (v0.4):

use twyg::{self, level};

let opts = twyg::Opts {
    level: level::debug(),
    ...
};

After (v0.5):

use twyg::{LogLevel, OptsBuilder};

let opts = OptsBuilder::new()
    .level(LogLevel::Debug)
    .build()
    .unwrap();

2. Replace stringly-typed output with Output enum

Before (v0.4):

use twyg::{self, out};

let opts = twyg::Opts {
    file: out::stdout(),
    ...
};

After (v0.5):

use twyg::{Output, OptsBuilder};

let opts = OptsBuilder::new()
    .output(Output::Stdout)
    .build()
    .unwrap();

3. Use OptsBuilder instead of struct literals

Before (v0.4):

let opts = twyg::Opts {
    coloured: true,
    level: level::debug(),
    report_caller: true,
    ..Default::default()
};

After (v0.5):

let opts = OptsBuilder::new()
    .coloured(true)
    .level(LogLevel::Debug)
    .report_caller(true)
    .build()
    .unwrap();

4. Error handling now uses custom TwygError

Before (v0.4):

match twyg::setup(&opts) {
    Ok(_) => {},
    Err(error) => { /* anyhow::Error */ },
}

After (v0.5):

match twyg::setup(opts) {
    Ok(_) => {},
    Err(error) => { /* twyg::TwygError with specific variants */ },
}

Backwards Compatibility

The old stringly-typed functions are still available but deprecated:

  • level::debug(), level::info(), etc. → Use LogLevel::Debug, LogLevel::Info
  • out::stdout(), out::stderr() → Use Output::Stdout, Output::Stderr

License

Copyright © 2020-2026, Oxur Group

Apache License, Version 2.0

Commit count: 87

cargo fmt