1#![doc = include_str!("../README.md")]
2
3use std::collections::BTreeMap;
4use std::path::PathBuf;
5use std::sync::{Arc, Once, OnceLock, RwLock};
6
7use rivet_foundation::ConfigValue;
8#[cfg(not(target_arch = "wasm32"))]
9use time::macros::format_description;
10#[cfg(not(target_arch = "wasm32"))]
11use time::OffsetDateTime;
12
13pub mod handlers;
14mod log;
15mod log_service;
16pub mod logger;
17pub mod processors;
18
19pub use log::{ChannelLog, Log};
20pub use log_service::LogService;
21pub use logger::{
22 ClosureContext, DeferredValue, Git, Handler, Hostname, Introspection, Level, LoadAverage,
23 LoadAverageWindow, LogRecord, LogValue, Logger, LoggerError, MemoryPeakUsage, MemoryUsage,
24 Mercurial, ProcessId, Processor, PsrLogMessage, Tag, Uid, Web,
25};
26
27pub(crate) const LOG_LEVEL_WIDTH: usize = 5;
28
29#[derive(Clone)]
30struct ChannelBuildConfig {
31 log_config: ConfigValue,
32 base_path: PathBuf,
33}
34
35pub fn init_default_tracing() {
36 static TRACING_INIT: Once = Once::new();
37 TRACING_INIT.call_once(|| {
38 let subscriber = tracing_subscriber::fmt()
39 .with_writer(std::io::stdout)
40 .with_max_level(tracing::Level::DEBUG)
41 .finish();
42 let _ = tracing::subscriber::set_global_default(subscriber);
43 });
44}
45
46pub fn set_handler(handler: Arc<dyn handlers::Handler>) {
47 if let Ok(mut slot) = handler_slot().write() {
48 *slot = Some(handler);
49 }
50}
51
52pub fn set_channel_handler(channel: impl Into<String>, handler: Arc<dyn handlers::Handler>) {
53 if let Ok(mut slot) = channel_handler_slot().write() {
54 slot.insert(channel.into(), handler);
55 }
56}
57
58pub fn set_channel_handlers(handlers: BTreeMap<String, Arc<dyn handlers::Handler>>) {
59 if let Ok(mut slot) = channel_handler_slot().write() {
60 *slot = handlers;
61 }
62}
63
64pub fn set_channel_handler_build_config(log_config: ConfigValue, base_path: impl Into<PathBuf>) {
65 if let Ok(mut slot) = channel_build_config_slot().write() {
66 *slot = Some(ChannelBuildConfig {
67 log_config,
68 base_path: base_path.into(),
69 });
70 }
71}
72
73fn handler_slot() -> &'static RwLock<Option<Arc<dyn handlers::Handler>>> {
74 static HANDLER: OnceLock<RwLock<Option<Arc<dyn handlers::Handler>>>> = OnceLock::new();
75 HANDLER.get_or_init(|| RwLock::new(None))
76}
77
78fn channel_handler_slot() -> &'static RwLock<BTreeMap<String, Arc<dyn handlers::Handler>>> {
79 static CHANNEL_HANDLERS: OnceLock<RwLock<BTreeMap<String, Arc<dyn handlers::Handler>>>> =
80 OnceLock::new();
81 CHANNEL_HANDLERS.get_or_init(|| RwLock::new(BTreeMap::new()))
82}
83
84fn channel_build_config_slot() -> &'static RwLock<Option<ChannelBuildConfig>> {
85 static CHANNEL_BUILD_CONFIG: OnceLock<RwLock<Option<ChannelBuildConfig>>> = OnceLock::new();
86 CHANNEL_BUILD_CONFIG.get_or_init(|| RwLock::new(None))
87}
88
89fn active_handler() -> Option<Arc<dyn handlers::Handler>> {
90 handler_slot()
91 .read()
92 .ok()
93 .and_then(|slot| slot.as_ref().cloned())
94}
95
96fn active_channel_handler(channel: &str) -> Option<Arc<dyn handlers::Handler>> {
97 channel_handler_slot()
98 .read()
99 .ok()
100 .and_then(|slot| slot.get(channel).cloned())
101}
102
103pub(crate) fn write_to_handler(message: &str) {
104 if let Some(handler) = active_handler() {
105 let _ = handler.log(message);
106 }
107}
108
109pub(crate) fn write_to_channel_or_handler(channel: &str, message: &str) {
110 if let Some(handler) = active_channel_handler(channel) {
111 let _ = handler.log(message);
112 return;
113 }
114
115 if let Some(handler) = resolve_channel_handler(channel) {
116 let _ = handler.log(message);
117 return;
118 }
119
120 write_to_handler(message);
121}
122
123fn resolve_channel_handler(channel: &str) -> Option<Arc<dyn handlers::Handler>> {
124 let build_config = channel_build_config_slot()
125 .read()
126 .ok()
127 .and_then(|slot| slot.clone())?;
128
129 let handler = handlers::build_handler_for_channel_from_config(
130 &build_config.log_config,
131 &build_config.base_path,
132 channel,
133 )
134 .ok()?;
135
136 if let Ok(mut slot) = channel_handler_slot().write() {
137 if let Some(existing) = slot.get(channel) {
138 return Some(existing.clone());
139 }
140 slot.insert(channel.to_string(), Arc::clone(&handler));
141 }
142
143 Some(handler)
144}
145
146pub(crate) fn format_log_line(level: &str, message: &str) -> String {
147 let timestamp = timestamp_utc();
148 format!(
149 "{timestamp} {level:<width$} {message}",
150 width = LOG_LEVEL_WIDTH
151 )
152}
153
154#[cfg(not(target_arch = "wasm32"))]
155fn timestamp_utc() -> String {
156 let ts_format =
157 format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
158 OffsetDateTime::now_utc()
159 .format(ts_format)
160 .unwrap_or_else(|_| "1970-01-01T00:00:00.000000Z".to_string())
161}
162
163#[cfg(target_arch = "wasm32")]
164fn timestamp_utc() -> String {
165 "1970-01-01T00:00:00.000000Z".to_string()
166}
167
168#[cfg(test)]
169mod tests {
170 use super::format_log_line;
171
172 #[test]
173 fn level_column_is_fixed_width_for_alignment() {
174 let debug = format_log_line("DEBUG", "debug");
175 let info = format_log_line("INFO", "info");
176 let error = format_log_line("ERROR", "error");
177
178 let debug_rest = debug
179 .split_once(' ')
180 .map(|(_, rest)| rest.to_string())
181 .expect("log line should include separator");
182 let info_rest = info
183 .split_once(' ')
184 .map(|(_, rest)| rest.to_string())
185 .expect("log line should include separator");
186 let error_rest = error
187 .split_once(' ')
188 .map(|(_, rest)| rest.to_string())
189 .expect("log line should include separator");
190
191 assert_eq!(debug_rest.find("debug"), info_rest.find("info"));
192 assert_eq!(debug_rest.find("debug"), error_rest.find("error"));
193 }
194}