"""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{extra[payload]}" # 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}])