1use crate::adapters::{LogDirectives, LoggingSetupBuilder, StandardLogAdapter, WasmStdoutAdapter};
7use crate::domain::{
8 EnhancedContextEnricher, LogKvExtractor, ProcessorChain, StructuredFieldsProcessor,
9 TimestampProcessor,
10};
11use crate::error::{ObservabilityError, ObservabilityResult};
12use crate::ports::{StandardLoggingPort, TransportPort};
13use crate::traits::LogLevel;
14use serde::{Deserialize, Serialize};
15use std::sync::{Arc, OnceLock};
16
17struct ForwardingLogger;
18
19static FORWARDING_LOGGER: ForwardingLogger = ForwardingLogger;
20static FORWARDING_ADAPTER: OnceLock<Arc<StandardLogAdapter>> = OnceLock::new();
21static LOGGER_REGISTRATION: OnceLock<LoggerRegistration> = OnceLock::new();
22
23enum LoggerRegistration {
24 InstalledByProxy,
25 AlreadySet(String),
26}
27
28impl log::Log for ForwardingLogger {
29 fn enabled(&self, metadata: &log::Metadata) -> bool {
30 FORWARDING_ADAPTER
31 .get()
32 .is_some_and(|adapter| log::Log::enabled(adapter.as_ref(), metadata))
33 }
34
35 fn log(&self, record: &log::Record) {
36 if let Some(adapter) = FORWARDING_ADAPTER.get() {
37 log::Log::log(adapter.as_ref(), record);
38 }
39 }
40
41 fn flush(&self) {
42 if let Some(adapter) = FORWARDING_ADAPTER.get() {
43 log::Log::flush(adapter.as_ref());
44 }
45 }
46}
47
48fn install_forwarding_logger() -> &'static LoggerRegistration {
49 LOGGER_REGISTRATION.get_or_init(|| match log::set_logger(&FORWARDING_LOGGER) {
50 Ok(()) => LoggerRegistration::InstalledByProxy,
51 Err(error) => LoggerRegistration::AlreadySet(error.to_string()),
52 })
53}
54
55pub struct GlobalLoggerSingleton {
57 adapter: Arc<StandardLogAdapter>,
58 config: ObservabilityConfig,
59}
60
61impl GlobalLoggerSingleton {
62 pub fn get_or_init(
66 config: ObservabilityConfig,
67 ) -> ObservabilityResult<&'static GlobalLoggerSingleton> {
68 static INSTANCE: OnceLock<Result<GlobalLoggerSingleton, String>> = OnceLock::new();
69
70 match INSTANCE.get_or_init(|| Self::create_instance(config).map_err(|e| e.to_string())) {
71 Ok(instance) => Ok(instance),
72 Err(error) => Err(ObservabilityError::logging(format!(
73 "Singleton initialization failed: {}",
74 error
75 ))),
76 }
77 }
78
79 fn create_instance(config: ObservabilityConfig) -> ObservabilityResult<GlobalLoggerSingleton> {
81 let directives = config.parse_directives();
82 let transport = config.create_transport();
83 let processor_chain = if config.structured {
84 let mut enricher = EnhancedContextEnricher::new();
85 if config.context_enrichment && !config.default_context.is_empty() {
86 for (k, v) in &config.default_context {
87 enricher = enricher.with_field(k.clone(), v.clone());
88 }
89 }
90
91 ProcessorChain::new()
92 .add_processor(Box::new(TimestampProcessor))
93 .add_processor(Box::new(LogKvExtractor::new()))
94 .add_processor(Box::new(enricher))
95 .add_processor(Box::new(StructuredFieldsProcessor))
96 } else {
97 ProcessorChain::new()
98 };
99
100 let adapter = LoggingSetupBuilder::new()
101 .with_processor_chain(processor_chain)
102 .with_transport(transport)
103 .with_directives(directives)
104 .build()?;
105
106 let adapter_arc = Arc::new(adapter);
107
108 match install_forwarding_logger() {
109 LoggerRegistration::InstalledByProxy => {
110 let _ = FORWARDING_ADAPTER.set(adapter_arc.clone());
111 }
112 LoggerRegistration::AlreadySet(error) => {
113 eprintln!("Global Rust logger already initialized: {}", error);
114 }
115 }
116
117 adapter_arc.initialize()?;
119
120 log::info!(
121 "🔍 Global logger singleton initialized: Standard Rust logging is now structured"
122 );
123
124 Ok(GlobalLoggerSingleton {
125 adapter: adapter_arc,
126 config,
127 })
128 }
129
130 pub fn adapter(&self) -> &Arc<StandardLogAdapter> {
132 &self.adapter
133 }
134
135 pub fn config(&self) -> &ObservabilityConfig {
137 &self.config
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143pub struct ObservabilityConfig {
144 pub level: String, pub format: String,
149
150 pub structured: bool,
152
153 pub context_enrichment: bool,
155
156 #[serde(default)]
158 pub default_context: std::collections::HashMap<String, serde_json::Value>,
159}
160
161impl Default for ObservabilityConfig {
162 fn default() -> Self {
163 Self {
164 level: "info".to_string(),
165 format: "compact".to_string(),
166 structured: true,
167 context_enrichment: true,
168 default_context: std::collections::HashMap::new(),
169 }
170 }
171}
172
173impl ObservabilityConfig {
174 pub fn parse_level(&self) -> ObservabilityResult<LogLevel> {
176 let global = self.parse_directives().global_level();
177 Ok(global)
178 }
179
180 pub fn parse_directives(&self) -> LogDirectives {
185 LogDirectives::parse(&self.level)
186 }
187
188 pub fn create_transport(&self) -> Arc<dyn TransportPort> {
190 match self.format.as_str() {
191 "json" => Arc::new(WasmStdoutAdapter::with_json_formatter()),
192 "plain" => Arc::new(WasmStdoutAdapter::with_plain_text_formatter()),
193 _ => Arc::new(WasmStdoutAdapter::with_compact_formatter()),
194 }
195 }
196
197 pub fn validate(&self) -> ObservabilityResult<()> {
199 let directives = self.parse_directives();
201 let has_any_valid = !self.level.is_empty()
202 && self.level.split(',').any(|p| {
203 let p = p.trim();
204 if p.contains('=') {
205 let (_, lvl) = p.split_once('=').unwrap();
206 LogDirectives::str_to_level(lvl.trim()).is_some()
207 } else {
208 LogDirectives::str_to_level(p).is_some()
209 }
210 });
211 if !has_any_valid {
212 return Err(ObservabilityError::configuration(format!(
213 "Invalid log level: '{}'. Expected e.g. 'info' or 'info,crate_name=debug'",
214 self.level
215 )));
216 }
217 let _ = directives;
218
219 match self.format.as_str() {
221 "json" | "compact" | "plain" => {}
222 _ => {
223 return Err(ObservabilityError::configuration(format!(
224 "Invalid format: {}. Must be 'json', 'compact', or 'plain'",
225 self.format
226 )));
227 }
228 }
229
230 Ok(())
231 }
232}
233
234pub struct ObservabilityManager {
239 config: ObservabilityConfig,
240 singleton_ref: &'static GlobalLoggerSingleton,
241}
242
243impl Default for ObservabilityManager {
244 fn default() -> Self {
245 let config = ObservabilityConfig::default();
246 Self::new(config).expect("Failed to create default observability manager")
247 }
248}
249
250impl ObservabilityManager {
251 pub fn new(config: ObservabilityConfig) -> ObservabilityResult<Self> {
253 config.validate()?;
255
256 let singleton_ref = GlobalLoggerSingleton::get_or_init(config.clone())?;
258
259 Ok(Self {
260 config,
261 singleton_ref,
262 })
263 }
264
265 pub fn initialize(&mut self) -> ObservabilityResult<()> {
267 log::info!("🔍 Observability manager initialized: Using singleton global logger");
269 Ok(())
270 }
271
272 pub fn config(&self) -> &ObservabilityConfig {
274 &self.config
275 }
276
277 pub fn is_enabled(&self, level: LogLevel) -> bool {
279 StandardLoggingPort::enabled(self.singleton_ref.adapter().as_ref(), &level)
280 }
281
282 pub fn global_logger() -> Option<Arc<StandardLogAdapter>> {
284 GlobalLoggerSingleton::get_or_init(ObservabilityConfig::default())
286 .ok()
287 .map(|singleton| singleton.adapter().clone())
288 }
289
290 pub fn capabilities(&self) -> Vec<String> {
292 let mut caps = vec![
293 "structured_logging".to_string(),
294 "standard_rust_logging".to_string(),
295 "context_enrichment".to_string(),
296 ];
297
298 if self.config.structured {
299 caps.push("json_output".to_string());
300 }
301
302 caps.push(format!("log_level_{}", self.config.level));
303 caps.push(format!("format_{}", self.config.format));
304
305 caps
306 }
307}
308
309pub fn create_observability_manager(
311 config: Option<serde_json::Value>,
312) -> ObservabilityResult<ObservabilityManager> {
313 let obs_config = match config {
314 Some(value) => serde_json::from_value(value).map_err(|e| {
315 ObservabilityError::configuration(format!("Invalid observability config: {}", e))
316 })?,
317 None => ObservabilityConfig::default(),
318 };
319
320 ObservabilityManager::new(obs_config)
321}
322
323pub mod convenience {
328 use super::*;
329
330 pub fn log_with_fields(
332 level: LogLevel,
333 message: &str,
334 fields: serde_json::Value,
335 ) -> ObservabilityResult<()> {
336 if let Some(logger) = ObservabilityManager::global_logger() {
337 if StandardLoggingPort::enabled(logger.as_ref(), &level) {
338 let entry = crate::domain::create_log_entry(level, message, fields);
339 logger.process_standard_log(entry)?;
340 }
341 }
342 Ok(())
343 }
344
345 pub fn add_log_context(key: &str, value: serde_json::Value) {
348 log::debug!("Adding log context: {} = {}", key, value);
351 }
352
353 pub fn info_with_fields(message: &str, fields: serde_json::Value) -> ObservabilityResult<()> {
355 log_with_fields(LogLevel::Info, message, fields)
356 }
357
358 pub fn error_with_fields(message: &str, fields: serde_json::Value) -> ObservabilityResult<()> {
360 log_with_fields(LogLevel::Error, message, fields)
361 }
362
363 pub fn debug_with_fields(message: &str, fields: serde_json::Value) -> ObservabilityResult<()> {
365 log_with_fields(LogLevel::Debug, message, fields)
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_observability_config_default() {
375 let config = ObservabilityConfig::default();
376 assert_eq!(config.level, "info");
377 assert_eq!(config.format, "compact");
378 assert!(config.structured);
379 }
380
381 #[test]
382 fn test_config_parse_level() {
383 let config = ObservabilityConfig {
384 level: "debug".to_string(),
385 ..Default::default()
386 };
387
388 assert!(matches!(config.parse_level().unwrap(), LogLevel::Debug));
389 }
390
391 #[test]
392 fn test_manager_creation() {
393 let config = ObservabilityConfig::default();
394 let manager = ObservabilityManager::new(config);
395 assert!(manager.is_ok());
396 }
397
398 #[test]
399 fn test_manager_capabilities() {
400 let config = ObservabilityConfig::default();
401 let manager = ObservabilityManager::new(config).unwrap();
402 let caps = manager.capabilities();
403
404 assert!(caps.contains(&"structured_logging".to_string()));
405 assert!(caps.contains(&"standard_rust_logging".to_string()));
406 assert!(caps.contains(&"log_level_info".to_string()));
407 }
408
409 #[test]
410 fn test_manager_default() {
411 let manager = ObservabilityManager::default();
412 assert_eq!(manager.config.level, "info");
413 }
414
415 #[test]
416 fn test_config_validation() {
417 let config = ObservabilityConfig {
418 level: "invalid".to_string(),
419 ..Default::default()
420 };
421 assert!(config.validate().is_err());
422
423 let config = ObservabilityConfig {
424 format: "invalid".to_string(),
425 ..Default::default()
426 };
427 assert!(config.validate().is_err());
428 }
429}