파이썬 logging
혼자 개발할때와 회사에서 개발할때의 가장 큰 차이점 중 하나가 바로 로깅이 아닐까 싶다. 혼자 개발할때는 여기저기 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를 사용하였기때문에 그보다 낮은 debug
와 info
메시지는 출력되지 않는다.
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
가 아닌 Handler
와 Formatter
를 사용하여 설정을 변경할 수 있다.
1) Logger
Logger
는 애플리케이션 코드에서 바로 함수들을 호출할 수 있는 클래스로 인터페이스를 제공한다. logging.getLogger(name)
을 호출하면 Logger
객체를 얻을 수 있다.
2) LogRecord
LogRecord
는 로거 이름, 함수 이름, 메시지와 같은 로깅되는 정보를 담고있는 클래스다. logging.warning()
과 같은 형태로 호출할 때 LogRecord
객체를 얻을 수 있다.
3) Handler
Handler
는 LogRecord
를 콘솔이나 파일과 같은 목적지로 전달하는 클래스다. 로그를 어디로 보낼지에 따라 적절한 StreamHandler
, FileHandler
, SMTPHandler
, HTTPHandler
등 handler를 사용할 수 있다. 이때, 하나의 logger에 여러 개의 handler를 지정하면 서로 다른 목적지로 전달할 수도 있다.
4) Formatter
Formatter
는 LogRecord
의 포맷을 지정하는 클래스다.
예시
다음은 로그를 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')