Last active
October 15, 2024 04:13
-
-
Save nkhitrov/a3e31cfcc1b19cba8e1b626276148c49 to your computer and use it in GitHub Desktop.
Configure uvicorn logs with loguru for FastAPI
This file contains 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
""" | |
WARNING: dont use loguru, use structlog | |
https://gist.github.com/nkhitrov/38adbb314f0d35371eba4ffb8f27078f | |
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): | |
# 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(): | |
""" | |
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() | |
logging.getLogger("uvicorn").handlers = [intercept_handler] | |
# set logs output, level and format | |
logger.configure( | |
handlers=[{"sink": sys.stdout, "level": logging.DEBUG, "format": format_record}] | |
) |
This file contains 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
""" | |
WARNING: dont use loguru, use structlog | |
https://gist.github.com/nkhitrov/38adbb314f0d35371eba4ffb8f27078f | |
Gist for original issue https://github.com/tiangolo/fastapi/issues/1276#issuecomment-663748916 | |
""" | |
from fastapi import FastAPI | |
from starlette.requests import Request | |
from logger import init_logging | |
app = FastAPI(title="Test Uvicorn Handlers") | |
init_logging() | |
# 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
@nosid91
Check dates.
Sentry sdk ~6 months ago
My comment ~8 months ago
It is your choice. I don't have a goal to convince anyone. I'm just sharing my knowledge and the problems I've discovered.