1use crate::context::Context;
4use crate::effect::{AsyncDisposer, EffectHandle};
5use crate::utils::lock;
6use crate::{Result, Value};
7use std::collections::{BTreeMap, HashMap, VecDeque};
8use std::fmt::{self, Debug, Formatter};
9use std::sync::{Arc, Mutex};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum LoggerType {
15 Error,
17 Info,
19 Warn,
21 Debug,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
27#[repr(u8)]
28pub enum LoggerLevel {
29 Error = 0,
31 Info = 1,
33 Warn = 2,
35 Debug = 3,
37}
38
39impl LoggerType {
40 fn level(self) -> LoggerLevel {
41 match self {
42 Self::Error => LoggerLevel::Error,
43 Self::Info => LoggerLevel::Info,
44 Self::Warn => LoggerLevel::Warn,
45 Self::Debug => LoggerLevel::Debug,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52pub enum LogArg {
53 String(String),
55 Integer(i64),
57 Unsigned(u64),
59 Float(f64),
61 Bool(bool),
63 Object(String),
65}
66
67impl LogArg {
68 fn string(&self) -> String {
69 match self {
70 Self::String(value) | Self::Object(value) => value.clone(),
71 Self::Integer(value) => value.to_string(),
72 Self::Unsigned(value) => value.to_string(),
73 Self::Float(value) => value.to_string(),
74 Self::Bool(value) => value.to_string(),
75 }
76 }
77
78 fn integer(&self) -> String {
79 match self {
80 Self::Integer(value) => value.to_string(),
81 Self::Unsigned(value) => value.to_string(),
82 Self::Float(value) => (*value as i64).to_string(),
83 Self::Bool(value) => (if *value { 1_i64 } else { 0_i64 }).to_string(),
84 Self::String(value) | Self::Object(value) => {
85 value.parse::<i64>().unwrap_or_default().to_string()
86 }
87 }
88 }
89
90 fn float(&self) -> String {
91 match self {
92 Self::Float(value) => value.to_string(),
93 Self::Integer(value) => (*value as f64).to_string(),
94 Self::Unsigned(value) => (*value as f64).to_string(),
95 Self::Bool(value) => (if *value { 1.0 } else { 0.0 }).to_string(),
96 Self::String(value) | Self::Object(value) => {
97 value.parse::<f64>().unwrap_or_default().to_string()
98 }
99 }
100 }
101}
102
103impl From<String> for LogArg {
104 fn from(value: String) -> Self {
105 Self::String(value)
106 }
107}
108
109impl From<&str> for LogArg {
110 fn from(value: &str) -> Self {
111 Self::String(value.to_owned())
112 }
113}
114
115macro_rules! integer_log_arg {
116 ($($ty:ty),* $(,)?) => {$ (
117 impl From<$ty> for LogArg {
118 fn from(value: $ty) -> Self { Self::Integer(value as i64) }
119 }
120 )* };
121}
122integer_log_arg!(i8, i16, i32, i64, isize);
123
124macro_rules! unsigned_log_arg {
125 ($($ty:ty),* $(,)?) => {$ (
126 impl From<$ty> for LogArg {
127 fn from(value: $ty) -> Self { Self::Unsigned(value as u64) }
128 }
129 )* };
130}
131unsigned_log_arg!(u8, u16, u32, u64, usize);
132
133impl From<f32> for LogArg {
134 fn from(value: f32) -> Self {
135 Self::Float(value.into())
136 }
137}
138
139impl From<f64> for LogArg {
140 fn from(value: f64) -> Self {
141 Self::Float(value)
142 }
143}
144
145impl From<bool> for LogArg {
146 fn from(value: bool) -> Self {
147 Self::Bool(value)
148 }
149}
150
151impl From<Value> for LogArg {
152 fn from(value: Value) -> Self {
153 Self::Object(format!("{value:?}"))
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct Message {
160 pub sequence: u64,
162 pub timestamp: u64,
164 pub name: String,
166 pub kind: LoggerType,
168 pub level: LoggerLevel,
170 pub args: Vec<LogArg>,
172 pub fiber_uid: Option<u64>,
174 pub fiber_name: String,
176}
177
178pub type FormatterFn =
180 Arc<dyn Fn(&LogArg, &ExporterConfig, &Message) -> String + Send + Sync + 'static>;
181
182#[derive(Clone)]
184pub struct ExporterConfig {
185 pub colors: u8,
187 pub max_length: usize,
189 pub levels: HashMap<String, LoggerLevel>,
191 pub formatters: HashMap<char, FormatterFn>,
193}
194
195impl Default for ExporterConfig {
196 fn default() -> Self {
197 Self {
198 colors: 0,
199 max_length: 10_240,
200 levels: HashMap::new(),
201 formatters: HashMap::new(),
202 }
203 }
204}
205
206impl Debug for ExporterConfig {
207 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
208 f.debug_struct("ExporterConfig")
209 .field("colors", &self.colors)
210 .field("max_length", &self.max_length)
211 .field("levels", &self.levels)
212 .field("formatters", &self.formatters.keys().collect::<Vec<_>>())
213 .finish()
214 }
215}
216
217pub trait Exporter: Send + Sync + 'static {
219 fn config(&self) -> ExporterConfig {
221 ExporterConfig::default()
222 }
223
224 fn config_ref(&self) -> Option<&ExporterConfig> {
229 None
230 }
231
232 fn export(&self, message: &Message);
234}
235
236struct ClosureExporter<F> {
237 config: ExporterConfig,
238 callback: F,
239}
240
241impl<F> Exporter for ClosureExporter<F>
242where
243 F: Fn(&Message) + Send + Sync + 'static,
244{
245 fn config(&self) -> ExporterConfig {
246 self.config.clone()
247 }
248
249 fn config_ref(&self) -> Option<&ExporterConfig> {
250 Some(&self.config)
251 }
252
253 fn export(&self, message: &Message) {
254 (self.callback)(message)
255 }
256}
257
258#[derive(Debug, Clone, Default)]
260pub struct LoggerIntercept {
261 pub name: Option<String>,
263 pub level: Option<LoggerLevel>,
265}
266
267struct LoggerState {
268 sequence: u64,
269 next_exporter: u64,
270 buffer_size: usize,
271 buffer: VecDeque<Arc<Message>>,
272 exporters: BTreeMap<u64, Arc<dyn Exporter>>,
273 exporter_snapshot: Arc<Vec<Arc<dyn Exporter>>>,
274}
275
276fn refresh_exporter_snapshot(state: &mut LoggerState) {
277 state.exporter_snapshot = Arc::new(state.exporters.values().cloned().collect());
278}
279
280pub(crate) struct LoggerRoot {
281 state: Mutex<LoggerState>,
282}
283
284impl LoggerRoot {
285 pub(crate) fn new() -> Self {
286 Self {
287 state: Mutex::new(LoggerState {
288 sequence: 0,
289 next_exporter: 0,
290 buffer_size: 1_000,
291 buffer: VecDeque::new(),
292 exporters: BTreeMap::new(),
293 exporter_snapshot: Arc::new(Vec::new()),
294 }),
295 }
296 }
297
298 fn send(
299 &self,
300 name: String,
301 default_level: Option<LoggerLevel>,
302 kind: LoggerType,
303 args: Vec<LogArg>,
304 fiber_uid: Option<u64>,
305 fiber_name: String,
306 ) {
307 let (message, exporters) = {
308 let mut state = lock(&self.state);
309 state.sequence += 1;
310 let timestamp = SystemTime::now()
311 .duration_since(UNIX_EPOCH)
312 .unwrap_or_default()
313 .as_millis()
314 .min(u64::MAX as u128) as u64;
315 let message = Message {
316 sequence: state.sequence,
317 timestamp,
318 name,
319 kind,
320 level: kind.level(),
321 args,
322 fiber_uid,
323 fiber_name,
324 };
325 let buffer_threshold = default_level.unwrap_or(LoggerLevel::Info);
326 let message = Arc::new(message);
327 if buffer_threshold >= message.level && state.buffer_size > 0 {
328 state.buffer.push_back(message.clone());
329 while state.buffer.len() > state.buffer_size {
330 state.buffer.pop_front();
331 }
332 } else if state.buffer_size == 0 {
333 state.buffer.clear();
334 }
335 (message, state.exporter_snapshot.clone())
336 };
337
338 for exporter in exporters.iter() {
339 let owned_config;
340 let config = match exporter.config_ref() {
341 Some(config) => config,
342 None => {
343 owned_config = exporter.config();
344 &owned_config
345 }
346 };
347 let threshold = config
348 .levels
349 .get(&message.name)
350 .or_else(|| config.levels.get("default"))
351 .copied()
352 .or(default_level)
353 .unwrap_or(LoggerLevel::Info);
354 if threshold >= message.level {
355 exporter.export(&message);
356 }
357 }
358 }
359}
360
361pub const C16: &[u8] = &[6, 2, 3, 4, 5, 1];
363pub const C256: &[u8] = &[
365 20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, 63, 68, 69, 74, 75, 76, 77,
366 78, 79, 80, 81, 92, 93, 98, 99, 112, 113, 129, 134, 135, 148, 149, 160, 161, 162, 163, 164,
367 165, 166, 167, 168, 169, 170, 171, 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201,
368 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221,
369];
370
371pub fn color_code(name: &str, colors: u8) -> u8 {
373 let mut hash: i32 = 0;
374 for byte in name.bytes() {
375 hash = hash
376 .wrapping_shl(3)
377 .wrapping_sub(hash)
378 .wrapping_add(i32::from(byte))
379 .wrapping_add(13);
380 }
381 let palette = if colors == 0 {
382 return 0;
383 } else if colors >= 2 {
384 C256
385 } else {
386 C16
387 };
388 palette[(hash.unsigned_abs() as usize) % palette.len()]
389}
390
391fn color(config: &ExporterConfig, code: u8, value: String) -> String {
392 if config.colors == 0 {
393 value
394 } else if code < 8 {
395 format!("\u{001b}[3{code}m{value}\u{001b}[0m")
396 } else {
397 format!("\u{001b}[38;5;{code}m{value}\u{001b}[0m")
398 }
399}
400
401pub fn default_format(config: &ExporterConfig, message: &Message) -> String {
403 let mut values = message.args.iter();
404 let format = match values.next() {
405 Some(LogArg::String(value)) => value.clone(),
406 Some(value) => {
407 let mut output = value.string();
408 for value in values {
409 output.push(' ');
410 output.push_str(&value.string());
411 }
412 return truncate_lines(output, config.max_length);
413 }
414 None => String::new(),
415 };
416
417 let remaining = values.cloned().collect::<Vec<_>>();
418 let mut next_value = 0;
419 let mut chars = format.chars().peekable();
420 let mut output = String::new();
421 while let Some(character) = chars.next() {
422 if character != '%' {
423 output.push(character);
424 continue;
425 }
426 let Some(placeholder) = chars.next() else {
427 output.push('%');
428 break;
429 };
430 if placeholder == '%' {
431 output.push('%');
432 continue;
433 }
434 let Some(value) = remaining.get(next_value) else {
435 output.push('%');
436 output.push(placeholder);
437 continue;
438 };
439 if let Some(formatter) = config.formatters.get(&placeholder) {
440 output.push_str(&formatter(value, config, message));
441 next_value += 1;
442 continue;
443 }
444 let rendered = match placeholder {
445 's' | 'o' | 'O' => Some(value.string()),
446 'd' | 'i' => Some(value.integer()),
447 'f' => Some(value.float()),
448 'c' => Some(String::new()),
449 'C' => Some(color(
450 config,
451 color_code(&message.name, config.colors),
452 value.string(),
453 )),
454 _ => None,
455 };
456 if let Some(rendered) = rendered {
457 output.push_str(&rendered);
458 next_value += 1;
459 } else {
460 output.push('%');
461 output.push(placeholder);
462 }
463 }
464 for value in &remaining[next_value..] {
465 output.push(' ');
466 output.push_str(&value.string());
467 }
468 truncate_lines(output, config.max_length)
469}
470
471fn truncate_lines(value: String, max_length: usize) -> String {
472 value
473 .lines()
474 .map(|line| {
475 let mut chars = line.chars();
476 let head = chars.by_ref().take(max_length).collect::<String>();
477 if chars.next().is_some() {
478 head + "..."
479 } else {
480 head
481 }
482 })
483 .collect::<Vec<_>>()
484 .join("\n")
485}
486
487#[derive(Clone)]
489pub struct Logger {
490 ctx: Context,
491 pub name: String,
493 pub level: Option<LoggerLevel>,
495}
496
497impl Logger {
498 fn write(&self, kind: LoggerType, format: impl Into<String>, args: Vec<LogArg>) {
499 let mut all = Vec::with_capacity(args.len() + 1);
500 all.push(LogArg::String(format.into()));
501 all.extend(args);
502 let fiber = self.ctx.fiber().ok();
503 self.ctx.root.logger.send(
504 self.name.clone(),
505 self.level,
506 kind,
507 all,
508 fiber.as_ref().and_then(|fiber| fiber.uid()),
509 fiber
510 .map(|fiber| fiber.name())
511 .unwrap_or_else(|| "disposed".to_owned()),
512 );
513 }
514
515 pub fn error(&self, format: impl Into<String>, args: impl IntoIterator<Item = LogArg>) {
517 self.write(LoggerType::Error, format, args.into_iter().collect());
518 }
519
520 pub fn info(&self, format: impl Into<String>, args: impl IntoIterator<Item = LogArg>) {
522 self.write(LoggerType::Info, format, args.into_iter().collect());
523 }
524
525 pub fn warn(&self, format: impl Into<String>, args: impl IntoIterator<Item = LogArg>) {
527 self.write(LoggerType::Warn, format, args.into_iter().collect());
528 }
529
530 pub fn debug(&self, format: impl Into<String>, args: impl IntoIterator<Item = LogArg>) {
532 self.write(LoggerType::Debug, format, args.into_iter().collect());
533 }
534}
535
536impl Debug for Logger {
537 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
538 f.debug_struct("Logger")
539 .field("name", &self.name)
540 .field("level", &self.level)
541 .finish()
542 }
543}
544
545#[derive(Clone, Debug)]
547pub struct LoggerService {
548 ctx: Context,
549}
550
551impl LoggerService {
552 pub(crate) fn new(ctx: Context) -> Self {
553 Self { ctx }
554 }
555
556 fn intercept(&self) -> LoggerIntercept {
557 let mut resolved = LoggerIntercept::default();
558 if let Ok(configs) = self.ctx.intercepts::<LoggerIntercept>("logger") {
559 for config in configs {
560 if config.name.is_some() {
561 resolved.name = config.name.clone();
562 }
563 if config.level.is_some() {
564 resolved.level = config.level;
565 }
566 }
567 }
568 resolved
569 }
570
571 pub fn logger(&self, name: Option<String>) -> Logger {
574 let intercept = self.intercept();
575 let name = name
576 .or(intercept.name)
577 .or_else(|| self.ctx.fiber().ok().map(|fiber| hyphenate(&fiber.name())))
578 .unwrap_or_else(|| "root".to_owned());
579 Logger {
580 ctx: self.ctx.clone(),
581 name,
582 level: intercept.level,
583 }
584 }
585
586 pub fn exporter<E: Exporter>(&self, exporter: E) -> Result<EffectHandle> {
588 self.exporter_arc(Arc::new(exporter))
589 }
590
591 pub fn exporter_fn<F>(&self, config: ExporterConfig, callback: F) -> Result<EffectHandle>
593 where
594 F: Fn(&Message) + Send + Sync + 'static,
595 {
596 self.exporter(ClosureExporter { config, callback })
597 }
598
599 pub fn exporter_arc(&self, exporter: Arc<dyn Exporter>) -> Result<EffectHandle> {
601 let id = {
602 let mut state = lock(&self.ctx.root.logger.state);
603 state.next_exporter += 1;
604 let id = state.next_exporter;
605 state.exporters.insert(id, exporter);
606 refresh_exporter_snapshot(&mut state);
607 id
608 };
609 let root = Arc::downgrade(&self.ctx.root);
610 let effect = self.ctx.fiber()?.register_effect(
611 "ctx.logger.exporter()",
612 AsyncDisposer::from_sync(move || {
613 if let Some(root) = root.upgrade() {
614 let mut state = lock(&root.logger.state);
615 state.exporters.remove(&id);
616 refresh_exporter_snapshot(&mut state);
617 }
618 Ok(())
619 }),
620 );
621 if effect.is_err() {
622 let mut state = lock(&self.ctx.root.logger.state);
623 state.exporters.remove(&id);
624 refresh_exporter_snapshot(&mut state);
625 }
626 effect
627 }
628
629 pub fn buffer(&self) -> Vec<Message> {
631 lock(&self.ctx.root.logger.state)
632 .buffer
633 .iter()
634 .map(|message| message.as_ref().clone())
635 .collect()
636 }
637
638 pub fn set_buffer_size(&self, size: usize) {
640 let mut state = lock(&self.ctx.root.logger.state);
641 state.buffer_size = size;
642 while state.buffer.len() > size {
643 state.buffer.pop_front();
644 }
645 }
646
647 pub fn buffer_size(&self) -> usize {
649 lock(&self.ctx.root.logger.state).buffer_size
650 }
651
652 pub fn exporter_count(&self) -> usize {
654 lock(&self.ctx.root.logger.state).exporters.len()
655 }
656
657 pub fn clear_buffer(&self) {
659 lock(&self.ctx.root.logger.state).buffer.clear();
660 }
661}
662
663fn hyphenate(value: &str) -> String {
664 let mut output = String::new();
665 for (index, character) in value.chars().enumerate() {
666 if character.is_uppercase() {
667 if index > 0 {
668 output.push('-');
669 }
670 for lower in character.to_lowercase() {
671 output.push(lower);
672 }
673 } else {
674 output.push(character);
675 }
676 }
677 output
678}