An application crashing in production and users not being sure what to do next. This happened to me last year on an application I was working on. Now, as the programmer, you have to run to the terminal to check for the error that has occurred so you are able to debug. This wasn't a pleasant experience until a senior developer introduced me to catching and logging errors. I was familiar with the concepts but I hadn't applied them in real life. After applying them, my programming life got a little smoother. Here are the benefits of logging and catching errors in your code:
Faster Debugging
Instead of guessing what went wrong, logs tell you what happened, when it happened, where it happened, and who triggered it.
Better Error Visibility in Production
In production, you aren't able to print errors or attach a debugger. Logs are your only window into production environment and this is especially critical for remote servers and api's consumed by external clients.
Graceful Failure (Better UX)
Catching errors allow you to return meaningful HTTP responses, avoid crashing the entire app and keeps services running even when something fails.
Security and Auditing
Logs help you detect suspicious behaviour, track who accessed what, and audit sensitive operations.
Here is a practical approach to logging and catching errors in Fastapi using an e-commerce application as the example
This is the folder structure that I am using:
In your schemas.py define your schemas:
from pydantic import BaseModel
from enum import Enum
class Status(str, Enum):
AVAILABLE = 'AVAILABLE'
UNAVAILABLE = 'UNAVAILABLE'
class ProductBase(BaseModel):
name: str
description: str
price: float
status: Status
class ProductCreate(ProductBase):
pass
class ProductUpdate(BaseModel):
name: str | None = None
description: str | None = None
price: float | None = None
status: Status | None = None
class Product(ProductBase):
id: int
In your main.py you need to initialise the Fastapi app and set up all logs to be written to the app.log file. You then set up the products database. In this case we will be using a list as the database. Then lastly, you will create a helper function which retrieves a particular product given the id of that product.
from fastapi import FastAPI, HTTPException, status, Response
import logging
from app.schemas import Product, ProductCreate, ProductUpdate
app = FastAPI()
logger = logging.getLogger(__name__)
logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
products = []
def fetch_product(id:int):
for product in products:
if product['id'] == id:
return product
return None
Still in the main.py file, create an endpoint for retrieving all products and retrieving a particular product:
@app.get('/', response_model=list[Product])
def get_products():
try:
return products
except Exception as e:
logger.error(f'An unexpected error occurred while getting products, {str(e)}')
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='An unexpected error occurred while getting products')
@app.get('/{id}', response_model=Product)
def get_product(id:int):
try:
product = fetch_product(id)
if not product:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Product not found')
return product
except Exception as e:
logger.error(f'An unexpected error occurred while getting the product, {str(e)}')
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='An unexpexted error occured while getting the product')
Notice how these endpoints are structured. get_products returns the list of products we created earlier. But we have put this code in a try block such that if the code crashes, lets say "products" is not defined, the app crashes gracefully. In the exception block (this is a generic exception), any error that occurs will be logged and the file app.log will receive this information. Now, the user will not see the "products is not defined" error, but rather they'll see something along the lines of an unexpected error occurred.
This is also the same with the get_product endpoint as well.
This is what logging and catching errors is all about at a high level.
Now to run this application and test it for yourself, notice we didn't use any fancy libraries, so in your virtual environment install the packages fastapi and uvicorn. After that run:
uvicorn main:app --reload
in your terminal to start your app.
If you want to see the error logs working, put a syntax error in any of the try blocks. For example in main.py:
@app.post('/', status_code=status.HTTP_201_CREATED, response_model=Product)
def create_product(product:ProductCreate):
try:
kkk
product = product.model_dump()
product['id'] = len(products) + 1
products.append(product)
return product
except Exception as e:
logger.error(f'An unexpected error occurred while creating the product, {str(e)}')
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='An unexpected error occurred while creating the product')
Run this create_product endpoint. You will see the app.log file being create which contains the errors that have occurred and it updates automatically as well.
This is basic error logging in FastAPI.
Link to this project:


Top comments (0)