Published on

django logging setting 으로 로그 남기는 방법 aws cloudwatch slack api 포함

Authors

Django의 Logging 설정은 프로젝트 내에서 로그를 분기하고 setting 이 매우 편합니다.

파이썬의 Logging 을 통해 로그의 레벨, 핸들러, 포맷 등을 정의하기 때문에 핸들러를 이용한 설계가 쉽게 구성되어있어, 사용하는데 매우 간편합니다.

다만 추상화 된 부분이 존재하기 때문에 커스텀 로그를 남기려면 문서를 잘 참고해야 합니다.

이번 글에서는 Django Logging 를 이용해 logging 하는 방법과 aws, slack 등과 같은 서드파티를 함께 사용하는 방법을 기록합니다.

1. Django LOGGING on Settings

Django 에서 기본적으로 제공하는 settings 파일에 LOGGING 변수가 정의 됩니다.

해당 부분에서 로깅 핸들러를 맵핑할 수도, 로그레벨을 지정할 수도, 로그 방식을 지정할 수도 있는 등의 컨트롤 타워 역할을 하게 됩니다.

아래는 기본적인 가이드 입니다.

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'formatter_name': {
            'format': '[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
    },
    'filters': {
        'filter_name': {
            '()': 'path.to.custom.Filter',
        },
    },
    'handlers': {
        'handler_name': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'formatter_name',
            'filters': ['filter_name'],
        },
        'file': {
            'level': 'WARNING',
            'class': 'logging.FileHandler',
            'filename': 'path/to/logfile.log',
            'formatter': 'formatter_name',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['handler_name'],
            'level': 'INFO',
            'propagate': True,
        },
        'myapp': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': False,
        },
    }
}

1.1 setting 구성

  • version1로 설정하면 됩니다. (오래전부터 version 1 로 사용되어 업데이트가 없어서 그대로 사용.)

  • disable_existing_loggers 은 기본 로거를 비활성화할지 여부를 결정해주는데, 이때 기본 로거란 Django 기본 로그를 의미하며 일반적으로 False로 설정해서 기존 로거를 유지 하는것이 편리하다고 생각합니다.

  • formatters는 로그 메시지의 형식을 정의합니다. format은 로그 포맷, datefmt는 날짜 형식을 지정합니다.

  • filters특정 조건을 만족하는 로그만 처리하도록 필터링합니다. (커스텀 가능)

  • handlers로그 메시지를 출력할 위치 혹은 방식 등을 지정할 수 있는 핵심 요소 이며, 커스텀 또한 가능합니다. (이번 글에서 이것을 활용한 서드파티 요소와 연동도 기록합니다.)

    • StreamHandler: 콘솔에 일반적으로 출력되는 형태 입니다.
    • FileHandler: 파일에 기록되며, filename 옵션으로 파일 경로 지정하면 로그 파일에 로그가 적재되는 형태 입니다.
    • 그 외(SMTPHandler,RotatingFileHandler, TimedRotatingFileHandler)등이 있는데 이메일로 발송하는 것 혹은 시간이나 규칙등에 따라서 로그 파일을 변경할지 와 같은 핸들러도 기본적으로 존재하니 나중에 다뤄보겠습니다.
  • loggers 가장 중요하다고 할 수 있는 부분입니다.

    • 특정 로거에 대해 핸들러, 레벨, 전파 여부를 설정합니다.
    • 이 부분에서 Django 기본 로거 Ex('django', 'django.request') 등을 설정 할 수 도 있고 앱별 로거를 추가할 수 있습니다. 각 로직에서 get_logger(pusedo code) 방식으로 끌어오는 것이 바로 이부분 입니다.
    • 참고로 예전 글에서 다룬 적 있는데 로그는 중요도에 따라 레벨을 설정하며 낮은 레벨부터 DEBUG- INFO-WARNING-ERROR-CRITICAL 순으로 배치됩니다.

2. 사용방법

2.1 특정 앱에 로거 설정

앱별 로그를 독립적으로 관리하려면 loggers에 앱 이름을 추가합니다.

아래의 myapp 로거는 DEBUG 레벨 이상의 로그를 콘솔과 파일에 기록하며, 상위 로거로 전파되지 않습니다.

'myapp': {
    'handlers': ['console', 'file'],
    'level': 'DEBUG',
    'propagate': False,
}

2.2 로깅 레벨에 따른 지정 혹은 맵핑

일단, 기본적으로 python 에서 사용하는 logging 모듈을 그대로 사용합니다.

logging 에 대한 사용이 제한되는 layer 는 따로 없습니다.

import logging

logger = logging.getLogger('myapp')

logger.debug(f"{result}")

그 의미는 log 를 남겨야하고, 남기는 방식은 전적으로 개발단에서 의사결정이 되는 것을 의미하며

실제로 view, serializer, model 혹은 util 등 어디에서나 사용하고 각자 컨벤션을 맞춰두는 것이 좋겠습니다.


def process_order(order_id):
    logger.debug(f"Order ID: {order_id}")
    try:
        logger.info(f"Order {order_id} success")
    except Exception as e:
        logger.error(f"Order {order_id} failed {order_id}: {e}", exc_info=True)

2.3 파일 로그 기록 형태

모자이크 되긴했지만, 파일 형태로 로그를 남기면 아래처럼 파일 상에 로그가 남습니다.

django_logging

2.4 format 에서 사용된 로깅 변수

settings 에서 detailed 라는 자세한 정보를 담는 로그를 세팅하는 예시 입니다.

"formatters": {
	"detailed": {
            "format": "[{asctime}] {levelname} {name}:{lineno} - {message} // {funcName} || {threadName} || {processName}",
            "style": "{",
        },

format 으로 사용가능한 변수들은 문서에 나와 있습니다. (하단 레퍼런스 링크에 더 자세하게 기록되어 있습니다.)

  • asctime: 로그 메시지가 생성된 시간을 출력합니다. 기본 형식은 YYYY-MM-DD HH:MM:SS,mmm이며, datefmt 옵션을 사용해 날짜 형식을 지정할 수 있습니다.
  • levelname: 로그 레벨을 출력합니다. 예를 들어 DEBUG, INFO, WARNING, ERROR, CRITICAL과 같은 레벨 이름이 여기에 포함됩니다.
  • name: 로거의 이름을 출력합니다. 보통 logging.getLogger("logger_name")에서 지정한 이름이 출력됩니다.
  • lineno: 로그가 발생한 코드 상의 라인(줄) 번호를 출력합니다.
  • message: 로그를 남길 때 작성하는 메시지 문자열이 여기에 포함됩니다.
  • pathname: 로그가 발생한 파일의 전체 경로를 출력합니다.
  • filename: 로그가 발생한 파일 이름을 출력합니다.
  • funcName: 로그가 발생한 함수 이름을 출력합니다.
  • threadName: 로그가 발생한 스레드 이름을 출력합니다.
  • processName: 로그가 발생한 프로세스 이름을 출력합니다.

3. 커스텀 로깅

3.1 AWS Cloud Watch

django_logging_aws_cloudwatch

AWS에는 Cloud watch 라는 Log 리소스가 존재하며, watchtower 라는 라이브러리에 CloudWatchLogHandler 클래스를 통해 이미 쉽게 로깅할 수 있도록 구현 되어있습니다.

핸들러 부분은 아래처럼 세팅하면 됩니다.

"handlers": {
        "cloudwatch_log_info": {
            "level": "INFO",
            "class": "watchtower.CloudWatchLogHandler",
            "boto3_session": boto3_client_object
            "log_group": "log-group-name",
            "stream_name": "{strftime:%Y-%m-%d}",
            "formatter": "detailed",
        },
    },


위에서 세팅한 핸들러 정보를 아래 처럼 logger에 맵핑하면 각 layer에서 로깅을 사용할 수 있게 됩니다.

"loggers": {
        "log_name_1": {
            "handlers": [
                "cloudwatch_log_info",
            ],
            "level": "INFO",
        },

3.2 Slack

slack 로깅 방식은 하나(혹은 2개이상이 될수도..)의 채널을 로깅 전용으로 세팅해두고 서버에서 발생하는 로그를 채널에 적재하는 방식 입니다. 결과적으로 아래 이미지처럼 하나의 채널에 로그를 쌓도록 하는 방법을 알아보겠습니다. slack_logging

3.2.1 Slack에서 채널 세팅

webhook_url 을 발급받아야하고 이는 slack 에서 진행합니다.

채널이 없다면 먼저 채널을 만들어줍니다. slack_logging slack_logging

slack 메뉴바에 왼쪽 하단 부에 앱추가 라는 부분이 존재합니다.

여기서 hook 을 검색해서 incoming webhooks 를 진입합니다.

slack_hook

slack 에 추가를 눌러서 진입합니다.

slack_logging

채널에 포스트 하는 것은 채널에 알림을 발송하는 것을 의미하며, 지정된 채널에 알림이 발송되게 됩니다.

slack_logging

앱을 추가하게 되면 웹후크 URL 이라는 것이 나오는데, 아래에서 사용할 webhook_url 과 동일합니다.

여기서 발급되는 URL을 로직에서 사용합니다.

slack_logging

AWS는 라이브러리를 통해 로깅하는 방식이 매우 간단했지만, slack 과 같은 서드파티 서비스에는 직접 핸들러(Handler)를 커스터마이징 하고 slack에서 제공하는 webhook app 을 연동해줘야 합니다.

3.2.2 Slack Custom Handler

  • settings 에서 먼저 커스텀 핸들러를 사용하려면 () 를 키값으로 두고 사용합니다.
  • slack 에서 발급받은 webhook_url 을 세팅합니다.
"handlers": {
	"slack": {
	            "()": SlackHandler,
	            "level": "DEBUG",
	            "webhook_url": "https://hooks.slack.com/services/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",  # SlackHandler에서 요구하는 매개변수
	            "formatter": "detailed",
	        },

() 키를 사용해 커스텀 핸들러를 지정하는 방식은 Python 표준 라이브러리의 logging.config.dictConfig 문서에서 handlers 섹션의 설명에 나와 있습니다.

참고로..Django 공식 문서에는 "class" 키와 "()" 키의 차이점에 대한 구체적인 설명은 없습니다.

그렇지만, Python의 dictConfig 구조를 따르는 것은 Django가 Python 표준 로깅 설정 방식을 그대로 사용하기 때문에, 동일한 방식으로 커스텀 핸들러를 설정할 수 있는 것 입니다.

class: The fully qualified name of the handler class. (): If this key is present, it is assumed to be a callable which will return a handler instance when called. It is evaluated using eval().

즉 정리하면

  • "class" 키는 기본 핸들러 클래스를 문자열로 지정합니다.
  • "()" 키는 커스텀 핸들러 클래스를 직접 참조하거나 팩토리 함수로 사용할 때 사용합니다.

이제 핸들러 차례는 생각 보다 간단합니다.

logging.Handler 를 오버라이딩 하고, setting 에서 설정한 webhook_url 을 인자로 넘겨줍니다.


class SlackHandler(logging.Handler):
    def __init__(self, webhook_url):
        super().__init__()
        self.webhook_url = webhook_url

    def emit(self, record):
        log_entry = self.format(record)
        payload = {"text": log_entry}
        try:
            requests.post(self.webhook_url, json=payload)
        except Exception as e:
            print(f"Failed to send log to Slack: {e}")

참고 링크

  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee