tari_log4rs/encode/pattern/
mod.rs

1//! A simple pattern-based encoder.
2//!
3//! Requires the `pattern_encoder` feature.
4//!
5//! The pattern syntax is similar to Rust's string formatting syntax. It
6//! consists of raw text interspersed with format arguments. The grammar is:
7//!
8//! ```not_rust
9//! format_string := <text> [ format <text> ] *
10//! format := '{' formatter [ ':' format_spec ] '}'
11//! formatter := [ name ] [ '(' argument ')' ] *
12//! name := identifier
13//! argument := format_string
14//!
15//! format_spec := [ [ fill ] align ] [ min_width ] [ '.' max_width ]
16//! fill := character
17//! align := '<' | '>'
18//! min_width := number
19//! max_width := number
20//! ```
21//!
22//! # Special characters
23//!
24//! The `{`, `}`, `(`, `)`, and `\` characters are part of the pattern syntax;
25//! they must be escaped to appear in output. Like with Rust's string
26//! formatting syntax, type the character twice to escape it. That is, `{{`
27//! will be rendered as `{` in output and `))` will be rendered as `)`.
28//!
29//! In addition, these characters may also be escaped by prefixing them with a
30//! `\` character. That is, `\{` will be rendered as `{`.
31//!
32//! # Formatters
33//!
34//! A formatter inserts a dynamic portion of text into the pattern. It may be
35//! text derived from a log event or from some other context like the current
36//! time. Formatters may be passed arguments consisting of parenthesized format
37//! strings.
38//!
39//! The following formatters are currently supported. Unless otherwise stated,
40//! a formatter does not accept any argument.
41//!
42//! * `d`, `date` - The current time. By default, the ISO 8601 format is used.
43//!     A custom format may be provided in the syntax accepted by `chrono`.
44//!     The timezone defaults to local, but can be specified explicitly by
45//!     passing a second argument of `utc` for UTC or `local` for local time.
46//!     * `{d}` - `2016-03-20T14:22:20.644420340-08:00`
47//!     * `{d(%Y-%m-%d %H:%M:%S)}` - `2016-03-20 14:22:20`
48//!     * `{d(%Y-%m-%d %H:%M:%S %Z)(utc)}` - `2016-03-20 22:22:20 UTC`
49//! * `f`, `file` - The source file that the log message came from, or `???` if
50//!     not provided.
51//! * `h`, `highlight` - Styles its argument according to the log level. The
52//!     style is intense red for errors, red for warnings, blue for info, and
53//!     the default style for all other levels.
54//!     * `{h(the level is {l})}` -
55//!         <code style="color: red; font-weight: bold">the level is ERROR</code>
56//! * `l`, `level` - The log level.
57//! * `L`, `line` - The line that the log message came from, or `???` if not
58//!     provided.
59//! * `m`, `message` - The log message.
60//! * `M`, `module` - The module that the log message came from, or `???` if not
61//!     provided.
62//! * `P`, `pid` - The current process id.
63//! * `i`, `tid` - The current system-wide unique thread ID.
64//! * `n` - A platform-specific newline.
65//! * `t`, `target` - The target of the log message.
66//! * `T`, `thread` - The name of the current thread.
67//! * `I`, `thread_id` - The pthread ID of the current thread.
68//! * `X`, `mdc` - A value from the [MDC][MDC]. The first argument specifies
69//!     the key, and the second argument specifies the default value if the
70//!     key is not present in the MDC. The second argument is optional, and
71//!     defaults to the empty string.
72//!     * `{X(user_id)}` - `123e4567-e89b-12d3-a456-426655440000`
73//!     * `{X(nonexistent_key)(no mapping)}` - `no mapping`
74//! * An "unnamed" formatter simply formats its argument, applying the format
75//!     specification.
76//!     * `{({l} {m})}` - `INFO hello`
77//!
78//! # Format Specification
79//!
80//! The format specification determines how the output of a formatter is
81//! adjusted before being returned.
82//!
83//! ## Fill/Alignment
84//!
85//! The fill and alignment values are used in conjunction with a minimum width
86//! value (see below) to control the behavior when a formatter's output is less
87//! than the minimum. While the default behavior is to pad the output to the
88//! right with space characters (i.e. left align it), the fill value specifies
89//! the character used, and the alignment value is one of:
90//!
91//! * `<` - Left align by appending the fill character to the formatter output
92//! * `>` - Right align by prepending the fill character to the formatter
93//!     output.
94//!
95//! ## Width
96//!
97//! By default, the full contents of a formatter's output will be inserted into
98//! the pattern output, but both the minimum and maximum lengths can be
99//! configured. Any output over the maximum length will be truncated, and
100//! output under the minimum length will be padded (see above).
101//!
102//! # Examples
103//!
104//! The default pattern is `{d} {l} {t} - {m}{n}` which produces output like
105//! `2016-03-20T22:22:20.644420340+00:00 INFO module::path - this is a log
106//! message`.
107//!
108//! The pattern `{m:>10.15}` will right-align the log message to a minimum of
109//! 10 bytes, filling in with space characters, and truncate output after 15
110//! bytes. The message `hello` will therefore be displayed as
111//! <code>     hello</code>, while the message `hello there, world!` will be
112//! displayed as `hello there, wo`.
113//!
114//! The pattern `{({l} {m}):15.15}` will output the log level and message
115//! limited to exactly 15 bytes, padding with space characters on the right if
116//! necessary. The message `hello` and log level `INFO` will be displayed as
117//! <code>INFO hello     </code>, while the message `hello, world!` and log
118//! level `DEBUG` will be truncated to `DEBUG hello, wo`.
119//!
120//! [MDC]: https://crates.io/crates/log-mdc
121
122use chrono::{Local, Utc};
123use derivative::Derivative;
124use log::{Level, Record};
125use std::{default::Default, io, process, thread};
126
127use crate::encode::{
128    self,
129    pattern::parser::{Alignment, Parameters, Parser, Piece},
130    Color, Encode, Style, NEWLINE,
131};
132
133#[cfg(feature = "config_parsing")]
134use crate::config::{Deserialize, Deserializers};
135
136mod parser;
137
138thread_local!(
139    /// Thread-locally cached thread ID.
140    static TID: usize = thread_id::get()
141);
142
143/// The pattern encoder's configuration.
144#[cfg(feature = "config_parsing")]
145#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, serde::Deserialize)]
146#[serde(deny_unknown_fields)]
147pub struct PatternEncoderConfig {
148    pattern: Option<String>,
149}
150
151fn is_char_boundary(b: u8) -> bool {
152    b as i8 >= -0x40
153}
154
155fn char_starts(buf: &[u8]) -> usize {
156    buf.iter().filter(|&&b| is_char_boundary(b)).count()
157}
158
159struct MaxWidthWriter<'a> {
160    remaining: usize,
161    w: &'a mut dyn encode::Write,
162}
163
164impl<'a> io::Write for MaxWidthWriter<'a> {
165    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
166        let mut remaining = self.remaining;
167        let mut end = buf.len();
168        for (idx, _) in buf
169            .iter()
170            .enumerate()
171            .filter(|&(_, &b)| is_char_boundary(b))
172        {
173            if remaining == 0 {
174                end = idx;
175                break;
176            }
177            remaining -= 1;
178        }
179
180        // we don't want to report EOF, so just act as a sink past this point
181        if end == 0 {
182            return Ok(buf.len());
183        }
184
185        let buf = &buf[..end];
186        match self.w.write(buf) {
187            Ok(len) => {
188                if len == end {
189                    self.remaining = remaining;
190                } else {
191                    self.remaining -= char_starts(&buf[..len]);
192                }
193                Ok(len)
194            }
195            Err(e) => Err(e),
196        }
197    }
198
199    fn flush(&mut self) -> io::Result<()> {
200        self.w.flush()
201    }
202}
203
204impl<'a> encode::Write for MaxWidthWriter<'a> {
205    fn set_style(&mut self, style: &Style) -> io::Result<()> {
206        self.w.set_style(style)
207    }
208}
209
210struct LeftAlignWriter<W> {
211    to_fill: usize,
212    fill: char,
213    w: W,
214}
215
216impl<W: encode::Write> LeftAlignWriter<W> {
217    fn finish(mut self) -> io::Result<()> {
218        for _ in 0..self.to_fill {
219            write!(self.w, "{}", self.fill)?;
220        }
221        Ok(())
222    }
223}
224
225impl<W: encode::Write> io::Write for LeftAlignWriter<W> {
226    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
227        match self.w.write(buf) {
228            Ok(len) => {
229                self.to_fill = self.to_fill.saturating_sub(char_starts(&buf[..len]));
230                Ok(len)
231            }
232            Err(e) => Err(e),
233        }
234    }
235
236    fn flush(&mut self) -> io::Result<()> {
237        self.w.flush()
238    }
239}
240
241impl<W: encode::Write> encode::Write for LeftAlignWriter<W> {
242    fn set_style(&mut self, style: &Style) -> io::Result<()> {
243        self.w.set_style(style)
244    }
245}
246
247enum BufferedOutput {
248    Data(Vec<u8>),
249    Style(Style),
250}
251
252struct RightAlignWriter<W> {
253    to_fill: usize,
254    fill: char,
255    w: W,
256    buf: Vec<BufferedOutput>,
257}
258
259impl<W: encode::Write> RightAlignWriter<W> {
260    fn finish(mut self) -> io::Result<()> {
261        for _ in 0..self.to_fill {
262            write!(self.w, "{}", self.fill)?;
263        }
264        for out in self.buf {
265            match out {
266                BufferedOutput::Data(ref buf) => self.w.write_all(buf)?,
267                BufferedOutput::Style(ref style) => self.w.set_style(style)?,
268            }
269        }
270        Ok(())
271    }
272}
273
274impl<W: encode::Write> io::Write for RightAlignWriter<W> {
275    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
276        self.to_fill = self.to_fill.saturating_sub(char_starts(buf));
277
278        let mut pushed = false;
279        if let Some(&mut BufferedOutput::Data(ref mut data)) = self.buf.last_mut() {
280            data.extend_from_slice(buf);
281            pushed = true;
282        };
283
284        if !pushed {
285            self.buf.push(BufferedOutput::Data(buf.to_owned()));
286        }
287        Ok(buf.len())
288    }
289
290    fn flush(&mut self) -> io::Result<()> {
291        Ok(())
292    }
293}
294
295impl<W: encode::Write> encode::Write for RightAlignWriter<W> {
296    fn set_style(&mut self, style: &Style) -> io::Result<()> {
297        self.buf.push(BufferedOutput::Style(style.clone()));
298        Ok(())
299    }
300}
301
302#[derive(Clone, Eq, PartialEq, Hash, Debug)]
303enum Chunk {
304    Text(String),
305    Formatted {
306        chunk: FormattedChunk,
307        params: Parameters,
308    },
309    Error(String),
310}
311
312impl Chunk {
313    fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> io::Result<()> {
314        match *self {
315            Chunk::Text(ref s) => w.write_all(s.as_bytes()),
316            Chunk::Formatted {
317                ref chunk,
318                ref params,
319            } => match (params.min_width, params.max_width, params.align) {
320                (None, None, _) => chunk.encode(w, record),
321                (None, Some(max_width), _) => {
322                    let mut w = MaxWidthWriter {
323                        remaining: max_width,
324                        w,
325                    };
326                    chunk.encode(&mut w, record)
327                }
328                (Some(min_width), None, Alignment::Left) => {
329                    let mut w = LeftAlignWriter {
330                        to_fill: min_width,
331                        fill: params.fill,
332                        w,
333                    };
334                    chunk.encode(&mut w, record)?;
335                    w.finish()
336                }
337                (Some(min_width), None, Alignment::Right) => {
338                    let mut w = RightAlignWriter {
339                        to_fill: min_width,
340                        fill: params.fill,
341                        w,
342                        buf: vec![],
343                    };
344                    chunk.encode(&mut w, record)?;
345                    w.finish()
346                }
347                (Some(min_width), Some(max_width), Alignment::Left) => {
348                    let mut w = LeftAlignWriter {
349                        to_fill: min_width,
350                        fill: params.fill,
351                        w: MaxWidthWriter {
352                            remaining: max_width,
353                            w,
354                        },
355                    };
356                    chunk.encode(&mut w, record)?;
357                    w.finish()
358                }
359                (Some(min_width), Some(max_width), Alignment::Right) => {
360                    let mut w = RightAlignWriter {
361                        to_fill: min_width,
362                        fill: params.fill,
363                        w: MaxWidthWriter {
364                            remaining: max_width,
365                            w,
366                        },
367                        buf: vec![],
368                    };
369                    chunk.encode(&mut w, record)?;
370                    w.finish()
371                }
372            },
373            Chunk::Error(ref s) => write!(w, "{{ERROR: {}}}", s),
374        }
375    }
376}
377
378impl<'a> From<Piece<'a>> for Chunk {
379    fn from(piece: Piece<'a>) -> Chunk {
380        match piece {
381            Piece::Text(text) => Chunk::Text(text.to_owned()),
382            Piece::Argument {
383                mut formatter,
384                parameters,
385            } => match formatter.name {
386                "d" | "date" => {
387                    if formatter.args.len() > 2 {
388                        return Chunk::Error("expected at most two arguments".to_owned());
389                    }
390
391                    let format = match formatter.args.get(0) {
392                        Some(arg) => {
393                            let mut format = String::new();
394                            for piece in arg {
395                                match *piece {
396                                    Piece::Text(text) => format.push_str(text),
397                                    Piece::Argument { .. } => {
398                                        format.push_str("{ERROR: unexpected formatter}");
399                                    }
400                                    Piece::Error(ref err) => {
401                                        format.push_str("{ERROR: ");
402                                        format.push_str(err);
403                                        format.push('}');
404                                    }
405                                }
406                            }
407                            format
408                        }
409                        None => "%+".to_owned(),
410                    };
411
412                    let timezone = match formatter.args.get(1) {
413                        Some(arg) => {
414                            if let Some(arg) = arg.get(0) {
415                                match *arg {
416                                    Piece::Text(z) if z == "utc" => Timezone::Utc,
417                                    Piece::Text(z) if z == "local" => Timezone::Local,
418                                    Piece::Text(z) => {
419                                        return Chunk::Error(format!("invalid timezone `{}`", z));
420                                    }
421                                    _ => return Chunk::Error("invalid timezone".to_owned()),
422                                }
423                            } else {
424                                return Chunk::Error("invalid timezone".to_owned());
425                            }
426                        }
427                        None => Timezone::Local,
428                    };
429
430                    Chunk::Formatted {
431                        chunk: FormattedChunk::Time(format, timezone),
432                        params: parameters,
433                    }
434                }
435                "h" | "highlight" => {
436                    if formatter.args.len() != 1 {
437                        return Chunk::Error("expected exactly one argument".to_owned());
438                    }
439
440                    let chunks = formatter
441                        .args
442                        .pop()
443                        .unwrap()
444                        .into_iter()
445                        .map(From::from)
446                        .collect();
447                    Chunk::Formatted {
448                        chunk: FormattedChunk::Highlight(chunks),
449                        params: parameters,
450                    }
451                }
452                "l" | "level" => no_args(&formatter.args, parameters, FormattedChunk::Level),
453                "m" | "message" => no_args(&formatter.args, parameters, FormattedChunk::Message),
454                "M" | "module" => no_args(&formatter.args, parameters, FormattedChunk::Module),
455                "n" => no_args(&formatter.args, parameters, FormattedChunk::Newline),
456                "f" | "file" => no_args(&formatter.args, parameters, FormattedChunk::File),
457                "L" | "line" => no_args(&formatter.args, parameters, FormattedChunk::Line),
458                "T" | "thread" => no_args(&formatter.args, parameters, FormattedChunk::Thread),
459                "I" | "thread_id" => no_args(&formatter.args, parameters, FormattedChunk::ThreadId),
460                "P" | "pid" => no_args(&formatter.args, parameters, FormattedChunk::ProcessId),
461                "i" | "tid" => no_args(&formatter.args, parameters, FormattedChunk::SystemThreadId),
462                "t" | "target" => no_args(&formatter.args, parameters, FormattedChunk::Target),
463                "X" | "mdc" => {
464                    if formatter.args.len() > 2 {
465                        return Chunk::Error("expected at most two arguments".to_owned());
466                    }
467
468                    let key = match formatter.args.get(0) {
469                        Some(arg) => {
470                            if let Some(arg) = arg.get(0) {
471                                match arg {
472                                    Piece::Text(key) => key.to_owned(),
473                                    Piece::Error(ref e) => return Chunk::Error(e.clone()),
474                                    _ => return Chunk::Error("invalid MDC key".to_owned()),
475                                }
476                            } else {
477                                return Chunk::Error("invalid MDC key".to_owned());
478                            }
479                        }
480                        None => return Chunk::Error("missing MDC key".to_owned()),
481                    };
482
483                    let default = match formatter.args.get(1) {
484                        Some(arg) => {
485                            if let Some(arg) = arg.get(0) {
486                                match arg {
487                                    Piece::Text(key) => key.to_owned(),
488                                    Piece::Error(ref e) => return Chunk::Error(e.clone()),
489                                    _ => return Chunk::Error("invalid MDC default".to_owned()),
490                                }
491                            } else {
492                                return Chunk::Error("invalid MDC default".to_owned());
493                            }
494                        }
495                        None => "",
496                    };
497
498                    Chunk::Formatted {
499                        chunk: FormattedChunk::Mdc(key.into(), default.into()),
500                        params: parameters,
501                    }
502                }
503                "" => {
504                    if formatter.args.len() != 1 {
505                        return Chunk::Error("expected exactly one argument".to_owned());
506                    }
507
508                    let chunks = formatter
509                        .args
510                        .pop()
511                        .unwrap()
512                        .into_iter()
513                        .map(From::from)
514                        .collect();
515                    Chunk::Formatted {
516                        chunk: FormattedChunk::Align(chunks),
517                        params: parameters,
518                    }
519                }
520                name => Chunk::Error(format!("unknown formatter `{}`", name)),
521            },
522            Piece::Error(err) => Chunk::Error(err),
523        }
524    }
525}
526
527fn no_args(arg: &[Vec<Piece>], params: Parameters, chunk: FormattedChunk) -> Chunk {
528    if arg.is_empty() {
529        Chunk::Formatted { chunk, params }
530    } else {
531        Chunk::Error("unexpected arguments".to_owned())
532    }
533}
534
535#[derive(Clone, Eq, PartialEq, Hash, Debug)]
536enum Timezone {
537    Utc,
538    Local,
539}
540
541#[derive(Clone, Eq, PartialEq, Hash, Debug)]
542enum FormattedChunk {
543    Time(String, Timezone),
544    Level,
545    Message,
546    Module,
547    File,
548    Line,
549    Thread,
550    ThreadId,
551    ProcessId,
552    SystemThreadId,
553    Target,
554    Newline,
555    Align(Vec<Chunk>),
556    Highlight(Vec<Chunk>),
557    Mdc(String, String),
558}
559
560impl FormattedChunk {
561    fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> io::Result<()> {
562        match *self {
563            FormattedChunk::Time(ref fmt, Timezone::Utc) => write!(w, "{}", Utc::now().format(fmt)),
564            FormattedChunk::Time(ref fmt, Timezone::Local) => {
565                write!(w, "{}", Local::now().format(fmt))
566            }
567            FormattedChunk::Level => write!(w, "{}", record.level()),
568            FormattedChunk::Message => w.write_fmt(*record.args()),
569            FormattedChunk::Module => w.write_all(record.module_path().unwrap_or("???").as_bytes()),
570            FormattedChunk::File => w.write_all(record.file().unwrap_or("???").as_bytes()),
571            FormattedChunk::Line => match record.line() {
572                Some(line) => write!(w, "{}", line),
573                None => w.write_all(b"???"),
574            },
575            FormattedChunk::Thread => {
576                w.write_all(thread::current().name().unwrap_or("unnamed").as_bytes())
577            }
578            FormattedChunk::ThreadId => w.write_all(thread_id::get().to_string().as_bytes()),
579            FormattedChunk::ProcessId => w.write_all(process::id().to_string().as_bytes()),
580            FormattedChunk::SystemThreadId => {
581                TID.with(|tid| w.write_all(tid.to_string().as_bytes()))
582            }
583            FormattedChunk::Target => w.write_all(record.target().as_bytes()),
584            FormattedChunk::Newline => w.write_all(NEWLINE.as_bytes()),
585            FormattedChunk::Align(ref chunks) => {
586                for chunk in chunks {
587                    chunk.encode(w, record)?;
588                }
589                Ok(())
590            }
591            FormattedChunk::Highlight(ref chunks) => {
592                match record.level() {
593                    Level::Error => {
594                        w.set_style(Style::new().text(Color::Red).intense(true))?;
595                    }
596                    Level::Warn => w.set_style(Style::new().text(Color::Yellow))?,
597                    Level::Info => w.set_style(Style::new().text(Color::Green))?,
598                    Level::Trace => w.set_style(Style::new().text(Color::Cyan))?,
599                    _ => {}
600                }
601                for chunk in chunks {
602                    chunk.encode(w, record)?;
603                }
604                match record.level() {
605                    Level::Error | Level::Warn | Level::Info | Level::Trace => {
606                        w.set_style(&Style::new())?
607                    }
608                    _ => {}
609                }
610                Ok(())
611            }
612            FormattedChunk::Mdc(ref key, ref default) => {
613                log_mdc::get(key, |v| write!(w, "{}", v.unwrap_or(default)))
614            }
615        }
616    }
617}
618
619/// An `Encode`r configured via a format string.
620#[derive(Derivative)]
621#[derivative(Debug)]
622#[derive(Clone, Eq, PartialEq, Hash)]
623pub struct PatternEncoder {
624    #[derivative(Debug = "ignore")]
625    chunks: Vec<Chunk>,
626    pattern: String,
627}
628
629/// Returns a `PatternEncoder` using the default pattern of `{d} {l} {t} - {m}{n}`.
630impl Default for PatternEncoder {
631    fn default() -> PatternEncoder {
632        PatternEncoder::new("{d} {l} {t} - {m}{n}")
633    }
634}
635
636impl Encode for PatternEncoder {
637    fn encode(&self, w: &mut dyn encode::Write, record: &Record) -> anyhow::Result<()> {
638        for chunk in &self.chunks {
639            chunk.encode(w, record)?;
640        }
641        Ok(())
642    }
643}
644
645impl PatternEncoder {
646    /// Creates a `PatternEncoder` from a pattern string.
647    ///
648    /// The pattern string syntax is documented in the `pattern` module.
649    pub fn new(pattern: &str) -> PatternEncoder {
650        PatternEncoder {
651            chunks: Parser::new(pattern).map(From::from).collect(),
652            pattern: pattern.to_owned(),
653        }
654    }
655}
656
657/// A deserializer for the `PatternEncoder`.
658///
659/// # Configuration
660///
661/// ```yaml
662/// kind: pattern
663///
664/// # The pattern to follow when formatting logs. Defaults to
665/// # "{d} {l} {t} - {m}{n}".
666/// pattern: "{d} {l} {t} - {m}{n}"
667/// ```
668#[cfg(feature = "config_parsing")]
669#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
670pub struct PatternEncoderDeserializer;
671
672#[cfg(feature = "config_parsing")]
673impl Deserialize for PatternEncoderDeserializer {
674    type Trait = dyn Encode;
675
676    type Config = PatternEncoderConfig;
677
678    fn deserialize(
679        &self,
680        config: PatternEncoderConfig,
681        _: &Deserializers,
682    ) -> anyhow::Result<Box<dyn Encode>> {
683        let encoder = match config.pattern {
684            Some(pattern) => PatternEncoder::new(&pattern),
685            None => PatternEncoder::default(),
686        };
687        Ok(Box::new(encoder))
688    }
689}
690
691#[cfg(test)]
692mod tests {
693    #[cfg(feature = "simple_writer")]
694    use log::{Level, Record};
695    #[cfg(feature = "simple_writer")]
696    use std::process;
697    #[cfg(feature = "simple_writer")]
698    use std::thread;
699
700    use super::{Chunk, PatternEncoder};
701    #[cfg(feature = "simple_writer")]
702    use crate::encode::writer::simple::SimpleWriter;
703    #[cfg(feature = "simple_writer")]
704    use crate::encode::Encode;
705
706    fn error_free(encoder: &PatternEncoder) -> bool {
707        encoder.chunks.iter().all(|c| match *c {
708            Chunk::Error(_) => false,
709            _ => true,
710        })
711    }
712
713    #[test]
714    fn invalid_formatter() {
715        assert!(!error_free(&PatternEncoder::new("{x}")));
716    }
717
718    #[test]
719    fn unclosed_delimiter() {
720        assert!(!error_free(&PatternEncoder::new("{d(%Y-%m-%d)")));
721    }
722
723    #[test]
724    #[cfg(feature = "simple_writer")]
725    fn log() {
726        let pw = PatternEncoder::new("{l} {m} at {M} in {f}:{L}");
727        let mut buf = vec![];
728        pw.encode(
729            &mut SimpleWriter(&mut buf),
730            &Record::builder()
731                .level(Level::Debug)
732                .args(format_args!("the message"))
733                .module_path(Some("path"))
734                .file(Some("file"))
735                .line(Some(132))
736                .build(),
737        )
738        .unwrap();
739
740        assert_eq!(buf, &b"DEBUG the message at path in file:132"[..]);
741    }
742
743    #[test]
744    #[cfg(feature = "simple_writer")]
745    fn unnamed_thread() {
746        thread::spawn(|| {
747            let pw = PatternEncoder::new("{T}");
748            let mut buf = vec![];
749            pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
750                .unwrap();
751            assert_eq!(buf, b"unnamed");
752        })
753        .join()
754        .unwrap();
755    }
756
757    #[test]
758    #[cfg(feature = "simple_writer")]
759    fn named_thread() {
760        thread::Builder::new()
761            .name("foobar".to_string())
762            .spawn(|| {
763                let pw = PatternEncoder::new("{T}");
764                let mut buf = vec![];
765                pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
766                    .unwrap();
767                assert_eq!(buf, b"foobar");
768            })
769            .unwrap()
770            .join()
771            .unwrap();
772    }
773
774    #[test]
775    #[cfg(feature = "simple_writer")]
776    fn thread_id_field() {
777        thread::spawn(|| {
778            let pw = PatternEncoder::new("{I}");
779            let mut buf = vec![];
780            pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
781                .unwrap();
782            assert_eq!(buf, thread_id::get().to_string().as_bytes());
783        })
784        .join()
785        .unwrap();
786    }
787
788    #[test]
789    #[cfg(feature = "simple_writer")]
790    fn process_id() {
791        let pw = PatternEncoder::new("{P}");
792        let mut buf = vec![];
793
794        pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
795            .unwrap();
796
797        assert_eq!(buf, process::id().to_string().as_bytes());
798    }
799
800    #[test]
801    #[cfg(feature = "simple_writer")]
802    fn system_thread_id() {
803        let pw = PatternEncoder::new("{i}");
804        let mut buf = vec![];
805
806        pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
807            .unwrap();
808
809        assert_eq!(buf, thread_id::get().to_string().as_bytes());
810    }
811
812    #[test]
813    #[cfg(feature = "simple_writer")]
814    fn default_okay() {
815        assert!(error_free(&PatternEncoder::default()));
816    }
817
818    #[test]
819    #[cfg(feature = "simple_writer")]
820    fn left_align() {
821        let pw = PatternEncoder::new("{m:~<5.6}");
822
823        let mut buf = vec![];
824        pw.encode(
825            &mut SimpleWriter(&mut buf),
826            &Record::builder().args(format_args!("foo")).build(),
827        )
828        .unwrap();
829        assert_eq!(buf, b"foo~~");
830
831        buf.clear();
832        pw.encode(
833            &mut SimpleWriter(&mut buf),
834            &Record::builder().args(format_args!("foobar!")).build(),
835        )
836        .unwrap();
837        assert_eq!(buf, b"foobar");
838    }
839
840    #[test]
841    #[cfg(feature = "simple_writer")]
842    fn right_align() {
843        let pw = PatternEncoder::new("{m:~>5.6}");
844
845        let mut buf = vec![];
846        pw.encode(
847            &mut SimpleWriter(&mut buf),
848            &Record::builder().args(format_args!("foo")).build(),
849        )
850        .unwrap();
851        assert_eq!(buf, b"~~foo");
852
853        buf.clear();
854        pw.encode(
855            &mut SimpleWriter(&mut buf),
856            &Record::builder().args(format_args!("foobar!")).build(),
857        )
858        .unwrap();
859        assert_eq!(buf, b"foobar");
860    }
861
862    #[test]
863    #[cfg(feature = "simple_writer")]
864    fn left_align_formatter() {
865        let pw = PatternEncoder::new("{({l} {m}):15}");
866
867        let mut buf = vec![];
868        pw.encode(
869            &mut SimpleWriter(&mut buf),
870            &Record::builder()
871                .level(Level::Info)
872                .args(format_args!("foobar!"))
873                .build(),
874        )
875        .unwrap();
876        assert_eq!(buf, b"INFO foobar!   ");
877    }
878
879    #[test]
880    #[cfg(feature = "simple_writer")]
881    fn right_align_formatter() {
882        let pw = PatternEncoder::new("{({l} {m}):>15}");
883
884        let mut buf = vec![];
885        pw.encode(
886            &mut SimpleWriter(&mut buf),
887            &Record::builder()
888                .level(Level::Info)
889                .args(format_args!("foobar!"))
890                .build(),
891        )
892        .unwrap();
893        assert_eq!(buf, b"   INFO foobar!");
894    }
895
896    #[test]
897    fn custom_date_format() {
898        assert!(error_free(&PatternEncoder::new(
899            "{d(%Y-%m-%d %H:%M:%S)} {m}{n}"
900        )));
901    }
902
903    #[test]
904    fn timezones() {
905        assert!(error_free(&PatternEncoder::new("{d(%+)(utc)}")));
906        assert!(error_free(&PatternEncoder::new("{d(%+)(local)}")));
907        assert!(!error_free(&PatternEncoder::new("{d(%+)(foo)}")));
908    }
909
910    #[test]
911    fn unescaped_parens() {
912        assert!(!error_free(&PatternEncoder::new("(hi)")));
913    }
914
915    #[test]
916    #[cfg(feature = "simple_writer")]
917    fn escaped_chars() {
918        let pw = PatternEncoder::new("{{{m}(())}}");
919
920        let mut buf = vec![];
921        pw.encode(
922            &mut SimpleWriter(&mut buf),
923            &Record::builder().args(format_args!("foobar!")).build(),
924        )
925        .unwrap();
926        assert_eq!(buf, b"{foobar!()}");
927    }
928
929    #[test]
930    #[cfg(feature = "simple_writer")]
931    fn quote_braces_with_backslash() {
932        let pw = PatternEncoder::new(r"\{\({l}\)\}\\");
933
934        let mut buf = vec![];
935        pw.encode(
936            &mut SimpleWriter(&mut buf),
937            &Record::builder().level(Level::Info).build(),
938        )
939        .unwrap();
940        assert_eq!(buf, br"{(INFO)}\");
941    }
942
943    #[test]
944    #[cfg(feature = "simple_writer")]
945    fn mdc() {
946        let pw = PatternEncoder::new("{X(user_id)}");
947        log_mdc::insert("user_id", "mdc value");
948
949        let mut buf = vec![];
950        pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
951            .unwrap();
952
953        assert_eq!(buf, b"mdc value");
954    }
955
956    #[test]
957    #[cfg(feature = "simple_writer")]
958    fn mdc_missing_default() {
959        let pw = PatternEncoder::new("{X(user_id)}");
960
961        let mut buf = vec![];
962        pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
963            .unwrap();
964
965        assert_eq!(buf, b"");
966    }
967
968    #[test]
969    #[cfg(feature = "simple_writer")]
970    fn mdc_missing_custom() {
971        let pw = PatternEncoder::new("{X(user_id)(missing value)}");
972
973        let mut buf = vec![];
974        pw.encode(&mut SimpleWriter(&mut buf), &Record::builder().build())
975            .unwrap();
976
977        assert_eq!(buf, b"missing value");
978    }
979}