-
-
Save clarksun/73024bfcbcff9e47cc79e83a2f687a76 to your computer and use it in GitHub Desktop.
Configure uvicorn logs with loguru for FastAPI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """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<level>{extra[payload]}</level>" | |
| 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment