Files
dirigate/src/logging.rs
T
g4borg c62d8daea8 chore: rename packages/ to crates/
Move all 29 workspace members from packages/<name>/ to crates/<name>/.
Updates: workspace Cargo.toml (members + path deps), justfile, root
CLAUDE.md, scripts/build/CARGO_INSTALL.md, docs/architecture/crates.md
(renamed from packages.md), structural references in docs/architecture
and docs/configuration, per-crate CLAUDE.md self-references. Historical
plans, reports, and building/ docs are left untouched.

No behavior change; just check-all stays green and fermata tests pass.
2026-04-30 21:58:57 +02:00

169 lines
5.6 KiB
Rust

//! Logging infrastructure for the ACP mocker.
//!
//! This module provides utilities for initializing and configuring structured logging
//! using the `tracing` ecosystem. It supports multiple output formats and log levels
//! configurable via environment variables.
//!
//! ## Important: stdio Mode
//!
//! When using stdio transport (the primary ACP communication method), ALL logs are
//! automatically written to stderr to keep stdout clean for JSON-RPC messages.
use std::fs::OpenOptions;
use std::sync::Arc;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
/// Output format for logs.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LogFormat {
/// Human-readable pretty format with colors (default for development).
#[default]
Pretty,
/// JSON format for structured logging (recommended for production/CI).
Json,
/// Compact format for minimal output.
Compact,
}
/// Initialize the logging system with the specified format.
///
/// # Log Levels
///
/// Log levels can be configured via the `RUST_LOG` environment variable:
/// - `RUST_LOG=trace` - Most verbose, includes all internal details
/// - `RUST_LOG=debug` - Detailed debugging information
/// - `RUST_LOG=info` - General informational messages (default)
/// - `RUST_LOG=warn` - Warning messages only
/// - `RUST_LOG=error` - Error messages only
///
/// You can also filter by module:
/// ```bash
/// RUST_LOG=dirigate=debug,axum=info
/// ```
///
/// # Examples
///
/// ```rust,no_run
/// use dirigate::logging::{init_logging, LogFormat};
///
/// // Initialize with pretty format (default)
/// init_logging(LogFormat::Pretty);
///
/// // Initialize with JSON format for production
/// init_logging(LogFormat::Json);
/// ```
///
/// # Panics
///
/// Panics if logging has already been initialized. This should be called
/// exactly once at the start of the application.
pub fn init_logging(format: LogFormat) {
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("dirigate=info,axum=info"));
// Try to create log file in system temp directory
// If this fails, we'll just log to stderr only
let log_file_path = std::env::temp_dir().join("dirigate.log");
let file_writer = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&log_file_path)
.ok()
.map(Arc::new);
// CRITICAL: All logs go to stderr (and optionally to file if available)
// In stdio mode, stdout is reserved for JSON-RPC messages only
match (format, file_writer) {
(LogFormat::Pretty, Some(file_writer)) => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().pretty().with_writer(std::io::stderr))
.with(fmt::layer().with_ansi(false).with_writer(file_writer))
.init();
tracing::info!(
"Logging initialized with Pretty format, writing to {:?}",
log_file_path
);
}
(LogFormat::Pretty, None) => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().pretty().with_writer(std::io::stderr))
.init();
tracing::info!("Logging initialized with Pretty format (file logging unavailable)");
}
(LogFormat::Json, Some(file_writer)) => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().json().with_writer(std::io::stderr))
.with(
fmt::layer()
.json()
.with_ansi(false)
.with_writer(file_writer),
)
.init();
tracing::info!(
"Logging initialized with Json format, writing to {:?}",
log_file_path
);
}
(LogFormat::Json, None) => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().json().with_writer(std::io::stderr))
.init();
tracing::info!("Logging initialized with Json format (file logging unavailable)");
}
(LogFormat::Compact, Some(file_writer)) => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().compact().with_writer(std::io::stderr))
.with(
fmt::layer()
.compact()
.with_ansi(false)
.with_writer(file_writer),
)
.init();
tracing::info!(
"Logging initialized with Compact format, writing to {:?}",
log_file_path
);
}
(LogFormat::Compact, None) => {
tracing_subscriber::registry()
.with(env_filter)
.with(fmt::layer().compact().with_writer(std::io::stderr))
.init();
tracing::info!("Logging initialized with Compact format (file logging unavailable)");
}
}
}
/// Initialize logging with default settings (pretty format, info level).
///
/// This is a convenience function equivalent to `init_logging(LogFormat::Pretty)`.
///
/// # Examples
///
/// ```rust,no_run
/// use dirigate::logging::init_default_logging;
///
/// init_default_logging();
/// ```
pub fn init_default_logging() {
init_logging(LogFormat::default());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_format_default() {
assert_eq!(LogFormat::default(), LogFormat::Pretty);
}
}