2
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

loggingの基礎を作ってみる【独学】

Posted at

Fastapiを使うときに、どういうログが必要なのか

結論はアプリログ、強いていうならエラーログも分けて出しておくと見やすいかもしれない。

ただ、個人的にはアプリログは、どっちも出した方がいいんじゃないかとは思っています。
変に切り分けるとどの正常処理の後に落ちた箇所なのかの追い方が少し面倒な気がしているので。

今回はアプリログ一つに出します。

Fastapi自体に届くアクセスログ自体はアプリ側では管理しなくていいのかなというところなので。

種類

アクセスログ

INFO: 127.0.0.1:53818 - "GET /boom HTTP/1.1" 500 Internal Server Error

uvicornが出している誰が何を叩いたかの記録

middlewareのlogger.*とは別

これは基本的にFastAPIでは今回は意図的には出さない。
一応練習としてアプリのmiddleware側でログを出すのである種アクセスログに近いものは出す。

アプリログ

API Error: request_id=... path=/boom Boom!
Traceback ...

アプリコードが出している

なぜそうなったのかの記録

こっちはアプリケーションに関してのログなのでアプリ側で管理して出していきたい。

方法

appディレクトリの配下に諸々ある想定

ロガーの設定

ロガーをcore配下で設定しmain.pyで読み込む。
そうすると各々のモジュールで共通で設定は流用できる。

今季は吐き出し先はlogs/app.log

core/logging.py

import logging
from logging import Logger
from pathlib import Path

LOG_DIR = Path("logs")
LOG_FILE = LOG_DIR / "app.log"


def setup_logger() -> Logger:
    LOG_DIR.mkdir(exist_ok=True)

    formatter = logging.Formatter(
        "%(asctime)s %(levelname)s %(name)s %(message)s"
    )

    #  ターミナルへ出力するハンドラ
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)

    # ファイルへ出力するハンドラ
    file_handler = logging.FileHandler(LOG_FILE)
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(formatter)

    root_logger: Logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)

    if not root_logger.hasHandlers():
        root_logger.addHandler(console_handler)
        root_logger.addHandler(file_handler)

    return root_logger

関連ファイル

main.py

import logging
from fastapi import FastAPI
from pydantic import BaseModel
from app.middlewares.application_middleware import ApplicationMiddleware
from app.core.logging import setup_logger

setup_logger()

app = FastAPI()
app.add_middleware(ApplicationMiddleware)


class UserIn(BaseModel):
    age: int


@app.get("/health")
async def health():
    return {"ok": True}


@app.get("/boom")
async def boom():
    raise Exception("Boom!")


@app.post("/users/")
async def create_user(user: UserIn):
    return user

middleware/authention_middleware.py

import uuid
import traceback
from starlette.middleware.base import BaseHTTPMiddleware
from logging import getLogger
from fastapi import Request, status
from fastapi import HTTPException
from fastapi.responses import JSONResponse

logger = getLogger(__name__)

REQUEST_ID_HEADER = "X-Request-ID"


class ApplicationMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        request_id = request.headers.get(
            REQUEST_ID_HEADER) or str(uuid.uuid4())
        try:
            response = await call_next(request)
            response.headers[REQUEST_ID_HEADER] = request_id

            logger.info(
                "API request_id=%s method=%s path=%s status=%s",
                request_id,
                request.method,
                request.url.path,
                response.status_code,
            )
            return response
        except Exception as e:
            status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
            detail = "Internal Server Error"
            headers = {"www-Authenticate": "Internal Server Error"}

            if isinstance(e, HTTPException):
                status_code = e.status_code
                detail = e.detail
                headers = e.headers

            headers = dict(headers)
            headers[REQUEST_ID_HEADER] = request_id

            logger.exception(
                "API Error: request_id=%s path=%s %s\n%s",
                request_id,
                request.url.path,
                e,
                traceback.format_exc(),
            )

            return JSONResponse(
                status_code=status_code,
                content={"detail": detail},
                headers=headers,
            )

## 結果

エンドポイントを叩いてみるとこんな感じになります。

2026-01-24 16:53:05,596 ERROR app.middlewares.application_middleware API Error: request_id=2e054e8d-df12-40da-b1fc-618ab165aaf7 path=/boom Boom!
Traceback (most recent call last):
  File "/hogehoge/app/middlewares/application_middleware.py", line 20, in dispatch
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 168, in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/middleware/base.py", line 144, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/hogehoge/venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app
    await route.handle(scope, receive, send)
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/routing.py", line 290, in handle
    await self.app(scope, receive, send)
  File "/hogehoge/venv/lib/python3.12/site-packages/fastapi/routing.py", line 115, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/hogehoge/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/hogehoge/venv/lib/python3.12/site-packages/fastapi/routing.py", line 101, in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
  File "/hogehoge/venv/lib/python3.12/site-packages/fastapi/routing.py", line 355, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/hogehoge/venv/lib/python3.12/site-packages/fastapi/routing.py", line 243, in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/hogehoge/app/main.py", line 24, in boom
    raise Exception("Boom!")
Exception: Boom!

2026-01-24 17:09:46,552 INFO app.middlewares.application_middleware API request_id=799f27c9-fbfd-472d-a8a6-9767f60a333d method=GET path=/health status=200
2
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?