provide_telemetry/logger/
emit.rs1use std::sync::{LazyLock, Mutex};
10
11use serde_json::{json, Value};
12
13use super::pretty::format_pretty_line;
14use super::{active_logging_config, LogEvent};
15
16static JSON_CAPTURE: LazyLock<Mutex<Option<Vec<u8>>>> = LazyLock::new(|| Mutex::new(None));
21
22pub fn enable_json_capture_for_tests() {
23 *crate::_lock::lock(&JSON_CAPTURE) = Some(Vec::new());
24}
25
26pub fn take_json_capture() -> Vec<u8> {
27 crate::_lock::lock(&JSON_CAPTURE).take().unwrap_or_default()
28}
29
30static CONSOLE_CAPTURE: LazyLock<Mutex<Option<Vec<u8>>>> = LazyLock::new(|| Mutex::new(None));
31
32pub fn enable_console_capture_for_tests() {
33 *crate::_lock::lock(&CONSOLE_CAPTURE) = Some(Vec::new());
34}
35
36pub fn take_console_capture() -> Vec<u8> {
37 crate::_lock::lock(&CONSOLE_CAPTURE)
38 .take()
39 .unwrap_or_default()
40}
41
42static PRETTY_CAPTURE: LazyLock<Mutex<Option<Vec<u8>>>> = LazyLock::new(|| Mutex::new(None));
43
44pub fn enable_pretty_capture_for_tests() {
45 *crate::_lock::lock(&PRETTY_CAPTURE) = Some(Vec::new());
46}
47
48pub fn take_pretty_capture() -> Vec<u8> {
49 crate::_lock::lock(&PRETTY_CAPTURE)
50 .take()
51 .unwrap_or_default()
52}
53
54pub(crate) fn now_iso8601() -> String {
59 use std::time::{SystemTime, UNIX_EPOCH};
60 let d = SystemTime::now()
61 .duration_since(UNIX_EPOCH)
62 .unwrap_or_default();
63 iso8601_from_unix_parts(d.as_secs(), d.subsec_millis())
64}
65
66fn iso8601_from_unix_parts(ts: u64, ms: u32) -> String {
67 let z: i64 = (ts / 86_400) as i64 + 719_468;
68 let era: i64 = z / 146_097;
69 let doe: i64 = z - era * 146_097;
70 let yoe: i64 = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
71 let y: i64 = yoe + era * 400;
72 let doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100);
73 let mp: i64 = (5 * doy + 2) / 153;
74 let day: i64 = doy - (153 * mp + 2) / 5 + 1;
75 let month: i64 = if mp < 10 { mp + 3 } else { mp - 9 };
76 let year: i64 = if month <= 2 { y + 1 } else { y };
77 let sod = ts % 86_400;
78 let hour = sod / 3_600;
79 let min = (sod % 3_600) / 60;
80 let sec = sod % 60;
81 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{min:02}:{sec:02}.{ms:03}Z")
82}
83
84fn emit_json_line(event: &LogEvent, include_timestamp: bool) {
89 let mut record = if include_timestamp {
90 json!({
91 "message": event.message,
92 "level": event.level,
93 "timestamp": now_iso8601(),
94 })
95 } else {
96 json!({
97 "message": event.message,
98 "level": event.level,
99 })
100 };
101 let obj = record.as_object_mut().expect("json object");
102 for (k, v) in &event.context {
103 obj.insert(k.clone(), v.clone());
104 }
105 if let Some(tid) = &event.trace_id {
106 obj.insert("trace_id".to_string(), Value::String(tid.clone()));
107 }
108 if let Some(sid) = &event.span_id {
109 obj.insert("span_id".to_string(), Value::String(sid.clone()));
110 }
111 obj.insert(
112 "logger_name".to_string(),
113 Value::String(event.target.clone()),
114 );
115 let line = serde_json::to_string(obj).unwrap_or_default();
116 let mut capture = crate::_lock::lock(&JSON_CAPTURE);
117 if let Some(buf) = capture.as_mut() {
118 buf.extend_from_slice(line.as_bytes());
119 buf.push(b'\n');
120 } else {
121 eprintln!("{line}");
122 }
123}
124
125pub(super) fn emit_if_json(event: &LogEvent) {
126 let logging = active_logging_config();
127 if logging.fmt.eq_ignore_ascii_case("json") {
128 emit_json_line(event, logging.include_timestamp);
129 }
130}
131
132fn format_console_line(event: &LogEvent, include_timestamp: bool) -> String {
137 let mut s = String::new();
138 if include_timestamp {
139 s.push_str(&now_iso8601());
140 s.push_str(" ");
141 }
142 s.push_str(&format!("{:<5}", event.level));
143 s.push_str(" ");
144 s.push_str(&event.target);
145 s.push_str(" ");
146 s.push_str(&event.message);
147 for (k, v) in &event.context {
148 s.push_str(&format!(" {k}={v}"));
149 }
150 s
151}
152
153pub(super) fn emit_if_console(event: &LogEvent) {
154 let logging = active_logging_config();
155 if logging.fmt.eq_ignore_ascii_case("json") || logging.fmt.eq_ignore_ascii_case("pretty") {
156 return;
157 }
158 let line = format_console_line(event, logging.include_timestamp);
159 let mut capture = crate::_lock::lock(&CONSOLE_CAPTURE);
160 if let Some(buf) = capture.as_mut() {
161 buf.extend_from_slice(line.as_bytes());
162 buf.push(b'\n');
163 } else {
164 eprintln!("{line}");
165 }
166}
167
168pub(super) fn emit_if_pretty(event: &LogEvent) {
169 let logging = active_logging_config();
170 if !logging.fmt.eq_ignore_ascii_case("pretty") {
171 return;
172 }
173 let line = format_pretty_line(event, &logging);
174 let mut capture = crate::_lock::lock(&PRETTY_CAPTURE);
175 if let Some(buf) = capture.as_mut() {
176 buf.extend_from_slice(line.as_bytes());
177 buf.push(b'\n');
178 } else {
179 eprintln!("{line}");
180 }
181}
182
183#[cfg(feature = "otel")]
184pub(super) fn emit_if_otel(event: &LogEvent) {
185 crate::otel::logs::emit_log(event);
186}
187
188#[cfg(not(feature = "otel"))]
189pub(super) fn emit_if_otel(_event: &LogEvent) {}
190
191#[cfg(test)]
192#[path = "emit_tests.rs"]
193mod tests;