이번에는 python의 로깅을 자세히 파고들어봤다.
지난번 tqdm에 이어서 파이썬 로깅 라이브러리인 rich를 실제 프로젝트에 적용해보았다.
우선 가상환경에 라이브러리를 설치한다.
pip install rich
다음 명령어로 설치 여부를 확인해 줄 수 있다.
python -m rich
로깅 포맷
logging
모듈을 사용할 때에는 로깅 메시지의 포맷을 커스터마이징 할 수 있다.
사용 가능한 어트리뷰트는 공식 문서에 자세히 나와있다.
ex)
"%(asctime)s - %(levelname)s — %(funcName)s:%(lineno)d — %(message)s"
# 위 포매팅 설정 결과는 아래와 같다.
# 2021-07-01 12:29:53,182 - INFO - <module>:1 - hello world
로깅 핸들러
핸들러(Handler)는 로깅 메시지를 특정 대상으로 전달하는 수단이다. logging
모듈을 통해 생성하는 Logger
객체에 원하는 만큼의 핸들러를 추가할 수 있다.
각각의 핸들러에는 별도의 설정을 통해 다른 포맷의 메시지를 전달할 수도 있다.
일반적으로 많이 사용하는 핸들러는 FileHandler
와 StreamHandler
이다.
FileHandler
: 디스크 내 파일에 로깅 메시지를 전달하여 저장한다.StreamHandler
: 스트림(ex. 콘솔창)에 로그 메시지를 전달해 출력한다.
본 글에서는 rich
모듈을 이용하여 StreamHandler
를 대체한다.
로깅 인스턴스 생성하기
현재의 목표는 다음과 같다.
- 콘솔에 출력될 StreamHandler로 rich 모듈의 RichHandler를 추가한다.
- 특정 경로에 저장하는 FileHandler를 추가한다.
- FileHandler에는 여러 파일이 추가될 수 있고, 같은 파일을 접근할 때 중복이 일어나지 않도록 한다.
예시 코드는 다음과 같다.
Logger/myLogger.py
import logging
import logging.handlers
from rich.logging import RichHandler
RICH_FORMAT = "[%(filename)s:%(lineno)s] >> %(message)s"
FILE_HANDLER_FORMAT = "[%(asctime)s]\t[%(levelname)s] %(message)s\t>>[%(filename)s:%(funcName)s:%(lineno)s]"
def set_logger(log_path) -> logging.Logger:
logging.basicConfig(
level=logging.INFO,
format=RICH_FORMAT,
handlers=[RichHandler(rich_tracebacks=True)]
)
logger = logging.getLogger(log_path)
if len(logger.handlers) > 0:
return logger
file_handler = logging.FileHandler(log_path, mode="a", encoding="utf-8")
file_handler.setFormatter(logging.Formatter(FILE_HANDLER_FORMAT))
logger.addHandler(file_handler)
return logger
def handle_exception(exc_type, exc_value, exc_traceback):
logger = logging.getLogger("rich")
logger.error("Unexpected exception",
exc_info=(exc_type, exc_value, exc_traceback))
set_logger(log_path)
: 이 함수는 로거를 설정하고 반환한다.
로그를 파일과 콘솔에 출력하며, 파일 핸들러는 지정된 log_path
파일에 로그를 기록한다.
만약 이미 핸들러가 추가되어 있다면 새로운 핸들러를 추가하지 않는다.
그리고 이 함수는 설정된 로거 객체를 반환한다.
logging.basicConfig()
에서는 로거의 기본 설정을 한다.
handle_exception
함수는 예외가 발생했을 때 해당 예외 정보를 로그에 기록한다.
만약 이 함수를 적지 않으면, 예외는 파일에 저장되지 않고 stream handler를 통해서만 작성된다.
또 위 코드에서 다음 부분이 굉장히 중요하다.
logger = logging.getLogger(log_path)
if len(logger.handlers) > 0:
return logger
위 코드 없이 로거를 사용 중, 한 파일에서 찍혀야 할 로그가 여러 파일에서 찍히기도 했고 같은 로그가 여러번 찍히는 현상도 발생했다. ex) 로그1, 로그2, 로그2, 로그3, 로그3, 로그3, ...
이는 set_logger를 호출할 때마다 addHandler를 호출해 계속해서 logger 인스턴스를 중첩해 만들었기 때문이었다.
따라서 각각의 파일마다 하나의 로거 인스턴스를 생성해주기 위해 logger = logging.getLogger(log_path)
이 코드를 추가 했고, 한 로그 핸들러가 여러번 생성되지 않도록 하기 위해 다음 if 문을 추가했다.
reference
'♾️Language & Framework > 🐍Python' 카테고리의 다른 글
[Python] - Selenium과 Chrome Driver log 숨기기 (0) | 2024.02.14 |
---|---|
[Python / Trouble Shooting] - pymysql 등 파이썬 모듈이 import 안되는 문제 (0) | 2023.09.01 |