"""Gist for issue https://github.com/tiangolo/fastapi/issues/1276#issuecomment-663748916""" import logging import sys from pprint import pformat from fastapi import FastAPI from loguru import logger from loguru._defaults import LOGURU_FORMAT from starlette.requests import Request # handler definition 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): # 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__: frame = frame.f_back 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}] >>> 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=88 ) format_string += "\n{extra[payload]}" format_string += "{exception}\n" return format_string def init_logging(custom_handler: logging.Handler): """Replaces logging handlers with a handler for using the custom handler.""" # NOTE: # to get all loggers # >>> loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict] # disable handlers for specific uvicorn loggers # to redirect their output to the default uvicorn logger for name in ("uvicorn.error", "uvicorn.access"): logging.getLogger(name).handlers = [] # change handler for default uvicorn logger logging.getLogger("uvicorn").handlers = [custom_handler] # set logs output, level and format logger.configure( handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}] ) ### main.py app = FastAPI(title="Logger Handler", debug=True) # 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. intercept_handler = InterceptHandler() init_logging(intercept_handler) # view.py @app.get("/") def index(request: Request) -> None: logger.info("loguru info log") logging.info("logging info log") logging.getLogger("fastapi").debug("fatapi info log") logger.bind(payload=dict(request.query_params)).debug("params with formating") return None