Django - logging middleware 간단 버전

로그 기능을 넣기는 해야 겠다. 콘솔로 계속 띄워서 볼 수 는 없으니

미들웨어 기능으로 모든 request 를 로그를 저장해 보자고 맘 먹고 막 찾기 일단 구현된 결과는 아래 참고.

 

먼저, settings 에 로그 기능에 대한 설정을 해 둬야 한다. 여러군데 참고하는 중 젤 많이 보는 Pybo 게시판 쪽의 글을 가져와서 맞춰서 쓰기로 함

https://wikidocs.net/77522

 

작성한 코드는 아래와 같다. 아래 코드에서 파일로 저장하는 부분에 주목해서 보세요.

(먼저 작업 폴더에 logs 라는 폴더를 수동으로 하나 만들어 둬야 한다)

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "filters": {
        "require_debug_false": {
            "()": "django.utils.log.RequireDebugFalse",
        },
        "require_debug_true": {
            "()": "django.utils.log.RequireDebugTrue",
        },
    },
    "formatters": {
        "django.server": {
            "()": "django.utils.log.ServerFormatter",
            "format": "[{server_time}] {message}",
            "style": "{",
        },
        "standard": {
            "format": "[%(asctime)s] [%(levelname)s] : %(message)s",
            "datefmt": "%d-%b-%Y %H:%M:%S",
        },
        "format1": {
            "format": "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
            "datefmt": "%d/%b/%Y %H:%M:%S",
        },
        "format2": {
            "format": "[%(levelname)s] %(message)s",
        },
    },
    "handlers": {
        "console": {
            "level": "INFO",
            "filters": ["require_debug_true"],
            "class": "logging.StreamHandler",
        },
        "django.server": {
            "level": "INFO",
            "class": "logging.StreamHandler",
            "formatter": "django.server",
        },
        "mail_admins": {"level": "ERROR", "filters": ["require_debug_false"], "class": "django.utils.log.AdminEmailHandler"},
        "file": {
            "level": "INFO",
            "encoding": "utf-8",
            "filters": ["require_debug_false"],  # DEBUG=FALSE 일때만 기록
            # "filters": ["require_debug_true"],
            "class": "logging.handlers.RotatingFileHandler",
            "filename": BASE_DIR / "logs/mysite.log",
            "maxBytes": 1024 * 1024 * 50,  # 50 MB
            "backupCount": 5,
            "formatter": "standard",
            # "formatter": "format1",
        },
    },
    "loggers": {
        "django": {
            "handlers": ["console", "mail_admins"],
            "level": "INFO",
        },
        "django.server": {
            "handlers": ["django.server"],
            "level": "INFO",
            "propagate": False,
        },
        "middleware.request_log": {
            "handlers": ["file"],
            "level": "INFO",
        },
    },
}

 

기본적으로는 loggers 세팅 부분에 file 이라는 핸들러를 추가하면 파일로 로그가 저장된다.

 

나는 미들웨어 방식으로 하는 것이므로, 맨 마지막 middleware 에서 쓰일 middleware.request_log 라는 이름의 logger를 하나 만들고 출력을 file 로 하는 것을 지정해 뒀다. level 은 INFO 이상인 경우 다 저장하도록

 

그럼 미들웨어 작업은 어떻게 되었나? 이것도 아래글을 참고해서 슬쩍 만든다.

https://wilspi.com/post/tech/django-middleware-to-log-requests/
 

Log requests and responses with Django Middleware

From Django’s context: Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output. Middleware is like a layer which processes every reques

wilspi.com

 

거의 글과 동일하게 구성, 나는 다 저장할 것이므로 예제 코드에서 json, api 보는 부분만 다 보도록 수정하는 것 정도 변경되었다. 그래도 코드 남기기

아, 참고로 장고 로그인 사용자 정보를 남기기도 추가했음.

 

"""

"""
import socket
import time
import json
import logging

request_logger = logging.getLogger(__name__)
# logger = logging.getLogger("my")


class RequestLogMiddleware:
    """Request Logging Middleware."""

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

    def __call__(self, request):
        # start_time = time.time()
        log_data = {
            "remote_address": request.META["REMOTE_ADDR"],
            "server_hostname": socket.gethostname(),
            "request_method": request.method,
            "request_path": request.get_full_path(),
        }

        if request.user.is_authenticated:
            log_data["request_user"] = request.user.user_id

        # Only logging "*/api/*" patterns
        # if "/api/" in str(request.get_full_path()):
        #     req_body = json.loads(request.body.decode("utf-8")) if request.body else {}
        #     log_data["request_body"] = req_body

        # request passes on to controller
        response = self.get_response(request)

        # # add runtime to our log_data
        # if response and response["content-type"] == "application/json":
        #     response_body = json.loads(response.content.decode("utf-8"))
        #     log_data["response_body"] = response_body
        # log_data["run_time"] = time.time() - start_time
        request_logger.info(msg=log_data)
        # logger.info(msg=log_data)

        return response

    # Log unhandled exceptions as well
    def process_exception(self, request, exception):
        try:
            raise exception
        except Exception as e:
            request_logger.exception("Unhandled Exception: " + str(e))
            # logger.exception("Unhandled Exception: " + str(e))
        return exception

 

 

formatters 부분에 쓰이는 내장 오브젝트는 아래 글에서 참고하면 된다. 살짝 테이블을 가져옴

https://docs.python.org/3/library/logging.html#formatter-objects

 

logging — Logging facility for Python — Python 3.10.4 documentation

logging — Logging facility for Python Source code: Lib/logging/__init__.py This module defines functions and classes which implement a flexible event logging system for applications and libraries. The key benefit of having the logging API provided by a s

docs.python.org

args You shouldn’t need to format this yourself. The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when there is only one argument, and it is a dictionary).
asctime %(asctime)s Human-readable time when the LogRecord was created. By default this is of the form ‘2003-07-08 16:49:45,896’ (the numbers after the comma are millisecond portion of the time).
created %(created)f Time when the LogRecord was created (as returned by time.time()).
exc_info You shouldn’t need to format this yourself. Exception tuple (à la sys.exc_info) or, if no exception has occurred, None.
filename %(filename)s Filename portion of pathname.
funcName %(funcName)s Name of function containing the logging call.
levelname %(levelname)s Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
levelno %(levelno)s Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL).
lineno %(lineno)d Source line number where the logging call was issued (if available).
message %(message)s The logged message, computed as msg % args. This is set when Formatter.format() is invoked.
module %(module)s Module (name portion of filename).
msecs %(msecs)d Millisecond portion of the time when the LogRecord was created.
msg You shouldn’t need to format this yourself. The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object (see Using arbitrary objects as messages).
name %(name)s Name of the logger used to log the call.
pathname %(pathname)s Full pathname of the source file where the logging call was issued (if available).
process %(process)d Process ID (if available).
processName %(processName)s Process name (if available).
relativeCreated %(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
stack_info You shouldn’t need to format this yourself. Stack frame information (where available) from the bottom of the stack in the current thread, up to and including the stack frame of the logging call which resulted in the creation of this record.
thread %(thread)d Thread ID (if available).
threadName %(threadName)s Thread name (if available).

고수의 길은 아주 험난하네

ROMAN ODINTSOV 님의 사진, 출처: Pexels