obs_wrapper/
log.rs

1use std::os::raw::c_char;
2
3use log::{Level, LevelFilter, Log, Metadata, Record, SetLoggerError};
4use obs_sys::{_bindgen_ty_1, blog, LOG_DEBUG, LOG_ERROR, LOG_INFO, LOG_WARNING};
5
6/// A logger that plugs into OBS's logging system.
7///
8/// Since OBS only has 4 logging levels and the lowest level is
9/// only enabled in debug builds of OBS, this logger provides a option
10/// to promote lower-level logs as `info`.
11///
12/// You can also use any other logger implementation, but we recommend this
13/// since OBS also writes everything in its logging system to a file, which can
14/// be viewed if there is a problem and OBS is not started from a console.
15///
16/// # Examples
17///
18/// A new logger with default settings.
19///
20/// ```compile_fail
21/// let _ = Logger::new().init();
22/// ```
23pub struct Logger {
24    max_level: LevelFilter,
25    promote_debug: bool,
26}
27
28impl Default for Logger {
29    fn default() -> Self {
30        Self {
31            max_level: LevelFilter::Trace,
32            promote_debug: false,
33        }
34    }
35}
36
37impl Logger {
38    /// Creates a new logger with default levle set to [`Level::Trace`] and does
39    /// not promote debug logs.
40    #[must_use = "You must call init() to begin logging"]
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    /// Initializes this logger, setting this as the global logger. This MUST be
46    /// called to be effective. This may fail if there is already a logger.
47    pub fn init(self) -> Result<(), SetLoggerError> {
48        log::set_max_level(self.max_level);
49        log::set_boxed_logger(Box::new(self))?;
50        Ok(())
51    }
52
53    /// Sets whether to promote [`Level::Debug`] and [`Level::Trace`] logs.
54    #[must_use = "You must call init() to begin logging"]
55    pub fn with_promote_debug(mut self, promote_debug: bool) -> Self {
56        self.promote_debug = promote_debug;
57        self
58    }
59
60    /// Sets the maximum logging level.
61    #[must_use = "You must call init() to begin logging"]
62    pub fn with_max_level(mut self, max_level: LevelFilter) -> Self {
63        self.max_level = max_level;
64        self
65    }
66}
67
68impl Log for Logger {
69    fn enabled(&self, metadata: &Metadata) -> bool {
70        self.max_level >= metadata.level()
71    }
72
73    fn log(&self, record: &Record) {
74        if !self.enabled(record.metadata()) {
75            return;
76        }
77        let level = record.level();
78        let native_level = to_native_level(level, self.promote_debug);
79        let target = if !record.target().is_empty() {
80            record.target()
81        } else {
82            record.module_path().unwrap_or_default()
83        };
84
85        let line = if self.promote_debug && level <= Level::Debug {
86            format!("({}) [{}] {}\0", level, target, record.args())
87        } else {
88            format!("[{}] {}\0", target, record.args())
89        };
90
91        unsafe {
92            blog(
93                native_level as i32,
94                "%s\0".as_ptr() as *const c_char,
95                line.as_ptr() as *const c_char,
96            );
97        }
98    }
99
100    fn flush(&self) {
101        // No need to flush
102    }
103}
104
105fn to_native_level(level: Level, promote_debug: bool) -> _bindgen_ty_1 {
106    match level {
107        Level::Error => LOG_ERROR,
108        Level::Warn => LOG_WARNING,
109        Level::Info => LOG_INFO,
110        _ => {
111            if promote_debug {
112                // Debug logs are only enabled in debug builds of OBS, make them accessible as
113                // info if needed
114                LOG_INFO
115            } else {
116                LOG_DEBUG
117            }
118        }
119    }
120}