Skip to main content

bitwarden_uniffi/
log_callback.rs

1use std::sync::Arc;
2
3use tracing_subscriber::{Layer, layer::Context};
4
5use crate::LogLevel;
6
7/// Callback interface for receiving SDK log events
8/// Mobile implementations forward these to Flight Recorder
9#[uniffi::export(with_foreign)]
10pub trait LogCallback: Send + Sync {
11    /// Called when SDK emits a log entry
12    ///
13    /// # Parameters
14    /// - level: Log level
15    /// - target: Module that emitted log (e.g., "bitwarden_core::auth")
16    /// - message: The log message text
17    ///
18    /// # Returns
19    /// Result<(), BitwardenError> - mobile implementations should catch exceptions
20    /// and return errors rather than panicking
21    fn on_log(&self, level: LogLevel, target: String, message: String) -> crate::Result<()>;
22}
23
24/// Custom tracing Layer that forwards events to UNIFFI callback
25pub(crate) struct CallbackLayer {
26    callback: Arc<dyn LogCallback>,
27}
28impl CallbackLayer {
29    pub(crate) fn new(callback: Arc<dyn LogCallback>) -> Self {
30        Self { callback }
31    }
32}
33impl<S> Layer<S> for CallbackLayer
34where
35    S: tracing::Subscriber,
36{
37    fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
38        let metadata = event.metadata();
39        let level = metadata.level().into();
40        let target = metadata.target().to_string();
41
42        let mut message = String::new();
43        let writer = tracing_subscriber::fmt::format::Writer::new(&mut message);
44        let mut visitor = tracing_subscriber::fmt::format::DefaultVisitor::new(writer, false);
45        event.record(&mut visitor);
46
47        // Forward to UNIFFI callback - errors are silently ignored
48        let _ = self.callback.on_log(level, target, message);
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use std::sync::{Arc, Mutex};
55
56    use super::*;
57
58    struct TestLogCallback {
59        logs: Arc<Mutex<Vec<(LogLevel, String, String)>>>,
60    }
61
62    impl LogCallback for TestLogCallback {
63        fn on_log(&self, level: LogLevel, target: String, message: String) -> crate::Result<()> {
64            self.logs.lock().unwrap().push((level, target, message));
65            Ok(())
66        }
67    }
68
69    #[test]
70    fn test_trait_can_be_implemented() {
71        let _callback: Arc<dyn LogCallback> = Arc::new(TestLogCallback {
72            logs: Arc::new(Mutex::new(Vec::new())),
73        });
74    }
75
76    #[test]
77    fn test_callback_layer_forwards_events() {
78        // Verify CallbackLayer correctly extracts and forwards log data
79        let logs = Arc::new(Mutex::new(Vec::new()));
80        let callback = Arc::new(TestLogCallback { logs: logs.clone() });
81        let _layer = CallbackLayer::new(callback);
82
83        // Test that layer compiles and can be created
84        // Full integration test will happen after Client::new() modification
85        assert!(logs.lock().unwrap().is_empty());
86    }
87}