1use std::collections::BTreeMap;
7use std::sync::{Arc, LazyLock, Mutex, OnceLock};
8
9use serde_json::Value;
10
11use crate::backpressure::{release, try_acquire, QueueTicket};
12use crate::config::TelemetryConfig;
13#[cfg(feature = "governance")]
14use crate::consent::should_allow;
15use crate::context::get_context;
16use crate::health::increment_emitted;
17use crate::runtime::get_runtime_config;
18use crate::sampling::{set_sampling_policy, should_sample, SamplingPolicy, Signal};
19use crate::tracer::get_trace_context;
20
21mod emit;
22mod levels;
23mod pretty;
24mod processors;
25
26use emit::{emit_if_console, emit_if_json, emit_if_otel, emit_if_pretty};
27pub use emit::{
28 enable_console_capture_for_tests, enable_json_capture_for_tests,
29 enable_pretty_capture_for_tests, take_console_capture, take_json_capture, take_pretty_capture,
30};
31use levels::{effective_level_threshold, level_order};
32use processors::process_event;
33
34#[cfg(feature = "governance")]
35#[inline(always)]
36fn consent_allows_logs(level: &str) -> bool {
37 should_allow("logs", Some(level))
38}
39
40#[cfg(not(feature = "governance"))]
41#[inline(always)]
42#[cfg_attr(test, mutants::skip)] fn consent_allows_logs(_level: &str) -> bool {
44 true
45}
46
47const MAX_FALLBACK_EVENTS: usize = 1000;
48
49static LOGGING_CONFIG_OVERRIDE: LazyLock<Mutex<Option<crate::config::LoggingConfig>>> =
50 LazyLock::new(|| Mutex::new(None));
51
52#[cfg_attr(test, mutants::skip)] fn empty_events_mutex() -> Mutex<Vec<LogEvent>> {
54 Mutex::new(Vec::new())
55}
56
57pub fn configure_logging(config: crate::config::LoggingConfig) {
63 *crate::_lock::lock(&LOGGING_CONFIG_OVERRIDE) = Some(config);
64}
65
66pub fn reset_logging_config_for_tests() {
68 *crate::_lock::lock(&LOGGING_CONFIG_OVERRIDE) = None;
69}
70
71fn active_logging_config() -> crate::config::LoggingConfig {
74 let override_cfg = crate::_lock::lock(&LOGGING_CONFIG_OVERRIDE).clone();
75 if let Some(cfg) = override_cfg {
76 return cfg;
77 }
78 if let Some(cfg) = get_runtime_config() {
79 return cfg.logging.clone();
80 }
81 match TelemetryConfig::from_env() {
82 Ok(cfg) => cfg.logging,
83 Err(err) => {
84 eprintln!("provide_telemetry: logging config parse failed, using defaults: {err}");
85 crate::config::LoggingConfig::default()
86 }
87 }
88}
89
90fn runtime_identity_config() -> Option<TelemetryConfig> {
91 match get_runtime_config() {
92 Some(cfg) => Some(cfg),
93 None => TelemetryConfig::from_env().ok(),
94 }
95}
96
97fn apply_lazy_logger_policies_from_env() {
98 if get_runtime_config().is_some() {
99 return;
100 }
101 if std::env::var_os("PROVIDE_SAMPLING_LOGS_RATE").is_none() {
102 return;
103 }
104 let Ok(cfg) = TelemetryConfig::from_env() else {
105 return;
106 };
107 let _ = set_sampling_policy(
108 Signal::Logs,
109 SamplingPolicy {
110 default_rate: cfg.sampling.logs_rate,
111 overrides: BTreeMap::new(),
112 },
113 );
114}
115
116fn inject_identity_fields(context: &mut BTreeMap<String, Value>, cfg: TelemetryConfig) {
117 context
118 .entry("service".to_string())
119 .or_insert(Value::String(cfg.service_name));
120 context
121 .entry("env".to_string())
122 .or_insert(Value::String(cfg.environment));
123 context
124 .entry("version".to_string())
125 .or_insert(Value::String(cfg.version));
126}
127
128fn inject_runtime_identity_fields(context: &mut BTreeMap<String, Value>) {
129 let cfg = match runtime_identity_config() {
130 Some(cfg) => cfg,
131 None => return,
132 };
133 inject_identity_fields(context, cfg);
134}
135
136fn emit_event(mut event: LogEvent) {
138 process_event(&mut event);
139 emit_if_json(&event);
140 emit_if_pretty(&event);
141 emit_if_console(&event);
142 emit_if_otel(&event);
143 let mut buf = crate::_lock::lock(events());
144 if buf.len() >= MAX_FALLBACK_EVENTS {
145 return;
146 }
147 buf.push(event);
148}
149
150fn acquire_log_ticket(level: &str, target: &str, sample_key: Option<&str>) -> Option<QueueTicket> {
151 let config = active_logging_config();
152 if level_order(level) < effective_level_threshold(target, &config) {
153 return None;
154 }
155 if !consent_allows_logs(level) {
156 return None;
157 }
158 if !should_sample(Signal::Logs, sample_key).unwrap_or(true) {
159 return None;
160 }
161 try_acquire(Signal::Logs)
162}
163
164fn log_event_with_fields(
166 level: &str,
167 target: &str,
168 message: &str,
169 extra: &BTreeMap<String, Value>,
170) {
171 let Some(ticket) = acquire_log_ticket(level, target, Some(message)) else {
172 return;
173 };
174 let mut event = new_event(target, level, message);
175 event.context.extend(extra.clone());
176 emit_event(event);
177 increment_emitted(Signal::Logs, 1);
178 release(ticket);
179}
180
181fn log_event(level: &str, target: &str, message: &str) {
183 let Some(ticket) = acquire_log_ticket(level, target, Some(message)) else {
184 return;
185 };
186 emit_event(new_event(target, level, message));
187 increment_emitted(Signal::Logs, 1);
188 release(ticket);
189}
190
191fn log_event_with_event(level: &str, target: &str, ev: &crate::schema::Event) {
193 let Some(ticket) = acquire_log_ticket(level, target, Some(&ev.event)) else {
194 return;
195 };
196 let mut event = new_event(target, level, &ev.event);
197 event.event_metadata = Some(EventMetadata {
198 domain: ev.domain.clone(),
199 action: ev.action.clone(),
200 resource: ev.resource.clone(),
201 status: ev.status.clone(),
202 });
203 emit_event(event);
204 increment_emitted(Signal::Logs, 1);
205 release(ticket);
206}
207
208#[derive(Clone, Debug, PartialEq, Eq)]
211pub struct EventMetadata {
212 pub domain: String,
213 pub action: String,
214 pub resource: Option<String>,
215 pub status: String,
216}
217
218#[derive(Clone, Debug, Default, PartialEq)]
219pub struct LogEvent {
220 pub level: String,
221 pub target: String,
222 pub message: String,
223 pub context: BTreeMap<String, Value>,
224 pub trace_id: Option<String>,
225 pub span_id: Option<String>,
226 pub event_metadata: Option<EventMetadata>,
227}
228
229#[derive(Clone, Debug, Default, PartialEq, Eq)]
230pub struct Logger {
231 target: String,
232}
233
234#[derive(Clone, Debug, Default, PartialEq, Eq)]
235pub struct NullLogger {
236 target: String,
237}
238
239#[derive(Clone, Debug, Default)]
240pub struct BufferLogger {
241 target: String,
242 events: Arc<Mutex<Vec<LogEvent>>>,
243}
244
245static EVENTS: OnceLock<Mutex<Vec<LogEvent>>> = OnceLock::new();
246
247fn events() -> &'static Mutex<Vec<LogEvent>> {
248 EVENTS.get_or_init(empty_events_mutex)
249}
250
251fn default_logger() -> Logger {
252 Logger::new(None)
253}
254
255pub static logger: LazyLock<Logger> = LazyLock::new(default_logger);
256
257fn logger_target(target: Option<&str>) -> String {
258 match target {
259 Some(target) => target.to_string(),
260 None => "provide.telemetry".to_string(),
261 }
262}
263
264fn new_event(target: &str, level: &str, message: &str) -> LogEvent {
265 let trace = get_trace_context();
266 let mut context = get_context();
267 inject_runtime_identity_fields(&mut context);
268 LogEvent {
269 level: level.to_string(),
270 target: target.to_string(),
271 message: message.to_string(),
272 context,
273 trace_id: trace.get("trace_id").and_then(Clone::clone),
274 span_id: trace.get("span_id").and_then(Clone::clone),
275 event_metadata: None,
276 }
277}
278
279impl Logger {
280 pub fn new(target: Option<&str>) -> Self {
281 Self {
282 target: logger_target(target),
283 }
284 }
285
286 pub fn target(&self) -> &str {
287 &self.target
288 }
289
290 pub fn debug(&self, message: &str) {
291 self.log("DEBUG", message);
292 }
293
294 pub fn info(&self, message: &str) {
295 self.log("INFO", message);
296 }
297
298 pub fn warn(&self, message: &str) {
299 self.log("WARN", message);
300 }
301
302 pub fn error(&self, message: &str) {
303 self.log("ERROR", message);
304 }
305
306 pub fn log(&self, level: &str, message: &str) {
307 log_event(level, &self.target, message);
308 }
309
310 pub fn log_fields(&self, level: &str, message: &str, fields: &BTreeMap<String, Value>) {
312 log_event_with_fields(level, &self.target, message, fields);
313 }
314
315 pub fn debug_fields(&self, message: &str, fields: &BTreeMap<String, Value>) {
316 self.log_fields("DEBUG", message, fields);
317 }
318
319 pub fn info_fields(&self, message: &str, fields: &BTreeMap<String, Value>) {
320 self.log_fields("INFO", message, fields);
321 }
322
323 pub fn warn_fields(&self, message: &str, fields: &BTreeMap<String, Value>) {
324 self.log_fields("WARN", message, fields);
325 }
326
327 pub fn error_fields(&self, message: &str, fields: &BTreeMap<String, Value>) {
328 self.log_fields("ERROR", message, fields);
329 }
330
331 pub fn debug_event(&self, event: &crate::schema::Event) {
332 self.log_event("DEBUG", event);
333 }
334
335 pub fn info_event(&self, event: &crate::schema::Event) {
336 self.log_event("INFO", event);
337 }
338
339 pub fn warn_event(&self, event: &crate::schema::Event) {
340 self.log_event("WARN", event);
341 }
342
343 pub fn error_event(&self, event: &crate::schema::Event) {
344 self.log_event("ERROR", event);
345 }
346
347 pub fn log_event(&self, level: &str, event: &crate::schema::Event) {
348 log_event_with_event(level, &self.target, event);
349 }
350
351 pub fn drain_events_for_tests() -> Vec<LogEvent> {
352 std::mem::take(&mut *crate::_lock::lock(events()))
353 }
354}
355
356impl NullLogger {
357 pub fn new(target: Option<&str>) -> Self {
358 Self {
359 target: logger_target(target),
360 }
361 }
362
363 pub fn target(&self) -> &str {
364 &self.target
365 }
366
367 pub fn debug(&self, _message: &str) {}
368
369 pub fn info(&self, _message: &str) {}
370
371 pub fn warn(&self, _message: &str) {}
372
373 pub fn error(&self, _message: &str) {}
374}
375
376impl BufferLogger {
377 pub fn new(target: Option<&str>) -> Self {
378 Self {
379 target: logger_target(target),
380 events: Arc::new(Mutex::new(Vec::new())),
381 }
382 }
383
384 pub fn target(&self) -> &str {
385 &self.target
386 }
387
388 pub fn debug(&self, message: &str) {
389 self.log("DEBUG", message);
390 }
391
392 pub fn info(&self, message: &str) {
393 self.log("INFO", message);
394 }
395
396 pub fn warn(&self, message: &str) {
397 self.log("WARN", message);
398 }
399
400 pub fn error(&self, message: &str) {
401 self.log("ERROR", message);
402 }
403
404 pub fn log(&self, level: &str, message: &str) {
405 let config = active_logging_config();
406 if level_order(level) < effective_level_threshold(&self.target, &config) {
407 return;
408 }
409 let mut event = new_event(&self.target, level, message);
410 process_event(&mut event);
411 crate::_lock::lock(&self.events).push(event);
412 }
413
414 pub fn drain(&self) -> Vec<LogEvent> {
415 std::mem::take(&mut *crate::_lock::lock(&self.events))
416 }
417}
418
419pub fn get_logger(name: Option<&str>) -> Logger {
420 apply_lazy_logger_policies_from_env();
421 Logger::new(name)
422}
423
424pub fn null_logger(name: Option<&str>) -> NullLogger {
425 NullLogger::new(name)
426}
427
428pub fn buffer_logger(name: Option<&str>) -> BufferLogger {
429 BufferLogger::new(name)
430}
431
432impl log::Log for Logger {
433 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
434 let config = active_logging_config();
435 let record_order: u8 = match metadata.level() {
436 log::Level::Error => 4,
437 log::Level::Warn => 3,
438 log::Level::Info => 2,
439 log::Level::Debug => 1,
440 log::Level::Trace => 0,
441 };
442 record_order >= effective_level_threshold(metadata.target(), &config)
443 }
444
445 fn log(&self, record: &log::Record<'_>) {
446 if !self.enabled(record.metadata()) {
447 return;
448 }
449 let level = match record.level() {
450 log::Level::Error => "ERROR",
451 log::Level::Warn => "WARN",
452 log::Level::Info => "INFO",
453 log::Level::Debug => "DEBUG",
454 log::Level::Trace => "TRACE",
455 };
456 log_event(level, record.target(), &record.args().to_string());
457 }
458
459 fn flush(&self) {}
460}
461
462pub fn set_as_global_logger() -> Result<(), log::SetLoggerError> {
464 match log::set_logger(&*logger) {
465 Ok(()) => {
466 log::set_max_level(log::LevelFilter::Trace);
467 Ok(())
468 }
469 Err(err) => Err(err),
470 }
471}
472
473#[cfg(test)]
474mod tests;
475
476#[cfg(test)]
477#[path = "log_trait_tests.rs"]
478mod log_trait_tests;