Skip to main content

session_log/
session.rs

1use crate::*;
2use std::sync::{Arc, Mutex};
3
4
5pub(crate) enum Ctx {
6  Raw(String),
7  Context(Context),
8}
9
10
11type Ctxs = Arc<Mutex<Vec<Ctx>>>;
12
13
14pub(crate) enum SessionSrc<'a> {
15  Logger (&'a Logger),
16  Session(&'a Session),
17}
18
19
20/// A session is a temporary logger that will print immediately but only write when dropped. All the
21/// logs during the session will be grouped together and write once. It's useful when you want to
22/// log a series of messages such as one session of a request. The session is panic-safe, which means
23/// it will write all the logs when panic.
24///
25/// Apart from the formatter, the session can be `Silent`. If it's silent, then the header & footer
26/// will not be printed or written if the session never logged anything. Due to the uncertainty of if
27/// the session will log anything, the header will be deferred until the first log. If the session
28/// logged anything, then it acts like a normal session.
29pub struct Session {
30  name       : String,
31  source     : Source,
32  writer     : Writer,
33  for_write  : fn(&Context) -> String,
34  for_print  : fn(&Context) -> String,
35  write_level: Level,
36  print_level: Level,
37  ctxs       : Ctxs,
38  parent     : Option<Ctxs>,
39  silent     : Mutex<bool>,
40}
41
42
43impl Session {
44  #[track_caller]
45  pub(crate) fn new(name: impl Into<String>, source: SessionSrc<'_>, silent: bool) -> Self {
46    let name = name.into();
47
48    let (
49      source,
50      writer,
51      for_write,
52      for_print,
53      write_level,
54      print_level,
55      parent,
56    ) = match source {
57      SessionSrc::Logger(l) => (
58        Source::new(&l.name).session(&name),
59        l.writer.clone(),
60        l.for_write,
61        l.for_print,
62        l.write_level,
63        l.print_level,
64        None,
65      ),
66
67      SessionSrc::Session(s) => (
68        s.source.session(&name),
69        s.writer.clone(),
70        s.for_write,
71        s.for_print,
72        s.write_level,
73        s.print_level,
74        Some(s.ctxs.clone()),
75      ),
76    };
77
78    let header = Context::new_header(source.clone());
79    if !silent {
80      println!("{}", for_print(&header));
81    }
82
83    Self {
84      ctxs: Arc::new(Mutex::new(
85        vec![Ctx::Context(header)])),
86      silent: Mutex::new(silent),
87      name,
88      parent,
89      source,
90      writer,
91      for_write,
92      for_print,
93      write_level,
94      print_level,
95    }
96  }
97
98  /// Create a new session from the session.
99  ///
100  /// This is useful when you want to create a sub-session from a session. The sub-session will later
101  /// be nested under the parent session when written.
102  #[track_caller]
103  pub fn session(&self, name: impl Into<String>, silent: bool) -> Self {
104    Self::new(name, SessionSrc::Session(self), silent)
105  }
106
107  /// Create a new session from the session and execute the callable with the session passed in.
108  ///
109  /// This can be handy for isolating the environment of each callable while also providing a common
110  /// logging interface for any callable that needs to be logged.
111  #[track_caller]
112  pub fn session_then<F, T>(&self, name: impl Into<String>, silent: bool, callable: F) -> T
113  where F: FnOnce(Self) -> T
114  {
115    callable(self.session(name, silent))
116  }
117}
118
119
120impl Drop for Session {
121  fn drop(&mut self) {
122    if *self.silent.lock().unwrap() { return; }
123
124    let mut ctxs = self.ctxs.lock().unwrap();
125
126    let Ctx::Context(header) = ctxs.first().unwrap()
127      else { unreachable!() };
128
129    let footer = Context::new_footer(
130      self.source.clone(),
131      &header.start().unwrap(),
132      *header.location());
133
134    println!("{}", (self.for_print)(&footer));
135
136    let mut lines = Vec::new();
137
138    lines.push("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".to_string());
139
140    if self.parent.is_none() {
141      lines.push(format!("┃ Logger : {}", self.source.logger()));
142    }
143
144    lines.push(format!("┃ Session: {}", self.name));
145    lines.push(format!("┃ Elapsed: {:?}", footer.elapsed().unwrap()));
146    lines.push("┃".to_string());
147
148    ctxs.push(Ctx::Context(footer));
149
150    for ctx in ctxs.drain(..) {
151      match ctx {
152        Ctx::Raw(string) => {
153          let tmp = string.trim_start_matches("┃");
154
155          if tmp.starts_with("┏━")
156          || tmp.starts_with("┗━")
157          {
158            lines.push(format!("┃{}", &string[..string.len()-3]));
159            continue;
160          }
161
162          lines.push(format!("┃{string}"));
163        }
164
165        Ctx::Context(ctx) => {
166          for line in (self.for_write)(&ctx).split('\n') {
167            lines.push(format!("┃ {line}"));
168          }
169        }
170      }
171    }
172
173    lines.push("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".to_string());
174
175    match &self.parent {
176      Some(parent) => {
177        let mut parent = parent.lock().unwrap();
178
179        for line in lines {
180          parent.push(Ctx::Raw(line));
181        }
182      }
183
184      None => {
185        self.writer.lock().unwrap()
186          .write(lines.join("\n"));
187      }
188    }
189  }
190}
191
192
193impl LoggableInner for Session {
194  fn log(&self, level: Level, message: &str) {
195    let mut ctxs = self.ctxs.lock().unwrap();
196
197    let ctx = Context::new_message(
198      self.source.clone(), level, message);
199
200    if level >= self.print_level {
201      let mut silent = self.silent.lock().unwrap();
202
203      if *silent {
204        let Ctx::Context(header) = ctxs.first().unwrap()
205          else { unreachable!() };
206
207        println!("{}", (self.for_print)(&header));
208        *silent = false;
209      }
210
211      println!("{}", (self.for_print)(&ctx));
212    }
213
214    if level >= self.write_level {
215      ctxs.push(Ctx::Context(ctx));
216    }
217  }
218}
219
220
221impl Loggable for Session {
222  fn root_name(&self) -> &str {
223    self.source.logger().as_ref()
224  }
225
226  fn name(&self) -> &str {
227    self.name.as_str()
228  }
229
230  fn path(&self) -> String {
231    self.writer.lock().unwrap().path.clone()
232  }
233
234  fn write_level(&self) -> Level {
235    self.write_level
236  }
237
238  fn print_level(&self) -> Level {
239    self.print_level
240  }
241}