core_crypto_ffi/core_crypto/
logger.rs1#[cfg(not(target_family = "wasm"))]
2use std::sync::Arc;
3use std::{
4 collections::BTreeMap,
5 ops::Deref as _,
6 sync::{LazyLock, Once},
7};
8
9use log::{
10 Level, LevelFilter, Metadata, Record,
11 kv::{Key, Value, VisitSource},
12};
13use log_reload::ReloadLog;
14#[cfg(target_family = "wasm")]
15use wasm_bindgen::prelude::*;
16
17#[cfg(target_family = "wasm")]
18use crate::{CoreCrypto, CoreCryptoError, CoreCryptoResult};
19
20#[derive(Debug, Clone, Copy)]
22#[cfg_attr(target_family = "wasm", wasm_bindgen)]
23#[cfg_attr(not(target_family = "wasm"), derive(uniffi::Enum))]
24#[repr(u8)]
25pub enum CoreCryptoLogLevel {
26 Off = 1,
27 Trace,
28 Debug,
29 Info,
30 Warn,
31 Error,
32}
33
34impl From<CoreCryptoLogLevel> for LevelFilter {
35 fn from(value: CoreCryptoLogLevel) -> LevelFilter {
36 match value {
37 CoreCryptoLogLevel::Off => LevelFilter::Off,
38 CoreCryptoLogLevel::Trace => LevelFilter::Trace,
39 CoreCryptoLogLevel::Debug => LevelFilter::Debug,
40 CoreCryptoLogLevel::Info => LevelFilter::Info,
41 CoreCryptoLogLevel::Warn => LevelFilter::Warn,
42 CoreCryptoLogLevel::Error => LevelFilter::Error,
43 }
44 }
45}
46
47impl From<Level> for CoreCryptoLogLevel {
48 fn from(value: Level) -> Self {
49 match value {
50 Level::Warn => CoreCryptoLogLevel::Warn,
51 Level::Error => CoreCryptoLogLevel::Error,
52 Level::Info => CoreCryptoLogLevel::Info,
53 Level::Debug => CoreCryptoLogLevel::Debug,
54 Level::Trace => CoreCryptoLogLevel::Trace,
55 }
56 }
57}
58
59#[cfg(not(target_family = "wasm"))]
60#[derive(Debug, thiserror::Error, uniffi::Error)]
61#[uniffi(flat_error)]
62pub enum LoggingError {
63 #[error("panic or otherwise unexpected error from foreign code")]
64 Ffi(#[from] uniffi::UnexpectedUniFFICallbackError),
65}
66
67#[cfg(not(target_family = "wasm"))]
69#[uniffi::export(with_foreign)]
70pub trait CoreCryptoLogger: std::fmt::Debug + Send + Sync {
71 fn log(&self, level: CoreCryptoLogLevel, message: String, context: Option<String>) -> Result<(), LoggingError>;
75}
76
77#[cfg(target_family = "wasm")]
79#[wasm_bindgen]
80#[derive(Debug, Clone, Default)]
81pub struct CoreCryptoLogger {
82 logger: js_sys::Function,
83 this: JsValue,
84}
85
86#[cfg(target_family = "wasm")]
87#[wasm_bindgen]
88impl CoreCryptoLogger {
89 #[wasm_bindgen(constructor)]
98 pub fn new(logger: js_sys::Function, this: JsValue) -> CoreCryptoResult<Self> {
99 if logger.length() != 3 {
100 return Err(CoreCryptoError::ad_hoc(format!(
101 "logger function must accept 3 arguments but accepts {}",
102 logger.length()
103 )));
104 }
105 Ok(Self { logger, this })
107 }
108}
109
110#[cfg(target_family = "wasm")]
111impl CoreCryptoLogger {
112 fn log(&self, level: CoreCryptoLogLevel, message: String, context: Option<String>) {
113 if let Err(meta_err) = self
114 .logger
115 .call3(&self.this, &level.into(), &(&message).into(), &context.into())
116 {
117 web_sys::console::error_2(&meta_err, &message.into());
118 }
119 }
120}
121
122#[cfg(target_family = "wasm")]
123unsafe impl Send for CoreCryptoLogger {}
127#[cfg(target_family = "wasm")]
128unsafe impl Sync for CoreCryptoLogger {}
130
131#[cfg(not(target_family = "wasm"))]
133#[derive(Debug)]
134struct DummyLogger;
135
136#[cfg(not(target_family = "wasm"))]
137impl CoreCryptoLogger for DummyLogger {
138 #[allow(unused_variables)]
139 fn log(&self, level: CoreCryptoLogLevel, message: String, context: Option<String>) -> Result<(), LoggingError> {
140 Ok(())
141 }
142}
143
144#[cfg(not(target_family = "wasm"))]
146#[derive(Clone, derive_more::Constructor)]
147struct LogShim {
148 logger: Arc<dyn CoreCryptoLogger>,
149}
150
151#[cfg(not(target_family = "wasm"))]
152impl Default for LogShim {
153 fn default() -> Self {
154 Self {
155 logger: Arc::new(DummyLogger),
156 }
157 }
158}
159
160#[cfg(not(target_family = "wasm"))]
161impl LogShim {
162 fn adjusted_log_level(&self, metadata: &Metadata) -> Level {
163 match (metadata.level(), metadata.target()) {
164 (level, "refinery_core::traits") if level >= Level::Info => Level::Debug,
166 (level, "refinery_core::traits::sync") if level >= Level::Info => Level::Debug,
167 (level, _) => level,
168 }
169 }
170}
171
172#[cfg(target_family = "wasm")]
173#[derive(Clone, Default)]
174struct LogShim {
175 logger: CoreCryptoLogger,
176}
177
178#[cfg(target_family = "wasm")]
179impl LogShim {
180 fn new(logger: CoreCryptoLogger) -> Self {
181 Self { logger }
182 }
183}
184
185impl log::Log for LogShim {
186 #[cfg_attr(target_family = "wasm", expect(unused_variables))]
187 fn enabled(&self, metadata: &Metadata) -> bool {
188 cfg_if::cfg_if! {
189 if #[cfg(target_family = "wasm")] {
190 true
191 } else {
192 log::max_level() >= self.adjusted_log_level(metadata)
193 }}
194 }
195
196 fn log(&self, record: &Record) {
197 struct KeyValueVisitor<'kvs>(BTreeMap<Key<'kvs>, Value<'kvs>>);
198
199 impl<'kvs> VisitSource<'kvs> for KeyValueVisitor<'kvs> {
200 #[inline]
201 fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), log::kv::Error> {
202 self.0.insert(key, value);
203 Ok(())
204 }
205 }
206
207 let kvs = record.key_values();
208 let mut visitor = KeyValueVisitor(BTreeMap::new());
209 let _ = kvs.visit(&mut visitor);
210
211 if !self.enabled(record.metadata()) {
212 return;
213 }
214
215 let message = format!("{}", record.args());
216 let context = serde_json::to_string(&visitor.0).ok();
217
218 #[cfg(not(target_family = "wasm"))]
220 {
221 let log_result = self.logger.log(
222 CoreCryptoLogLevel::from(self.adjusted_log_level(record.metadata())),
223 message.clone(),
224 context,
225 );
226 if let Err(LoggingError::Ffi(meta_err @ uniffi::UnexpectedUniFFICallbackError { .. })) = log_result {
227 eprintln!("{meta_err} while attempting to produce {message}");
228 }
229 }
230
231 #[cfg(target_family = "wasm")]
233 {
234 self.logger.log(record.metadata().level().into(), message, context);
235 }
236 }
237
238 fn flush(&self) {}
239}
240
241static INIT_LOGGER: Once = Once::new();
242static LOGGER: LazyLock<ReloadLog<LogShim>> = LazyLock::new(|| ReloadLog::new(LogShim::default()));
243
244#[cfg(not(target_family = "wasm"))]
246type Logger = Arc<dyn CoreCryptoLogger>;
247
248#[cfg(target_family = "wasm")]
249type Logger = CoreCryptoLogger;
250
251#[cfg(not(target_family = "wasm"))]
255#[uniffi::export]
256pub fn set_logger(logger: Arc<dyn CoreCryptoLogger>, level: CoreCryptoLogLevel) {
257 set_logger_only_inner(logger);
258 set_max_log_level(level);
259}
260
261fn set_logger_only_inner(logger: Logger) {
262 LOGGER
263 .handle()
264 .replace(LogShim::new(logger))
265 .expect("no poisoned locks should be possible as we never panic while holding the lock");
266
267 INIT_LOGGER.call_once(|| {
268 log::set_logger(LOGGER.deref())
269 .expect("no poisoned locks should be possible as we never panic while holding the lock");
270 log::set_max_level(LevelFilter::Warn);
271 });
272}
273
274#[cfg(not(target_family = "wasm"))]
276#[uniffi::export]
277pub fn set_logger_only(logger: Logger) {
278 set_logger_only_inner(logger);
279}
280
281#[cfg(not(target_family = "wasm"))]
283#[uniffi::export]
284pub fn set_max_log_level(level: CoreCryptoLogLevel) {
285 log::set_max_level(level.into());
286}
287
288#[cfg(target_family = "wasm")]
289#[wasm_bindgen]
290impl CoreCrypto {
291 pub fn set_logger(logger: CoreCryptoLogger) {
292 set_logger_only_inner(logger);
293 }
294
295 pub fn set_max_log_level(level: CoreCryptoLogLevel) {
296 log::set_max_level(level.into());
297 }
298}