Skip to content

Instantly share code, notes, and snippets.

@clarksun
Forked from nkhitrov/logger.py
Created September 18, 2020 23:19
Show Gist options
  • Select an option

  • Save clarksun/73024bfcbcff9e47cc79e83a2f687a76 to your computer and use it in GitHub Desktop.

Select an option

Save clarksun/73024bfcbcff9e47cc79e83a2f687a76 to your computer and use it in GitHub Desktop.
Configure uvicorn logs with loguru for FastAPI
"""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