use crate::*;
use std::sync::{Arc, Mutex};
pub(crate) enum Ctx {
Raw(String),
Context(Context),
}
type Ctxs = Arc<Mutex<Vec<Ctx>>>;
pub(crate) enum SessionSrc<'a> {
Logger (&'a Logger),
Session(&'a Session),
}
pub struct Session {
name : String,
source : Source,
writer : Writer,
for_write : fn(&Context) -> String,
for_print : fn(&Context) -> String,
write_level: Level,
print_level: Level,
ctxs : Ctxs,
parent : Option<Ctxs>,
silent : Mutex<bool>,
}
impl Session {
#[track_caller]
pub(crate) fn new(name: impl Into<String>, source: SessionSrc<'_>, silent: bool) -> Self {
let name = name.into();
let (
source,
writer,
for_write,
for_print,
write_level,
print_level,
parent,
) = match source {
SessionSrc::Logger(l) => (
Source::new(&l.name).session(&name),
l.writer.clone(),
l.for_write,
l.for_print,
l.write_level,
l.print_level,
None,
),
SessionSrc::Session(s) => (
s.source.session(&name),
s.writer.clone(),
s.for_write,
s.for_print,
s.write_level,
s.print_level,
Some(s.ctxs.clone()),
),
};
let header = Context::new_header(source.clone());
if !silent {
println!("{}", for_print(&header));
}
Self {
ctxs: Arc::new(Mutex::new(
vec![Ctx::Context(header)])),
silent: Mutex::new(silent),
name,
parent,
source,
writer,
for_write,
for_print,
write_level,
print_level,
}
}
#[track_caller]
pub fn session(&self, name: impl Into<String>, silent: bool) -> Self {
Self::new(name, SessionSrc::Session(self), silent)
}
#[track_caller]
pub fn session_then<F, T>(&self, name: impl Into<String>, silent: bool, callable: F) -> T
where F: FnOnce(Self) -> T
{
callable(self.session(name, silent))
}
}
impl Drop for Session {
fn drop(&mut self) {
if *self.silent.lock().unwrap() { return; }
let mut ctxs = self.ctxs.lock().unwrap();
let Ctx::Context(header) = ctxs.first().unwrap()
else { unreachable!() };
let footer = Context::new_footer(
self.source.clone(),
&header.start().unwrap(),
*header.location());
println!("{}", (self.for_print)(&footer));
let mut lines = Vec::new();
lines.push("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".to_string());
if self.parent.is_none() {
lines.push(format!("┃ Logger : {}", self.source.logger()));
}
lines.push(format!("┃ Session: {}", self.name));
lines.push(format!("┃ Elapsed: {:?}", footer.elapsed().unwrap()));
lines.push("┃".to_string());
ctxs.push(Ctx::Context(footer));
for ctx in ctxs.drain(..) {
match ctx {
Ctx::Raw(string) => {
let tmp = string.trim_start_matches("┃");
if tmp.starts_with("┏━")
|| tmp.starts_with("┗━")
{
lines.push(format!("┃{}", &string[..string.len()-3]));
continue;
}
lines.push(format!("┃{string}"));
}
Ctx::Context(ctx) => {
for line in (self.for_write)(&ctx).split('\n') {
lines.push(format!("┃ {line}"));
}
}
}
}
lines.push("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".to_string());
match &self.parent {
Some(parent) => {
let mut parent = parent.lock().unwrap();
for line in lines {
parent.push(Ctx::Raw(line));
}
}
None => {
self.writer.lock().unwrap()
.write(lines.join("\n"));
}
}
}
}
impl LoggableInner for Session {
fn log(&self, level: Level, message: &str) {
let mut ctxs = self.ctxs.lock().unwrap();
let ctx = Context::new_message(
self.source.clone(), level, message);
if level >= self.print_level {
let mut silent = self.silent.lock().unwrap();
if *silent {
let Ctx::Context(header) = ctxs.first().unwrap()
else { unreachable!() };
println!("{}", (self.for_print)(&header));
*silent = false;
}
println!("{}", (self.for_print)(&ctx));
}
if level >= self.write_level {
ctxs.push(Ctx::Context(ctx));
}
}
}
impl Loggable for Session {
fn root_name(&self) -> &str {
self.source.logger().as_ref()
}
fn name(&self) -> &str {
self.name.as_str()
}
fn path(&self) -> String {
self.writer.lock().unwrap().path.clone()
}
fn write_level(&self) -> Level {
self.write_level
}
fn print_level(&self) -> Level {
self.print_level
}
}