90 lines
3.5 KiB
Python
90 lines
3.5 KiB
Python
"""Configure handlers and formats for application loggers."""
|
|
import logging
|
|
import sys
|
|
from pprint import pformat
|
|
|
|
# if you dont like imports of private modules
|
|
# you can move it to typing.py module
|
|
from loguru import logger
|
|
from loguru._defaults import LOGURU_FORMAT
|
|
|
|
|
|
class InterceptHandler(logging.Handler):
|
|
"""
|
|
Default handler from examples in loguru documentaion.
|
|
See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
|
|
"""
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
# Get corresponding Loguru level if it exists
|
|
try:
|
|
level = logger.level(record.levelname).name
|
|
except ValueError:
|
|
level = record.levelno
|
|
|
|
# Find caller from where originated the logged message
|
|
frame, depth = logging.currentframe(), 2
|
|
while frame.f_code.co_filename == logging.__file__: # type: ignore
|
|
frame = frame.f_back # type: ignore
|
|
depth += 1
|
|
|
|
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
|
|
|
|
|
|
def format_record(record: dict) -> str:
|
|
"""
|
|
Custom format for loguru loggers.
|
|
Uses pformat for log any data like request/response body during debug.
|
|
Works with logging if loguru handler it.
|
|
|
|
Example:
|
|
>>> payload = [{"users":[{"name": "Nick", "age": 87, "is_active": True}, {"name": "Alex", "age": 27, "is_active": True}], "count": 2}] # noqa: E501
|
|
>>> logger.bind(payload=).debug("users payload")
|
|
>>> [ { 'count': 2,
|
|
>>> 'users': [ {'age': 87, 'is_active': True, 'name': 'Nick'},
|
|
>>> {'age': 27, 'is_active': True, 'name': 'Alex'}]}]
|
|
"""
|
|
|
|
format_string = LOGURU_FORMAT
|
|
if record["extra"].get("payload") is not None:
|
|
record["extra"]["payload"] = pformat(record["extra"]["payload"], indent=4, compact=True, width=120)
|
|
format_string += "\n<level>{extra[payload]}</level>" # type: ignore
|
|
|
|
format_string += "{exception}\n" # type: ignore
|
|
return format_string
|
|
|
|
|
|
def init_logging() -> None:
|
|
"""
|
|
Replaces logging handlers with a handler for using the custom handler.
|
|
|
|
WARNING!
|
|
if you call the init_logging in startup event function,
|
|
then the first logs before the application start will be in the old format
|
|
|
|
>>> app.add_event_handler("startup", init_logging)
|
|
stdout:
|
|
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
|
INFO: Started reloader process [11528] using statreload
|
|
INFO: Started server process [6036]
|
|
INFO: Waiting for application startup.
|
|
2020-07-25 02:19:21.357 | INFO | uvicorn.lifespan.on:startup:34 - Application startup complete.
|
|
|
|
"""
|
|
|
|
# disable handlers for specific uvicorn loggers
|
|
# to redirect their output to the default uvicorn logger
|
|
# works with uvicorn==0.11.6
|
|
loggers = (logging.getLogger(name) for name in logging.root.manager.loggerDict if name.startswith("uvicorn."))
|
|
for uvicorn_logger in loggers:
|
|
uvicorn_logger.handlers = []
|
|
|
|
# change handler for default uvicorn logger
|
|
intercept_handler: InterceptHandler = InterceptHandler()
|
|
logging.getLogger("uvicorn").handlers = [intercept_handler]
|
|
logging.getLogger("reader").handlers = [intercept_handler]
|
|
logging.getLogger("apscheduler.executors.default").handlers = [intercept_handler]
|
|
|
|
# set logs output, level and format
|
|
logger.configure(handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}])
|