logging은 프로그램이 작동할 때 발생하는 여러가지 사건들을 추적할 수 있는 기능을 가진 모듈이다.
우리는 이런 사건을 보고 어떤식으로 해결을 해야할지 판단을 할 수 있다.
logging모듈에서 사용하는 용어로 level이 있는데, 발생하는 여러 사건들 중에서 어떤 사건이 중요한지 등급을 나눈 것 이다.
Level | 설명 |
---|---|
DEBUG | 문제를 진단하고 싶을 때 필요한 자세한 정보를 기록 |
INFO | 예상대로 작동하고 있다는 것을 확인 기록 |
WARNING | 예상치 못한 일이 발생하거나, 발생 할 것으로 예측된다는 것을 기록 ex) <디스크 공간 부족> |
ERROR | 소프트웨어의 몇몇 기능들이 수행을 못함을 기록 |
CRITICAL | 작동이 불가능한 수준의 심각한 에러 발생을 기록 |
로깅출력
상황 | 방법 |
---|---|
일반적인 콘솔 출력 | print() |
프로그램 실행 중 발생하는 정상적인 이벤트 알림 | logging.info() |
실행 중 발생한 이벤트와 관련한 경고 | 문제를 해결할 수 있는 경우 warnings.warn(), 처리할 수 없는 경우 logging.warning() |
실행중 발생한 이벤트와 관련한 에러 | raise Exception : 예외를 일으킴 |
예외를 발생시키지 않고 에러를 보고 | logging.error(), logging.exception(), logging.critical() |
기본 level은 WARNING이다.
모듈을 다르게 구성하지 않는 한, 이 수준 이상의 이벤트만 추적할 수 있다.
기본 level이 WARNING이기 때문에 info의 메세지는 출력되지 않는다.
root부분에 대한 내용은 아래에서 설명합니다.
위와 같은 이벤트를 파일에 기록하는 방법을 살펴본다.
import logging
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.INFO)
logging.debug('debug print')
logging.info('info print')
logging.warning('warning print')
logging.error('error print')
logging.critical('critical print')
INFO:root:info print
WARNING:root:warning print
ERROR:root:error print
CRITICAL:root:critical print
위와 같은 메세지가 example.log파일에 저장된다.
import logging
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
logging.debug('debug print')
logging.info('info print')
logging.warning('warning print')
logging.error('error print')
logging.critical('critical print')
2021-10-19 17:07:23,717:INFO:info print
2021-10-19 17:07:23,717:WARNING:warning print
2021-10-19 17:07:23,717:ERROR:error print
2021-10-19 17:07:23,717:CRITICAL:critical print
결과를 보면 위에서 발생한 root가 사라졌음을 알 수 있다.
fomat
에 나타낼 수 있는 형식
이름 | 포맷 | 설명 |
---|---|---|
asctime | %(asctime)s | <2021-10-19 16:32:45,896> 형식 |
created | %(created)f | time.time()과 같은 형식 |
filename | %(filename)s | pathname 의 파일명 부분 |
funcName | %(funcName)s | 로깅 호출을 포함하는 함수의 이름 |
levelname | %(levelname)s | DEBUG, INFO, WARNING,ERROR, CRITICAL 메세지 텍스트 |
levelno | %(levelno)s | 레벨 별 숫자 (debug:10, info:20, warning:30, error:40, critical:50) |
message | %(message)s | 로그 된 메세지 Formatter.format()이 호출 될 때 설정 |
name | %(name)s | 로깅 호출에 사용된 로거의 이름 |
pathname | %(pathname)s | 로깅 호출이 일어난 소스 파일의 경로명 |
format에 asctime을 사용할 때, datefmt 인자를 제공하여 형식을 바꿔줄 수 있다.
datefmt 인자의 형식은 time.strftime()과 같다.
import logging
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', level=logging.INFO)
logging.debug('debug print')
logging.info('info print')
logging.warning('warning print')
logging.error('error print')
logging.critical('critical print')
10/19/2021 05:26:56 PM:INFO:info print
10/19/2021 05:26:56 PM:WARNING:warning print
10/19/2021 05:26:56 PM:ERROR:error print
10/19/2021 05:26:56 PM:CRITICAL:critical print
이런 기능은 로거 이름이 패키지/모듈 계층을 추적할 수 있다는 것을 의미하며, 로거 이름으로부터 이벤트가 기록되는 위치를 직관적으로 알 수 있다.
로깅의 구성요소
Logger의 세 가지 역할
로그를 생성할 수 있는 method 제공 ( Logger.debug(), Logger.info(), Logger.error()...)
Logger객체는 Filter 객체에 따라 어떤 로그 메세지를 처리할 지 결정
로그 관련 메세지(LoggRecord 인스턴스)를 Handler에 전달
로깅은 Logger 클래스(로거) 인스턴스의 메서드를 호출하여 사용할 수 있다.
각 인스턴스에는 이름(원하는 어떤 것이든)을 부여할 수 있고, 점(마침표)를 사용하여 클래스의 부모-자식 관계를 나타낼 수 있다.
예를 들어, A라는 로거는 A.B, A.C 로거의 부모이다.
logger = logging.getLogger(__name__)
이런 기능은 로거 이름이 패키지/모듈 계층을 추적할 수 있다는 것을 의미하며, 로거 이름으로부터 이벤트가 기록되는 위치를 직관적으로 알 수 있다.
name
이 전달 되는 경우, 해당 이름에 해당하는 Logger를, 주어지지 않으면 root를 받는다.
name
은 마침표(.)로 구분되는 계층구조를 가지고 있다.
로거를 생성하고 logger.setLevel()
로 레벨을 설정할 수 있다.
레벨이 정해지지 않은 경우, 부모 Logger의 레벨을 사용한다. (끝에 root Logger는 기본값이 WARNING)
자식 Logger는 메세지를 부모 Logger에게 전달한다. 부모 Logger에 handler가 설정되어 있는 경우, 자식 Logger에서 handler를 다시 설정해야 한다.
( Logger.propagate = False
로 이 과정을 막을 수 있다.)
Logger 일반적인 구성
Logger.setLevel()
: 로거가 처리할 가장 낮은 수준을 지정한다.
( debug -> info -> warning -> error -> critial )
Logger.addHandler(), Looger.removeHandler()
: 로거 객체에서 handler 객체를 추가하고 제거한다. handler가 없는 경우도 있으며 여러개의 handler가 하나의 로거에 추가되는 경우도 있다.
(handler에 대해서는 아래에서 자세히 알아본다.)
Logger.addFilter(), Logger.removeFilter()
: 로거 객체에서 filter 객체를 추가하고 제거한다.
로그 메시지 생성
로거 객체가 구성된 상태에서 로그 메시지를 만드는 방법
Logger.debug()
,Logger.info()
,Logger.warning()
, Logger.error()
,Logger.critical()
위 방법은 모두 메시지와 메서드 이름에 해당하는 수준으로 로그 레코드를 생성한다.
메시지는 포맷 문자열이며, %s
, %d
, %f
등표준 문자열 치환 문법을 사용할 수 있다.
Logger.exception()
과 Logger.error()
는 비슷한 메시지를 생성하지만 차이점은 Logger.exception()
을 사용하면 스택 트레이스를 포함한 로그를 발생시킨다. 따라서 Logger.exception()
은 Exception handler에서만 사용한다.
Logger.log()
는 사용자 정의 로그 수준으로 로깅하는 방법이다.
Handler 객체는 로그 메시지의 Level을 기반으로 적절한 로그 메시지를 Handler에 지정된 대상으로 전달하는 역할을 한다.
Logger 객체는 addHandler() 메서드를 사용하여 0개 혹은 그 이상의 Handler 객체를 추가할 수 있다.
예를 들어, 한개의 Handler는 모든 로그 메시지를 로그 파일로 메시지를 보내고, 다른 Handler는 심각한 에러(critical) 메시지를 전자 메일 주소로 보내는 역할을 할 수 있습니다.
다양한 역할을 하는 Handler는 Useful Handler에서 참조할 수 있다.
Handler에는 개발자가 직접 신경 써야 할 메서드가 거의 없다.
사용자 정의 Handler를 만들지 않는 이상, 관련이 있는 메서드는 아래와 같다.
setLevel()
: 로거 객체에서와 마찬가지로 적절한 목적지로 보내지는 Level을 지정한다.
Logger 객체에서 설정된 Level은 Handler에 전달할 메시지를 판별하는 역할을 한다.
각 Handler 객체에서 설정된 Level은 전송할 메시지를 결정한다.
setFormatter()
: Handler가 사용할 formatter 객체를 선택한다.
addFilter()
, removeFilter()
: 각각 Handler에서 filter 객체를 구성하고 삭제한다.
formatter 객체는 로그 메시지의 최종 순서, 구조 및 내용을 구성한다.
logging.basicConfig처럼 기본 fmt
, datefmt
옵션을 사용할 수 있으며 인스턴스화 시킬 수 있다.
logging.Formatter(fmt=None, datefmt=None, style='%')
style
은 <%> , <{>, <$> 중 하나를 선택할 수 있으며 default는 <%>로 설정되어 있다.
<%> : LogRecord
<{> : str.format
<$> : string.Template.substitute
로깅을 구성하는 3가지 방법
위에 나열된 메서드를 호출하는 코드를 사용하여 만든다.
로깅 구성 파일을 만들고 fileConfig()
함수를 사용하여 파일을 읽어서 만든다.
구성 정보의 딕셔너리를 만들고, dictConfig()
함수에 전달한다.
import logging
#create logger
logger = logging.getLogger('Test')
logger.setLevel(logging.INFO)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create fomatter
formatter = logging.Formatter(
fmt='%(asctime)s-%(name)s-%(levelname)s-%(message)s',
datefmt='%Y-%m-%d | %H:%M:%S'
)
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')
출력 :
2021-10-20 | 10:43:27-Test-INFO-info message
2021-10-20 | 10:43:27-Test-WARNING-warning message
2021-10-20 | 10:43:27-Test-ERROR-error message
2021-10-20 | 10:43:27-Test-CRITICAL-critical message
file handler 추가
import logging
#create logger
logger = logging.getLogger('Test')
logger.setLevel(logging.INFO)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create file handler and set level to error
ch2 = logging.FileHandler(filename='test.log')
ch2.setLevel(logging.ERROR)
# create fomatter
formatter = logging.Formatter(
fmt='%(asctime)s-%(name)s-%(levelname)s-%(message)s',
datefmt='%Y-%m-%d | %H:%M:%S'
)
# add formatter to ch
ch.setFormatter(formatter)
ch2.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
logger.addHandler(ch2)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')
출력 1 ( stream handler )
2021-10-20 | 10:48:23-Test-INFO-info message
2021-10-20 | 10:48:23-Test-WARNING-warning message
2021-10-20 | 10:48:23-Test-ERROR-error message
2021-10-20 | 10:48:23-Test-CRITICAL-critical message
출력 2 ( file handler )
test.log 파일이 생성되며, 그 안에는 아래와 같은 로그 기록이 출력된다.
2021-10-20 | 10:48:23-Test-ERROR-error message
2021-10-20 | 10:48:23-Test-CRITICAL-critical message
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"basic":{
"format" : "%(asctime)s:%(name)s:%(levelname)s:%(message)s",
"datefmt" : "%Y-%m-%d %H:%M:%S"
}
},
"handlers": {
"console": {
"class": "logging.SteramHandler",
"level": "DEBUG",
"formatter": "basic"
},
"file_error":{
"class": "logging.FileHandler",
"level": "ERROR",
"formatter":"basic"
}
},
"loggers":{
"__main__":{
"level": "INFO",
"handlers":["console", "file_error"]
"propagate": true
}
}
}
version
- 스키마 버전, 현재 유효한 값은 1이다.
disable_existing_loggers
- fileConfig()
, dictConfig()
를 사용하면 기존의 logger들은 비활성화 되기 때문에 이를 방지하기 위한 옵션이다.
fomatters - 각 키는 formatter의 ID이고 각 값은 Formatter인스턴스를 구성하는 방법이다.
foramt
datefmt
style
validate
handlers - 각 키는 handler의 ID이고 각 값은 Handler인스턴스를 구성하는 방법이다.
class
(필수) : Handler의 완전한 이름
level
(선택) : Handler의 Level
foramtter
(선택) : Handler에 적용할 formatter ID
filters
(선택) : Handler에 적용할 filter ID list
한번 사용된 설정들을 다른 모듈에서 다시 사용하고 싶을 경우, 위와 같이 딕셔너리 형태의 파일을 통해 적용할 수 있다. 실제로 적용하는 방법은 아래와 같다.
import logging
import logging.config
import json
config = json.load(open('./config.json'))
logging.config.dictConfig(config)
logger = logging.getLogger(__name__)