Logging¶
Sayer provides a lightweight but powerful logging system that supports:
- Lazy and safe logger initialization
- Pluggable logging backends via
LoggerProtocol
- Configuration using
Settings
- Rich or standard terminal output
- Thread-safe logger proxy
This document covers how Sayer handles logging internally and how you can customize it.
π§ Core Concepts¶
Sayer uses a proxy-based design for logging:
- You access
sayer.logging.logger
anywhere in your app - Internally, this is a
LoggerProxy
β a thread-safe wrapper - At runtime, the proxy is bound to a concrete logger (like
StandardLoggingConfig
) usingbind_logger(...)
- Logging is controlled through the
settings.logging_level
π§ How to Use It¶
Basic Usage¶
from sayer.logging import logger
logger.info("This is an info message")
logger.warning("Watch out!")
logger.debug("Debugging details")
No setup required if your app uses Sayer()
or sayer.core.client.run()
, since logging is automatically configured.
π Logging Setup Flow¶
Logging is initialized through:
from sayer.logging import setup_logging
setup_logging(settings)
You normally donβt need to call this manually β Sayer handles it on startup.
π§ Configuration via Settings¶
Logging behavior is configured using the active settings object.
Key fields:
Field | Type | Description |
---|---|---|
logging_level |
str |
One of: "DEBUG" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" |
is_logging_setup |
bool |
Prevents double-initialization |
logging_config |
property | Returns a LoggingConfig instance |
π¨ Output Styles¶
Sayer defaults to using Rich for colorful, formatted terminal logs.
Example:
[INFO] Starting app
[ERROR] Something went wrong
You can override the logging backend by setting a custom LoggingConfig
:
from sayer.logging import logger
from sayer.core.logging import StandardLoggingConfig
settings.logging_config = StandardLoggingConfig(level="DEBUG")
logger.info("Using custom backend")
π§ͺ Testing Logging¶
In test mode, you can mock or replace the logger entirely:
from sayer.logging import logger
logger.bind_logger(None) # Disable
Or inject a test-specific logger that captures logs for assertions.
π¦ LoggingProtocol¶
All loggers must implement LoggerProtocol
, which includes:
class LoggerProtocol(Protocol):
def info(self, msg: str): ...
def debug(self, msg: str): ...
def warning(self, msg: str): ...
def error(self, msg: str): ...
def critical(self, msg: str): ...
You can build and bind your own structured logger like Loguru, structlog, etc.
π§° Recap¶
Feature | Supported |
---|---|
Proxy logger access | β |
Rich logging output | β |
Standard fallback | β |
Pluggable config | β |
Settings-integrated | β |
Thread-safe access | β |
Logging in Sayer is ready to use, safe by default, and fully extensible.