use colored::Colorize;
static SPACE: usize = 4;
pub struct Logger {
path: Option<String>,
stdout: bool,
stderr: bool,
}
impl Logger {
pub fn builder() -> Self {
Logger {
path: None,
stdout: false,
stderr: false,
}
}
pub fn with_stdout(mut self) -> Self {
self.stdout = true;
self
}
pub fn with_stderr(mut self) -> Self {
self.stderr = true;
self
}
pub fn with_path(mut self, path: &str) -> Self {
self.path = Some(path.to_string());
self
}
pub fn build(self) -> Box<Self> {
if let Some(path) = &self.path {
let requests = log4rs::append::file::FileAppender::builder()
.encoder(Box::new(log4rs::encode::pattern::PatternEncoder::new(
"{d} - {m}{n}",
)))
.append(true)
.build(path)
.expect("Failed to create file appender");
let config = log4rs::config::Config::builder()
.appender(log4rs::config::Appender::builder().build("requests", Box::new(requests)))
.build(
log4rs::config::Root::builder()
.appender("requests")
.build(log::LevelFilter::Info),
)
.unwrap();
log4rs::init_config(config).expect("Failed to create logging builder");
}
Box::new(self)
}
pub fn handle_log(&self, log: &str) {
if self.path.is_some() {
info!("{}", log);
}
if self.stdout {
println!("{}", log);
}
}
}
pub fn is_replay() -> bool {
std::env::args().any(|e| e == "--replay")
}
impl crate::Backend for Logger {
fn context_ended(&self, ctx: &crate::Context) {
if !is_replay() {
println!("{}", print_context(ctx));
}
}
}
pub(crate) fn print_context(ctx: &crate::Context) -> String {
let mut buffer = "".to_string();
buffer.push_str(&format!(
"context: {} [{}] ",
ctx.id(),
ctx.created_on.to_rfc3339()
));
for span in ctx.span_stack.borrow().iter() {
print_span(&mut buffer, span, 0);
}
buffer
}
pub(crate) fn print_span(buffer: &mut String, span: &crate::Span, space: usize) {
buffer.push_str(&format!(
"{}: {}\n",
{
let span_id = if span.duration() > std::time::Duration::from_millis(1) {
span.id.red()
} else {
span.id.green()
};
span.success
.map(|v| {
if v {
span_id.clone()
} else {
span_id.clone().underline()
}
})
.unwrap_or_else(|| span_id.bold())
},
elapsed(span.duration()),
));
for (ts, item) in span.items.iter() {
let d = elapsed(ts.0);
match item {
crate::SpanItem::Log { message } => {
buffer.push_str(&format!(
"{:_>space$}- {}: {message}\n",
"",
d,
message = message,
space = space,
));
}
crate::SpanItem::Field { name, value } => {
buffer.push_str(&format!(
"{:_>space$}- {}: {}={}\n",
"",
d,
name,
if let serde_json::Value::String(s) = value {
s.to_string()
} else {
value.to_string()
},
space = space
));
}
crate::SpanItem::TransientField { name, value } => {
buffer.push_str(&format!(
"{:_>space$}- {}: {}:={}\n",
"",
d,
name,
if let serde_json::Value::String(s) = value {
s.to_string()
} else {
value.to_string()
},
space = space
));
}
crate::SpanItem::Query {
query,
bind,
result,
} => {
buffer.push_str(&format!(
"{:_>space$}- query: {}\n",
"",
query,
space = space
));
if let Some(bind) = bind {
buffer.push_str(&format!(
"{:_>space$} bind: {}\n",
"",
bind,
space = space
));
}
match result {
Ok(rows) => buffer.push_str(&format!(
"{:_>space$} rows: {}\n",
"",
rows,
space = space
)),
Err(e) => {
buffer.push_str(&format!("{:_>space$} error: {}\n", "", e, space = space))
}
};
}
crate::SpanItem::Frame(inner) => {
buffer.push_str(&format!("{:_>space$}- {}: ", "", d, space = space));
print_span(buffer, inner, space + SPACE);
}
}
}
if let Some(result) = &span.result {
buffer.push_str(&format!(
"{:_>space$}result: {}\n",
"",
result,
space = SPACE + space - 2
));
}
if let Some(err) = &span.err {
buffer.push_str(&format!(
"{:_>space$}error: {}\n",
"",
err,
space = SPACE + space - 2
));
}
}
pub fn elapsed(d: std::time::Duration) -> String {
let nanos = d.subsec_nanos();
let fraction = match nanos {
t if nanos < 1000 => format!("{: >3}ns", t),
t if nanos < 1_000_000 => format!("{: >3.*}µs", 0, f64::from(t) / 1000.0),
t => format!("{: >3.*}ms", 0, f64::from(t) / 1_000_000.0),
};
let secs = d.as_secs();
match secs {
_ if secs == 0 => fraction,
t if secs < 5 => format!("{}.{:03}s", t, nanos / 1000),
t if secs < 60 => format!("{}.{:03}s", t, nanos / 1_000_000),
t if secs < 3600 => format!("{}m {}s", t / 60, t % 60),
t if secs < 86400 => format!("{}h {}m", t / 3600, (t % 3600) / 60),
t => format!("{}s", t),
}
}