-
-
Save nkhitrov/a3e31cfcc1b19cba8e1b626276148c49 to your computer and use it in GitHub Desktop.
| """ | |
| 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}] | |
| ) |
| """ | |
| 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 |
FWIW with uvicorn==0.18.2 the above did not print the access logs (i.e. GET /). I needed to add
logging.getLogger("uvicorn.access").handlers = [intercept_handler]
Hi, Im able to get this working. However default uvicorn logs are not being shown. The logs shows only if its explicitly logged to console.
Am i missing anything here ? thanks.
Hello there! I not recommend to use loguru anymore. If you want use custom formatting and other cool features, see this new example with structlog
@nkhitrov what is the reason of not using loguru?
- it can not work with libraries that use default
logginghandlers (sentry-sdk) out the box becauseloguruwrite tosdtoutdirectly (withoutlogging) - when you have error in formatter function,
logurucrashes without any useful info
structlog is more powerful library. you can write your own processors to modify logs and use plugins
- https://docs.sentry.io/platforms/python/integrations/loguru/
- if that's it, you haven't convinced me)
Check dates.
Sentry sdk ~6 months ago
My comment ~8 months ago
- if that's it, you haven't convinced me)
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.
Great work, thanks for sharing!
It still works, thanks for sharing!
perfect solution! 💯 🚀