0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Django Rest FrameworkでLoggerのMiddlewareを作成してログの出力を共通化させよう

Last updated at Posted at 2023-07-04

概要

ログを出力する際に各Viewごとに一つずつ設定するのは手間なのでMiddlewareとして共通化させる方法について解説します

前提

  • loggerの基本的な知識を有している
  • システムユーザのModelを作成済み

UserのModelについてはDjangoのAbstractUserを継承して作成します
詳細に知りたい方は以下の記事を参考にしてください

ディレクトリ構成

今回作成していくのは以下のファイルです

  • application/pyproject.toml
  • project/settings.py
  • application/logs.py
  • application/middleware.py
tree
・
└── application
    ├── application
    │   └── utils
    │       ├── logs.py
    │       └── middleware.py
    ├── manage.py
    ├── poetry.lock
    ├── pyproject.toml
    └── project
        └── settings.py

では一つずつ解説していきます

ログの設定

toml parserを使ったログの設定方法について詳細に知りたい方は以下の記事を参照してください

pyproject.toml

pyproject.tomlにログを設定していきます

pyproject.toml
# logの設定
[logging]
version = 1
[logging.formatters.simple]
format = "[%(levelname)s] %(name)s - %(message)s "
[logging.handlers.applicationHandler]
class = "logging.StreamHandler"
level = "INFO"
formatter = "simple"
stream = "ext://sys.stdout"
[logging.handlers.errorHandler]
class = "logging.StreamHandler"
level = "ERROR"
formatter = "simple"
stream = "ext://sys.stdout"
[logging.loggers.application]
level = "DEBUG"
handlers = ["applicationHandler"]
propagate = "no"
[logging.loggers.emergency]
level = "DEBUG"
handlers = ["errorHandler"]
propagate = "no"

今回はpyproject.toml内にLoggerの設定を記載しています

settings.py

dictConfigを使ってtoml内のLoggerの設定を取得していきます

project/settings.py
from logging.config import dictConfig


# ログ設定
dictConfig(ConfFile.get()["logging"])

application/utils/logs.py

Loggerの設定を行います
今回はエラーを含めた想定内のログは

  • Application

想定外のエラーのログは

  • Emergency

として定義しています

logs.py
import tomllib
from enum import Enum
from typing import Any, Optional


class LoggerName(Enum):
    """ロガー名"""

    APPLICATION = "application"
    EMERGENCY = "emergency"


class ConfFile:
    """confファイル取得用クラス
    Attributes:
        _conf_file (Optional[dict[Any, Any]]): pyproject.tomlのデータを辞書形式に変換した内容<br>
            最初の1回だけ読み込まれる
    """

    _conf_file: Optional[dict[Any, Any]] = None

    @classmethod
    def get(cls) -> dict[Any, Any]:
        """pyproject.tomlのデータを辞書形式で返す
        2回目以降はファイルの読み込みは実施しない
        Returns:
            dict[Any, Any]: pyproject.tomlの設定データの辞書形式
        """
        if cls._conf_file is None:
            with open("pyproject.toml", mode="rb") as file:
                cls._conf_file = tomllib.load(file)
        return cls._conf_file

middlewareの設定

ログを出力する用のMiddlewareを作成します

application/utils/middleware.py
"""ミドルウェア用のモジュール"""
import datetime
from logging import getLogger

from rest_framework import status

from application.utils.logs import LoggerName

application_logger = getLogger(LoggerName.APPLICATION.value)



class LoggingMiddleware:
    """APIの開始と終了をロギングするミドルウェア"""

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # API呼び出し以外はログに出さない
        if request.path.endswith(".js") or request.path in (
            # Swaggerのパス
            "/api/docs/",
            "/api/schema/",
        ):
            return self.get_response(request)

        method = request.method
        ip = get_client_ip(request)
        path = request.path

        user = request.user
        user_info = "未ログイン"
        if user.is_authenticated:
            user_info = f"{user.employee_number} {user.username}"

        start_time = datetime.datetime.now()
        response = self.get_response(request)
        status_code = response.status_code
        end_time = datetime.datetime.now()
        duration_time = end_time - start_time
        message = f"{ip} {user_info} {method} {path} 実行時間: {duration_time} {status_code} "
        if status.is_success(response.status_code):
            application_logger.info(message)
        else:
            application_logger.warning(message)
        return response


def get_client_ip(request):
    """クライアントのIPアドレスを取得"""
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
    if x_forwarded_for:
        ip = x_forwarded_for.split(",")[0]
    else:
        ip = request.META.get("REMOTE_ADDR")
    return ip

get_response

application/utils/middleware.py
response = self.get_response(request)

からAPIを実行した際のresponseを変数に格納します

ログのフォーマット

API実行時のログの

  • IPアドレス
  • ユーザ情報
  • HTTPメソッド
  • APIのパス
  • 実行時間
  • ステータスコード

を出力したいので以下のフォーマットにします

application/utils/middleware.py
message = f"{ip} {user_info} {method} {path} 実行時間: {duration_time} {status_code} "

settings.py

MIDDLEWAREにLoggingMiddlewareを追加することでログの出力を有効化します

settings.py
MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    # 先ほど作成したLoggingMiddlewareを追加
    "application.utils.middleware.LoggingMiddleware",
]

ログの出力

試しに私の方でいくつかAPIを作成
以下のようにログが出力されたら成功です

[INFO] application 2023-07-04 16:43:01,753 - 127.0.0.1 00000001 test01 POST /api/users/send_invite_user_mail/ 200 実行時間:0:00:00.070526
[INFO] application 2023-07-04 16:43:02,402 - 127.0.0.1 未ログイン POST /api/login/ 200 実行時間:0:00:00.213168 

参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?