Skip to main content

embeddenator_obs/obs/
logging.rs

1//! Structured Logging Infrastructure
2//!
3//! Provides configurable logging with multiple output formats and
4//! environment-based filtering. Integrates with the `tracing` ecosystem
5//! for structured, leveled logging.
6//!
7//! # Features
8//!
9//! - Environment-based log filtering
10//! - Multiple output formats (compact, pretty, JSON)
11//! - Zero-cost when disabled
12//! - Standard error and warning helpers
13//!
14//! # Configuration
15//!
16//! Set log level via environment variables:
17//! - `EMBEDDENATOR_LOG="info"` - custom filter
18//! - `RUST_LOG="debug"` - fallback filter
19//!
20//! Set output format:
21//! - `EMBEDDENATOR_LOG_FORMAT="json"` - structured JSON output
22//! - `EMBEDDENATOR_LOG_FORMAT="pretty"` - pretty-printed output
23//! - `EMBEDDENATOR_LOG_FORMAT="compact"` - compact output (default)
24
25#[cfg(feature = "logging")]
26use std::io;
27
28/// Initialize structured logging.
29///
30/// Behavior:
31/// - With `--features logging`: installs a `tracing_subscriber` configured by
32///   `EMBEDDENATOR_LOG` or `RUST_LOG`.
33/// - Without the feature: no-op.
34///
35/// By default (no env var), logging is disabled.
36#[cfg(feature = "logging")]
37pub fn init() {
38    use tracing_subscriber::fmt;
39
40    let filter = std::env::var("EMBEDDENATOR_LOG")
41        .ok()
42        .or_else(|| std::env::var("RUST_LOG").ok())
43        .unwrap_or_else(|| "off".to_string());
44
45    let format = std::env::var("EMBEDDENATOR_LOG_FORMAT")
46        .ok()
47        .unwrap_or_else(|| "compact".to_string());
48
49    match format.as_str() {
50        "json" => {
51            let _ = fmt()
52                .json()
53                .with_env_filter(filter)
54                .with_writer(io::stderr)
55                .try_init();
56        }
57        "pretty" => {
58            let _ = fmt()
59                .pretty()
60                .with_env_filter(filter)
61                .with_writer(io::stderr)
62                .try_init();
63        }
64        _ => {
65            let _ = fmt()
66                .compact()
67                .with_env_filter(filter)
68                .with_writer(io::stderr)
69                .try_init();
70        }
71    }
72}
73
74#[cfg(not(feature = "logging"))]
75pub fn init() {}
76
77/// Emit a warning in the best available way.
78///
79/// This intentionally preserves existing default behavior for builds without
80/// `logging` (warnings still go to stderr). When `logging` is enabled, warnings
81/// become structured `tracing` events.
82#[cfg(feature = "logging")]
83pub fn warn(message: &str) {
84    tracing::warn!(message = %message);
85}
86
87#[cfg(not(feature = "logging"))]
88pub fn warn(message: &str) {
89    eprintln!("WARN: {}", message);
90}
91
92/// Emit an error message.
93#[cfg(feature = "logging")]
94pub fn error(message: &str) {
95    tracing::error!(message = %message);
96}
97
98#[cfg(not(feature = "logging"))]
99pub fn error(message: &str) {
100    eprintln!("ERROR: {}", message);
101}
102
103/// Emit an info message.
104#[cfg(feature = "logging")]
105pub fn info(message: &str) {
106    tracing::info!(message = %message);
107}
108
109#[cfg(not(feature = "logging"))]
110pub fn info(_message: &str) {
111    // No-op without logging feature
112}
113
114/// Emit a debug message.
115#[cfg(feature = "logging")]
116pub fn debug(message: &str) {
117    tracing::debug!(message = %message);
118}
119
120#[cfg(not(feature = "logging"))]
121pub fn debug(_message: &str) {
122    // No-op without logging feature
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_init_no_panic() {
131        init();
132    }
133
134    #[test]
135    fn test_warn() {
136        warn("test warning");
137    }
138
139    #[test]
140    fn test_error() {
141        error("test error");
142    }
143
144    #[test]
145    fn test_info() {
146        info("test info");
147    }
148
149    #[test]
150    fn test_debug() {
151        debug("test debug");
152    }
153}