Skip to main content

rivet_logger/
lib.rs

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}