1 //! Formatting for log records. 2 //! 3 //! This module contains a [`Formatter`] that can be used to format log records 4 //! into without needing temporary allocations. Usually you won't need to worry 5 //! about the contents of this module and can use the `Formatter` like an ordinary 6 //! [`Write`]. 7 //! 8 //! # Formatting log records 9 //! 10 //! The format used to print log records can be customised using the [`Builder::format`] 11 //! method. 12 //! Custom formats can apply different color and weight to printed values using 13 //! [`Style`] builders. 14 //! 15 //! ``` 16 //! use std::io::Write; 17 //! 18 //! let mut builder = env_logger::Builder::new(); 19 //! 20 //! builder.format(|buf, record| { 21 //! writeln!(buf, "{}: {}", 22 //! record.level(), 23 //! record.args()) 24 //! }); 25 //! ``` 26 //! 27 //! [`Formatter`]: struct.Formatter.html 28 //! [`Style`]: struct.Style.html 29 //! [`Builder::format`]: ../struct.Builder.html#method.format 30 //! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html 31 32 use std::cell::RefCell; 33 use std::fmt::Display; 34 use std::io::prelude::*; 35 use std::rc::Rc; 36 use std::{fmt, io, mem}; 37 38 #[cfg(feature = "color")] 39 use log::Level; 40 use log::Record; 41 42 #[cfg(feature = "humantime")] 43 mod humantime; 44 pub(crate) mod writer; 45 46 #[cfg(feature = "color")] 47 mod style; 48 #[cfg(feature = "color")] 49 pub use style::{Color, Style, StyledValue}; 50 51 #[cfg(feature = "humantime")] 52 pub use self::humantime::Timestamp; 53 pub use self::writer::glob::*; 54 55 use self::writer::{Buffer, Writer}; 56 57 pub(crate) mod glob { 58 pub use super::{Target, TimestampPrecision, WriteStyle}; 59 } 60 61 /// Formatting precision of timestamps. 62 /// 63 /// Seconds give precision of full seconds, milliseconds give thousands of a 64 /// second (3 decimal digits), microseconds are millionth of a second (6 decimal 65 /// digits) and nanoseconds are billionth of a second (9 decimal digits). 66 #[derive(Copy, Clone, Debug)] 67 pub enum TimestampPrecision { 68 /// Full second precision (0 decimal digits) 69 Seconds, 70 /// Millisecond precision (3 decimal digits) 71 Millis, 72 /// Microsecond precision (6 decimal digits) 73 Micros, 74 /// Nanosecond precision (9 decimal digits) 75 Nanos, 76 } 77 78 /// The default timestamp precision is seconds. 79 impl Default for TimestampPrecision { default() -> Self80 fn default() -> Self { 81 TimestampPrecision::Seconds 82 } 83 } 84 85 /// A formatter to write logs into. 86 /// 87 /// `Formatter` implements the standard [`Write`] trait for writing log records. 88 /// It also supports terminal colors, through the [`style`] method. 89 /// 90 /// # Examples 91 /// 92 /// Use the [`writeln`] macro to format a log record. 93 /// An instance of a `Formatter` is passed to an `env_logger` format as `buf`: 94 /// 95 /// ``` 96 /// use std::io::Write; 97 /// 98 /// let mut builder = env_logger::Builder::new(); 99 /// 100 /// builder.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())); 101 /// ``` 102 /// 103 /// [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html 104 /// [`writeln`]: https://doc.rust-lang.org/stable/std/macro.writeln.html 105 /// [`style`]: #method.style 106 pub struct Formatter { 107 buf: Rc<RefCell<Buffer>>, 108 write_style: WriteStyle, 109 } 110 111 impl Formatter { new(writer: &Writer) -> Self112 pub(crate) fn new(writer: &Writer) -> Self { 113 Formatter { 114 buf: Rc::new(RefCell::new(writer.buffer())), 115 write_style: writer.write_style(), 116 } 117 } 118 write_style(&self) -> WriteStyle119 pub(crate) fn write_style(&self) -> WriteStyle { 120 self.write_style 121 } 122 print(&self, writer: &Writer) -> io::Result<()>123 pub(crate) fn print(&self, writer: &Writer) -> io::Result<()> { 124 writer.print(&self.buf.borrow()) 125 } 126 clear(&mut self)127 pub(crate) fn clear(&mut self) { 128 self.buf.borrow_mut().clear() 129 } 130 } 131 132 #[cfg(feature = "color")] 133 impl Formatter { 134 /// Begin a new [`Style`]. 135 /// 136 /// # Examples 137 /// 138 /// Create a bold, red colored style and use it to print the log level: 139 /// 140 /// ``` 141 /// use std::io::Write; 142 /// use env_logger::fmt::Color; 143 /// 144 /// let mut builder = env_logger::Builder::new(); 145 /// 146 /// builder.format(|buf, record| { 147 /// let mut level_style = buf.style(); 148 /// 149 /// level_style.set_color(Color::Red).set_bold(true); 150 /// 151 /// writeln!(buf, "{}: {}", 152 /// level_style.value(record.level()), 153 /// record.args()) 154 /// }); 155 /// ``` 156 /// 157 /// [`Style`]: struct.Style.html style(&self) -> Style158 pub fn style(&self) -> Style { 159 Style { 160 buf: self.buf.clone(), 161 spec: termcolor::ColorSpec::new(), 162 } 163 } 164 165 /// Get the default [`Style`] for the given level. 166 /// 167 /// The style can be used to print other values besides the level. default_level_style(&self, level: Level) -> Style168 pub fn default_level_style(&self, level: Level) -> Style { 169 let mut level_style = self.style(); 170 match level { 171 Level::Trace => level_style.set_color(Color::Cyan), 172 Level::Debug => level_style.set_color(Color::Blue), 173 Level::Info => level_style.set_color(Color::Green), 174 Level::Warn => level_style.set_color(Color::Yellow), 175 Level::Error => level_style.set_color(Color::Red).set_bold(true), 176 }; 177 level_style 178 } 179 180 /// Get a printable [`Style`] for the given level. 181 /// 182 /// The style can only be used to print the level. default_styled_level(&self, level: Level) -> StyledValue<'static, Level>183 pub fn default_styled_level(&self, level: Level) -> StyledValue<'static, Level> { 184 self.default_level_style(level).into_value(level) 185 } 186 } 187 188 impl Write for Formatter { write(&mut self, buf: &[u8]) -> io::Result<usize>189 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { 190 self.buf.borrow_mut().write(buf) 191 } 192 flush(&mut self) -> io::Result<()>193 fn flush(&mut self) -> io::Result<()> { 194 self.buf.borrow_mut().flush() 195 } 196 } 197 198 impl fmt::Debug for Formatter { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result199 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 200 f.debug_struct("Formatter").finish() 201 } 202 } 203 204 pub(crate) type FormatFn = Box<dyn Fn(&mut Formatter, &Record) -> io::Result<()> + Sync + Send>; 205 206 pub(crate) struct Builder { 207 pub format_timestamp: Option<TimestampPrecision>, 208 pub format_module_path: bool, 209 pub format_target: bool, 210 pub format_level: bool, 211 pub format_indent: Option<usize>, 212 pub custom_format: Option<FormatFn>, 213 pub format_suffix: &'static str, 214 built: bool, 215 } 216 217 impl Builder { 218 /// Convert the format into a callable function. 219 /// 220 /// If the `custom_format` is `Some`, then any `default_format` switches are ignored. 221 /// If the `custom_format` is `None`, then a default format is returned. 222 /// Any `default_format` switches set to `false` won't be written by the format. build(&mut self) -> FormatFn223 pub fn build(&mut self) -> FormatFn { 224 assert!(!self.built, "attempt to re-use consumed builder"); 225 226 let built = mem::replace( 227 self, 228 Builder { 229 built: true, 230 ..Default::default() 231 }, 232 ); 233 234 if let Some(fmt) = built.custom_format { 235 fmt 236 } else { 237 Box::new(move |buf, record| { 238 let fmt = DefaultFormat { 239 timestamp: built.format_timestamp, 240 module_path: built.format_module_path, 241 target: built.format_target, 242 level: built.format_level, 243 written_header_value: false, 244 indent: built.format_indent, 245 suffix: built.format_suffix, 246 buf, 247 }; 248 249 fmt.write(record) 250 }) 251 } 252 } 253 } 254 255 impl Default for Builder { default() -> Self256 fn default() -> Self { 257 Builder { 258 format_timestamp: Some(Default::default()), 259 format_module_path: false, 260 format_target: true, 261 format_level: true, 262 format_indent: Some(4), 263 custom_format: None, 264 format_suffix: "\n", 265 built: false, 266 } 267 } 268 } 269 270 #[cfg(feature = "color")] 271 type SubtleStyle = StyledValue<'static, &'static str>; 272 #[cfg(not(feature = "color"))] 273 type SubtleStyle = &'static str; 274 275 /// The default format. 276 /// 277 /// This format needs to work with any combination of crate features. 278 struct DefaultFormat<'a> { 279 timestamp: Option<TimestampPrecision>, 280 module_path: bool, 281 target: bool, 282 level: bool, 283 written_header_value: bool, 284 indent: Option<usize>, 285 buf: &'a mut Formatter, 286 suffix: &'a str, 287 } 288 289 impl<'a> DefaultFormat<'a> { write(mut self, record: &Record) -> io::Result<()>290 fn write(mut self, record: &Record) -> io::Result<()> { 291 self.write_timestamp()?; 292 self.write_level(record)?; 293 self.write_module_path(record)?; 294 self.write_target(record)?; 295 self.finish_header()?; 296 297 self.write_args(record) 298 } 299 subtle_style(&self, text: &'static str) -> SubtleStyle300 fn subtle_style(&self, text: &'static str) -> SubtleStyle { 301 #[cfg(feature = "color")] 302 { 303 self.buf 304 .style() 305 .set_color(Color::Black) 306 .set_intense(true) 307 .clone() 308 .into_value(text) 309 } 310 #[cfg(not(feature = "color"))] 311 { 312 text 313 } 314 } 315 write_header_value<T>(&mut self, value: T) -> io::Result<()> where T: Display,316 fn write_header_value<T>(&mut self, value: T) -> io::Result<()> 317 where 318 T: Display, 319 { 320 if !self.written_header_value { 321 self.written_header_value = true; 322 323 let open_brace = self.subtle_style("["); 324 write!(self.buf, "{}{}", open_brace, value) 325 } else { 326 write!(self.buf, " {}", value) 327 } 328 } 329 write_level(&mut self, record: &Record) -> io::Result<()>330 fn write_level(&mut self, record: &Record) -> io::Result<()> { 331 if !self.level { 332 return Ok(()); 333 } 334 335 let level = { 336 #[cfg(feature = "color")] 337 { 338 self.buf.default_styled_level(record.level()) 339 } 340 #[cfg(not(feature = "color"))] 341 { 342 record.level() 343 } 344 }; 345 346 self.write_header_value(format_args!("{:<5}", level)) 347 } 348 write_timestamp(&mut self) -> io::Result<()>349 fn write_timestamp(&mut self) -> io::Result<()> { 350 #[cfg(feature = "humantime")] 351 { 352 use self::TimestampPrecision::*; 353 let ts = match self.timestamp { 354 None => return Ok(()), 355 Some(Seconds) => self.buf.timestamp_seconds(), 356 Some(Millis) => self.buf.timestamp_millis(), 357 Some(Micros) => self.buf.timestamp_micros(), 358 Some(Nanos) => self.buf.timestamp_nanos(), 359 }; 360 361 self.write_header_value(ts) 362 } 363 #[cfg(not(feature = "humantime"))] 364 { 365 // Trick the compiler to think we have used self.timestamp 366 // Workaround for "field is never used: `timestamp`" compiler nag. 367 let _ = self.timestamp; 368 Ok(()) 369 } 370 } 371 write_module_path(&mut self, record: &Record) -> io::Result<()>372 fn write_module_path(&mut self, record: &Record) -> io::Result<()> { 373 if !self.module_path { 374 return Ok(()); 375 } 376 377 if let Some(module_path) = record.module_path() { 378 self.write_header_value(module_path) 379 } else { 380 Ok(()) 381 } 382 } 383 write_target(&mut self, record: &Record) -> io::Result<()>384 fn write_target(&mut self, record: &Record) -> io::Result<()> { 385 if !self.target { 386 return Ok(()); 387 } 388 389 match record.target() { 390 "" => Ok(()), 391 target => self.write_header_value(target), 392 } 393 } 394 finish_header(&mut self) -> io::Result<()>395 fn finish_header(&mut self) -> io::Result<()> { 396 if self.written_header_value { 397 let close_brace = self.subtle_style("]"); 398 write!(self.buf, "{} ", close_brace) 399 } else { 400 Ok(()) 401 } 402 } 403 write_args(&mut self, record: &Record) -> io::Result<()>404 fn write_args(&mut self, record: &Record) -> io::Result<()> { 405 match self.indent { 406 // Fast path for no indentation 407 None => write!(self.buf, "{}{}", record.args(), self.suffix), 408 409 Some(indent_count) => { 410 // Create a wrapper around the buffer only if we have to actually indent the message 411 412 struct IndentWrapper<'a, 'b: 'a> { 413 fmt: &'a mut DefaultFormat<'b>, 414 indent_count: usize, 415 } 416 417 impl<'a, 'b> Write for IndentWrapper<'a, 'b> { 418 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { 419 let mut first = true; 420 for chunk in buf.split(|&x| x == b'\n') { 421 if !first { 422 write!( 423 self.fmt.buf, 424 "{}{:width$}", 425 self.fmt.suffix, 426 "", 427 width = self.indent_count 428 )?; 429 } 430 self.fmt.buf.write_all(chunk)?; 431 first = false; 432 } 433 434 Ok(buf.len()) 435 } 436 437 fn flush(&mut self) -> io::Result<()> { 438 self.fmt.buf.flush() 439 } 440 } 441 442 // The explicit scope here is just to make older versions of Rust happy 443 { 444 let mut wrapper = IndentWrapper { 445 fmt: self, 446 indent_count, 447 }; 448 write!(wrapper, "{}", record.args())?; 449 } 450 451 write!(self.buf, "{}", self.suffix)?; 452 453 Ok(()) 454 } 455 } 456 } 457 } 458 459 #[cfg(test)] 460 mod tests { 461 use super::*; 462 463 use log::{Level, Record}; 464 write_record(record: Record, fmt: DefaultFormat) -> String465 fn write_record(record: Record, fmt: DefaultFormat) -> String { 466 let buf = fmt.buf.buf.clone(); 467 468 fmt.write(&record).expect("failed to write record"); 469 470 let buf = buf.borrow(); 471 String::from_utf8(buf.as_bytes().to_vec()).expect("failed to read record") 472 } 473 write_target(target: &str, fmt: DefaultFormat) -> String474 fn write_target(target: &str, fmt: DefaultFormat) -> String { 475 write_record( 476 Record::builder() 477 .args(format_args!("log\nmessage")) 478 .level(Level::Info) 479 .file(Some("test.rs")) 480 .line(Some(144)) 481 .module_path(Some("test::path")) 482 .target(target) 483 .build(), 484 fmt, 485 ) 486 } 487 write(fmt: DefaultFormat) -> String488 fn write(fmt: DefaultFormat) -> String { 489 write_target("", fmt) 490 } 491 492 #[test] format_with_header()493 fn format_with_header() { 494 let writer = writer::Builder::new() 495 .write_style(WriteStyle::Never) 496 .build(); 497 498 let mut f = Formatter::new(&writer); 499 500 let written = write(DefaultFormat { 501 timestamp: None, 502 module_path: true, 503 target: false, 504 level: true, 505 written_header_value: false, 506 indent: None, 507 suffix: "\n", 508 buf: &mut f, 509 }); 510 511 assert_eq!("[INFO test::path] log\nmessage\n", written); 512 } 513 514 #[test] format_no_header()515 fn format_no_header() { 516 let writer = writer::Builder::new() 517 .write_style(WriteStyle::Never) 518 .build(); 519 520 let mut f = Formatter::new(&writer); 521 522 let written = write(DefaultFormat { 523 timestamp: None, 524 module_path: false, 525 target: false, 526 level: false, 527 written_header_value: false, 528 indent: None, 529 suffix: "\n", 530 buf: &mut f, 531 }); 532 533 assert_eq!("log\nmessage\n", written); 534 } 535 536 #[test] format_indent_spaces()537 fn format_indent_spaces() { 538 let writer = writer::Builder::new() 539 .write_style(WriteStyle::Never) 540 .build(); 541 542 let mut f = Formatter::new(&writer); 543 544 let written = write(DefaultFormat { 545 timestamp: None, 546 module_path: true, 547 target: false, 548 level: true, 549 written_header_value: false, 550 indent: Some(4), 551 suffix: "\n", 552 buf: &mut f, 553 }); 554 555 assert_eq!("[INFO test::path] log\n message\n", written); 556 } 557 558 #[test] format_indent_zero_spaces()559 fn format_indent_zero_spaces() { 560 let writer = writer::Builder::new() 561 .write_style(WriteStyle::Never) 562 .build(); 563 564 let mut f = Formatter::new(&writer); 565 566 let written = write(DefaultFormat { 567 timestamp: None, 568 module_path: true, 569 target: false, 570 level: true, 571 written_header_value: false, 572 indent: Some(0), 573 suffix: "\n", 574 buf: &mut f, 575 }); 576 577 assert_eq!("[INFO test::path] log\nmessage\n", written); 578 } 579 580 #[test] format_indent_spaces_no_header()581 fn format_indent_spaces_no_header() { 582 let writer = writer::Builder::new() 583 .write_style(WriteStyle::Never) 584 .build(); 585 586 let mut f = Formatter::new(&writer); 587 588 let written = write(DefaultFormat { 589 timestamp: None, 590 module_path: false, 591 target: false, 592 level: false, 593 written_header_value: false, 594 indent: Some(4), 595 suffix: "\n", 596 buf: &mut f, 597 }); 598 599 assert_eq!("log\n message\n", written); 600 } 601 602 #[test] format_suffix()603 fn format_suffix() { 604 let writer = writer::Builder::new() 605 .write_style(WriteStyle::Never) 606 .build(); 607 608 let mut f = Formatter::new(&writer); 609 610 let written = write(DefaultFormat { 611 timestamp: None, 612 module_path: false, 613 target: false, 614 level: false, 615 written_header_value: false, 616 indent: None, 617 suffix: "\n\n", 618 buf: &mut f, 619 }); 620 621 assert_eq!("log\nmessage\n\n", written); 622 } 623 624 #[test] format_suffix_with_indent()625 fn format_suffix_with_indent() { 626 let writer = writer::Builder::new() 627 .write_style(WriteStyle::Never) 628 .build(); 629 630 let mut f = Formatter::new(&writer); 631 632 let written = write(DefaultFormat { 633 timestamp: None, 634 module_path: false, 635 target: false, 636 level: false, 637 written_header_value: false, 638 indent: Some(4), 639 suffix: "\n\n", 640 buf: &mut f, 641 }); 642 643 assert_eq!("log\n\n message\n\n", written); 644 } 645 646 #[test] format_target()647 fn format_target() { 648 let writer = writer::Builder::new() 649 .write_style(WriteStyle::Never) 650 .build(); 651 652 let mut f = Formatter::new(&writer); 653 654 let written = write_target( 655 "target", 656 DefaultFormat { 657 timestamp: None, 658 module_path: true, 659 target: true, 660 level: true, 661 written_header_value: false, 662 indent: None, 663 suffix: "\n", 664 buf: &mut f, 665 }, 666 ); 667 668 assert_eq!("[INFO test::path target] log\nmessage\n", written); 669 } 670 671 #[test] format_empty_target()672 fn format_empty_target() { 673 let writer = writer::Builder::new() 674 .write_style(WriteStyle::Never) 675 .build(); 676 677 let mut f = Formatter::new(&writer); 678 679 let written = write(DefaultFormat { 680 timestamp: None, 681 module_path: true, 682 target: true, 683 level: true, 684 written_header_value: false, 685 indent: None, 686 suffix: "\n", 687 buf: &mut f, 688 }); 689 690 assert_eq!("[INFO test::path] log\nmessage\n", written); 691 } 692 693 #[test] format_no_target()694 fn format_no_target() { 695 let writer = writer::Builder::new() 696 .write_style(WriteStyle::Never) 697 .build(); 698 699 let mut f = Formatter::new(&writer); 700 701 let written = write_target( 702 "target", 703 DefaultFormat { 704 timestamp: None, 705 module_path: true, 706 target: false, 707 level: true, 708 written_header_value: false, 709 indent: None, 710 suffix: "\n", 711 buf: &mut f, 712 }, 713 ); 714 715 assert_eq!("[INFO test::path] log\nmessage\n", written); 716 } 717 } 718