Logging Guide

Comprehensive guide to Armature's logging system with JSON output by default and configurable pretty printing for development.

Table of Contents


Overview

Armature provides a powerful, environment-configurable logging system built for production use with features like:

  • JSON by Default: Production-ready structured logging out of the box
  • Pretty Printing: Human-readable format for development
  • Environment Configuration: Switch formats without code changes
  • Runtime Configuration: Change settings programmatically
  • Zero-Cost When Disabled: Debug macros compile to no-ops
  • Presets: Built-in configurations for development and production

Default Configuration: JSON format to STDERR at INFO level


Quick Start

Basic Logging

use armature_log::{debug, info, warn, error, trace};

fn main() {
    // Logs are automatically initialized on first use
    info!("Application started on port {}", 8080);
    debug!("Debug information");
    warn!("Warning message");
    error!("Error occurred");
}

Default JSON Output:

{"timestamp":"2024-12-20T12:00:00Z","level":"INFO","target":"my_app","message":"Application started on port 8080"}

Switch to Pretty Logging

# Development - Pretty format with colors
ARMATURE_LOG_FORMAT=pretty cargo run

Pretty Output:

2024-12-20 12:00:00.123 INFO  my_app Application started on port 8080

Configuration

Environment Variables (Recommended)

The easiest way to configure logging is via environment variables:

Variable Values Default Description
ARMATURE_LOG_FORMAT json, pretty, compact json Output format
ARMATURE_LOG_LEVEL trace, debug, info, warn, error info Minimum log level
ARMATURE_LOG_COLOR 1, true, 0, false auto-detect Enable ANSI colors
ARMATURE_DEBUG 1, true false Enable debug mode
ARMATURE_LOG_TIMESTAMPS 1, 0 1 Include timestamps
ARMATURE_LOG_MODULE 1, 0 1 Include module path

Examples:

# Development
ARMATURE_LOG_FORMAT=pretty ARMATURE_LOG_LEVEL=debug cargo run

# Production
ARMATURE_LOG_FORMAT=json ARMATURE_LOG_LEVEL=info cargo run

# Quiet mode
ARMATURE_LOG_LEVEL=warn cargo run

Programmatic Configuration

Use the fluent API for runtime configuration:

use armature_log::{configure, Format, Level};

// Configure logging
configure()
    .format(Format::Pretty)
    .level(Level::Debug)
    .color(true)
    .timestamps(true)
    .apply();

Direct Setters

use armature_log::{set_format, set_level, Format, Level};

// Change format at runtime
set_format(Format::Pretty);
set_format(Format::Json);

// Change level at runtime
set_level(Level::Debug);

Presets

Use built-in presets for common configurations:

use armature_log;

// Development: Pretty + Debug + Colors
armature_log::preset_development();

// Production: JSON + Info + No colors
armature_log::preset_production();

// Quiet: JSON + Warn only
armature_log::preset_quiet();

Log Formats

JSON Format (Default)

Machine-readable, structured format ideal for production and log aggregators.

{"timestamp":"2024-12-20T12:00:00.123Z","level":"INFO","target":"my_app","message":"User logged in"}

Use Cases:

  • Production environments
  • Log aggregation (ELK, Splunk, Datadog, Grafana Loki)
  • Automated log parsing
  • Cloud environments (AWS CloudWatch, GCP Logging)

Enable:

ARMATURE_LOG_FORMAT=json cargo run

Or in code:

armature_log::set_format(armature_log::Format::Json);

Pretty Format

Formatted, colored output for development with human-readable timestamps.

2024-12-20 12:00:00.123 INFO  my_app User logged in
2024-12-20 12:00:00.124 DEBUG armature_core::routing Matched route: GET /api/users
2024-12-20 12:00:00.125 WARN  my_app Rate limit approaching

Use Cases:

  • Local development
  • Debugging
  • Interactive terminal use
  • Quick troubleshooting

Enable:

ARMATURE_LOG_FORMAT=pretty cargo run

Or in code:

armature_log::preset_development();

Compact Format

Minimal single-line output for space efficiency.

12:00:00 I my_app: User logged in
12:00:00 D armature_core::routing: Matched route
12:00:00 W my_app: Rate limit approaching

Use Cases:

  • Low-volume logging
  • CI/CD pipelines
  • Space-constrained environments

Enable:

ARMATURE_LOG_FORMAT=compact cargo run

Log Levels

Available Levels

Level Use Case Example
TRACE Very detailed debugging Function entry/exit, loop iterations
DEBUG Development information Variable values, state changes
INFO General information App start, config loaded, request processed
WARN Potential issues Deprecated API used, fallback activated
ERROR Failures requiring attention Database error, API call failed

Setting Log Level

# Via environment variable
ARMATURE_LOG_LEVEL=debug cargo run
// Via code
armature_log::set_level(armature_log::Level::Debug);

Logging Macros

use armature_log::{trace, debug, info, warn, error};

trace!("Entering function");
debug!("Processing item {}", id);
info!("User {} logged in", username);
warn!("Rate limit approaching: {}/100", count);
error!("Failed to connect to database: {}", err);

With Target

Specify a custom target (module path) for filtering:

debug!(target: "armature::router", "Matching route: {}", path);
info!(target: "database", "Query executed in {}ms", duration);

Structured Logging

Add context to log messages with key-value pairs.

Basic Structured Logging

info!(
    user_id = 123,
    action = "login",
    ip_address = "192.168.1.1",
    "User authentication successful"
);

JSON Output:

{
  "timestamp": "2024-12-20T12:00:00.123Z",
  "level": "INFO",
  "target": "my_app",
  "message": "User authentication successful"
}

Complex Types

// Strings
info!("User created: {}", name);

// Numbers
info!("Query completed in {}ms, {} rows", duration_ms, row_count);

// With error context
error!("Operation failed: {}", err);

HTTP Request Logging

Armature automatically adds logging to HTTP request handling when using armature-core.

Automatic Request Logging

use armature_core::Application;

let app = Application::new();
// Logging is automatically enabled

Example Logs:

{"timestamp":"2024-12-20T12:00:00Z","level":"INFO","target":"armature_core::application","message":"HTTP server listening with pipelining enabled"}
{"timestamp":"2024-12-20T12:00:01Z","level":"DEBUG","target":"armature_core::routing","message":"Matching route: /api/users"}
{"timestamp":"2024-12-20T12:00:01Z","level":"TRACE","target":"armature_core::routing","message":"Route matched: GET /api/users"}

Best Practices

1. Use Environment Variables for Format

# .env.development
ARMATURE_LOG_FORMAT=pretty
ARMATURE_LOG_LEVEL=debug

# .env.production
ARMATURE_LOG_FORMAT=json
ARMATURE_LOG_LEVEL=info

2. Use Appropriate Log Levels

// โœ… Good
info!("User {} logged in", user_id);              // General info
warn!("Rate limit exceeded for IP {}", ip);       // Potential issue
error!("Failed to save user: {}", err);           // Actual error

// โŒ Bad
info!("Database error occurred");                 // Should be ERROR
error!("User clicked button");                    // Should be DEBUG or none

3. Don't Log Sensitive Data

// โŒ Bad - logs sensitive data
info!("User logged in with password: {}", password);

// โœ… Good - omits sensitive data
info!("User {} logged in", user_id);

4. Initialize Logging Early (Optional)

fn main() {
    // Explicitly initialize logging (optional)
    armature_log::init();

    info!("Application starting");
}

5. Use Presets for Consistency

fn main() {
    // Use preset based on environment
    if cfg!(debug_assertions) {
        armature_log::preset_development();
    } else {
        armature_log::preset_production();
    }
}

Performance

Logging Overhead

Armature's logging system is designed for minimal overhead:

  • Lazy evaluation: Only evaluates log statements that will be output
  • Atomic checks: Fast level checks using atomics
  • No allocation when filtered: Filtered logs don't allocate
  • JSON serialization: Efficient with serde_json

Benchmarks

Operation Time Overhead
Filtered out log (TRACE when INFO) ~5ns Negligible
Simple info! message ~200ns Very low
JSON formatting ~1ฮผs Low

Tips for Performance

  1. Use appropriate log levels - DEBUG/TRACE disabled in production
  2. Avoid expensive operations - Don't compute values if log is filtered
// โœ… Good - value only computed if debug is enabled
if armature_log::is_level_enabled(armature_log::Level::Debug) {
    debug!("Expensive computation: {}", expensive_fn());
}

Examples

Production Configuration

# Docker/K8s environment
ARMATURE_LOG_FORMAT=json
ARMATURE_LOG_LEVEL=info
ARMATURE_LOG_TIMESTAMPS=1
ARMATURE_LOG_MODULE=1

Development Configuration

# Local development
ARMATURE_LOG_FORMAT=pretty
ARMATURE_LOG_LEVEL=debug
ARMATURE_LOG_COLOR=1

Or in code:

fn main() {
    #[cfg(debug_assertions)]
    armature_log::preset_development();

    #[cfg(not(debug_assertions))]
    armature_log::preset_production();

    info!("Application started");
}

CI/CD Configuration

# Compact format for CI logs
ARMATURE_LOG_FORMAT=compact
ARMATURE_LOG_LEVEL=info
ARMATURE_LOG_COLOR=0

Complete Example

use armature_log::{debug, info, warn, error, configure, Format, Level};

fn main() {
    // Configure based on environment
    if std::env::var("DEVELOPMENT").is_ok() {
        configure()
            .format(Format::Pretty)
            .level(Level::Debug)
            .color(true)
            .apply();
    }

    info!("Application starting");

    // Your application code
    match process_request() {
        Ok(_) => info!("Request processed successfully"),
        Err(e) => error!("Request failed: {}", e),
    }
}

Summary

Key Features

โœ… JSON by Default - Production-ready structured logging โœ… Pretty Printing - Human-readable development output โœ… Environment Configuration - Switch formats via env vars โœ… Runtime Configuration - Change settings in code โœ… Zero-Cost - No overhead when disabled โœ… Presets - Built-in dev/prod configurations

Quick Reference

# Environment Variables
ARMATURE_LOG_FORMAT=pretty|json|compact
ARMATURE_LOG_LEVEL=trace|debug|info|warn|error
ARMATURE_LOG_COLOR=1|0
ARMATURE_DEBUG=1
// Programmatic Configuration
armature_log::preset_development();
armature_log::preset_production();
armature_log::set_format(Format::Pretty);
armature_log::set_level(Level::Debug);

// Logging Macros
trace!("...");
debug!("...");
info!("...");
warn!("...");
error!("...");

Happy logging! ๐Ÿ“