console_log/
lib.rs

1#![deny(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/console_log/1.0.0")]
3
4//! A logger that logs to the browser's console.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use log::Level;
10//! use log::info;
11//! fn main() {
12//!     console_log::init_with_level(Level::Debug);
13//!
14//!     info!("It works!");
15//!
16//!     // ...
17//! }
18//! ```
19//!
20//! # Log Levels
21//!
22//! Rust's log levels map to the browser's console log in the following way.
23//!
24//! | Rust       | Web Console       |
25//! |------------|-------------------|
26//! | `trace!()` | `console.debug()` |
27//! | `debug!()` | `console.log()`   |
28//! | `info!()`  | `console.info()`  |
29//! | `warn!()`  | `console.warn()`  |
30//! | `error!()` | `console.error()` |
31//!
32//! # Getting Fancy
33//!
34//! The feature set provided by this crate is intentionally very basic. If you need more flexible
35//! formatting of log messages (timestamps, file and line info, etc.) this crate can be used with
36//! the [`fern`] logger via the [`console_log::log`] function.
37//!
38//! ## Colors
39//!
40//! The `"color"` feature adds styling to the log messages.
41//!
42//! `Cargo.toml`
43//! ```toml
44//! console_log = { version = "0.2", features = ["color"] }
45//! ```
46//!
47//! The styled log messages will be rendered as follows:
48//!
49//! ![Styled log messages](img/log_messages_styled.png)
50//!
51//! # Code Size
52//!
53//! [Twiggy] reports this library adding about 180Kb to the size of a minimal wasm binary in a
54//! debug build. If you want to avoid this, mark the library as optional and conditionally
55//! initialize it in your code for non-release builds.
56//!
57//! `Cargo.toml`
58//! ```toml
59//! [dependencies]
60//! cfg-if = "0.1"
61//! log = "0.4"
62//! console_log = { version = "0.2", optional = true }
63//!
64//! [features]
65//! default = ["console_log"]
66//! ```
67//!
68//! `lib.rs`
69//! ```rust,ignore
70//! use wasm_bindgen::prelude::*;
71//! use cfg_if::cfg_if;
72//!
73//! cfg_if! {
74//!     if #[cfg(feature = "console_log")] {
75//!         fn init_log() {
76//!             use log::Level;
77//!             console_log::init_with_level(Level::Trace).expect("error initializing log");
78//!         }
79//!     } else {
80//!         fn init_log() {}
81//!     }
82//! }
83//!
84//! #[wasm_bindgen]
85//! pub fn main() {
86//!     init_log();
87//!     // ...
88//! }
89//! ```
90//!
91//! # Limitations
92//!
93//! The file and line number information associated with the log messages reports locations from
94//! the shims generated by `wasm-bindgen`, not the location of the logger call.
95//!
96//! [Twiggy]: https://github.com/rustwasm/twiggy
97//! [`console_log::log`]: fn.log.html
98//! [`fern`]: https://docs.rs/fern
99
100use log::{Level, Log, Metadata, Record, SetLoggerError};
101use web_sys::console;
102
103#[cfg(feature = "color")]
104use wasm_bindgen::JsValue;
105
106#[cfg(feature = "color")]
107const STYLE: style::Style<'static> = style::Style::default();
108
109#[cfg(feature = "color")]
110#[doc(hidden)]
111mod style;
112
113static LOGGER: WebConsoleLogger = WebConsoleLogger {};
114
115struct WebConsoleLogger {}
116
117impl Log for WebConsoleLogger {
118    #[inline]
119    fn enabled(&self, metadata: &Metadata) -> bool {
120        metadata.level() <= log::max_level()
121    }
122
123    fn log(&self, record: &Record) {
124        if !self.enabled(record.metadata()) {
125            return;
126        }
127
128        log(record);
129    }
130
131    fn flush(&self) {}
132}
133
134/// Print a `log::Record` to the browser's console at the appropriate level.
135///
136/// This function is useful for integrating with the [`fern`](https://crates.io/crates/fern) logger
137/// crate.
138///
139/// ## Example
140/// ```rust,ignore
141/// fern::Dispatch::new()
142///     .chain(fern::Output::call(console_log::log))
143///     .apply()?;
144/// ```
145#[cfg_attr(not(feature = "color"), inline)]
146pub fn log(record: &Record) {
147    #[cfg(not(feature = "color"))]
148    {
149        // pick the console.log() variant for the appropriate logging level
150        let console_log = match record.level() {
151            Level::Error => console::error_1,
152            Level::Warn => console::warn_1,
153            Level::Info => console::info_1,
154            Level::Debug => console::log_1,
155            Level::Trace => console::debug_1,
156        };
157
158        console_log(&format!("{}", record.args()).into());
159    }
160
161    #[cfg(feature = "color")]
162    {
163        // pick the console.log() variant for the appropriate logging level
164        let console_log = match record.level() {
165            Level::Error => console::error_4,
166            Level::Warn => console::warn_4,
167            Level::Info => console::info_4,
168            Level::Debug => console::log_4,
169            Level::Trace => console::debug_4,
170        };
171
172        let message = {
173            let message = format!(
174                "%c{level}%c {file}:{line} %c\n{text}",
175                level = record.level(),
176                file = record.file().unwrap_or_else(|| record.target()),
177                line = record
178                    .line()
179                    .map_or_else(|| "[Unknown]".to_string(), |line| line.to_string()),
180                text = record.args(),
181            );
182            JsValue::from(&message)
183        };
184
185        let level_style = {
186            let style_str = match record.level() {
187                Level::Trace => STYLE.trace,
188                Level::Debug => STYLE.debug,
189                Level::Info => STYLE.info,
190                Level::Warn => STYLE.warn,
191                Level::Error => STYLE.error,
192            };
193
194            JsValue::from(style_str)
195        };
196
197        let file_line_style = JsValue::from_str(STYLE.file_line);
198        let text_style = JsValue::from_str(STYLE.text);
199        console_log(&message, &level_style, &file_line_style, &text_style);
200    }
201}
202
203/// Initializes the global logger setting `max_log_level` to the given value.
204///
205/// ## Example
206///
207/// ```
208/// use log::Level;
209/// fn main() {
210///     console_log::init_with_level(Level::Debug).expect("error initializing logger");
211/// }
212/// ```
213#[inline]
214pub fn init_with_level(level: Level) -> Result<(), SetLoggerError> {
215    log::set_logger(&LOGGER)?;
216    log::set_max_level(level.to_level_filter());
217    Ok(())
218}
219
220/// Initializes the global logger with `max_log_level` set to `Level::Info` (a sensible default).
221///
222/// ## Example
223///
224/// ```
225/// fn main() {
226///     console_log::init().expect("error initializing logger");
227/// }
228/// ```
229#[inline]
230pub fn init() -> Result<(), SetLoggerError> {
231    init_with_level(Level::Info)
232}