Skip to main content

glsdk/
logging.rs

1// SDK logging — apps install a LogListener to receive log output
2// emitted by this SDK and by the underlying gl-client library.
3//
4// The bridge sits on top of the `log` crate facade: gl-sdk's own
5// `tracing` calls are routed via `tracing`'s `log` feature, and
6// gl-client's direct `log::*!` calls flow through the same channel.
7
8use crate::Error;
9use log::{Level, LevelFilter, Log, Metadata, Record};
10
11/// Log level for filtering messages.
12#[derive(Clone, Copy, Debug, uniffi::Enum)]
13pub enum LogLevel {
14    Error,
15    Warn,
16    Info,
17    Debug,
18    Trace,
19}
20
21impl From<Level> for LogLevel {
22    fn from(level: Level) -> Self {
23        match level {
24            Level::Error => LogLevel::Error,
25            Level::Warn => LogLevel::Warn,
26            Level::Info => LogLevel::Info,
27            Level::Debug => LogLevel::Debug,
28            Level::Trace => LogLevel::Trace,
29        }
30    }
31}
32
33impl From<LogLevel> for LevelFilter {
34    fn from(level: LogLevel) -> Self {
35        match level {
36            LogLevel::Error => LevelFilter::Error,
37            LogLevel::Warn => LevelFilter::Warn,
38            LogLevel::Info => LevelFilter::Info,
39            LogLevel::Debug => LevelFilter::Debug,
40            LogLevel::Trace => LevelFilter::Trace,
41        }
42    }
43}
44
45/// A single log message from the SDK.
46#[derive(Clone, Debug, uniffi::Record)]
47pub struct LogEntry {
48    pub level: LogLevel,
49    pub message: String,
50    /// The module that produced this log (e.g. "gl_client::scheduler").
51    pub target: String,
52    /// Source file path, if the log macro recorded one.
53    pub file: Option<String>,
54    /// Source line number, if the log macro recorded one.
55    pub line: Option<u32>,
56}
57
58/// Callback interface for receiving log messages.
59///
60/// `on_log` is invoked on the thread that emitted the log — which can
61/// be any tokio worker or background thread inside the SDK. Keep the
62/// implementation cheap and non-blocking; if you need UI updates,
63/// hand the entry off to your app's main thread.
64#[uniffi::export(callback_interface)]
65pub trait LogListener: Send + Sync {
66    fn on_log(&self, entry: LogEntry);
67}
68
69struct SdkLogger {
70    listener: Box<dyn LogListener>,
71}
72
73impl Log for SdkLogger {
74    fn enabled(&self, metadata: &Metadata) -> bool {
75        // Use the `log` crate's global max level so `set_log_level`
76        // can change the filter at runtime. `log::max_level()` is an
77        // atomic read that every log macro also consults.
78        metadata.level() <= log::max_level()
79    }
80
81    fn log(&self, record: &Record) {
82        if self.enabled(record.metadata()) {
83            self.listener.on_log(LogEntry {
84                level: record.level().into(),
85                message: record.args().to_string(),
86                target: record.target().to_string(),
87                file: record.file().map(|s| s.to_string()),
88                line: record.line(),
89            });
90        }
91    }
92
93    fn flush(&self) {}
94}
95
96/// Install a log listener. Call this once, as early as possible, so
97/// logs emitted during node bring-up are captured.
98///
99/// Returns `Err` if a logger is already installed in the process
100/// (either from an earlier successful call to `set_logger` or from a
101/// different crate). To change the filter after installation use
102/// `set_log_level`.
103pub fn set_logger(level: LogLevel, listener: Box<dyn LogListener>) -> Result<(), Error> {
104    let filter: LevelFilter = level.into();
105    log::set_boxed_logger(Box::new(SdkLogger { listener })).map_err(|e| {
106        Error::other(format!("a `log` logger is already installed: {e}"))
107    })?;
108    log::set_max_level(filter);
109    Ok(())
110}
111
112/// Change the log filter at runtime without reinstalling the listener.
113///
114/// Useful for features like a "verbose logs" toggle in app settings.
115/// Safe to call before `set_logger` — it adjusts the `log` crate's
116/// global max level immediately; the listener (if any) picks it up
117/// on the next emitted message.
118pub fn set_log_level(level: LogLevel) {
119    log::set_max_level(level.into());
120}