bitwarden_uniffi/
log_callback.rs

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