Course contentsShow
← Rust
Lesson 895 of 1,9107. Error Handling PatternsFree lesson

Custom Error Formatters

Implement trait-based formatting to provide uniform error logs across modules.

In this lesson, you will learn how to unify disparate error sources into a consistent, readable log format across your entire project using trait-based formatting.

The Problem: Log Fragmentation

In large applications, different modules often define their own error types. If Module A logs errors as JSON strings and Module B logs them as unstructured text, your log aggregation tools will struggle to parse the information. By decoupling the internal representation of an error from its presentation layer, you ensure your logs remain predictable regardless of which module triggered the fault.

Implementing a Uniform Formatter

Rather than letting every error type manage its own display logic via std::fmt::Display, we define a custom trait that forces a standard output schema.

pub trait LoggableError {
    /// Returns a structured, consistent log message
    fn to_log_format(&self) -> String;
}

// Implement this for your custom error types
impl LoggableError for DatabaseError {
    fn to_log_format(&self) -> String {
        format!("[DB_ERROR] Code: {} | Message: {}", self.code, self.message)
    }
}

By requiring all your internal error types to implement LoggableError, you can create a central logging utility that handles errors polymorphically. Instead of calling println!() or eprintln!() inside your business logic, you pass the error to a standardized sink:

fn report_error(err: &dyn LoggableError) {
    let message = err.to_log_format();
    // Send to centralized logger or observability platform
    tracing::error!(target: "system_logs", "{}", message);
}

Why This Matters

This pattern prevents "log pollution," where inconsistent formatting makes debugging a nightmare. By using a trait, you gain the ability to change the global log format (e.g., switching from raw text to structured JSON or adding a timestamp to every error) in exactly one place, without refactoring the code inside every module that generates an error. It transforms error handling from a module-specific concern into a project-wide infrastructure policy.

Key Takeaway: Use a custom trait to enforce a standardized serialization schema for all internal error types, ensuring your logs remain machine-readable and predictable across module boundaries.