prax_query/logging.rs
1//! Logging infrastructure for Prax ORM.
2//!
3//! This module provides structured JSON logging controlled by the `PRAX_DEBUG` environment variable.
4//!
5//! # Environment Variables
6//!
7//! - `PRAX_DEBUG=true` - Enable debug logging
8//! - `PRAX_DEBUG=1` - Enable debug logging
9//! - `PRAX_LOG_LEVEL=debug|info|warn|error|trace` - Set specific log level
10//! - `PRAX_LOG_FORMAT=json|pretty|compact` - Set output format (default: json)
11//!
12//! # Usage
13//!
14//! ```rust,no_run
15//! use prax_query::logging;
16//!
17//! // Initialize logging (call once at startup)
18//! logging::init();
19//!
20//! // Or with custom settings
21//! logging::init_with_level("debug");
22//! ```
23//!
24//! # Internal Logging
25//!
26//! Within Prax, use the standard tracing macros:
27//!
28//! ```rust,ignore
29//! use tracing::{debug, info, warn, error, trace};
30//!
31//! debug!(filter = ?filter, "Building SQL for filter");
32//! info!(table = %table, "Executing query");
33//! warn!(latency_ms = %ms, "Slow query detected");
34//! error!(error = %e, "Query failed");
35//! ```
36
37use std::env;
38use std::sync::Once;
39
40static INIT: Once = Once::new();
41
42/// Check if debug logging is enabled via `PRAX_DEBUG` environment variable.
43///
44/// Returns `true` if `PRAX_DEBUG` is set to "true", "1", or "yes" (case-insensitive).
45#[inline]
46pub fn is_debug_enabled() -> bool {
47 env::var("PRAX_DEBUG")
48 .map(|v| matches!(v.to_lowercase().as_str(), "true" | "1" | "yes"))
49 .unwrap_or(false)
50}
51
52/// Get the configured log level from `PRAX_LOG_LEVEL` environment variable.
53///
54/// Defaults to "debug" if `PRAX_DEBUG` is enabled, otherwise "warn".
55pub fn get_log_level() -> &'static str {
56 if let Ok(level) = env::var("PRAX_LOG_LEVEL") {
57 match level.to_lowercase().as_str() {
58 "trace" => "trace",
59 "debug" => "debug",
60 "info" => "info",
61 "warn" => "warn",
62 "error" => "error",
63 _ => {
64 if is_debug_enabled() {
65 "debug"
66 } else {
67 "warn"
68 }
69 }
70 }
71 } else if is_debug_enabled() {
72 "debug"
73 } else {
74 "warn"
75 }
76}
77
78/// Get the configured log format from `PRAX_LOG_FORMAT` environment variable.
79///
80/// Defaults to "json" for structured logging.
81pub fn get_log_format() -> &'static str {
82 env::var("PRAX_LOG_FORMAT")
83 .map(|f| match f.to_lowercase().as_str() {
84 "pretty" => "pretty",
85 "compact" => "compact",
86 _ => "json",
87 })
88 .unwrap_or("json")
89}
90
91/// Initialize the Prax logging system.
92///
93/// This should be called once at application startup. Subsequent calls are no-ops.
94///
95/// Logging is controlled by:
96/// - `PRAX_DEBUG=true` - Enable debug-level logging
97/// - `PRAX_LOG_LEVEL` - Override the log level (trace, debug, info, warn, error)
98/// - `PRAX_LOG_FORMAT` - Output format (pretty, json, compact)
99///
100/// # Example
101///
102/// ```rust,no_run
103/// use prax_query::logging;
104///
105/// // Initialize at the start of your application
106/// logging::init();
107/// ```
108pub fn init() {
109 INIT.call_once(|| {
110 if !is_debug_enabled() && env::var("PRAX_LOG_LEVEL").is_err() {
111 // No logging requested, skip initialization
112 return;
113 }
114
115 #[cfg(feature = "tracing-subscriber")]
116 {
117 use tracing_subscriber::{EnvFilter, fmt, prelude::*};
118
119 let level = get_log_level();
120 let filter = EnvFilter::try_new(format!(
121 "prax={},prax_query={},prax_schema={}",
122 level, level, level
123 ))
124 .unwrap_or_else(|_| EnvFilter::new("warn"));
125
126 match get_log_format() {
127 "json" => {
128 tracing_subscriber::registry()
129 .with(filter)
130 .with(fmt::layer().json())
131 .init();
132 }
133 "compact" => {
134 tracing_subscriber::registry()
135 .with(filter)
136 .with(fmt::layer().compact())
137 .init();
138 }
139 _ => {
140 tracing_subscriber::registry()
141 .with(filter)
142 .with(fmt::layer().pretty())
143 .init();
144 }
145 }
146
147 tracing::info!(
148 level = level,
149 format = get_log_format(),
150 "Prax logging initialized"
151 );
152 }
153
154 #[cfg(not(feature = "tracing-subscriber"))]
155 {
156 // Tracing subscriber not available, logging will be silent
157 // unless the user sets up their own subscriber
158 }
159 });
160}
161
162/// Initialize logging with a specific level.
163///
164/// # Example
165///
166/// ```rust,no_run
167/// use prax_query::logging;
168///
169/// // Enable trace-level logging
170/// logging::init_with_level("trace");
171/// ```
172///
173/// # Safety
174///
175/// This function modifies environment variables, which is unsafe in
176/// multi-threaded programs. Call this early in your program before
177/// spawning threads.
178pub fn init_with_level(level: &str) {
179 // SAFETY: This should only be called at program startup before threads are spawned.
180 // The user is responsible for calling this safely.
181 unsafe {
182 env::set_var("PRAX_LOG_LEVEL", level);
183 }
184 init();
185}
186
187/// Initialize logging for debugging (convenience function).
188///
189/// Equivalent to setting `PRAX_DEBUG=true` and calling `init()`.
190///
191/// # Safety
192///
193/// This function modifies environment variables, which is unsafe in
194/// multi-threaded programs. Call this early in your program before
195/// spawning threads.
196pub fn init_debug() {
197 // SAFETY: This should only be called at program startup before threads are spawned.
198 unsafe {
199 env::set_var("PRAX_DEBUG", "true");
200 }
201 init();
202}
203
204/// Macro for conditional debug logging.
205///
206/// Only logs if `PRAX_DEBUG` is enabled at runtime.
207#[macro_export]
208macro_rules! prax_debug {
209 ($($arg:tt)*) => {
210 if $crate::logging::is_debug_enabled() {
211 tracing::debug!($($arg)*);
212 }
213 };
214}
215
216/// Macro for conditional trace logging.
217#[macro_export]
218macro_rules! prax_trace {
219 ($($arg:tt)*) => {
220 if $crate::logging::is_debug_enabled() {
221 tracing::trace!($($arg)*);
222 }
223 };
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_debug_disabled_by_default() {
232 // Clear env var to test default behavior
233 // SAFETY: Test runs in isolation
234 unsafe {
235 env::remove_var("PRAX_DEBUG");
236 }
237 assert!(!is_debug_enabled());
238 }
239
240 #[test]
241 fn test_log_level_default() {
242 // SAFETY: Test runs in isolation
243 unsafe {
244 env::remove_var("PRAX_DEBUG");
245 env::remove_var("PRAX_LOG_LEVEL");
246 }
247 assert_eq!(get_log_level(), "warn");
248 }
249}