Logging i en FastAPI App

Skip to content

Info

Nå skal vi bruke alt vi har lært om logging i en faktisk web-applikasjon! Vi bruker FastAPI - et populært Python-rammeverk for å lage API-er. Hvis du har brukt Flask fra før, vil mye av dette føles kjent! 🚀

Les mer: FastAPI dokumentasjon

Del 1 - Oppsett

Vi trenger et par pakker. Lag en requirements.txt:

fastapi
uvicorn

Installer med:

pip install -r requirements.txt

Hva er uvicorn?

uvicorn er en “ASGI server” - altså programmet som faktisk kjører FastAPI-appen din og lytter etter forespørsler. Tenk på det som motoren som driver web-serveren. 🏎️

Del 2 - Grunnleggende FastAPI med Logging

Her er en enkel FastAPI-app med skikkelig logging oppsatt:

Filstruktur

📁fastapi_logging
    🐍main.py
    🐍logger_config.py
    📄requirements.txt

logger_config.py - Sentralisert logging-oppsett

Det er god praksis å ha logging-oppsettet i en egen fil, slik at alle delene av appen bruker samme konfigurasjon:

import logging
from logging.handlers import RotatingFileHandler


class ColorFormatter(logging.Formatter):
    _colors = {
        logging.CRITICAL: "\033[91m",  # Red
        logging.ERROR: "\033[91m",     # Red
        logging.WARNING: "\033[93m",   # Yellow
        logging.INFO: "\033[92m",      # Green
        logging.DEBUG: "\033[95m",     # Magenta
    }
    _marks = {
        logging.CRITICAL: "!",
        logging.ERROR: "E",
        logging.WARNING: "W",
        logging.INFO: "*",
        logging.DEBUG: "D",
    }
    _reset = "\033[0m"
    _blue = "\033[94m"
    _bold = "\033[1m"

    def format(self, rec):
        marks, colors = self._marks, self._colors
        return (
            f"{self._blue}{self._bold}[{colors[rec.levelno]}{marks[rec.levelno]}"
            f"{self._blue}]{self._reset} {super().format(rec)}"
        )


def setup_logger(name: str = "app") -> logging.Logger:
    """Sett opp en logger med farge i terminalen og detaljert fil-logging."""
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)

    # Unngå å legge til handlers flere ganger
    if logger.handlers:
        return logger

    # --- Terminal Handler (INFO og oppover, med farger) ---
    stream_handler = logging.StreamHandler()
    stream_handler.setLevel(logging.INFO)
    stream_formatter = ColorFormatter(
        "[%(asctime)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )
    stream_handler.setFormatter(stream_formatter)

    # --- Fil Handler (ALT, inkludert DEBUG) ---
    file_handler = RotatingFileHandler(
        "app.log",
        maxBytes=5_000_000,  # 5 MB
        backupCount=3
    )
    file_handler.setLevel(logging.DEBUG)
    file_formatter = logging.Formatter(
        "[%(asctime)s] %(levelname)-8s %(name)s (%(filename)s:%(lineno)d): %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )
    file_handler.setFormatter(file_formatter)

    # Legg til begge handlers
    logger.addHandler(stream_handler)
    logger.addHandler(file_handler)

    return logger

main.py - FastAPI appen

from fastapi import FastAPI, HTTPException
from logger_config import setup_logger

app = FastAPI()
logger = setup_logger("api")


@app.get("/")
async def root():
    logger.info("Noen besøkte forsiden!")
    return {"message": "Velkommen til mitt API! 🎉"}


@app.get("/products/{product_id}")
async def get_product(product_id: int):
    logger.debug(f"Forespørsel mottatt for product_id={product_id}")

    # Simulert database med noen produkter
    products = {
        1: {"name": "Laptop", "price": 9999},
        2: {"name": "Tastatur", "price": 499},
        3: {"name": "Mus", "price": 299},
    }

    if product_id not in products:
        logger.warning(f"Produkt {product_id} finnes ikke!")
        raise HTTPException(status_code=404, detail="Produkt ikke funnet")

    logger.info(f"Returnerer produkt: {products[product_id]}")
    return products[product_id]


@app.get("/error")
async def cause_error():
    logger.error("Noen trigget en feil med vilje!")
    raise HTTPException(status_code=500, detail="Noe gikk galt!")

For å kjøre appen:

uvicorn main:app --reload

--reload flagget

--reload gjør at serveren automatisk restarter når du endrer koden. Perfekt for utvikling! Men IKKE bruk det i produksjon. 🚧

Gå til http://127.0.0.1:8000 i nettleseren for å teste. Du kan også bruke http://127.0.0.1:8000/docs for å se den automatiske API-dokumentasjonen! 🤯


Oppgaver

Easy Oppgave 3.1 - Sett opp prosjektet

  1. Lag filstrukturen som vist over
  2. Installer fastapi og uvicorn
  3. Kopier koden fra logger_config.py og main.py
  4. Start appen med uvicorn main:app --reload
  5. Besøk nettsiden og se hva som vises i terminalen og i app.log
Hva bør du se?

I terminalen (med farger!):

[*] [2026-04-09 12:00:00] Noen besøkte forsiden!

I app.log (uten farger, med mer detaljer):

[2026-04-09 12:00:00] INFO     api (main.py:12): Noen besøkte forsiden!

Medium Oppgave 3.2 - Utvid API-et

Legg til flere endpoints i FastAPI-appen din. For eksempel:

  1. POST /products - Legg til et nytt produkt (logg på INFO-nivå)
  2. DELETE /products/{product_id} - Slett et produkt (logg på WARNING-nivå, fordi sletting er potensielt farlig!)
  3. GET /products - Hent alle produkter (logg på DEBUG-nivå)

Bruk passende logging-nivåer for hver operasjon. Husk å sjekke app.log for å se at DEBUG-meldinger havner der!

POST i FastAPI
from pydantic import BaseModel

class Product(BaseModel):
    name: str
    price: float

@app.post("/products")
async def create_product(product: Product):
    logger.debug(f"Mottatt POST /products med data: {product.model_dump()}")
    # ... lagre produkt
    logger.info(f"Nytt produkt opprettet: {product.name} ({product.price} kr)")
    return {"message": "Produkt opprettet!", "product": product}

Legg merke til at vi bruker DEBUG for å logge rådataene som kom inn, og INFO for å bekrefte at produktet ble opprettet. I app.log vil du se begge, men i terminalen kun INFO-meldingen!

Hard Oppgave 3.3 - Separer loggfiler

Sett opp logging slik at:

  1. app.log - Inneholder INFO og høyere (generell drift)
  2. debug.log - Inneholder DEBUG og høyere (alt, for feilsøking)
  3. error.log - Inneholder kun ERROR og CRITICAL (for å raskt finne feil)

Oppdater setup_logger() funksjonen i logger_config.py til å ha tre FileHandler-instanser.

Tips

Du kan lage så mange handlers du vil! Bare lag en ny FileHandler for hver fil og sett riktig nivå med setLevel().