Add logging with loguru
This commit is contained in:
89
discord_rss_bot/logger.py
Normal file
89
discord_rss_bot/logger.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""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}])
|
Reference in New Issue
Block a user