Ripgrep logger.rs: Code Companion¶
Reference code for the logger.rs lecture. Sections correspond to the lecture document.
Section 1: The Log Crate Facade¶
/*!
Defines a super simple logger that works with the `log` crate.
We don't do anything fancy. We just need basic log levels and the ability to
print to stderr. We therefore avoid bringing in extra dependencies just for
this functionality.
*/
use log::Log;
The facade pattern:
// Library code (doesn't know about Logger):
log::debug!("Processing file: {}", path.display());
log::trace!("Binary detection: {:?}", mode);
// Application code (provides the implementation):
Logger::init()?; // Now debug!/trace! flow to our Logger
Log levels (from lowest to highest priority):
log::trace!("..."); // Most verbose, rarely needed
log::debug!("..."); // Debugging information
log::info!("..."); // General information
log::warn!("..."); // Warnings
log::error!("..."); // Errors
Section 2: The Logger Struct¶
/// The simplest possible logger that logs to stderr.
///
/// This logger does no filtering. Instead, it relies on the `log` crates
/// filtering via its global max_level setting.
#[derive(Debug)]
pub(crate) struct Logger(());
/// A singleton used as the target for an implementation of the `Log` trait.
const LOGGER: &'static Logger = &Logger(());
Zero-sized type (ZST):
// Logger contains () which has size 0
std::mem::size_of::<Logger>() // Returns 0
// No runtime cost — exists only for type system
// The reference &Logger also has a fixed size (pointer)
// but points to "nothing" in a sense
Why the newtype pattern:
// Can't do this:
impl Log for () { ... } // Error: can't impl foreign trait on foreign type
// Can do this:
struct Logger(());
impl Log for Logger { ... } // OK: Logger is our type
Section 3: Initialization¶
impl Logger {
/// Create a new logger that logs to stderr and initialize it as the
/// global logger. If there was a problem setting the logger, then an
/// error is returned.
pub(crate) fn init() -> Result<(), log::SetLoggerError> {
log::set_logger(LOGGER)
}
}
Usage in ripgrep startup:
// In argument processing (simplified):
if args.debug {
log::set_max_level(log::LevelFilter::Debug);
Logger::init()?;
} else if args.trace {
log::set_max_level(log::LevelFilter::Trace);
Logger::init()?;
} else {
log::set_max_level(log::LevelFilter::Off);
// Logger::init() not needed — no logs will be emitted
}
Level filtering:
log::set_max_level(log::LevelFilter::Debug);
// Now: trace! → skipped, debug!/info!/warn!/error! → logged
log::set_max_level(log::LevelFilter::Off);
// Now: all log macros → skipped (no overhead)
Section 4: The Log Trait — enabled¶
impl Log for Logger {
fn enabled(&self, _: &log::Metadata<'_>) -> bool {
// We set the log level via log::set_max_level, so we don't need to
// implement filtering here.
true
}
// ...
}
Why always return true:
Log macro expansion (simplified):
log::debug!("message")
↓
if log::max_level() >= Debug { // First filter (fast)
if logger.enabled(metadata) { // Second filter (we return true)
logger.log(record);
}
}
Since max_level() handles filtering, enabled() just says "yes, proceed"
Section 5: The Log Trait — log¶
fn log(&self, record: &log::Record<'_>) {
match (record.file(), record.line()) {
(Some(file), Some(line)) => {
eprintln_locked!(
"{}|{}|{}:{}: {}",
record.level(),
record.target(),
file,
line,
record.args()
);
}
(Some(file), None) => {
eprintln_locked!(
"{}|{}|{}: {}",
record.level(),
record.target(),
file,
record.args()
);
}
_ => {
eprintln_locked!(
"{}|{}: {}",
record.level(),
record.target(),
record.args()
);
}
}
}
Output format examples:
DEBUG|ripgrep::search|search.rs:42: searching file.txt
TRACE|ignore::walk|walk.rs:128: skipping hidden directory
DEBUG|grep_searcher: mmap enabled for large file
Record fields:
record.level() // DEBUG, TRACE, etc.
record.target() // Usually module path: "ripgrep::search"
record.file() // Source file: Some("search.rs") or None
record.line() // Line number: Some(42) or None
record.args() // The formatted message
Thread-safety via eprintln_locked:
// From messages.rs — ensures debug output doesn't interleave
eprintln_locked!("{}|{}: {}", level, target, message);
// Locks stdout (to coordinate with search output)
// Then locks stderr
// Writes complete message atomically
Section 6: The Log Trait — flush¶
Why empty:
// eprintln_locked! does:
writeln!(stderr, ...)?; // writeln flushes line-buffered streams
drop(stdout); // Releases lock, implicit flush
// No buffering in our logger → nothing to flush
Contrast with buffered logger:
// A buffered logger might do:
fn log(&self, record: &Record) {
self.buffer.push(format!("{}", record.args()));
if self.buffer.len() > 100 {
self.flush();
}
}
fn flush(&self) {
for msg in self.buffer.drain(..) {
eprintln!("{}", msg);
}
}
Section 7: Integration with ripgrep¶
Activation via flags:
rg --debug pattern file.txt
# Output includes: DEBUG|...|search.rs:N: ...
rg --trace pattern file.txt
# Output includes: TRACE|...|... (even more verbose)
RIPGREP_LOG=debug rg pattern file.txt
# Same as --debug, via environment variable
Example log output during search:
DEBUG|ripgrep::flags::hiargs: using 8 threads
DEBUG|ripgrep::search|search.rs:255: file.txt: binary detection: Quit
TRACE|ignore::walk|walk.rs:892: ignoring .git (hidden directory)
TRACE|grep_searcher::searcher: searching with memory map
DEBUG|ripgrep::search|search.rs:347: file.txt: found 3 matches
Log statements in search.rs:
No overhead when disabled:
// When max_level is Off:
log::debug!("expensive: {}", compute_something());
// Expands to roughly:
if log::max_level() >= Debug { // false, so...
// This entire block is skipped
// compute_something() is never called
}
Quick Reference: Log Crate API¶
// Setting up
log::set_max_level(LevelFilter::Debug); // Enable debug and above
log::set_logger(&LOGGER)?; // Register our implementation
// Emitting logs (from anywhere in the codebase)
log::trace!("very verbose: {}", detail);
log::debug!("debugging: {}", info);
log::info!("general info: {}", msg);
log::warn!("warning: {}", issue);
log::error!("error: {}", err);
// Level filters
log::LevelFilter::Off // Nothing
log::LevelFilter::Error // Only errors
log::LevelFilter::Warn // Warn and above
log::LevelFilter::Info // Info and above
log::LevelFilter::Debug // Debug and above
log::LevelFilter::Trace // Everything
Complete Module (72 lines)¶
use log::Log;
#[derive(Debug)]
pub(crate) struct Logger(());
const LOGGER: &'static Logger = &Logger(());
impl Logger {
pub(crate) fn init() -> Result<(), log::SetLoggerError> {
log::set_logger(LOGGER)
}
}
impl Log for Logger {
fn enabled(&self, _: &log::Metadata<'_>) -> bool {
true
}
fn log(&self, record: &log::Record<'_>) {
match (record.file(), record.line()) {
(Some(file), Some(line)) => {
eprintln_locked!(
"{}|{}|{}:{}: {}",
record.level(), record.target(), file, line, record.args()
);
}
(Some(file), None) => {
eprintln_locked!(
"{}|{}|{}: {}",
record.level(), record.target(), file, record.args()
);
}
_ => {
eprintln_locked!(
"{}|{}: {}",
record.level(), record.target(), record.args()
);
}
}
}
fn flush(&self) {}
}
Relationship to Other Modules¶
┌─────────────────────────────────────────────────────────────┐
│ ripgrep binary │
├─────────────────────────────────────────────────────────────┤
│ main.rs │ Calls Logger::init() on --debug/--trace │
│ logger.rs │ Implements Log trait (this file) │
│ messages.rs │ Provides eprintln_locked! │
├─────────────────────────────────────────────────────────────┤
│ Library crates │
├─────────────────────────────────────────────────────────────┤
│ grep-searcher │ log::trace!("mmap decision...") │
│ ignore │ log::debug!("skipping hidden...") │
│ grep-printer │ log::trace!("formatting match...") │
└─────────────────────────────────────────────────────────────┘
Log flow:
Library code → log::debug!() → max_level check → Logger::log() → eprintln_locked!() → stderr