처음 접한 파이썬 로깅(logging)

jomminii_before·2020년 6월 17일
3

실무를 접할 때 새로웠던 것 중 하나는 로깅이었다. 공부를 하면서 프로젝트를 진행할 때는 기껏해봐야 print를 찍어가면서 내 코드가 어느정도 실행이 되고, 또 어디서 에러가 나는지 파악을 했었는데, 실무에서는 이러한 작업을 로깅으로 진행하고 있었다.

로깅은 어떤 소프트웨어가 실행될 때 발생하는 이벤트를 추적하는 수단입니다. 소프트웨어 개발자는 코드에 로깅 호출을 추가하여 특정 이벤트가 발생했음을 나타냅니다. 이벤트는 선택적으로 가변 데이터 (즉, 이벤트 발생마다 잠재적으로 다른 데이터)를 포함할 수 있는 설명 메시지로 기술됩니다. 이벤트는 또한 개발자가 이벤트에 부여한 중요도를 가지고 있습니다 - python 3.8.3 document

위의 로깅에 대한 설명을 보면 내가 print로 해결하려고 했던 일과 모두 겹치는 것을 알 수 있다.


🔎 그래서 로깅을 어떻게 사용하는건데?

파이썬은 사랑스럽게도 logging 모듈을 기본으로 제공하고 있어서 쉽게 사용할 수 있다.


1. 로깅 인스턴스 생성

먼저 로깅을 사용하기 위해 인스턴스를 생성합니다. getLogger의 인자로는 만들고 싶은 로거 이름을 적으면 되는데, 적지 않으면 root로 생성됩니다.

import logging

def __get_logger():
"""로거 인스턴스 반환
"""

    __logger = logging.getLogger('logger')

2. 로깅 레벨 설정

그리고 로깅 레벨을 설정해야합니다. 여기서 레벨은 로깅을 출력해 줄 역치를 설정한다고 보면 될 것 같습니다. 특정 레벨을 설정하면, 그 레벨 이상의 로깅만 출력하게 됩니다.

따로 설정하지 않으면 기본 레벨은 WARNING으로 지정되어 있습니다. 이때 INFO 수준으로 로깅 코드를 작성하면 콘솔에 출력되지 않습니다.

#  예시
import logging
logging.warning('Watch out!')
logging.info('I told you so')
# 콘솔 출력
WARNING:root:Watch out!

로그 레벨은 아래와 같이 5가지가 있습니다.

level 사용할 때
DEBUG 상세한 정보. 보통 문제를 진단할 때만 필요합니다.
INFO 예상대로 작동하는지에 대한 확인.
WARNING 예상치 못한 일이 발생했거나 가까운 미래에 발생할 문제(예를 들어 〈디스크 공간 부족〉)에 대한 표시. 소프트웨어는 여전히 예상대로 작동합니다.
ERROR 더욱 심각한 문제로 인해, 소프트웨어가 일부 기능을 수행하지 못했습니다.
CRITICAL 심각한 에러. 프로그램 자체가 계속 실행되지 않을 수 있음을 나타냅니다.

출처 - python 3.8.3 document

로그 레벨은 다음과 같이 정의할 수 있습니다. <로거 인스턴스>.setLevel(logging.<로그 레벨>)

    # 로그 레벨 정의
    __logger.setLevel(logging.DEBUG)

3. 로깅 포매팅 설정

로깅 메시지도 포매팅 설정을 통해 커스터마이징 할 수 있습니다. 파이썬의 문자열 포매팅처럼 말이죠.

    # 로그 포맷 정의
    formatter = logging.Formatter(
        'LOG##LOGSAMPLE##%(levelname)s##%(asctime)s##%(message)s >> @@file::%(filename)s@@line::%(lineno)s')
# 로그 출력 예시
> LOG##LOGSAMPLE##INFO##2020-06-12 14:40:32, 790##startProgram >> @@file::sample.py@@line::250

사용할 수 있는 어트리뷰트는 아래와 같습니다.

어트리뷰트 이름

포맷

설명

args

직접 포맷할 필요는 없습니다.

message 를 생성하기 위해 msg 에 병합되는 인자의 튜플. 또는 (인자가 하나뿐이고 딕셔너리일 때) 병합을 위해 값이 사용되는 딕셔너리.

asctime

%(asctime)s

사람이 읽을 수 있는, LogRecord 가 생성된 시간. 기본적으로 〈2003-07-08 16:49:45,896〉 형식입니다 (쉼표 뒤의 숫자는 밀리 초 부분입니다).

created

%(created)f

LogRecord 가 생성된 시간 (time.time() 이 반환하는 시간).

exc_info

직접 포맷할 필요는 없습니다.

예외 튜플 (sys.exc_info 에서 제공) 또는, 예외가 발생하지 않았다면, None.

filename

%(filename)s

pathname 의 파일명 부분.

funcName

%(funcName)s

로깅 호출을 포함하는 함수의 이름.

levelname

%(levelname)s

메시지의 텍스트 로깅 수준 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').

levelno

%(levelno)s

메시지의 숫자 로깅 수준 (DEBUG, INFO, WARNING, ERROR, CRITICAL).

lineno

%(lineno)d

로깅 호출이 일어난 소스 행 번호 (사용 가능한 경우).

message

%(message)s

로그 된 메시지. msg % args 로 계산됩니다. Formatter.format() 이 호출 될 때 설정됩니다.

module

%(module)s

모듈 (filename 의 이름 부분).

msecs

%(msecs)d

LogRecord 가 생성된 시간의 밀리 초 부분.

msg

직접 포맷할 필요는 없습니다.

원래 로깅 호출에서 전달된 포맷 문자열. args 와 병합하여 message 를 만듭니다. 또는 임의의 객체 (임의의 객체를 메시지로 사용하기 를 보세요).

name

%(name)s

로깅 호출에 사용된 로거의 이름.

pathname

%(pathname)s

로깅 호출이 일어난 소스 파일의 전체 경로명 (사용 가능한 경우).

process

%(process)d

프로세스 ID (사용 가능한 경우).

processName

%(processName)s

프로세스 이름 (사용 가능한 경우).

relativeCreated

%(relativeCreated)d

logging 모듈이 로드된 시간을 기준으로 LogRecord가 생성된 시간 (밀리 초).

stack_info

직접 포맷할 필요는 없습니다.

현재 스레드의 스택 바닥에서 이 레코드를 생성한 로깅 호출의 스택 프레임까지의 스택 프레임 정보 (사용 가능한 경우).

thread

%(thread)d

스레드 ID (사용 가능한 경우).

threadName

%(threadName)s

스레드 이름 (사용 가능한 경우).

출처 - python 3.8.3 document


4. 핸들러 설정

핸들러(처리기, handler)는 로그 메시지를 지정된 대상으로 전달하는 역할을 합니다. Logger 객체는 addHandler 메서드를 사용해 0개 이상의 핸들러를 자신에게 추가할 수 있습니다. 주로 사용하는 핸들러는 StreamHandlerFileHandler가 있습니다. 이 외 핸들러에 대해서는 python 3.8.3 document - 유용한 처리기 부분을 확인해 보세요!

  • StreamHandler : 스트림(터미널과 같은 콘솔창으로 보면 됨)에 로그 메시지를 보냄
  • FileHandler : 특정 파일에 로그 메시지를 보내 저장시킴

핸들러를 적용하는 방법은 아래와 같습니다. 사용할 핸들러를 정의한 후, 3번에서 작성한 포매터를 핸들러에 적용한 다음 addHandler 메서드를 통해 로거에 핸들러를 넣어줍니다.

    # 스트림 핸들러 정의
    stream_handler = logging.StreamHandler()
    # 각 핸들러에 포멧 지정
    stream_handler.setFormatter(formatter)
    # 로거 인스턴스에 핸들러 삽입
    __logger.addHandler(stream_handler)

실제 사용

위에서 만들어 본 로거 인스턴스를 정의하는 함수는 아래와 같습니다.

import logging

def __get_logger():
    """로거 인스턴스 반환
    """

    __logger = logging.getLogger('logger')

    # 로그 포멧 정의
    formatter = logging.Formatter(
        'BATCH##AWSBATCH##%(levelname)s##%(asctime)s##%(message)s >> @@file::%(filename)s@@line::%(lineno)s')
    # 스트림 핸들러 정의
    stream_handler = logging.StreamHandler()
    # 각 핸들러에 포멧 지정
    stream_handler.setFormatter(formatter)
    # 로거 인스턴스에 핸들러 삽입
    __logger.addHandler(stream_handler)
    # 로그 레벨 정의
    __logger.setLevel(logging.DEBUG)

    return __logger

이제 이 함수를 이용해서 로거 인스턴스를 만든 후, 로거를 작성하고 싶은 위치에 메시지를 정의해주면 됩니다.

# 로거 정의
logger = __get_logger()

# 원하는 위치에 로그 메시지 작성 - info level로 아래의 메시지를 출력하겠다.
logger.info("[BATCH EXCHANGE WAIT] %s @@RunTime[%.02f] @@SendCount[%d]" % (
        __BATCH_MODE, running_time, send_count))

처음 찾아볼때는 이게 뭔가 싶었는데, 직관적으로 잘 사용할 수 있게 모듈이 만들어져 있어서 금방 이해할 수 있었습니다. 이제 print 말고 logger를 써봅시다!

참고 자료

profile
https://velog.io/@jomminii 로 이동했습니다.

1개의 댓글

comment-user-thumbnail
2020년 6월 20일

오호 잘보고 갑니다~

답글 달기