One of my favorite features of Rich is the LogHandler with Rich Traceback Support. Rich Traceback offers a robust and easy-to-read traceback that has made developing applications easier and faster. Check out Will's blog post to see some examples.
From the Rich Creator Will McGugan
There is highlighting to help pick out filename, line, and function, etc. There's also a snippet of code for each stack frame, with line numbers and syntax highlighting. It's configurable, but I find that 7 lines of code are enough to make it relatable to the file in my editor, and give me a better understanding of the context that lead to the exception.
It's clear that Rich is a worthwhile dependency to consider for development, let's take a look at how we can integrate its logging and traceback behavior into FastAPI.
TLDR: Skip to the end for the finale code snippet
Step by Step
First, we're going to create a dataclass that will hold our logger config. While not necessary, I find having an object that contains configurations value to be a useful pattern.
Next, we'll need to create a function that will either install rich as the handler or use the production log configuration.
Finally, you'll need to call the function and pass those values into the logger configuration.
Disabling Uvicorn Logger
Updated Aug 10, 2021:
You may also need to override the logger for Uvicorn.
def main(): uvicorn.run( "app:app", host="0.0.0.0", log_level="debug", use_colors=True, log_config=None, # See Here )
import logging import sys from dataclasses import dataclass from functools import lru_cache from .config import settings, DATA_DIR LOGGER_FILE = DATA_DIR.joinpath("mealie.log") DATE_FORMAT = "%d-%b-%y %H:%M:%S" LOGGER_FORMAT = "%(levelname)s: %(asctime)s \t%(message)s" LOGGER_HANDLER = None @dataclass class LoggerConfig: handlers: list format: str date_format: str logger_file: str level: str = logging.INFO @lru_cache def get_logger_config(): if not settings.PRODUCTION: from rich.logging import RichHandler return LoggerConfig( handlers=[RichHandler(rich_tracebacks=True)], format=None, date_format=None, logger_file=None, ) output_file_handler = logging.FileHandler(LOGGER_FILE) handler_format = logging.Formatter(LOGGER_FORMAT, datefmt=DATE_FORMAT) output_file_handler.setFormatter(handler_format) # Stdout stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(handler_format) return LoggerConfig( handlers=[output_file_handler, stdout_handler], format="%(levelname)s: %(asctime)s \t%(message)s", date_format="%d-%b-%y %H:%M:%S", logger_file=LOGGER_FILE, ) logger_config = get_logger_config() logging.basicConfig( level=logger_config.level, format=logger_config.format, datefmt=logger_config.date_format, handlers=logger_config.handlers, )