tracing_web/
console_writer.rs1use std::io::Write;
2
3use tracing_core::Level;
4use tracing_subscriber::fmt::MakeWriter;
5use wasm_bindgen::JsValue;
6use web_sys::console;
7
8pub struct MakeConsoleWriter;
30
31pub struct MakeWebConsoleWriter {
44 use_pretty_label: bool,
45}
46
47impl Default for MakeWebConsoleWriter {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl MakeWebConsoleWriter {
54 pub fn new() -> Self {
56 Self {
57 use_pretty_label: false,
58 }
59 }
60 pub fn with_pretty_level(mut self) -> Self {
66 self.use_pretty_label = true;
67 self
68 }
69}
70
71type LogDispatcher = fn(Level, &str);
72
73pub struct ConsoleWriter {
75 buffer: Vec<u8>,
76 level: Level,
77 log: LogDispatcher,
78}
79
80impl Write for ConsoleWriter {
81 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
82 self.buffer.write(buf)
83 }
84
85 fn flush(&mut self) -> std::io::Result<()> {
86 Ok(())
88 }
89}
90
91impl Drop for ConsoleWriter {
92 fn drop(&mut self) {
93 let message = String::from_utf8_lossy(&self.buffer);
97 (self.log)(self.level, message.as_ref())
98 }
99}
100
101trait LogImpl {
107 fn log_simple(level: Level, msg: &str);
108 fn log_pretty(level: Level, msg: &str);
109}
110
111const MESSAGE_STYLE: &str = "background: inherit; color: inherit;";
112macro_rules! make_log_impl {
113 ($T:ident {
114 simple: $s:expr,
115 pretty: {
116 log: $p:expr, fmt: $f:expr, label_style: $l:expr $(,)?
117 } $(,)?
118 }) => {
119 struct $T;
120 impl LogImpl for $T {
121 #[inline(always)]
122 fn log_simple(_level: Level, msg: &str) {
123 $s(&JsValue::from(msg));
124 }
125 #[inline(always)]
126 fn log_pretty(_level: Level, msg: &str) {
127 let fmt = JsValue::from(wasm_bindgen::intern($f));
128 let label_style = JsValue::from(wasm_bindgen::intern($l));
129 let msg_style = JsValue::from(wasm_bindgen::intern(MESSAGE_STYLE));
130 $p(&fmt, &label_style, &msg_style, &JsValue::from(msg));
131 }
132 }
133 };
134}
135
136make_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;" } });
138make_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;" } });
139make_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;" } });
140make_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;" } });
141make_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;" } });
142
143struct LogLevelFallback;
145impl LogImpl for LogLevelFallback {
146 #[inline(always)]
147 fn log_simple(_level: Level, msg: &str) {
148 console::log_1(&JsValue::from(msg))
149 }
150
151 #[inline(always)]
152 fn log_pretty(level: Level, msg: &str) {
153 let fmt = JsValue::from(wasm_bindgen::intern("%c%s%c %s"));
154 let label_level = JsValue::from(format!("{}", level));
155 let label_style = JsValue::from(wasm_bindgen::intern(
157 "color: white; font-weight: bold; padding: 0 5px; background: #424242; text-transform: uppercase;",
158 ));
159 let msg_style = JsValue::from(wasm_bindgen::intern(MESSAGE_STYLE));
160 let msg = JsValue::from(msg);
161 console::log_5(&fmt, &label_style, &label_level, &msg_style, &msg)
162 }
163}
164
165trait LogImplStyle {
169 fn get_dispatch<L: LogImpl>(&self) -> LogDispatcher;
170}
171struct SimpleStyle;
172impl LogImplStyle for SimpleStyle {
173 #[inline(always)]
174 fn get_dispatch<L: LogImpl>(&self) -> LogDispatcher {
175 L::log_simple
176 }
177}
178struct PrettyStyle;
179impl LogImplStyle for PrettyStyle {
180 #[inline(always)]
181 fn get_dispatch<L: LogImpl>(&self) -> LogDispatcher {
182 L::log_pretty
183 }
184}
185
186fn select_dispatcher(style: impl LogImplStyle, level: Level) -> LogDispatcher {
187 if level == Level::TRACE {
188 style.get_dispatch::<LogLevelTrace>()
189 } else if level == Level::DEBUG {
190 style.get_dispatch::<LogLevelDebug>()
191 } else if level == Level::INFO {
192 style.get_dispatch::<LogLevelInfo>()
193 } else if level == Level::WARN {
194 style.get_dispatch::<LogLevelWarn>()
195 } else if level == Level::ERROR {
196 style.get_dispatch::<LogLevelError>()
197 } else {
198 style.get_dispatch::<LogLevelFallback>()
199 }
200}
201
202impl MakeConsoleWriter {
203 fn upgrade(&self) -> MakeWebConsoleWriter {
205 MakeWebConsoleWriter {
206 use_pretty_label: false,
207 }
208 }
209}
210impl<'a> MakeWriter<'a> for MakeConsoleWriter {
211 type Writer = ConsoleWriter;
212
213 fn make_writer(&'a self) -> Self::Writer {
214 self.upgrade().make_writer()
215 }
216
217 fn make_writer_for(&'a self, meta: &tracing_core::Metadata<'_>) -> Self::Writer {
218 self.upgrade().make_writer_for(meta)
219 }
220}
221
222impl<'a> MakeWriter<'a> for MakeWebConsoleWriter {
223 type Writer = ConsoleWriter;
224
225 fn make_writer(&'a self) -> Self::Writer {
226 ConsoleWriter {
227 buffer: vec![],
228 level: Level::TRACE, log: if self.use_pretty_label {
230 PrettyStyle.get_dispatch::<LogLevelFallback>()
231 } else {
232 SimpleStyle.get_dispatch::<LogLevelFallback>()
233 },
234 }
235 }
236
237 fn make_writer_for(&'a self, meta: &tracing_core::Metadata<'_>) -> Self::Writer {
238 let level = *meta.level();
239 let log_fn = if self.use_pretty_label {
240 select_dispatcher(PrettyStyle, level)
241 } else {
242 select_dispatcher(SimpleStyle, level)
243 };
244 ConsoleWriter {
245 buffer: vec![],
246 level,
247 log: log_fn,
248 }
249 }
250}