1use tracing::Level;
7
8#[derive(Debug, Clone)]
13pub struct LogConfig {
14 pub base_level: Level,
16 pub log_constraint_details: bool,
18 pub log_data_operations: bool,
20 pub log_metrics: bool,
22 pub max_field_length: usize,
24}
25
26impl Default for LogConfig {
27 fn default() -> Self {
28 Self {
29 base_level: Level::INFO,
30 log_constraint_details: false,
31 log_data_operations: true,
32 log_metrics: true,
33 max_field_length: 256,
34 }
35 }
36}
37
38impl LogConfig {
39 pub fn verbose() -> Self {
41 Self {
42 base_level: Level::DEBUG,
43 log_constraint_details: true,
44 log_data_operations: true,
45 log_metrics: true,
46 max_field_length: 1024,
47 }
48 }
49
50 pub fn production() -> Self {
52 Self {
53 base_level: Level::WARN,
54 log_constraint_details: false,
55 log_data_operations: false,
56 log_metrics: false,
57 max_field_length: 128,
58 }
59 }
60
61 pub fn balanced() -> Self {
63 Self::default()
64 }
65}
66
67#[macro_export]
72macro_rules! perf_debug {
73 ($config:expr, $($arg:tt)*) => {
74 if $config.base_level <= tracing::Level::DEBUG {
75 tracing::debug!($($arg)*);
76 }
77 };
78}
79
80#[macro_export]
82macro_rules! log_constraint {
83 ($config:expr, $($arg:tt)*) => {
84 if $config.log_constraint_details {
85 tracing::debug!($($arg)*);
86 }
87 };
88}
89
90#[macro_export]
92macro_rules! log_data_op {
93 ($config:expr, $($arg:tt)*) => {
94 if $config.log_data_operations {
95 tracing::info!($($arg)*);
96 }
97 };
98}
99
100pub fn truncate_field(value: &str, max_length: usize) -> String {
102 if value.len() <= max_length {
103 value.to_string()
104 } else {
105 let truncated = &value[..max_length];
106 format!("{truncated}...(truncated)")
107 }
108}
109
110pub mod setup {
112 use tracing::Level;
113
114 #[derive(Debug, Clone)]
116 pub struct LoggingConfig {
117 pub level: Level,
119 pub term_level: Level,
121 pub json_format: bool,
123 pub trace_correlation: bool,
125 pub env_filter: Option<String>,
127 }
128
129 impl Default for LoggingConfig {
130 fn default() -> Self {
131 Self {
132 level: Level::INFO,
133 term_level: Level::DEBUG,
134 json_format: false,
135 trace_correlation: false,
136 env_filter: None,
137 }
138 }
139 }
140
141 impl LoggingConfig {
142 pub fn production() -> Self {
144 Self {
145 level: Level::WARN,
146 term_level: Level::INFO,
147 json_format: true,
148 trace_correlation: true,
149 env_filter: None,
150 }
151 }
152
153 pub fn development() -> Self {
155 Self {
156 level: Level::DEBUG,
157 term_level: Level::DEBUG,
158 json_format: false,
159 trace_correlation: false,
160 env_filter: None,
161 }
162 }
163
164 pub fn structured() -> Self {
166 Self {
167 level: Level::INFO,
168 term_level: Level::DEBUG,
169 json_format: true,
170 trace_correlation: true,
171 env_filter: None,
172 }
173 }
174
175 pub fn with_level(mut self, level: Level) -> Self {
177 self.level = level;
178 self
179 }
180
181 pub fn with_term_level(mut self, level: Level) -> Self {
183 self.term_level = level;
184 self
185 }
186
187 pub fn with_json_format(mut self, enabled: bool) -> Self {
189 self.json_format = enabled;
190 self
191 }
192
193 pub fn with_trace_correlation(mut self, enabled: bool) -> Self {
195 self.trace_correlation = enabled;
196 self
197 }
198
199 pub fn with_env_filter(mut self, filter: impl Into<String>) -> Self {
201 self.env_filter = Some(filter.into());
202 self
203 }
204
205 pub fn env_filter(&self) -> String {
207 if let Some(ref filter) = self.env_filter {
208 filter.clone()
209 } else {
210 format!(
211 "{}={},term_guard={}",
212 self.level.as_str().to_lowercase(),
213 self.level.as_str().to_lowercase(),
214 self.term_level.as_str().to_lowercase()
215 )
216 }
217 }
218 }
219
220 pub fn init_logging(config: LoggingConfig) -> Result<(), Box<dyn std::error::Error>> {
238 use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
239
240 let env_filter = EnvFilter::try_from_default_env()
241 .unwrap_or_else(|_| EnvFilter::new(config.env_filter()));
242
243 let fmt_layer = if config.json_format {
244 tracing_subscriber::fmt::layer().json().boxed()
245 } else {
246 tracing_subscriber::fmt::layer().boxed()
247 };
248
249 let subscriber = tracing_subscriber::registry()
250 .with(env_filter)
251 .with(fmt_layer);
252
253 subscriber.init();
254
255 Ok(())
256 }
257
258 #[cfg(feature = "telemetry")]
279 #[allow(dead_code)] pub fn init_logging_with_telemetry<T>(
281 _config: LoggingConfig,
282 _tracer: T,
283 ) -> Result<(), Box<dyn std::error::Error>>
284 where
285 T: tracing_opentelemetry::PreSampledTracer + opentelemetry::trace::Tracer + 'static,
286 {
287 Err("init_logging_with_telemetry is temporarily disabled due to dependency version conflicts. Please initialize telemetry and logging separately.".into())
291 }
292
293 #[cfg(not(feature = "telemetry"))]
295 pub fn init_logging_with_telemetry(
296 config: LoggingConfig,
297 _tracer: (),
298 ) -> Result<(), Box<dyn std::error::Error>> {
299 tracing::warn!("Telemetry feature not enabled, falling back to basic logging");
300 init_logging(config)
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn test_log_config_defaults() {
310 let config = LogConfig::default();
311 assert_eq!(config.base_level, Level::INFO);
312 assert!(!config.log_constraint_details);
313 assert!(config.log_data_operations);
314 assert!(config.log_metrics);
315 assert_eq!(config.max_field_length, 256);
316 }
317
318 #[test]
319 fn test_log_config_verbose() {
320 let config = LogConfig::verbose();
321 assert_eq!(config.base_level, Level::DEBUG);
322 assert!(config.log_constraint_details);
323 assert!(config.log_data_operations);
324 assert!(config.log_metrics);
325 assert_eq!(config.max_field_length, 1024);
326 }
327
328 #[test]
329 fn test_log_config_production() {
330 let config = LogConfig::production();
331 assert_eq!(config.base_level, Level::WARN);
332 assert!(!config.log_constraint_details);
333 assert!(!config.log_data_operations);
334 assert!(!config.log_metrics);
335 assert_eq!(config.max_field_length, 128);
336 }
337
338 #[test]
339 fn test_truncate_field() {
340 let short_text = "hello";
341 assert_eq!(truncate_field(short_text, 10), "hello");
342
343 let long_text = "this is a very long text that should be truncated";
344 assert_eq!(truncate_field(long_text, 10), "this is a ...(truncated)");
345 }
346}