파이썬 logging

seoyeon hwang
8 min readAug 7, 2022

--

혼자 개발할때와 회사에서 개발할때의 가장 큰 차이점 중 하나가 바로 로깅이 아닐까 싶다. 혼자 개발할때는 여기저기 print를 남발했다면 회사에서는 logging을 통해 조금 더 체계적으로 로그를 남기고 있다. logging 잘 써보자..!

logging 왜 쓸까?

애플리케이션에 로깅을 잘 사용하면 개발할 때 코드의 흐름을 더 잘 이해하고, 생각하지 못했던 에러가 발생했을때 더 많은 인사이트를 얻을 수 있다.

단순히 print로 출력할때보다 logging을 사용하면 IP주소나 발생 시간같은 더 많은 정보를 얻을 수 있고, 중요도에 따라 로그를 분류할 수 있다. 또한, 코드를 직접 수정하지 않고 로그를 컨트롤할 수 있다.

logging module

파이썬은 기본 라이브러리로 logging 모듈을 제공하기 때문에 쉽고 빠르게 로깅을 추가할 수 있다. logging 모듈을 import 하면 메시지를 로깅할 수 있는 logger 를 사용할 수 있다. 해당 logger는 module-level로 정의되어있는 root-logger로 기본 레벨이 warning으로 설정되어있다.

레벨이란 로그의 중요도를 나타내는 기준으로, logging 모듈은 다섯가지 레벨을 가지고 있다. 레벨을 설정함으로써 로그를 중요도에 따라 관리할 수 있다. 다섯가지 레벨을 중요도가 낮은 순서대로 나열하면 다음과 같다.

  • DEBUG : 디버깅할 때 필요한 자세한 정보
  • INFO : 정상적으로 동작하고 있음을 나타내는 정보
  • WARNING : 예상하지 못한 일이 일어났음을 나타내는 정보
  • ERROR : 함수를 실행하지 못할 정도의 심각한 문제
  • CRITICAL : 프로그램이 실행하지 못할 정도의 심각한 무넺
import logginglogging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

이때 logger에 설정된 레벨보다 낮은 중요도의 로그는 출력되지 않고 무시된다. 예를 들어, 위의 코드에서 레벨이 warning 으로 설정된 root-logger를 사용하였기때문에 그보다 낮은 debuginfo 메시지는 출력되지 않는다.

root-logger 설정 바꾸기

root-logger의 설정을 바꾸고 싶다면 baseConfig 메소드를 사용하면 된다. baseConfig 메소드에서 가장 자주 쓰이는 파라미터는 다음과 같다.

  • level: 출력하고자 하는 로그의 레벨
  • filename: 로그를 저장하는 파일 이름
  • filemode: filename 파일을 열 때 사용하는 모드 (default는 ‘a’)
  • format: 로그 메시지 포맷
import logging

logging.basicConfig(
level=logging.DEBUG,
filename='app.log',
filemode='w',
format='[%(asctime)s] %(name)s - %(levelname)s - %(message)s'
)
# DEBUG 레벨 로그도 출력됨
logging.debug('This will get logged')

나만의 logger 정의하기

하나의 애플리케이션에서 여러 개의 logger를 사용하고 싶은 경우 root-logger외에 나만의 logger를 정의해서 사용할 수 있다

logging 모듈에서 로그 이벤트는 LogRecord 형태로 Logger 에서 Handler 를 거쳐 Formatter 순서대로 전달된다. root-logger와 달리 baseConfig 가 아닌 HandlerFormatter 를 사용하여 설정을 변경할 수 있다.

1) Logger

Logger 는 애플리케이션 코드에서 바로 함수들을 호출할 수 있는 클래스로 인터페이스를 제공한다. logging.getLogger(name) 을 호출하면 Logger 객체를 얻을 수 있다.

2) LogRecord

LogRecord 는 로거 이름, 함수 이름, 메시지와 같은 로깅되는 정보를 담고있는 클래스다. logging.warning() 과 같은 형태로 호출할 때 LogRecord 객체를 얻을 수 있다.

3) Handler

HandlerLogRecord 를 콘솔이나 파일과 같은 목적지로 전달하는 클래스다. 로그를 어디로 보낼지에 따라 적절한 StreamHandler, FileHandler, SMTPHandler, HTTPHandler 등 handler를 사용할 수 있다. 이때, 하나의 logger에 여러 개의 handler를 지정하면 서로 다른 목적지로 전달할 수도 있다.

4) Formatter

FormatterLogRecord 의 포맷을 지정하는 클래스다.

예시

다음은 로그를 sys.stdout 으로 출력하고 file.log 파일에 저장하는 커스텀 로거를 정의한 예제 코드다.

import logging

# Create a custom logger
logger = logging.getLogger(__name__)

# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler('file.log')
c_handler.setLevel(logging.WARNING)
f_handler.setLevel(logging.ERROR)

# Create formatters and add it to handlers
c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)

# Add handlers to the logger
logger.addHandler(c_handler)
logger.addHandler(f_handler)

logger.warning('This is a warning')
logger.error('This is an error')

예제 코드에서 로그를 정의할 때 사용한 __name__ 변수는 해당 로거 파일에서는 __main__으로 출력되고, 로거 파일이 다른 모듈에서 import 되었다면 import한 모듈 이름이 된다. 즉, __name__ 이 모듈의 계층구조를 나타내기 때문에 로거 이름으로부터 어디서 로깅되었는지 직관적으로 알 수 있다.

설정 파일로 정의하기

위의 예제 코드처럼 코드로 로거를 정의할 수 도 있지만 fileConfig 로 로그 설정을 할 수도 있다. 설정 파일을 사용하면 로깅 설정을 더 쉽게 할 수 있다.

먼저, 아래와 같이 logging.conf 파일에 원하는 스펙으로 loggers, handlers, formatters를 정의한다.

[loggers]
keys=root,sampleLogger

[handlers]
keys=consoleHandler

[formatters]
keys=sampleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)

[formatter_sampleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

그 다음, 아래와 같이 fileConfig에 설정 파일을 연결하면 이름이 simpleExample 인 로거를 사용할 수 있다. 설정 파일과 정의하는 코드가 분리되어있어서 훨씬 간편해보인다 :)

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

--

--