I wanted to override the structlog logger for the whole application, by doing this:
import enum
from collections.abc import Iterable
import structlog
from structlog.typing import Processor
from typing_extensions import override
class WarningCode(enum.Enum):
...
class WrappedLogger(structlog.stdlib.BoundLogger):
@override
def warning(self, event, code: WarningCode, **kw): # type: ignore[override]
# i.e. make code required here, for tracking in DataDog
return self._proxy_to_logger('warning', event, code=code, **kw)
def trace(self, event, *args, **kw):
# to be replaced in future by debug() call
return self._proxy_to_logger('info', event, *args, **kw)
def debug_sensitive(self, event, **kw):
"""
Log sensitive debug information (such as containing PII).
"""
# will implement some mechanism for not logging sensitive information certain environments in the future
return self._proxy_to_logger('debug', event, **kw)
def configure_structlog(processors: Iterable[Processor]) -> None:
return structlog.configure(
processors=processors,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
wrapper_class=WrappedLogger,
)
Then I use it in my Django application config like this:
_STRUCTLOG_PROCESSORS: list[Processor] = [
*_SHARED_PROCESSORS,
dev_formatter,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
]
configure_structlog(
processors=_STRUCTLOG_PROCESSORS, # type: ignore
)
In application code I instantiate the logger by doing get_logger():
from structlog import get_logger
logger = get_logger(__name__)
However, logger here is of type Any. And if someone forgets that code parameter is required, or that trace function is called trace and not trace_log or something, the application will fail in runtime, which I want to avoid.
How can I redefine get_logger return type so that it returns my WrappedLogger instead?
I've tried doing mypy stubs like this:
mypy.ini
[mypy]
mypy_path = stubs
and in stubs do this structure:
stubs/
structlog/
__init__.py # re-define ONLY the get_logger() function here.
But this results in a lot of mypy errors, which tell me that:
admin_project/settings_dev.py:22: error: Module has no attribute "ProcessorFormatter" [attr-defined]
admin_project/settings_dev.py:35: error: Module has no attribute "ProcessorFormatter" [attr-defined]
admin_project/settings_dev.py:37: error: Module has no attribute "ProcessorFormatter" [attr-defined]
admin_project/settings_dev.py:38: error: Module has no attribute "dev" [attr-defined]
How can I redefine only the get_logger part of the structlog exports? Or how can I "extend" the existing stubs, without copying all of them into my codebase?
Is there a better practice of adding new methods / requirements to logger calls in structlog?
structlog.get_loggeris annotated with a return type ofAnyprobably to specifically allow you to explicitly annotate the assigned type. Remove the whole stubs idea and just dologger: WrappedLogger = get_logger(__name__).