Python logging
What is Computer?/etc

Python logging

DeepLearning 관련 github python 코드를 참고하다가

<logger = logging.getLogger(__name__)>이라는 코드를 보고

logging에 대해 검색해보며 정리한 글입니다.

 

python 기초 로깅 자습서 | (자료 출처)

 

로깅(logging)이란?

소프트웨어가 실행될 때, 발생하는 event를 추적하는 수단

개발자 입장에서는, 코드에 logging을 추가해서, 특정 event가 발생했음을 나타낼 수 있음

개인적인 해석으로, logging은 시각적으로 보이는 출력과 보이지 않는 출력 모두를, 저장하고 옮기는 수단이 될 수 있음

 

logging의 함수?

debug(), info(), warning(), error(), critical() 이 있음

logging.info() or logging.debug()

 - 프로그램의 정상 작동 중에서 발생하는 event를 보고함 (상태 모니터링 or 결함 등 조사)

 - logging.debug()는 상태 진단 목적 등으로 아주 자세한 출력이 필요로 할 때 사용함.

▷warning.warn()

 - 문제를 생기지 않게 하거나 경고를 제거하기 위해서, 클라이언트 응용 프로그램이 수정되어야 하는 경우, 특정 실행시간 event와 관련하여 경고를 발행.

logging.warning()

 - 클라이언트 응용 프로그램이 할 수 있는 일이 없는 상황이지만 event를 계속 주목해야 하는 경우, 특정 실행시간 event와 관련하여 경고를 발행.

logging.error(), logging.exception(), logging.critical() 

 - 구체적인 에러 & 응용 프로그램 영역에 사용, 예외를 발생시키지 않고 에러의 억제를 보고함.

▷print()

 - 명령행 스크립트 또는 일반적인 출력을 보기 위해, 콘솔에서 출력을 확인.

 

logging 함수의 수준?

debug(), info(), warning(), error(), critical()와 같은 "logging의 함수"는 추적되는 event의 수준(Level) 또는 심각도(Levelname)에 따라 명명된 것임

 

수준 특징
DEBUG 상세한 정보를 보여줌. 보통 문제를 진단할 때만 필요함.
INFO 예상대로 작동하는지에 대한 확인.
WARNING 예상치 못한 일이 발생했거나 가까운 미래에 발생할 문제에 대한 표시를 해줌.
ERROR 더욱 심각한 문제로 인해 소프트웨어가 일부 기능을 수행하지 못함.
CRITICAL 심각한 에러. 프로그램 자체가 계속 실행되지 않을 수 있음을 나타냄.

기본 로깅의 수준은 "WARNING"

logging 패키지가 수정되지 않는 한, WARNING 수준 이상의 event만 추적됨

(가령, logging.basicConfig(level=logging.INFO) 와 같은 방법으로 logging 패키지를 수정해서 logging의 기본 수준을 INFO 수준으로 설정할 수 있음. 원래는 WARNING 수준이 default 값)

수준: DEBUG < INFO < WARNING < ERROR < CRITICAL (왼쪽으로 갈수록 깊음)

 

더보기

ex) WARNING 수준 이상의 event만 추적되는 것

import logging

logging.warning('Watch out!')  # will print a message to the console
logging.info('I told you so')  # will not print anything

Console-------------------------------------------------------------
WARNING:root:Watch out!

logging 함수의 기본 수준이 WARNING 이므로, INFO 메세지는 나타나지 않음

콘솔에는 "logging 함수의 수준"과 "logging 호출에 제공된 event의 설명(='Watch out!')"이 나타남

root는 root logger를 나타냄. 뒤에서 설명

 

더보기

ex) 파일에 logging 하기

import logging

logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)

logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like Øresund and Malmö')

Console-------------------------------------------------------------
DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too
ERROR:root:And non-ASCII stuff, too, like Øresund and Malmö

 

logging.basicConfig에서 "level"인자에 logging.DEBUG를 줌으로써, logging의 수준 임계값을 DEBUG로 설정했기 때문에, WARNING과 ERROR의 출력이 보여질 뿐만 아니라, DEBUG와 INFO의 수준도 보여짐.

(DEBUG < INFO < WARNING < ERROR < CRITICAL)

 

더보기

logging.basicConfig() 좀 더 알아보기

<참고>

기본 Formatter로 StreamHandler를 생성하고, root logger에 추가하여 logging 시스템의 기본 구성을 수행해줌.

함수 debug(), info(), warning(), error(), critical()은 root logger에 처리기가 정의되어 있지 않으면 자동으로 basicConfig()를 호출

Formatter란?

객체로써 LogRecord를 사람이나 외부 시스템이 해석 할 수 있는 문자열로 변환하는 역할을 함.

StreamHandler란?

logging 패키지에 있는 핵심 클래스로써, sys.stdout 또는 sys.stderr, 파일류 객체와 같은 Stream으로 logging의 출력을 보냄. (write()와 flush() 메서드를 지원하는 모든 객체에게 logging의 출력을 보냄)

 

LogRecord, Handler 등의 객체가 궁금하면 아래 사진과 글의 후반부 내용 참고

logging 흐름

 

 

더보기

ex) 여러 모듈(여러 .py 파일)에서 logging 활용

# myapp.py----------------------------------------------------------------
import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
    
# mylib.py-----------------------------------------------------------------
import logging

def do_something():
    logging.info('Doing something')
    
    
Console(myapp.py를 실행)-----------------------------------------------------
INFO:root:Started
INFO:root:Doing something
INFO:root:Finished


# py 파일 이름이 myapp이면, basicConfig 설정 해줄 때, filename인자로 myapp.log로 해야함
# 위 main() 함수의 첫째줄 코드 참고

 

logging 포멧?

%(levelname)s, %(message)s, %(asctime)s 등이 있음

logging.basicConfig() 함수에 format 인자로 위와 같은 문자열을 입력하면 됨.

더보기

ex) logging format

import logging

logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
logging.debug('This message should appear on the console')
logging.info('Hellow')
logging.warning('World')

Console-------------------------------------------------------------
INFO: Hellow
WARNING: World

%(levelname)s, %(message)s 포멧 (심각도, event 설명)

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

Console-------------------------------------------------------------
2021-03-25 15:557:42,612 is when this event was logged.

%(asctime)s 포멧 (날짜/시간)

 


 

logging 라이브러리는 모듈 방식으로 구성되며

Logger, LogRecord, Handler, Filter, Formatter와 같은 여러 범주의 객체가 있음

Logger 응용 프로그램 코드가 직접 사용하는 인터페이스를 노출
Handler Logger에 의해 만들어진 LogRecord를 적절한 목적지로 보냄
Filter 출력할 LogRecord를 결정하기 위한 보다 정밀한 기능을 제공
Formatter  최종 출력에서 로그 레코드의 배치를 지정

* log event의 정보는 LogRecord 인스턴스를 통해 Logger, Handler, Filter, Formatter에 전달됨

 

Logger?

지금까지 공부한 logging은 결국 Logger 클래스의 인스턴스의 메서드를 호출하여 수행하게 되는 것

예를 들어, do_somthing.py을 실행하면(=Logger의 인스턴스의 메서드를 호출하는 것) logging이 수행됨

Logger 클래스의 각 인스턴스에는 이름(__name__)이 있으며 마침표를 구분 기호로 사용하여 이름, 공간 계층 구조로 배열됩니다. 예를 들어 scan.text, scan.html, scan.pdf Logger의 부모는 scan이라는 Logger임

logger = logging.getLogger(__name__)

 Logger의 이름을 지을 때 사용하는 좋은 규칙은 logging을 사용하는 각 모듈에서 묘듈 수준 Logger를 사용하는 것. 위와 같은 <logger = logging.getLogger(__name__)>명령은 Logger의 이름(__name__)이 패키지/모듈 계층을 추적한다는 것을 의미하고, Logger 이름으로부터 event가 기록되는 위치를 직관적으로 알 수 있음

 위 코드에서 getLogger()는 이름이 제공되는 경우, 지정된 이름을 가진 logger 인스턴스에 대한 참조를 반환하고, 그렇지 않으면 root를 반환. 또한,이름이 foo인 logger가 주어지면, foo.scr, foo.bar, foo.bar.baz 의 이름을 가진 logger는 모두 foo의 자손

Logger의 역할을 보면,

응용 프로그램이 실행시간에 메세지를 기록할 수 있도록 여러 메서드를 응용 프로그램 코드에 노출함

Logger 객체는 Levelname(debug, info, warning, error, critical)에 따라 어떤 log message를 처리할지 결정

또한 관련 log message를 관심 있는 모든 log handler로 전달

 

Handler?

Handler 객체는 적절한 log message를 handler의 지정된 대상으로 전달하는 역할

Logger 객체는 addHandler()라는 메서드를 사용하여 handler 객체를 추가

예를 들어, 응용 프로그램이 모든 log message를 log file로 보내고, error와 그 이상의 모든 log message를 표준 출력으로 보내고, critical 메세지를 전자 메일 주소로 보냄

( Handler 객체가 하는 일은 debug, info, warning, error, critical와 같은 logging수준에 따라 출력되는 log message를, handler의 지정된 대상, 즉 log file이나 전자 메일 주소 등으로 보낼 수 있다는 것)

더보기

유용한 Handler 정리

StreamHandler Stream(파일류 객체)에 message를 보냄
FileHandler 디스크 파일에 message를 보냄
SocketHandler  TCP/IP 소켓에 메시지를 보냄
DatagramHandler UDP 소켓에 메시지를 보냄
SMTPHandler   지정된 전자 우편 주소로 메시지를 보냄
SysLogHandler  유닉스 syslog 데몬(원격 기계에 있는 것도 가능)에 메시지를 보냄
NTEventLogHandler 윈도우 NT/2000/XP 이벤트 로그에 메시지를 보냄
MemoryHandler 메모리에 있는 버퍼에 메시지를 보내는데, 특정 기준이 만족 될 때마다 플러시 됨
HTTPHandler GET 또는 POST 을 사용해서 HTTP 서버에 메시지를 보냄
WatchedFileHandler  logging하고 있는 파일을 감시. 파일이 변경되면 닫히고 파일 이름을 사용하여 다시 열림. 이 처리기는 유닉스 계열 시스템에서만 유용
QueueHandler queue 또는 multiprocessing 모듈에 구현된 것과 같은 큐로 메시지를 보냄
NullHandler
에러 메시지로 아무것도 하지 않음. 라이브러리 개발자가 로깅을 사용하지만, 라이브러리 사용자가 로깅을 구성하지 않으면 표시될 수 있는 〈No handlers could be found for logger XXX〉 라는 메시지를 피하려고 할 때 사용

 

 

Formatter?

Formatter 객체는 log message의 최종 순서, 구조 및 내용을 구성

위에서 본 "ex) logging format"에서 나온 바와 같이 

더보기

ex) logging format

import logging

logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
logging.debug('This message should appear on the console')
logging.info('Hellow')
logging.warning('World')

Console-------------------------------------------------------------
INFO: Hellow
WARNING: World

basicConfig() 함수의 format인자에서 Formatter 객체를 사용한 것을 볼 수 있음

 


프로그래밍할 때의 logging 구성?

더보기

파이썬에서 제공하는 logging-tutorial 먼저보기

import logging

# logger 생성
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# handler(처리기) 생성 후 logging 수준을 debug로 설정
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# formatter 생성
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch (생성한 formatter를 이용해 handler에 추가함 = log message 포멧을 설정멧을 설정)
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message').

Console----------------------------------------------------------------------------------
$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

Formatter에서 설정한 포멧대로 출력된 것을 볼 수 있음.

StreamHandler는 거의 모든 파일류 객체에 log message 출력을 보내는 handler임.

그리고 이름을 simple_example로 설정한 logger의 출력임을 알 수 있음.

 

  1. 구성 메서드를 호출하는 파이썬 코드를 사용하여 로거, 처리기 및 포매터를 명시적으로 만듬
  2. 로깅 구성 파일을 만들고, fileConfig() 함수를 사용하여 그것을 읽음
  3. 구성 정보의 딕셔너리를 만들고, dictConfig() 함수에 전달

 

더보기

logging.conf 라는 logging 구성 파일을 이용

import logging
import logging.config

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

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

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

Console-----------------------------------------------------------------------------------
$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message
# logging.conf 파일 내용-----------------------------------------------------------------------

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

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

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