로그 남기기

jurin·2021년 7월 22일
0

장고의 로깅은 파이썬의 로깅 체계를 그대로 따르면서 일부만 추가됐다. 파이썬의 로깅 모듈은 로거, 핸들러, 필터, 포맷터 4가지 주요 컴포넌트를 정의하고 있다.

장고의 runserver나 웹 서버에 의해 장고가 실행될 때 장고는 settings.py 파일에 정의된 LOGGING_CONFIG, LOGGING 항목을 참고하여 로깅에 관련된 설정을 처리한다. settings.py 파일에 관련 항목이 없더라도 디폴트 로깅 설정으로 처리된다. 따라서 장고의 로깅은 실행되는 시점부터 작동하여 로그가 출력된다.

로거

로거는 로깅 시스템의 시작점으로 로그 메시지를 처리하기 위해 메시지를 담아두는 저장소라고 할 수 있습니다. 모든 로거는 이름을 가지고 있고, 로그 레벨을 갖게 되는데 이는 로그 메시지의 중요도에 따라 어느 레벨 이상의 메시지를 처리할지에 대한 기준이 된다.

파이썬의 로그 레벨

로거에 저장되는 메시지를 로그 레코드라고 하며, 역시 그 메시지의 심각정을 나타내는 로그 레벨을 가진다. 로그 레코드는 로그 이벤트에 대한 메타 정보도 가질 수 있다.(스택 트레이스 정보, 에러 코드 등)

메시지가 로거에 도착하면, 로그 레코드의 로그 레벨과 로거의 로그 베벨을 비교해서 (로그 레코드 >= 로거) 이면 메시지 처리를 계속 진행하고 더 낮으면 그 메시지를 무시한다. 계속 진행할 경우 로거는 메시지를 핸들러에게 넘겨준다.

핸들러

핸들러는 로거에 있는 메시지에 무슨 작업을 할지 결정하는 엔진으로 메시지를 화면이나 파일 또는 네트워크 소켓 등 어디에 기록할 것인지와 같은 로그 동작을 정의한다. 마찬가지로 로그 레벨을 가지고 있는데 (로그 레코드 < 핸들러) 이면 헨들러는 메시지를 무시한다.

로거는 핸들러를 여러 개 가질 수 있고, 각 핸들러는 서로 다른 로그 레벨을 가질 수 있다. 이를 통해 메시지의 중요도에 따라 다른 방식의 로그 처리가 가능하다.

필터

로그 레코드가 로거에서 핸들러로 넘겨질 때, 필터를 사용해서 로그 레코드에 추가적인 제어를 할 수 있다. 기본 제어 방식은 로그 레벨을 지정하여 그 로그 레벨에 해당되면 관련 로그 메시지를 처리하는 것이다.

  • 필터를 적용하여 로그 처리 기준을 추가할 수 있다.
    ex) 필터를 추가하여 ERROR 메시지 중 특정 소스로부터 오는 메시지만 핸들러로 넘기기

  • 필터를 적용하여 로그 레코드를 보내기 전에 수정하는 것도 가능하다.
    ex) 어떤 조건에 맞으면 ERROR 로그 레코드를 WARNING 로그 레벨로 낮춰주는 필터

  • 필터는 로거 또는 핸들러 어디에나 적용이 가능하고, 여러 개의 필터를 체인 방식으로 동작시킬 수 있다.

포맷터

로그 레코드는 최종적으로 텍스트로 표현되는데 포맷터는 텍스트로 표현 시 사용할 포맷을 지정해준다. 보통 파이썬의 포맷 스트링을 사용하지만 사용자 정의 포맷터도 만들 수 있다.

로거 사용 및 로거 이름 계층화

로그를 기록하기 위해서는 로거, 핸들러, 필터, 포맷터 등을 설정한 후 코드 내에서 로깅 메소드를 호출하면 된다.

로깅 설정은 settings.py 파일에 작성한다.

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler'
        },
    },
    'loggers': {
        'myloger': {
            'handlers': ['console'],
            'level': 'INFO'
        },
    },
}

로그 메시지를 기록하기 위해 로거를 취득하고 적절한 위치에서 로깅 메소드를 호출한다.

import logging

# settings.py 파일에서 설정된 로거를 취득
logger = logging.getLogger('mylogger')

def my_view(request, arg1, arg):
    # 필요한 로직
    if bad_mojo:
        # ERROR 레벨의 로그 레코드 생성
        logger.error(('Something went wrong!!'))

logging.getLogger() 메소드를 호출해서 로거 객체를 얻어온다. 로거 객체는 이름을 가지며, 로거를 계층화할 때 이름으로 각 로거를 구분한다.

보통 로거 이름에 __name__ 구문을 사용하는데 이는 이 구문이 있는 파일의 파이썬 모듈 경로를 뜻한다. 만일 web_programing/voting/views.py 파일에서 이 구문을 사용하고 있다면 __name__ 변수값은 voting/views 가 된다. 로깅 호출을 모듈 단위로 처리할 수 있어서 많이 사용하는 구문이다.

모듈 단위로 로그를 기록하고 싶으면 로거를 구분하는 이름을 도트(.) 방식으로 명명해주면 된다.

# 로거 이름으로 계층화
logger = logging.getLogger('project.interesting.stuff')

도트 방식의 로거 이름은 계층화를 이룬다.

로거의 이름을 빈 문자열('')로 지정하면 파이썬의 최상위 로거(루트 로거)가 된다.

로거의 계층화가 중요한 이유

로킹 호출은 부모 로거에게 전파되기 때문이다. 로거 트리의 최상단 루트 로거에서 핸들러 하나만을 만들어도 하위 로거의 모든 로깅 호출을 잡을 수 있다.

로거 객체는 각 로그 레벨별로 로깅 호출 메소드를 가지고 있다.

  • logger.debug()
  • logger.info()
  • logger.warning()
  • logger.error()
  • logger.critical()
  • logger.log() : 원하는 로그 레벨을 정해 로그 메시지 생성
  • logger.exception() : 익셉션 스택 트레이스 정보를 포함하는 ERROR 레벨의 로그 메시지 생성

장고의 디폴트 로깅 설정

장고는 사전형 설정 방식을 디폴트로 사용한다. 이 방식은 settings.py 파일의 LOGGING 항목에 로깅 속성을 사전 형식으로 정의하게 된다.

setting.py 파일에 lOGGING 항목을 지정하지 않으면 장고는 디폴트 로깅 설정을 사용한다.

디폴트 로깅 설정 파일의 위치와 설정 내용

DEFAULT_LOGGING = {
    # 설정이 dictConfig version 1 형식
    # 현재는 버전 하나 뿐
    'version': 1,

    # 기존의 로거들을 비활성화하지 않는다.
    # 이전 버전과의 호환성을 위한 항목
    'disable_existing_loggers': False,

    # 필터 2개 정의
    'filters': {
        # DEBUG=False인 경우만 핸들러 동작.
        'require_debug_false': {
            # 특별키 (): 필터 객체를 생성하기 위한 클래스를
            # 장고에서 별도로 정의
            '()': 'django.utils.log.RequireDebugFalse',
        },

        # DEBUG=True인 경우만 핸들러 동작
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },

    # 포맷터 1개 정의
    'formatters': {
        # 로그 생성 시각과 로그 메시지만을 출력
        'django.server': {
            # 포맷터 객체를 생성하기 위한 클래스를 별도로 정의
            '()': 'django.utils.log.ServerFormatter',
            'format': '[{server_time}] {message}',
            'style': '{',
        }
    },

    # 핸들러 3개 정의
    'handlers': {
        # INFO 레벨 및 그 이상의 메시지를 표준 에러로 출력해주는
        # StreamHandler 클래스 사용
       'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
        },

        # django.server 로거에서 이 핸들러 사용
        'django.server': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'django.server',
        },

        # ERROR 및 그 이상의 로그 메시지를 사이트 관리자에게
        # 이메일로 보내주는 AdminEmailHandler 클래스 사용
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        }
    },

    # 로거 2개 정의
    'loggers': {
        # INFO 및 그 이상의 로그 메시지를
        # console 및 mail_admins 핸들러에게 전송
        # django.* 계층. 즉, django 패키지 최상위 로거
        'django': {
            'handlers': ['console', 'mail_admins'],
            'level': 'INFO',
        },

        # INFO 레벨 및 그 이상의 메시지를
        # django.server 핸들러에게 전송
        # runserver에서 사용하는 로거
        # 5XX : EROOR, 4XX: WARNING, 그 외 INFO 메시지
        'django.server': {
            'handlers': ['django.server'],
            'level': 'INFO',
            # 상위 로거로 로그 메시지 전파하지 않는다.
            'propagate': False,
        },
    }
}

디폴트 설정 내용을 통해 알 수 있는 것

  • DEBUG=True이면 django.* 계층에서 발생하는 로그 레코드는 INFO 이상일 때 콘솔로 보내짐
  • DEBUG=False이면 django.* 계층에서 발생하는 로그 레코드는 ERROR 이상일 때 관리자에게 이메일 전송
  • django.server 로거는 DEBUG값과 무관하게 INFO 레벨 이상이면 콘솔로 보내고 django.* 계층의 다른 로거들과는 다르게 django 로거로 전파하지 않는다.

장고의 로깅 추가 사항 정리

장고에서 추가한 로거

  • django 로거
  • django.request 로거: 요청 처리과 관련된 메시지 기록. 5XX : EROOR, 4XX: WARNING 메시지로 발생. 이 로거에 담기는 메시지는 2개의 추가적인 메타 항목을 가진다.
    • status_code: HTTP 응답코드
    • request: 로그 메시지를 생성하는 요청 객체
  • django.server 로거
  • django.template 로거: 템플릿을 렌더링하는 과정에서 발생하는 로그 메시지 기록
  • django.db.backends 로거: DB 관련 메시지 기록. 애플리케이션에서 사용하는 모든 SQL 문장들이 DEBUG 레벨로 기록 성능상의 이유로 로깅은 settings.DEBUG 항목이 True인 경우만 활성화
    • duration: SQL 문장을 실행하는데 걸린 시간
    • sql: 실행된 SQL 문장
    • params: SQL 호출에 사용된 파라미터
  • django.security.* 로거: 사용자가 보안 측면에서 해를 끼칠 수 있는 동작을 한 경우 이에 대한 메시지 기록. HTTP 헤더가 ALLOWED_HOSTS에 없다면 장고는 400 응답을 리턴하고 에러 메시지가 djago.security.DisallowedHost 로거에 기록
  • django.db.backends.schema 로거: DB의 스키마 변경 시 사용된 SQL 쿼리 기록

장고에서 추가한 핸들러

  • AdminEmailHandler

장고에서 추가한 필터

  • CallBackFilter: 콜백 함수를 지정해서 필터를 통과하는 모든 메시지에 대해 콜백 함수 호출. 콜백 함수의 리턴값이 False면 메시지 로깅은 더 이상 처리하지 않는다.
  • RequireDebugFalse
  • RequireDebugTrue

로깅 설정 - 디폴트 설정 유지

장고는 항상 로깅이 가능한 상태로 로그가 필요한 시점에 로거를 취득하고 로깅 메소드를 호출하면 된다.

우리가 필요한 로거를 추가로 설정해주고 이 로거를 취득하여 사용하면 된다. 로거를 추가하는 것은 settings.py 파일에 LOGGING 항목으로 가능하다. LOGGING 항목은 새로운 로거를 추가하는 것도 가능하지만, 기존의 디폴트로 설정된 로거들을 오버라이딩하여 핸들러, 필터, 포맷터 등의 동작을 변경하는 방법을 많이 사용한다.

# 로깅 설정에 사용하는 함수 지정. 생략 가능
LOGGING_CONFIG = 'logging.config.dictConfig'

# 개발자가 로깅을 설정할 때는 LOGGING 항목 사용
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        
        # [로그 메시지를 기록한 시간], 로그 레벨 이름
        # [로거이름:라인번호], 로그 메시지 순서로 출력
        'verbose': {
            'format': "[%(asctime)s %(levelname)s [%(name)s:%(lineno)s] %(message)s",
            'datefmt': "%d%b%Y %H:%M:%S"
        }
    },
    'handlers': {
        # DEBUG 이상의 메시지를 파일로 출력해주는 FileHandler 사용
        # 로그가 기록되는 파일 이름은 D:web_programing\web_programing\logs\testsite.log
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filters': os.path.join(BASEDIR, 'logs', 'testsite.log'),
            # 위에서 정의한 verbose 포맷터 사용
            'formatter': 'verbose'
        },
    },
    'loggers': {
        # 원래 있던 django 로거의 핸들러와 로거 레벨을 
        # 오버라이딩하여 동작 방식 변경
        'django': {
            'handlers': ['file'],
            'level': 'DEBUG',
        },
        # DEBUG 및 그 이상의 메시지를 file 핸들러에 전송
        # 로그 메시지를 파일에 기록
        # 로거에서 level을 정의하면 핸들러에서 정의한 level을 오버라이딩
        'testsite': {
            'handlers': ['file'],
            'level': 'DEBUG',
        },
    }
}




출처: Django로 배우는 파이썬 웹 프로그래밍(기초) - 김석훈님

profile
anaooauc1236@naver.com

0개의 댓글