logging
programming best practices
logger wrapper
software development
code optimization

Logger wrapper best practice

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A logger wrapper can help, but it can also make logging worse if it hides useful features from the underlying framework. The best practice is not to build a giant custom logging system. Build a thin adapter only when it adds something concrete such as context propagation, standardized fields, or simplified application-wide conventions.

When a Wrapper Is Worth It

If your wrapper does nothing except rename info() to logInfo(), it is probably unnecessary. Good wrappers usually solve one of these problems:

  • Adding consistent metadata such as request id, tenant id, or service name.
  • Enforcing structured logging fields.
  • Providing a stable interface across a few internal packages.
  • Centralizing configuration for tests and local development.

The key is that the wrapper should add policy, not just indirection.

Keep the Interface Thin

A wrapper should preserve the important capabilities of the real logger, especially severity levels, exception logging, and structured fields. If you remove those, callers will eventually work around the wrapper or misuse it.

Here is a simple Python example that adds shared context while still delegating to the standard library logger:

python
1import logging
2from typing import Any
3
4
5class AppLogger:
6    def __init__(self, logger: logging.Logger, **base_context: Any):
7        self._logger = logger
8        self._base_context = base_context
9
10    def info(self, message: str, **context: Any) -> None:
11        self._logger.info(message, extra={"context": {**self._base_context, **context}})
12
13    def error(self, message: str, **context: Any) -> None:
14        self._logger.error(message, extra={"context": {**self._base_context, **context}})
15
16    def exception(self, message: str, **context: Any) -> None:
17        self._logger.exception(message, extra={"context": {**self._base_context, **context}})

This wrapper adds value because it merges shared context and still exposes a method that preserves stack traces through exception().

Prefer Composition Over Reinventing Logging

A common mistake is creating a wrapper that invents its own formatting, levels, buffering, or transport. Logging libraries already solve those problems well. Your wrapper should compose with them, not replace them.

For example, configuration should stay in the logging framework:

python
1logging.basicConfig(
2    level=logging.INFO,
3    format="%(asctime)s %(levelname)s %(name)s %(message)s %(context)s",
4)
5
6base_logger = logging.getLogger("checkout")
7logger = AppLogger(base_logger, service="billing")
8
9logger.info("payment started", order_id=1234)

This keeps the wrapper small and makes log routing, filtering, and formatting configurable without touching application code.

Design for Structured Context

Modern systems benefit from logs that machines can parse. Even if your current sink is plain text, designing the wrapper around structured fields helps later migrations to JSON or centralized search systems.

A good wrapper should encourage this:

  • Message for human-readable intent.
  • Key-value context for searchable metadata.
  • Explicit exception logging rather than stringifying errors manually.

That is better than assembling one long formatted string at every call site.

Common Pitfalls

The biggest pitfall is swallowing caller information. Some wrappers log everything from the wrapper class itself, making it harder to see the real source file or line number. If your language or framework supports caller skipping, use it carefully.

Another mistake is losing exception details. If callers must pass str(error) instead of using the framework's exception-aware API, your logs become much less useful during debugging.

It is also common to over-abstract. Teams sometimes build wrappers around the idea that they may switch logging frameworks someday. That switch rarely happens, and the abstraction cost is paid every day. Wrap only the parts that genuinely need shared behavior.

Summary

  • Use a logger wrapper only when it adds real policy such as shared context or structured fields.
  • Keep the wrapper thin and preserve important framework features such as levels and exception logging.
  • Compose with the underlying logging library instead of reinventing configuration and transport.
  • Prefer structured context over manually formatted message strings.
  • Avoid wrappers that hide caller location or strip away useful error information.

Course illustration
Course illustration

All Rights Reserved.