slog_html/
lib.rs

1//! Html formatter for `slog-rs`
2//!
3//! # Examples
4//!
5//! Writing logs to an HTML file
6//!
7//! ```
8//! #[macro_use]
9//! extern crate slog;
10//! extern crate slog_html;
11//! extern crate slog_stream;
12//!
13//! use slog::DrainExt;
14//!
15//! use std::fs::OpenOptions;
16//!
17//! fn main() {
18//!     let file = OpenOptions::new()
19//!         .create(true)
20//!         .write(true)
21//!         .truncate(true)
22//!         .open("target/log.html").unwrap();
23//!
24//!     let log = slog::Logger::root(
25//!         slog_stream::stream(
26//!             file,
27//!             slog_html::default()
28//!         ).fuse(),
29//!         o!("version" => env!("CARGO_PKG_VERSION"))
30//!     );
31//!
32//!     debug!(log, "debug values"; "x" => 1, "y" => -1);
33//! }
34//! ```
35//!
36//! Create HTML logger with custom options
37//!
38//! Use a greyscale color palette for the log levels and disable boldness for the message part.
39//!
40//! ```
41//! # #[macro_use]
42//! # extern crate slog;
43//! # extern crate slog_html;
44//! # extern crate slog_stream;
45//! #
46//! # use slog::DrainExt;
47//! #
48//! # use std::fs::OpenOptions;
49//! #
50//! # fn main() {
51//! #     let file = OpenOptions::new()
52//! #         .create(true)
53//! #         .write(true)
54//! #         .truncate(true)
55//! #         .open("target/log.html").unwrap();
56//! #
57//! #     let log = slog::Logger::root(
58//! #         slog_stream::stream(
59//! #             file,
60//!             slog_html::new()
61//!                 .compact()
62//!                 .color_palette(slog_html::ColorPalette {
63//!                     critical: "000000",
64//!                     error: "1e1e1e",
65//!                     warning: "3c3c3c",
66//!                     info: "5a5a5a",
67//!                     debug: "787878",
68//!                     trace: "969696",
69//!                 })
70//!                 .message_style(slog_html::Style {
71//!                     bold: false,
72//!                     .. slog_html::Style::default()
73//!                 })
74//!                 .build()
75//! #         ).fuse(),
76//! #         o!("version" => env!("CARGO_PKG_VERSION"))
77//! #     );
78//! #
79//! #     debug!(log, "debug values"; "x" => 1, "y" => -1);
80//! # }
81//! ```
82#![warn(missing_docs)]
83
84#[macro_use]
85extern crate slog;
86extern crate slog_stream;
87extern crate chrono;
88
89mod decorator;
90mod serializer;
91mod color_palette;
92mod style;
93
94use std::io;
95use std::sync::Mutex;
96
97use slog::Record;
98use slog::OwnedKeyValueList;
99use slog_stream::{Decorator, RecordDecorator};
100
101use decorator::HtmlDecorator;
102use serializer::Serializer;
103use style::StyleTable;
104pub use style::Style;
105pub use color_palette::ColorPalette;
106
107/// Formatting mode
108pub enum FormatMode {
109    /// Compact logging format
110    Compact,
111    /// Full logging format
112    Full,
113}
114
115/// Html formatter
116pub struct Format<D: Decorator> {
117    mode: FormatMode,
118    value_stack: Mutex<Vec<Vec<u8>>>,
119    decorator: D,
120    fn_timestamp: Box<TimestampFn>,
121}
122
123impl<D: Decorator> Format<D> {
124    /// Create a new Html formatter
125    pub fn new(mode: FormatMode, decorator: D, fn_timestamp: Box<TimestampFn>) -> Self {
126        Format {
127            mode: mode,
128            value_stack: Mutex::new(Vec::new()),
129            decorator: decorator,
130            fn_timestamp: fn_timestamp,
131        }
132    }
133
134    fn format_full(&self,
135                   io: &mut io::Write,
136                   record: &Record,
137                   logger_values: &OwnedKeyValueList)
138                   -> io::Result<()> {
139
140        let r_decorator = self.decorator.decorate(record);
141
142        try!(io.write_all(b"<pre style=\"margin-bottom:-0.5em\">"));
143
144        try!(r_decorator.fmt_timestamp(io, &*self.fn_timestamp));
145        try!(r_decorator.fmt_level(io, &|io| write!(io, " {} ", record.level().as_short_str())));
146        try!(r_decorator.fmt_msg(io, &|io| write!(io, "{}", record.msg())));
147
148        let mut serializer = Serializer::new(io, r_decorator);
149
150        for (k, v) in logger_values.iter() {
151            try!(serializer.print_comma());
152            try!(v.serialize(record, k, &mut serializer));
153        }
154
155        for &(k, v) in record.values().iter() {
156            try!(serializer.print_comma());
157            try!(v.serialize(record, k, &mut serializer));
158        }
159
160        let (mut io, _) = serializer.finish();
161
162        io.write_all(b"</pre>\n")
163    }
164
165    fn format_compact(&self,
166                      io: &mut io::Write,
167                      record: &Record,
168                      logger_values: &OwnedKeyValueList)
169                      -> io::Result<()> {
170
171        let mut value_stack = self.value_stack.lock().expect("failed to lock value_stack");
172        let mut record_value_stack = try!(self.record_value_stack(record, logger_values));
173        record_value_stack.reverse();
174        let indent = record_value_stack.len();
175
176        let mut changed = false;
177        for i in 0..record_value_stack.len() {
178            if value_stack.len() <= i || value_stack[i] != record_value_stack[i] {
179                changed = true;
180            }
181
182            if changed {
183                try!(io.write_all(b"<pre style=\"margin-bottom:-0.5em\">"));
184                try!(self.print_indent(io, i));
185                try!(io.write_all(&record_value_stack[i]));
186                try!(io.write_all(b"</pre>\n"));
187            }
188        }
189        if changed || value_stack.len() != record_value_stack.len() {
190            *value_stack = record_value_stack;
191        }
192
193        let r_decorator = self.decorator.decorate(record);
194
195        try!(io.write_all(b"<pre style=\"margin-bottom:-0.5em\">"));
196
197        try!(self.print_indent(io, indent));
198        try!(r_decorator.fmt_timestamp(io, &*self.fn_timestamp));
199        try!(r_decorator.fmt_level(io, &|io| write!(io, " {} ", record.level().as_short_str())));
200        try!(r_decorator.fmt_msg(io, &|io| write!(io, "{}", record.msg())));
201
202        let mut serializer = Serializer::new(io, r_decorator);
203
204        for &(k, v) in record.values().iter() {
205            try!(serializer.print_comma());
206            try!(v.serialize(record, k, &mut serializer));
207        }
208
209        let (mut io, _) = serializer.finish();
210
211        io.write_all(b"</pre>\n")
212    }
213
214    /// Get formatted record_value_stack from `logger_values_ref`
215    fn record_value_stack(&self,
216                          record: &slog::Record,
217                          logger_values_ref: &slog::OwnedKeyValueList)
218                          -> io::Result<Vec<Vec<u8>>> {
219
220        let mut value_stack = if let Some(logger_values) = logger_values_ref.values() {
221            let r_decorator = self.decorator.decorate(record);
222            let buf: Vec<u8> = Vec::with_capacity(128);
223            let mut serializer = Serializer::new(buf, r_decorator);
224
225            let mut clean = true;
226            let mut logger_values = logger_values;
227            loop {
228                let (k, v) = logger_values.head();
229                if !clean {
230                    try!(serializer.print_comma());
231                }
232                try!(v.serialize(record, k, &mut serializer));
233                clean = false;
234                logger_values = if let Some(v) = logger_values.tail() {
235                    v
236                } else {
237                    break;
238                }
239            }
240            let (buf, _) = serializer.finish();
241            vec![buf]
242        } else {
243            Vec::new()
244        };
245
246        if let Some(ref parent) = *logger_values_ref.parent() {
247            let mut value = try!(self.record_value_stack(record, parent));
248            value_stack.append(&mut value);
249        }
250
251        Ok(value_stack)
252    }
253
254    fn print_indent(&self, io: &mut io::Write, indent: usize) -> io::Result<()> {
255        for _ in 0..indent {
256            try!(write!(io, "  "));
257        }
258        Ok(())
259    }
260}
261
262impl<D: Decorator> slog_stream::Format for Format<D> {
263    fn format(&self,
264              io: &mut io::Write,
265              record: &Record,
266              logger_values: &OwnedKeyValueList)
267              -> io::Result<()> {
268        match self.mode {
269            FormatMode::Compact => self.format_compact(io, record, logger_values),
270            FormatMode::Full => self.format_full(io, record, logger_values),
271        }
272    }
273}
274
275/// Timestamp function type
276pub type TimestampFn = Fn(&mut io::Write) -> io::Result<()> + Send + Sync;
277
278const TIMESTAMP_FORMAT: &'static str = "%b %d %H:%M:%S%.3f";
279
280/// Default local timestamp function used by `Format`
281///
282/// The exact format used, is still subject to change.
283pub fn timestamp_local(io: &mut io::Write) -> io::Result<()> {
284    write!(io, "{}", chrono::Local::now().format(TIMESTAMP_FORMAT))
285}
286
287/// Default UTC timestamp function used by `Format`
288///
289/// The exact format used, is still subject to change.
290pub fn timestamp_utc(io: &mut io::Write) -> io::Result<()> {
291    write!(io, "{}", chrono::UTC::now().format(TIMESTAMP_FORMAT))
292}
293
294/// Streamer builder
295pub struct FormatBuilder {
296    mode: FormatMode,
297    color_palette: ColorPalette,
298    style: StyleTable,
299    fn_timestamp: Box<TimestampFn>,
300}
301
302impl FormatBuilder {
303    /// New `FormatBuilder`
304    fn new() -> Self {
305        FormatBuilder {
306            mode: FormatMode::Full,
307            color_palette: ColorPalette::default(),
308            style: StyleTable::default(),
309            fn_timestamp: Box::new(timestamp_local),
310        }
311    }
312
313    /// Output using full mode (default)
314    pub fn full(mut self) -> Self {
315        self.mode = FormatMode::Full;
316        self
317    }
318
319    /// Output using compact mode
320    pub fn compact(mut self) -> Self {
321        self.mode = FormatMode::Compact;
322        self
323    }
324
325    /// Use custom color palette
326    pub fn color_palette(mut self, color_palette: ColorPalette) -> Self {
327        self.color_palette = color_palette;
328        self
329    }
330
331    /// Use custom style for the log level
332    pub fn level_style(mut self, style: Style) -> Self {
333        self.style.level = style;
334        self
335    }
336
337    /// Use custom style for the timestamp
338    pub fn timestamp_style(mut self, style: Style) -> Self {
339        self.style.timestamp = style;
340        self
341    }
342
343    /// Use custom style for the message
344    pub fn message_style(mut self, style: Style) -> Self {
345        self.style.message = style;
346        self
347    }
348
349    /// Use custom style for keys
350    pub fn key_style(mut self, style: Style) -> Self {
351        self.style.key = style;
352        self
353    }
354
355    /// Use custom style for values
356    pub fn value_style(mut self, style: Style) -> Self {
357        self.style.value = style;
358        self
359    }
360
361    /// Use custom style for separators
362    pub fn separator_style(mut self, style: Style) -> Self {
363        self.style.separator = style;
364        self
365    }
366
367    /// Use the UTC time zone for the timestamp
368    pub fn use_utc_timestamp(mut self) -> Self {
369        self.fn_timestamp = Box::new(timestamp_utc);
370        self
371    }
372
373    /// Use the local time zone for the timestamp (default)
374    pub fn use_local_timestamp(mut self) -> Self {
375        self.fn_timestamp = Box::new(timestamp_local);
376        self
377    }
378
379    /// Provide a custom function to generate the timestamp
380    pub fn use_custom_timestamp<F>(mut self, f: F) -> Self
381        where F: Fn(&mut io::Write) -> io::Result<()> + 'static + Send + Sync
382    {
383        self.fn_timestamp = Box::new(f);
384        self
385    }
386
387    /// Build Html formatter
388    pub fn build(self) -> Format<HtmlDecorator> {
389        Format {
390            mode: self.mode,
391            value_stack: Mutex::new(Vec::new()),
392            decorator: HtmlDecorator::new(self.color_palette, self.style),
393            fn_timestamp: self.fn_timestamp,
394        }
395    }
396}
397
398impl Default for FormatBuilder {
399    fn default() -> Self {
400        Self::new()
401    }
402}
403
404/// Create new `FormatBuilder` to create `Format`
405pub fn new() -> FormatBuilder {
406    FormatBuilder::new()
407}
408
409/// Default html `Format`
410pub fn default() -> Format<HtmlDecorator> {
411    FormatBuilder::new().build()
412}