logging
을 알아본 이유자동화 툴이나 api를 개발하다보면 내가 짠 코드가 잘 돌아가고 있는지, 어디서 문제인지 알고 싶어질 때가 있다. 파이썬을 처음 배웠을 때부터 최근까지 대부분 vsc의 디버그 모드
로 하나씩 뜯어보거나 의심가는 부분에 print
를 붙여 출력문을 보곤 했다.
하지만, 작성하는 파일이 많아지거나 계층적 구조를 띄게 되면서 점점 단순한 방법으로는 커버를 할 수가 없어졌다. 따라서 좀 더 알기 쉽고 추후 장애 대응에도 유연하게 대처하기 위해 파이썬의 logging
을 알아보기로 했다.
print
와 logging
의 차이"프로그램이 코드의 그 부분을 통과한지 터미널을 통해 알고싶다" 정도라면 그냥 print문을 쓰는게 편할 것이다. 실제 logging을 쓰려면 아래와 같은 수요 정도는 있어야 한다.
이렇게, 프로그램의 동작 로그를 좀 더 플렉서블하게 출력하고 싶은 경우에는, python에 이미 내장되어있는 logging 모듈을 사용하면 좋다.
logging
의 사용법기본적인 logging의 사용법은 많은 블로그에서 설명하고 있으며, 심지어 파이썬 공식문서에서도 나름 알기 쉽게 쓰여있으니 꼭 한 번 읽어보길 바란다.
간단하게 요약만 하자면,
가장 기본 logger인 root logger를 쓸 때는 아래와 같이 작성하면 된다.
import logging
logging.{로그를 남기고 싶은 수준}("{어떤 에러 메시지를 남길 건지}")
#example
logging.warning("Watch out!")
logging.error("You should fix it!")
기본 logger 말고 커스텀 logger를 쓸 때는 아래와 같이 작성하면 된다.
import logging
logger = logging.getLogger({지정하고 싶은 logger 이름})
#example
logger = logging.getLogger("my_logger")
logger
란위의 이미지는 아래와 같은 순서로 이해하면 된다.
다만, System-B, System-C 등이 추가되면 문제가 생긴다.
docker나 pipenv 등 여러개의 python이 제대로 분리되어있는 환경이 아니라면 위의 2번과 5번 때문에 에러가 생길 수 있다.
왜냐하면, 기본적으로 컴퓨터에는 1개의 python.exe밖에 존재하지 않는데, 여러개의 시스템이 모두 같은 python.exe를 사용하게 되면 System-A
에게 속해있는 핸들러와 System-B
에게 속해있는 핸들러가 충돌하여 생각지도 못 한 버그가 생길 수도 있기 때문이다.
따라서, 직접 logging을 사용하는 것이 아닌, logger
를 사용하는 것이다.
그래서 위에서 얘기한 root logger나 커스텀 logger를 따로 지정해서 사용해야 한다는 것이다.
logger
의 출력 레벨 설정import logging
logger = logging.getLogger("my_logger")
# my_logger가 출력하는 최저출력 레벨을 DEBUG 레벨로 설정
logger.setLevel(logging.DEBUG)
logger.info("I am info log.")
logger.warning("I am warning log.")
DEBUG=10 / INFO=20 / WARNING=30 / ERROR=40 / CRITICAL=50 이라서 logger.setLevel(10)
이런식으로도 표현할 수 있다.
Handler
핸들러란, logger가 로그 출력을 할 떄 사용하는 도구 같은 것으로, 아래와 같은 종류가 있다.
핸들러 인스턴스 | logger에 부여되는 기능 |
---|---|
StreamHandler | 터미널에 출력 |
FileHandler | 파일로 출력 |
SMTPHandler | 특정 메일주소로 송신출력 |
HTTPHandler | HTTP서버로 출력 |
기본적인 사용법은 아래와 같다.
import logging
logger = logging.getLogger("my_logger")
logger.setLevel(logging.DEBUG)
# StreamHandler를 인스턴스화
st_handler = logging.StreamHandler()
# StreamHandler의 최저출력 레벨을 DEBUG로 설정
st_handler.setLevel(logging.DEBUG)
# FileHandler를 인스턴스화
fl_handler = logging.FileHandler(filename="sample.log", encoding="utf-8")
# FileHandler의 최저출력 레벨을 WARNING으로 설정
fl_handler.setLevel(logging.WARNING)
# 인스턴스화 한 핸들러를 my_logger에게 전달
logger.addHandler(st_handler)
logger.addHandler(fl_handler)
logger.info("I am info log.")
logger.warning("I am warning log.")
Handler
가 출력하는 출력포맷너무 많기 때문에 링크를 남긴다. 예시 코드는 아래와 같다.
import logging
logger = logging.getLogger("mny_logger")
logger.setLevel(logging.DEBUG)
format = "%(levelname)-9s %(asctime)s [%(filename)s:%(lineno)d] %(message)s"
st_handler = logging.StreamHandler()
st_handler.setLevel(logging.DEBUG)
# StreamHandler에서 나오는 로그의 포맷을 format에 저장한 형태로 설정
st_handler.setFormatter(logging.Formatter(format))
fl_handler = logging.FileHandler(filename="sample.log", encoding="utf-8")
fl_handler.setLevel(logging.WARNING)
# FileHandler에서 나오는 로그의 포맷을 format에 저장한 형태로 설정
fl_handler.setFormatter(logging.Formatter(format))
logger.addHandler(st_handler)
logger.addHandler(fl_handler)
logger.info("I am info log.")
logger.warning("I am warning log.")
SMTPHandler
와 HTTPHandler
import logging
# SMTPHandler
logger = logging.getLogger()
logger.addHandler(SMTP_SSLHandler(
mailhost=['{이메일 서버}', '{포트번호}'],
fromaddr='{보내는이 이메일 주소}',
toaddrs=['{받는이 이메일 주소}'],
subject='{이메일 제목}',
credentials=['{유저 네임}', '{유저 비번}'],
secure=() # SSL 또는 TLS를 사용하려면 ('tls',) 또는 ('ssl',)로 설정 )
)
import logging
# HTTPHandler(Slack을 예시로)
import logging
import logging.handlers
SLACK_TOKEN = 'xoxb-YOUR_BOT_TOKEN-COMES_HERE'
class SlackHandler(logging.handlers.HTTPHandler):
def __init__(self, token, channel='#general', emoji=True):
super().__init__(host='slack.com', url='/api/chat.postMessage', secure=True)
self.token = token
self.channel = channel
self.emoji = emoji
def mapLogRecord(self, record):
if self.formatter is None: # Formatter가 설정되지 않은 경우
text = record.msg
else:
text = self.formatter.format(record)
emoji = (
'' if self.emoji == False else
':bug:' if record.levelname == 'DEBUG' else
':pencil2:' if record.levelname == 'INFO' else
':warning:' if record.levelname == 'WARNING' else
':no_entry:' if record.levelname == 'ERROR' else
':rotating_light:' if record.levelname == 'CRITICAL' else
''
)
return {
'token': self.token,
'channel': self.channel,
'text': f'{emoji} {text}',
'as_user': True,
}
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
fmt='%(asctime)s *%(module)s* : %(message)s',
datefmt='%H:%M:%S',
)
slack_handler = SlackHandler(SLACK_TOKEN)
slack_handler.setFormatter(formatter)
logger.addHandler(slack_handler)
logger.debug('디버깅 로그입니다.')
logger.info('일반 로그입니다.')
logger.warning('경고 로그입니다.')
logger.error('에러 로그입니다.')
try:
1/0
except:
logger.exception('예외처리 로그입니다.')
위의 코드는 여기서 가져왔다.
https://docs.python.org/ko/3/howto/logging.html
https://docs.python.org/ko/3/library/logging.handlers.html#
https://hwangheek.github.io/2019/python-logging/
https://jh-bk.tistory.com/40