Django

[Django] 로그 남기기

dio-han 2022. 1. 21. 20:44

 

장고의 로깅은 기본적으로 파이썬의 로깅 체계를 그대로 따르면서 일부만 추가되었다. 파이썬의 로깅 모듈을 보면 로거, 핸들러,

필터, 포맷터 4가지 주요 컴포넌트를 정의하고 있다.

또한, 장고의 runserver나 웹 서버에 의해 장고가 실행될 때 장고는 settings.py 파일에 정의된 LOGGING_CONFIG, LOGGING

항목을 참고하여 로깅에 관련도니 설정을 처리한다. settings.py 파일에 관련 항목이 없더라도 디폴트 로깅 설정으로 처리된다.

따라서 장고의 로깅은 실행되는 시점부터 작동하여 로그가 출력된다. 또는 적절한 로거만 획득하면 우리가 원하는 로그를 기록하

는것이 가능한 상태이다.

 

1. 로거

 

로거(Logger)는 로깅 시스템의 시작점으로, 로그 메시지를 처리하기 위해 메시지를 담아두는 저장소라고 할 수 있다. 모든 로거는

이름을 가지고 있다. 또한 로거는 로그 레벨을 갖게 되는데, 이는 로그 메시지의 중요도에 따라 자신이 어느 레벨 이상의 메시지를

처리할지에 대한 기준이 된다.

 

파이썬의 로그 레벨

로거에 저장되는 메시지를 로그 레코드라고 하며, 로그 레코드 역시 그 메시지의 심각성을 나타내는 로그 레벨을 가진다.

로그 레코드는 로그 이벤트에 대한 메타 정보도 가질 수 있는데, 일례로 스택 트레이스 정보나 에러 코드 등을 담을 수 있다.

 

메시지가 로거에 도착하면, 로그 레코드의 로그 레벨과 로거의 로그 레벨을 비교한다. 로그 레코드의 로그 레벨이 로거 자체의 

로그 레벨과 같거나 그보다 높으면 메시지 처리를 계속 진행하고, 더 낮으면 그 메시지는 무시된다. 이렇게 로그 레코드와 로거의

로그 레벨을 비교하여 메시지 처리를 진행하는 것으로 결정되면 로거는 메시지를 핸들러에게 넘겨준다.

 

 

2. 핸들러

 

핸들러는 로거에 있는 메시지에 무슨 작업을 할지 결정하는 엔진이다. 즉, 메시지를 화면이나 파일 또는 네트워크 소켓 등 어디에 

기록할 것인지와 같은 로그 동작을 정의한다. 핸들러도 로거와 마찬가지로 로그 레벨을 가지고 있다. 로그 레코드의 로그 레벨이

핸들러의 로그 레벨보다 더 낮으면 핸들러는 메시지를 무시한다.

 

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

다른 방식의 로그 처리가 가능하다. 예를 들어 ERROR 또는 CRITICAL 메시지는 표준 출력으로 보내는 핸들러를 하나 만들고,

차후 분석을 위해 ERROR 및 CRITICAL 메시지를 포함한 모든 메시지를 파일에 기록하는 또 다른 핸들러를 만들 수도 있다.

 

 

3. 필터

 

로그 레코드가 로거에서 핸들러로 넘겨질 때, 필터를 사용해서 로그 레코드에 추가적인 제어를 할 수 있다. 기본 제어 방식은 로그 

레벨을 지정하여 그 로그 레벨에 해당되면 관련 로그 메시지를 처리하는 것이다. 그런데 필터를 적용하면 로그 처리 기준을 추가할 

수 있다. 예를 들어 필터를 추가하여 ERROR 메시지 중에서 특정 소스로부터 오는 메시지만 핸들러로 넘길 수 있다.

필터를 사용하면 로그 레 코드를 보내기 전에 수정하는 것도 가능하다. 예를 들어 어떤 조건에 맞으면 ERROR 로그 레코드를 

WARNING 로그 레벨로 낮춰주는 필터를 만들 수도 있다.

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

 

 

4. 포맷터

 

로그 레코드는 최종적으로 텍스트로 표현되는데, 포멧터는 텍스트로 표현 시 사용할 포맷을 지정해준다. 포맷터는 보통 파이썬의

포맷 스트링을 사용하지만, 사용자 정의 포맷터도 만들 수 있다.

 

 

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

 

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

 

 

장고에서 로깅 설정은 settings.py 파일에 작성한다. 로그 메시지를 기록하기 위해, 아래처럼 로거를 취득하고 적절한 위치에서

로깅 메소드를 호출하면 된다.

 

 

logging.getLogger() 메소드를 호출하면 로거 객체를 얻을 수 있따. 로거 객체는 이름을 가지며, 로거를 계층화할 때 이름으로

각 로거를 구분한다. 앞의 예시에서는 mylogger라는 이름의 로거를 사용하였다.

 

관행적으로 로거 이름에 _ _ name _ _ 구문을 사용하는데, 이는 이 구문이 있는 파일의 파이썬 모듈 경로를 말한다. 

ch3/polls/views.py 파일에서 이 구문을 사용하고 있다면 __name__ 변수값은 polls.views가 된다. 로깅 호출을 모듈 단위로 

처리할 수 있어서 많이 사용하는 구문이다. 이렇게 모듈 단위로 로그를 기록하고 싶으면 로거를 구분하는 이름을 아래처럼 도트(.)

방식으로 명명해주면 된다.

 

logger = logging.getLogger('project.interesting.stuff')

 

도트 방식의 로거 이름은 계층화를 이룬다. 즉, project.interesting.stuff 로거의 부모는 project.interesting 로거이고, project.interesting 의 부모는 project 로거가 된다. 참고로 로거의 이름을 빈 문자열('')로 지정하면 파이썬의 최상위 로거(루트

로거)가 된다.

 

로거의 계층화가 왜 중요할까 로깅 호출은 부모 로거에게 전파되기 때문이다. 이렇게 하면 로거 트리의 최상단 루트 로거에서 

핸들러 하나만을 만들어도 하위 로거의 모든 로깅 호출을 잡을 수 있다. 위 예에서 project 이름 공간에 정의된 로그 핸들러는 

project.interesting 로거 및 project.interesting.stuff 로거가 보내주는 모든 로그 메시지를 잡을 수 있다. 이런 로깅 호출의 

전파는 로거 단위로 제어할 수 있는데, 트정 로거에서 상위 로거로 전파되는 것을 원하지 않으면 전파 기능을 비활성화 가능.

 

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

 

- logger.debug() : DEBUG 레벨의 로그 레코드를 생성한다.

- logger.info() : INFO 레벨의 로그 레코드를 생성한다.

- logger.warning() : WARNING 레벨의 로그 레코드를 생성한다.

- logger.error() : ERROR 레벨의 로그 레코드를 생성한다.

- logger.critical() : CRITICAL 레벨의 로그 레코드를 생성한다.

 

이외의 두 가지 로깅 메소드가 추가 가능

 

- logger.log() : 원하는 로그 레벨을 정해서 로그 메시지를 생성한다.

- logger.exception() : 익셉션 스택 트레이스 정보를 포함하는 ERROR 레벨의 로그 메시지를 생성한다.

 

 

6. 장고의 디폴트 로깅 설정

 

로깅 메소드를 호출했을 때 로그 메시지를 원하는 대로 기록하기 위해서는 당연히 로거, 핸들러, 필터, 포맷터 등을 설정해야 한다.

파이썬의 로깅 라이브러리는 다양한 설정 방식을 제공하고 있느데, 장고는 그중에서 사전형 설정(dictConfig) 방식을 디폴트로 

사용한다. 이 방식은 settings.py 파일의 LOGGING 항목에, 로깅 속성을 사전 형식으로 정의하게 된다. 이러한 설정에는 로거, 핸들러, 필터, 포맷터에 대한 정의뿐만 아니라, 각 컴포넌트의 로그 레벨과 같은 속성들도 정의한다.

 

settings.py 파일에 LOGGING 항목을 지정하지 않으면 장고는 디폴트 로깅 설정을 사용한다. 우리가 로깅 설정을 할 때, 이러한 

디폴트 로깅 설정을 유지할 수도 있고, 디폴트 설정을 무시하고 새로 로깅 설정을 할 수도 있다.

1.  설정이 dictConfig version 1 형식인데, 현재는 버전이 하나뿐이다.

 

2. 기존의 로거들을 비활성화하지 않는다. 이전 버전과의 호환성을 위한 항목들로, 디폴트는 True 이며, True로 유지하면 기존

로거들을 비활성화 한다. 비활성화의 의미는 로거들을 삭제하는 것이 아니고, 로깅 동작만을 중지시키는 것이므로 혼동의 여지가

있다. 따라서 장고는 이 항목들을 False로 사용하도록 권장한다.

 

3. 필터 2개를 정의한다.

 

4. require_debug_false 필터는 DEBUG=False 인 경우만 핸들러가 동작하도록 한다. 특별키 ()의 의미는, 필터 객체를 생성하기

위한 클래스를, 파이썬의 기본 클래스와는 다르게 장고에서 별도로 정의했다는 것을 알려준다.

 

5. require_debug_true 필터는 DEBUG=True 인 경우만 핸들러가 동작하도록 한다.

 

6. 포맷터 1개를 정의한다.

 

7. django.server 포맷터는 로그 생성 시각과 로그 메시지만을 출력한다. 특별키 ()의 의미는 포맷터 객체를 생성하기 위한 클래스

를 별도로 정의했다는 것을 알려준다.

 

8. 3개의 핸들러르 정의한다.

 

9. console 핸들러는 INFO 레벨 및 그 이상의 메시지를 표준 에러로 출력해주는 StreamHandler 클래스를 사용한다. 

이 핸들러는 require_debug_true 필터를 사용한다.

 

10. django.server 핸들러는 INFO 레벨 및 그 이상의 메시지를 표준 에러로 출력해주는 StreamHandler 클래스를 사용한다.

이 핸들러는 django.server 포맷터를 사용한다. 14에서 설명하는 django.server 로거에서 이 핸들러를 사용한다.

 

11. mail_admins 핸들러는 ERROR 및 그 이상의 로그 메시지를 사이트 관리자에게 이메일로 보내주는 AdminEmailHandler

클래스를 사용한다. 이 핸들러는 require_debug_false 필터를 사용한다.

 

12. 2개의 로거를 정의한다.

 

13. django 로거는 INFO 및 그 이상의 로그 메시지를 console 및 mail_admins 핸들러에게 보낸다. django.* 계층 즉,

django 패키지의 최상위 로거이다.

 

14. django.server. 로거는 INFO 레벨 및 그 이상의 메시지를 django.server 핸들러에게 보낸다. 상위 로거로 로그 메시지를

전파하지 않는다. 이 로거는 장고의 개발용 웹 서버인 runserver에서 사용하는 로거이다. 5XX 응답은 ERROR 메시지로, 4XX

응답은 WARNING 메시지로, 그 외는 INFO 메시지로 출력된다.

 

위 디폴트 설정 내용을 보면 다음과 같은 사항을 알 수 있다.

 

- 만일 DEBUG = True 이면 django.* 계층에서 발생하는 로그 레코드는 INFO 레벨 이상일 때 콘솔로 보내진다.

- 만일 DEBUG = False 이면 django.* 계층에서 발생하는 로그 레코드는 ERROR 레벨 이상일 때 관리자에게 이메일 전송된다.

- django.server 로거는, DEBUG 값에 무관하게, 로그 레코드가 INFO 레벨 이상이면 콘솔로 보낸다. django.* 계층의 다른 

로거들과는 다르게 django 로거로 전파하지 않는다.

 

 

7. 장고의 로깅 추가 사항 정리

 

앞 절에서 설명한 내용 이외에도 장고 패키지에는 몇 가지가 더 추가되었다. 

 

장고에서 추가한 로거들

 

- django.reqeust 로거 : 요청 처리와 관련된 메시지를 기록한다. 5XX 응답은 ERROR 메시지로, 4XX 응답은 WARNING 메시지로

발생한다. 이 로거에 담기는 메시지는 2개의 추가적인 메타 항목을 가진다.

  - status_code : HTTP 응답 코드

  - requess : 로그 메시지를 생성하는 요청 객체

 

- django.server 로거 : 앞 절에서 설명

- django.template 로거 : 템플릿을 렌더링하는 과정에서 발생하는 로그 메시지를 기록한다.

- django.db.backends 로거 : 데이터베이스와 관련된 메시지를 기록한다. 예를 들어, 애플리케이션에서 사용하는 모든 SQL

문장들이 이 로거에 DEBUG 레벨로 기록된다. 이 로거에 담기는 메시지는 아래처럼 추가적인 메타 항목들을 가진다.

성능상의 이유로, SQL 로깅은 settings.DEBUG 항목인 True인 경우만 활성화된다.

  - duration : SQL 문장을 실행하는 데 걸린 시간

  - sql : 실행된 SQL 문장

  - params : SQL 호출에 사용된 파라미터

 

- django.security.* 로거 : 사용자가 보안 측면에서 해를 끼칠수 있는 동작을 실행한 경우, 이에 대한 메시지를 기록한다.

예를 들어, HTTP Host 헤더가 ALLOWED_HOSTS에 없다면 장고는 400  응답을 리턴하고, 에러 메시지가 

django.security.DisallowedHost 로거에 기록된다.

 

- django.db.backends.schema 로거 : 데이터베이스의 스키마 변경 시 사용된 SQL 쿼리를 기록한다.

 

장고에서 추가한 핸들러는 1개이다.

  - AdminEmailHandler : 앞에서 설명함

 

장고는 또한 3개의 필터를 추가하였다.

  - CallBackFilter : 이 필터를 콜백 함수를 지정해서 필터를 통과하는 모든 메시지에 대해 콜백 함수를 호출해준다. 콜백 함수의

리턴값이 False이면 메시지 로깅은 더 이상 처리하지 않는다.