fluence_sdk_main/
logger.rs1use log::LevelFilter;
46use std::collections::HashMap;
47
48pub const WASM_LOG_ENV_NAME: &str = "WASM_LOG";
50
51const WASM_DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Info;
53
54pub type TargetMap = HashMap<&'static str, i32>;
59
60#[derive(Debug)]
62struct LogDirective {
63 module_name: String,
64 level: LevelFilter,
65}
66
67impl LogDirective {
68 pub fn new(module_name: String, level: LevelFilter) -> Self {
69 Self { module_name, level }
70 }
71}
72
73struct WasmLogger {
84 target_map: TargetMap,
85 modules_directives: Vec<LogDirective>,
86 default_log_level: LevelFilter,
87}
88
89pub struct WasmLoggerBuilder {
93 wasm_logger: WasmLogger,
94}
95
96impl WasmLoggerBuilder {
97 pub fn new() -> Self {
101 use std::str::FromStr;
102
103 let default_log_level = std::env::var(WASM_LOG_ENV_NAME)
104 .map_or(WASM_DEFAULT_LOG_LEVEL, |log_level_str| {
105 LevelFilter::from_str(&log_level_str).unwrap_or(WASM_DEFAULT_LOG_LEVEL)
106 });
107
108 let wasm_logger = WasmLogger {
109 target_map: HashMap::new(),
110 modules_directives: Vec::new(),
111 default_log_level,
112 };
113
114 Self { wasm_logger }
115 }
116
117 pub fn with_log_level(mut self, level: LevelFilter) -> Self {
119 self.wasm_logger.default_log_level = level;
120 self
121 }
122
123 pub fn with_target_map(mut self, map: TargetMap) -> Self {
126 self.wasm_logger.target_map = map;
127 self
128 }
129
130 pub fn filter(mut self, module_name: impl Into<String>, level: LevelFilter) -> Self {
131 let module_name = module_name.into();
132 let log_directive = LogDirective::new(module_name, level);
133
134 self.wasm_logger.modules_directives.push(log_directive);
135 self
136 }
137
138 pub fn build(mut self) -> Result<(), log::SetLoggerError> {
156 let max_level = self.max_log_level();
157 self.sort_directives();
158
159 let Self { wasm_logger } = self;
160
161 log::set_boxed_logger(Box::new(wasm_logger))?;
162 log::set_max_level(max_level);
163 Ok(())
164 }
165
166 fn sort_directives(&mut self) {
168 self.wasm_logger.modules_directives.sort_by(|l, r| {
169 let llen = l.module_name.len();
170 let rlen = r.module_name.len();
171
172 rlen.cmp(&llen)
173 });
174 }
175
176 fn max_log_level(&self) -> log::LevelFilter {
177 let default_level = self.wasm_logger.default_log_level;
178 let max_filter_level = self
179 .wasm_logger
180 .modules_directives
181 .iter()
182 .map(|d| d.level)
183 .max()
184 .unwrap_or(LevelFilter::Off);
185
186 std::cmp::max(default_level, max_filter_level)
187 }
188}
189
190impl log::Log for WasmLogger {
191 #[inline]
192 fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
193 let target = metadata.target();
194
195 for directive in self.modules_directives.iter() {
196 if target.starts_with(&directive.module_name) {
197 return metadata.level() <= directive.level;
198 }
199 }
200
201 metadata.level() <= self.default_log_level
202 }
203
204 #[inline]
205 fn log(&self, record: &log::Record<'_>) {
206 if !self.enabled(record.metadata()) {
207 return;
208 }
209
210 let level = record.metadata().level() as i32;
211 let default_target = 0;
212 let target = *self
213 .target_map
214 .get(record.metadata().target())
215 .unwrap_or(&default_target);
216 let msg = record.args().to_string();
217
218 log_utf8_string(level, target, msg.as_ptr() as _, msg.len() as _);
219 }
220
221 #[inline]
223 fn flush(&self) {}
224}
225
226#[cfg(target_arch = "wasm32")]
227pub fn log_utf8_string(level: i32, target: i32, msg_ptr: i32, msg_size: i32) {
228 unsafe { log_utf8_string_impl(level, target, msg_ptr, msg_size) };
229}
230
231#[cfg(not(target_arch = "wasm32"))]
232pub fn log_utf8_string(level: i32, target: i32, msg_ptr: i32, msg_size: i32) {
233 use std::str::from_utf8_unchecked;
234 use core::slice::from_raw_parts;
235
236 let level = level_from_i32(level);
237 let msg = unsafe { from_utf8_unchecked(from_raw_parts(msg_ptr as _, msg_size as _)) };
238 println!("[{}] {} {}", level, target, msg);
239}
240
241#[cfg(target_arch = "wasm32")]
244#[link(wasm_import_module = "host")]
245extern "C" {
246 #[link_name = "log_utf8_string"]
248 fn log_utf8_string_impl(level: i32, target: i32, msg_ptr: i32, msg_size: i32);
249}
250
251#[allow(dead_code)]
252fn level_from_i32(level: i32) -> log::Level {
253 match level {
254 1 => log::Level::Error,
255 2 => log::Level::Warn,
256 3 => log::Level::Info,
257 4 => log::Level::Debug,
258 5 => log::Level::Trace,
259 _ => log::Level::max(),
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::WasmLogger;
266 use super::LogDirective;
267 use super::WasmLoggerBuilder;
268 use log::LevelFilter;
269 use log::Log;
270
271 use std::collections::HashMap;
272
273 fn create_metadata(module_name: &str, level: log::Level) -> log::Metadata<'_> {
274 log::MetadataBuilder::new()
275 .level(level)
276 .target(module_name)
277 .build()
278 }
279
280 #[test]
281 fn enabled_by_module_name() {
282 let module_1_name = "module_1";
283 let module_2_name = "module_2";
284
285 let modules_directives = vec![
286 LogDirective::new(module_1_name.to_string(), LevelFilter::Info),
287 LogDirective::new(module_2_name.to_string(), LevelFilter::Warn),
288 ];
289
290 let logger = WasmLogger {
291 target_map: HashMap::new(),
292 modules_directives,
293 default_log_level: LevelFilter::Error,
294 };
295
296 let allowed_metadata = create_metadata(module_1_name, log::Level::Info);
297 assert!(logger.enabled(&allowed_metadata));
298
299 let allowed_metadata = create_metadata(module_1_name, log::Level::Warn);
300 assert!(logger.enabled(&allowed_metadata));
301
302 let allowed_metadata = create_metadata(module_2_name, log::Level::Warn);
303 assert!(logger.enabled(&allowed_metadata));
304
305 let not_allowed_metadata = create_metadata(module_1_name, log::Level::Debug);
306 assert!(!logger.enabled(¬_allowed_metadata));
307
308 let not_allowed_metadata = create_metadata(module_2_name, log::Level::Info);
309 assert!(!logger.enabled(¬_allowed_metadata));
310 }
311
312 #[test]
313 fn default_log_level() {
314 let modules_directives = vec![LogDirective::new("module_1".to_string(), LevelFilter::Info)];
315
316 let logger = WasmLogger {
317 target_map: HashMap::new(),
318 modules_directives,
319 default_log_level: LevelFilter::Warn,
320 };
321
322 let module_name = "some_module";
323 let allowed_metadata = create_metadata(module_name, log::Level::Warn);
324 assert!(logger.enabled(&allowed_metadata));
325
326 let not_allowed_metadata = create_metadata(module_name, log::Level::Info);
327 assert!(!logger.enabled(¬_allowed_metadata));
328 }
329
330 #[test]
331 fn longest_directive_first() {
332 let module_1_name = "module_1";
333 let module_2_name = "module_1::some_name::func_name";
334
335 WasmLoggerBuilder::new()
336 .filter(module_1_name, LevelFilter::Info)
337 .filter(module_2_name, LevelFilter::Warn)
338 .build()
339 .unwrap();
340
341 let logger = log::logger();
342
343 let allowed_metadata = create_metadata(module_1_name, log::Level::Info);
344 assert!(logger.enabled(&allowed_metadata));
345
346 let not_allowed_metadata = create_metadata(module_2_name, log::Level::Info);
347 assert!(!logger.enabled(¬_allowed_metadata));
348 }
349}