Logging Module
logging 모듈은 python 자체에 내장되어 있는 모듈이다. logging은 소프트웨어가 작동 중일 때 발생하는 여러 사건을 추적하고, 이를 통해 어떤 사건이 발생했는지에 따라 어떤 해결책을 내야하는지 판단한다.
def cal(a, b):
try:
result = a/b
except ZeroDivisionError:
logger.exception("Division by zero is not possible")
else:
return result
위 코드에서 b에 0을 대입하면 에러가 발생할 것이다. 개발 코드 중에 실수로 b에 0을 대입할 가능성이 있다면 언제 어떻게 에러가 발생하는지 기록으로 남겨두어야 한다. 디버깅을 편리하고 효율적으로 하기 위함이다.
Level 설정
logging은 level 설정을 통해 메시지의 중요도를 구분한다. 총 5개의 기본 level이 제공되는데, 설정에 변화를 주지 않는다면 WARNING이 기본 level로 지정되어 있다.
Level | 설명 |
DEBUG | 간단히 문제를 진단하고 싶을 때 필요한 자세한 정보를 기록함 |
INFO | 계획대로 작동하고 있음을 알리는 확인 메시지 |
WARNING | 소프트웨어가 작동은 하고 있지만, 예상치 못한 일이 발생했거나 할 것으로 예측된다는 것을 알림 |
ERROR | 중대한 문제로 인해 소프트웨어가 몇몇 기능들을 수행하지 못함을 알림 |
CRITICAL | 작동이 불가능한 수준의 심각한 에러가 발생함을 알림 |
logging work flow 확인
Log 사건 정보들은 LogRecord Instance 안에 있는 요소들 사이에서 전송된다.
- Logger : 어플리케이션 코드가 직접 사용할 수 있는 인터페이스를 제공함 - Handler : logger에 의해 만들어진 log 기록들을 적합한 위치로 보냄 - Filter : 어떤 log 기록들이 출력되어야 하는지를 결정함 - Formatter : log 기록들의 최종 출력본의 레이아웃을 결정함 |
logging은 Logger class의 Instance (=logger)를 선언하는 것으로 부터 시작한다. 각 logger는 name을 가지는데, 이 name들은 마침표를 통해 계층적 관계를 형성하게 된다. 예를 들어, Football.html이라는 logger가 있다고 한다면, 이는 Football이라는 logger가 html이라는 logger의 부모 역할을 하게 되는 것이다. python의 부모-자식 상속 관계를 투영한 것으로, 설정을 변화시키지 않으면 자식 logger는 부모 logger의 여러 특성들을 물려받게 된다.
이후 Handler를 통해 log 기록들을 어디에 표시하고, 어디에 기록할지 결정하게 된다. Filter는 logging 모듈을 간단히 사용할 때는 잘 쓰이지는 않지만 level보다 더 복잡한 필터링을 원할 때 사용된다.
Formatter는 실제로 출력되는 형식을 결정한다.
logger 생성
logger = logging.getLogger("name")
“name” 에는 String이 들어가는데, 아무것도 입력하지 않을 경우 root logger가 생성된다. root logger는 모든 logger의 부모와 같은 존재로, 다른 모든 logger는 설정을 변화시키지 않으면 root logger의 자식이다. root logger을 바로 사용할 수도 있지만, 기능과 목적에 따라 다른 logger들을 생성하는 것이 낫다.
logger.setLevel(logging.INFO) # logger에 level 부여
생성한 logger에 INFO level을 부여하였다. 이제 이 logger 객체는 INFO 이상의 메시지를 출력할 수 있다. level을 소문자로 바꾸어 메서드로 사용하면 메시지를 출력할 수 있다.
logger.info("Message")
여기까지 logger는 오직 console에만 메시지를 출력할 수 있을 뿐이다. 더욱 정교하게 만들기 위해서는 handler가 필요하다.
handler object는 log 메시지의 level에 따라 적절한 log 메시지를 지정된 위치에 전달 (dispatch)하는 역할을 수행한다.
logger는 addHandler 메서드를 통해 이러한 handler를 추가할 수 있다. handler는 기능과 목적에 따라 여러 개일 수 있으며, 각 handler는 다른 level과 다른 format을 가질 수도 있다.
handler의 종류는 15개 정도가 있는데, 가장 기본적인 것은 StreamHandler와 FileHandler이다. 전자는 Stream (console)에 메시지를 전달하고, 후자는 File (ex; info.log)에 메시지를 전달하는 역할을 한다.
logging.Formatter(
fmt = None, # 메시지 출력 형태. None일 경우 raw 메시지를 출력.
datefmt = None, # 날짜 출력 형태. None일 경우 '%Y-%m-%d %H:%M:%S'.
style = '%' # '%', '{', '$' 중 하나. `fmt`의 style을 결정.
)
# handler 객체 생성
stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler(filename="information.log")
# formatter 객체 생성
formatter = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# handler에 level 설정
stream_handler.setLevel(logging.INFO)
file_handler.setLevel(logging.DEBUG)
# handler에 format 설정
stream_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
자식 logger는 부모 logger와 관련된 handler로 메시지를 전파 (propagate)한다. 즉, 부모 logger의 설정은 자식 logger과 연결되어 있다. 이 때문에 사실 모든 logger에 대해 handler를 일일히 정의하고 설정하는 것은 불필요한 일이라고 볼 수 있다. 따라서 가장 효율적인 방법은 최상위 logger에 대해 handler 설정을 완료하고 때에 따라 자식 logger를 생성하는 것이 될 것이다. 만약 이러한 연결을 원치 않는다면, 아래와 같이 logger의 propagate attribute를 False로 설정해주면 된다.
logger.addHandler(stream_handler) # logger에 생성한 handler 추가
logger.addHandler(file_handler)
{ # json 파일 세팅
"version": 1,
"formatters": {
"basic": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "basic",
"stream": "ext://sys.stdout"
},
"file_handler": {
"class": "logging.FileHandler",
"level": "DEBUG",
"formatter": "basic",
"filename": "info.log"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file_handler"]
}
}
첫 문단에서 basic이라는 이름의 format은 앞으로 설정을 변경하지 않는 이상 [시간-logger이름-level이름-메시지] 형식으로 출력됨을 의미한다.
두 번째 문단과 세 번째 문단은 2개의 handler에 대한 설정이다. console은 말 그대로 console (Stream)에 출력되는 handler로, logging.StreamHandler class로 구성되며 위에서 설정한 basic format을 사용함을 알 수 있다. 이 handler의 level은 INFO이다. file_handler는 디렉토리 내에 info.log란 파일을 생성하여 로그를 기록하면서 저장하는 handler이다. 이 handler의 level은 DEBUG이다.
def make_logger(name=None):
#1 logger instance를 만든다.
logger = logging.getLogger(name)
#2 logger의 level을 가장 낮은 수준인 DEBUG로 설정해둔다.
logger.setLevel(logging.DEBUG)
#3 formatter 지정
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
#4 handler instance 생성
console = logging.StreamHandler()
file_handler = logging.FileHandler(filename="test.log")
#5 handler 별로 다른 level 설정
console.setLevel(logging.INFO)
file_handler.setLevel(logging.DEBUG)
#6 handler 출력 format 지정
console.setFormatter(formatter)
file_handler.setFormatter(formatter)
#7 logger에 handler 추가
logger.addHandler(console)
logger.addHandler(file_handler)
return logger
logger = make_logger()
logger.debug("test")
logger.info("test")
logger.warning("test")
Format 설정
logging 모듈은 이러한 log 기록들을 남길 때 굉장히 다양한 형식을 지원하고 있다. LogRecord 객체는 Logger에 의해 자동적으로 생성되며, 수동으로 생성하려면 makeLogRecord 메서드를 이용한다.
logging.LogRecord(name, level, pathname, lineno, msg, …)
pathname은 logging call이 만들어지는 소스 파일의 전체 pathname을 의미한다. lineno는 logging call이 만들어지는 소스파일의 라인 번호를 말한다. msg는 event description 메시지를 의미한다. LogRecord는 여러 속성 (attribute)을 갖고 있는데, 이 속성들은 format을 정의하는데 활용된다.
속성 이름 | format | 설명 |
asctime | %(asctime)s | 인간이 읽을 수 있는 시간 표시 |
created | %(created)f | logRecord가 만들어진 시간 |
filename | %(filename)s | pathname의 file 이름 부분 |
funcName | %(funcName)s | logging call을 포함하는 function의 이름 |
levelname | %(levelname)s | 메시지의 Text logging level (ex. INFO) |
lineno | %(lineno)d | logging call이 발생한 코드의 line 숫자 |
module | %(module)s | filename의 모듈 이름 부분 |
message | %(message)s | 메시지 |
name | %(name)s | logger의 이름 |
pathname | %(pathname)s | full pathname |
thread | %(thread)d | thread ID |
threadName | %(threadName)s | thread 이름 |
LOG_FORMAT = "[%(asctime)-10s] (줄 번호: %(lineno)d) %(name)s:%(levelname)s - %(message)s"
logging.basicConfig(format=LOG_FORMAT)
logger = logging.getLogger("setting")
logger.setLevel(20)
logger.info("sth happened")
https://www.machinelearningplus.com/python/python-logging-guide/
'Programming > Python' 카테고리의 다른 글
[Python] datetime (0) | 2022.08.17 |
---|---|
[Python] zip (0) | 2022.08.07 |
[Python] 날짜와 시간 다루기 (0) | 2022.02.15 |
[Python] pip (패키지 매니저) (0) | 2022.02.10 |
[Python] 가상환경 (pyenv / virtualenv / conda) (0) | 2022.02.10 |