결과를 매번 print
로 출력한지는 벌써 오래됐다. 파이썬을 쓰면서 기록을 남길 때 logging
을 사용한지는 좀 됐는데 문제는 너무 허술하게 배워서 아직까지도 정확하게는 어떻게 쓰는지 모른다. 무능하다 무능해
Logging
으로 뭘하냐
- 기초적인 사용법
- 구성요소
- 포맷팅하기
- 파일로 저장하기
- 에러가 날 걸 알고 있다. 하지만 코드는 계속 돌아갔으면 한다. 우짜누?
기초적인 사용법
import logging
logging.info("일하는 중")
logging.debug("개발자만 볼 수 있음")
logging.warn("쓰지 마라")
logging.error("조진듯.")
짠 이렇게 하면 여러분 콘솔창에 저 4줄이 나온다. 저렇게만 사용하면
INFO:root:일하는 중
구성요소
Logging 모듈에는 4개의 구성요소가 있다. 하지만 이 표현법을 좋아하지 않는다. 구성 요소라하니까 뭔가 짜임새 있게 짜놓은 모듈을 이해하기 힘들게 만드는 것 같다. "구성 요소"보다는 이 Logging을 customizing하는 방법의 구성 요소라고 표현하는 게 더 정확한 것 같다.
- Logger 메인이다. 이 Logger가 우리가 원하는 일을 수행해준다.
- Handlers 얘는 내가 사용하는게 두 가지다: StreamHandler, FileHandler
- StreamHandler의 경우는 이 스크립트에서 뽑아낸 모든 로그들을 스트림으로 보내준다. 쉽게 말해서 pipe로 bash에서 넘겨줄 수 있다는 뜻.
- FileHandler는 이 로그들을 하드에 저장한다. 보통
.log
확장자로 저장하게 된다.
- Filters 모든 로그에는 레벨이 있다. 찍히는 정보에 등급이 있다는 뜻. 공식 홈페이지에는 총 5개로 공지되어 있다. Filters의 역할은 레벨 기준에 따라 그 이상의 심각도를 가진 메시지만 출력할 수 있게 된다. 별도로 설정하지 않으면 WARN 이상의 레벨만 출력된다. INFO로 돌리고 싶으면
.setLevel
로 설정해줘야한다.- DEBUG 디버깅 용도다. 특별하게
exception
이라는 메소드가 있는데 얘는 우리가 평소에 에러 나면 볼 수 있는 에러메세지를 그대로 출력해준다. - INFO 코드가 어떻게 돌고있다~ 하는 것 공유한다. 뭐 epoch 별로 뭐가 돌아가고 있는지 찍을 때 쓰자.
- WARN 하면 안되지만 하고 있을 때, 하지만 코드는 돌 때. Deprecated한 함수 같은 걸 쓸 때 달아주자.
- ERROR 정말 에러.
- CRITICAL 제일 심각한 케이스로 코드가 돌아가지 않는 지경의 에러를 찍어주자.
- DEBUG 디버깅 용도다. 특별하게
- Formatter 최종적으로 이 결과물이 어떤 형태로 출력될지 정한다. 가령 [날짜] 돌아가는코드스레드 - 메시지 이런식으로 포맷팅을 하고 싶다면 포맷터에 적어주면 된다.
구성요소 별로 살펴보자.
Logger
기본적으로 logger 클래스가 모든 기록 행위를 담당한다. 그냥 import logging
을 하고 logging.log()
를 진행해줘도 되지만, 더 자세하게 사용하고 싶으면 처음에는 logger = logging.getLogger()
로 진행할 수 있다. 기록을 하는 방법은 .log()
로 할 수 있다. 하지만 이렇게 로깅을 하려면 심각도 레벨을 직접 적어줘야한다. 상술한 5개의 레벨은 각각 10/20/30/40/50의 레벨을 가진다. 그러니까 INFO 레벨의 메시지를 찍고 싶다면 logger.log(10, 메시지)
를 해야한다는 거다. 너무 귀찮으니 그냥 logger.info(메시지)
로 작성하자. 나머지 레벨도 마찬가지다.
getLogger
에서 인자는... 이름인데 별로 생각 없이 __name__
을 넣자. 이렇게 하면 현재 파일이 수행되는 스크립트의 이름을 기록하게 된다. 여기에 내가 기록하고 싶은 이름을 넣고 싶으면 문자열로 넣어주면 끝이다.
Handlers
찍힌 정보들을 어떻게 처리하느냐이다. 기본적으로 StreamHandler
가 들어가있다. 그래서 별일 없이 로그를 찍으면 콘솔에 찍히는 거다. 이걸 파일로 저장하고 싶으면 FileHandler를 사용하면 된다.
handler = FileHandler(파일이름)
을 사용하면handler
객체가 만들어진다. 보통 "~.log"로 지정한다.logger.addHandler(handler)
를 수행하면 이logger
에 FileHandler를 추가하는 것이다. 이러면 콘솔에도 찍히고 위에서 지정한 로그 파일에도 저장된다.- 특정 handler를 지우고 싶으면
logger.removeHandler(handler)
를 수행하면 된다. 기본적으로 콘솔에는 출력을 안하고 파일에만 저장하고 싶으면 (현재까지 알아본 거는) removeHandler로는 안되고 loggeing.basicConfig를 통해 수행할 수 있다. 여기서 filename을 지정해주면 StreamHandler는 알아서 작동 안한다 (=콘솔에는 안찍히고 .log파일만 생성한다.)
Filters
특정 레벨의 필터만 나오게 설정한다. 이건 logger.setLevel()
을 통해 수행할 수 있다. 숫자를 넣어줘도 되고 혹은 logging.INFO
같이 넣어줘도 관계 없다. 애초에 logging.INFO
에는 20이 저장되어 있다. 이러면 지정한 레벨 이상의 메시지만 저장/전송한다.
Formatter
어떤 방식으로 정보를 출력할지 지정한다. 문자열 안에 %(요소)
를 적어주면 된다. 이 요소에 대한 설명은 아래 링크에 있다.
LogRecord Attributes
[01/13/2022 20:39:55] INFO - main: On my way 로 출력하고 싶으면 formatter를 "[%(asctime)s] %(levelname)s - %(name)s: %(message)s"
로 설정해주면 된다. 이 Formatter는 setFormatter()
메소드로 Handler 객체에 달아준다.
여기서 한 번 꼬아서 생각하면, .log
파일과 콘솔에 출력할 때 다르게 레벨이나 포맷팅을 설정하고 싶으면 어떻게할까? 각 StreamHandler와 FileHandler에게 다른 .setFormatter
, .setLevel
을 걸어주면 된다. 그런데 Default로 StreamHandler가 설정되어 있어도 만들어낸 logger에서 StreamHandler를 찾을 수가 없다. 읭...
logging.basicConfig
이 모든 과정을 좀 쉽게 만들어주는 게 있다. 바로 몇 개의 configuration을 한 번에 셋업할 수 있는 basicConfig
라는 메소드다. 여기서 file_name
을 설정하면 해당 파일로 로그를 저장하고 StreamHandler를 사용하지 않는다. 동시에 사용하고 싶으면 후에 StreamHandler를 직접 추가해주면 된다.
import logging
logging.basicConfig(
filename="time.log",
format="[%(asctime)s] %(levelname)s - %(name)s: %(message)s",
datefmt="%m/%d/%Y %H:%M:%S",
level=logging.INFO,
)
logger = logging.getLogger(__name__)
stream_handler = logging.StreamHandler()
console_format = logging.Formatter("[%(asctime)s] %(levelname)s - %(message)s")
stream_handler.setLevel(logging.WARN)
stream_handler.setFormatter(console_format)
logger.addHandler(stream_handler)
이해해보면 쉽다.
설정 저장하기
이 모든 과정을 한 번 수행하면 좋은데 프로젝트마다 넣어줘야 하니까 귀찮을 수 있다. (좋은 말로 할 때 복붙하자) 현재 로깅 설정을 바로 저장하는 방법은 없는 것 같다. 나도 저장하고 쓰는 편은 아니라 (그냥 코드 복붙 ㅋ;) 잘 몰랐는데 보통 두 가지를 사용한다 - .conf
, .json
. 이 파일을 직접 로드해서 dict 형태의 객체를 logging.config.dictConfig(config)
로 불러오거나 파일의 경로를 문자열로 logging.config.fileConfig(path)로 넣어줘도 된다. 파일의 형식은 아래 같이 만든다.
에러도 찍고 싶은데 코드는 안 멈추고 싶어 아니 증말
try-except 구문을 떠올리자. except쪽에서 에러를 처리할 때 코드 중단 없이 실행하는 코드를 넣을 수 있다. 그런데 에러 메시지 자체를 저장하고 싶은 경우가 있다. 이 때 logger.error()로 설정하는 게 아니라, logger.exception으로 지정하면 에러 메시지 전체를 저장할 수 있다. 유후~ 이건 DEBUG 레벨로 처리되기 때문에 보통은 보이지 않는다. ERROR랑 다르다!
try:
42 / 0
except Exception as e:
logger.exception(e)
휴 나도 이해 못하고 대충 쓰고 살았다가 정리해놓으니까 좀 머리가 안 아파졌다. 프로젝트에는 잘 넣어놓고 정작 연구 코드 짤 떄는 이 모듈을 잘 몰라서 제대로 안 넣었다. 이제 제대로 정리해야지
참고로 wandb
모듈에 생기는 .log도 같이 적용되는 것 같다. 이건 자세히 알아봐야겠다.
'_SANDBOX' 카테고리의 다른 글
맥북 세팅하기 (0) | 2022.01.09 |
---|---|
GitHub Actions로 README.md 자동화하기 (2) | 2022.01.04 |