DEV Community

Cover image for Basic Logging in Fastapi using Logger module
Chengetanai Mukanhairi
Chengetanai Mukanhairi

Posted on

Basic Logging in Fastapi using Logger module

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:

Folder Structure

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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.

Logs File

This is basic error logging in FastAPI.

Link to this project:

Basic Logging FastAPI Github

Top comments (0)