tracing_web/
console_writer.rsuse std::io::Write;
use tracing_core::Level;
use tracing_subscriber::fmt::MakeWriter;
use wasm_bindgen::JsValue;
use web_sys::console;
pub struct MakeConsoleWriter;
pub struct MakeWebConsoleWriter {
use_pretty_label: bool,
}
impl Default for MakeWebConsoleWriter {
fn default() -> Self {
Self::new()
}
}
impl MakeWebConsoleWriter {
pub fn new() -> Self {
Self {
use_pretty_label: false,
}
}
pub fn with_pretty_level(mut self) -> Self {
self.use_pretty_label = true;
self
}
}
type LogDispatcher = fn(Level, &str);
pub struct ConsoleWriter {
buffer: Vec<u8>,
level: Level,
log: LogDispatcher,
}
impl Write for ConsoleWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.buffer.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Drop for ConsoleWriter {
fn drop(&mut self) {
let message = String::from_utf8_lossy(&self.buffer);
(self.log)(self.level, message.as_ref())
}
}
trait LogImpl {
fn log_simple(level: Level, msg: &str);
fn log_pretty(level: Level, msg: &str);
}
const MESSAGE_STYLE: &str = "background: inherit; color: inherit;";
macro_rules! make_log_impl {
($T:ident {
simple: $s:expr,
pretty: {
log: $p:expr, fmt: $f:expr, label_style: $l:expr $(,)?
} $(,)?
}) => {
struct $T;
impl LogImpl for $T {
#[inline(always)]
fn log_simple(_level: Level, msg: &str) {
$s(&JsValue::from(msg));
}
#[inline(always)]
fn log_pretty(_level: Level, msg: &str) {
let fmt = JsValue::from(wasm_bindgen::intern($f));
let label_style = JsValue::from(wasm_bindgen::intern($l));
let msg_style = JsValue::from(wasm_bindgen::intern(MESSAGE_STYLE));
$p(&fmt, &label_style, &msg_style, &JsValue::from(msg));
}
}
};
}
make_log_impl!(LogLevelTrace { simple: console::debug_1, pretty: { log: console::debug_4, fmt: "%cTRACE%c %s", label_style: "color: white; font-weight: bold; padding: 0 5px; background: #75507B;" } });
make_log_impl!(LogLevelDebug { simple: console::debug_1, pretty: { log: console::debug_4, fmt: "%cDEBUG%c %s", label_style: "color: white; font-weight: bold; padding: 0 5px; background: #3465A4;" } });
make_log_impl!(LogLevelInfo { simple: console::info_1, pretty: { log: console::info_4, fmt: "%c INFO%c %s", label_style: "color: white; font-weight: bold; padding: 0 5px; background: #4E9A06;" } });
make_log_impl!(LogLevelWarn { simple: console::warn_1, pretty: { log: console::warn_4, fmt: "%c WARN%c %s", label_style: "color: white; font-weight: bold; padding: 0 5px; background: #C4A000;" } });
make_log_impl!(LogLevelError { simple: console::error_1, pretty: { log: console::error_4, fmt: "%cERROR%c %s", label_style: "color: white; font-weight: bold; padding: 0 5px; background: #CC0000;" } });
struct LogLevelFallback;
impl LogImpl for LogLevelFallback {
#[inline(always)]
fn log_simple(_level: Level, msg: &str) {
console::log_1(&JsValue::from(msg))
}
#[inline(always)]
fn log_pretty(level: Level, msg: &str) {
let fmt = JsValue::from(wasm_bindgen::intern("%c%s%c %s"));
let label_level = JsValue::from(format!("{}", level));
let label_style = JsValue::from(wasm_bindgen::intern(
"color: white; font-weight: bold; padding: 0 5px; background: #424242; text-transform: uppercase;",
));
let msg_style = JsValue::from(wasm_bindgen::intern(MESSAGE_STYLE));
let msg = JsValue::from(msg);
console::log_5(&fmt, &label_style, &label_level, &msg_style, &msg)
}
}
trait LogImplStyle {
fn get_dispatch<L: LogImpl>(&self) -> LogDispatcher;
}
struct SimpleStyle;
impl LogImplStyle for SimpleStyle {
#[inline(always)]
fn get_dispatch<L: LogImpl>(&self) -> LogDispatcher {
L::log_simple
}
}
struct PrettyStyle;
impl LogImplStyle for PrettyStyle {
#[inline(always)]
fn get_dispatch<L: LogImpl>(&self) -> LogDispatcher {
L::log_pretty
}
}
fn select_dispatcher(style: impl LogImplStyle, level: Level) -> LogDispatcher {
if level == Level::TRACE {
style.get_dispatch::<LogLevelTrace>()
} else if level == Level::DEBUG {
style.get_dispatch::<LogLevelDebug>()
} else if level == Level::INFO {
style.get_dispatch::<LogLevelInfo>()
} else if level == Level::WARN {
style.get_dispatch::<LogLevelWarn>()
} else if level == Level::ERROR {
style.get_dispatch::<LogLevelError>()
} else {
style.get_dispatch::<LogLevelFallback>()
}
}
impl MakeConsoleWriter {
fn upgrade(&self) -> MakeWebConsoleWriter {
MakeWebConsoleWriter {
use_pretty_label: false,
}
}
}
impl<'a> MakeWriter<'a> for MakeConsoleWriter {
type Writer = ConsoleWriter;
fn make_writer(&'a self) -> Self::Writer {
self.upgrade().make_writer()
}
fn make_writer_for(&'a self, meta: &tracing_core::Metadata<'_>) -> Self::Writer {
self.upgrade().make_writer_for(meta)
}
}
impl<'a> MakeWriter<'a> for MakeWebConsoleWriter {
type Writer = ConsoleWriter;
fn make_writer(&'a self) -> Self::Writer {
ConsoleWriter {
buffer: vec![],
level: Level::TRACE, log: if self.use_pretty_label {
PrettyStyle.get_dispatch::<LogLevelFallback>()
} else {
SimpleStyle.get_dispatch::<LogLevelFallback>()
},
}
}
fn make_writer_for(&'a self, meta: &tracing_core::Metadata<'_>) -> Self::Writer {
let level = *meta.level();
let log_fn = if self.use_pretty_label {
select_dispatcher(PrettyStyle, level)
} else {
select_dispatcher(SimpleStyle, level)
};
ConsoleWriter {
buffer: vec![],
level,
log: log_fn,
}
}
}