[python] logging

hyunsooo·2021년 10월 20일
0

logging?

logging은 프로그램이 작동할 때 발생하는 여러가지 사건들을 추적할 수 있는 기능을 가진 모듈이다.
우리는 이런 사건을 보고 어떤식으로 해결을 해야할지 판단을 할 수 있다.

logging모듈에서 사용하는 용어로 level이 있는데, 발생하는 여러 사건들 중에서 어떤 사건이 중요한지 등급을 나눈 것 이다.

Level설명
DEBUG문제를 진단하고 싶을 때 필요한 자세한 정보를 기록
INFO예상대로 작동하고 있다는 것을 확인 기록
WARNING예상치 못한 일이 발생하거나, 발생 할 것으로 예측된다는 것을 기록 ex) <디스크 공간 부족>
ERROR소프트웨어의 몇몇 기능들이 수행을 못함을 기록
CRITICAL작동이 불가능한 수준의 심각한 에러 발생을 기록

로깅출력

상황방법
일반적인 콘솔 출력print()
프로그램 실행 중 발생하는 정상적인 이벤트 알림logging.info()
실행 중 발생한 이벤트와 관련한 경고문제를 해결할 수 있는 경우 warnings.warn(), 처리할 수 없는 경우 logging.warning()
실행중 발생한 이벤트와 관련한 에러raise Exception : 예외를 일으킴
예외를 발생시키지 않고 에러를 보고logging.error(), logging.exception(), logging.critical()

기본 level은 WARNING이다.
모듈을 다르게 구성하지 않는 한, 이 수준 이상의 이벤트만 추적할 수 있다.

기본 level이 WARNING이기 때문에 info의 메세지는 출력되지 않는다.
root부분에 대한 내용은 아래에서 설명합니다.

파일에 로깅

위와 같은 이벤트를 파일에 기록하는 방법을 살펴본다.

import logging

logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.INFO)
logging.debug('debug print')
logging.info('info print')
logging.warning('warning print')
logging.error('error print')
logging.critical('critical print')
INFO:root:info print
WARNING:root:warning print
ERROR:root:error print
CRITICAL:root:critical print

위와 같은 메세지가 example.log파일에 저장된다.

메세지 포맷 변경

import logging

logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
logging.debug('debug print')
logging.info('info print')
logging.warning('warning print')
logging.error('error print')
logging.critical('critical print')
2021-10-19 17:07:23,717:INFO:info print
2021-10-19 17:07:23,717:WARNING:warning print
2021-10-19 17:07:23,717:ERROR:error print
2021-10-19 17:07:23,717:CRITICAL:critical print

결과를 보면 위에서 발생한 root가 사라졌음을 알 수 있다.

fomat에 나타낼 수 있는 형식

이름포맷설명
asctime%(asctime)s<2021-10-19 16:32:45,896> 형식
created%(created)ftime.time()과 같은 형식
filename%(filename)spathname의 파일명 부분
funcName%(funcName)s로깅 호출을 포함하는 함수의 이름
levelname%(levelname)sDEBUG, INFO, WARNING,ERROR, CRITICAL 메세지 텍스트
levelno%(levelno)s레벨 별 숫자 (debug:10, info:20, warning:30, error:40, critical:50)
message%(message)s로그 된 메세지 Formatter.format()이 호출 될 때 설정
name%(name)s로깅 호출에 사용된 로거의 이름
pathname%(pathname)s로깅 호출이 일어난 소스 파일의 경로명

format에 asctime을 사용할 때, datefmt 인자를 제공하여 형식을 바꿔줄 수 있다.
datefmt 인자의 형식은 time.strftime()과 같다.

import logging

logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', level=logging.INFO)
logging.debug('debug print')
logging.info('info print')
logging.warning('warning print')
logging.error('error print')
logging.critical('critical print')
10/19/2021 05:26:56 PM:INFO:info print
10/19/2021 05:26:56 PM:WARNING:warning print
10/19/2021 05:26:56 PM:ERROR:error print
10/19/2021 05:26:56 PM:CRITICAL:critical print

고급

이런 기능은 로거 이름이 패키지/모듈 계층을 추적할 수 있다는 것을 의미하며, 로거 이름으로부터 이벤트가 기록되는 위치를 직관적으로 알 수 있다.

로깅의 구성요소

  • Logger : 로그 메세지 생성 및 전달
  • Handler : Logger로 부터 전달 된 LogRecord를 처리하며 적절한 위치로 보냄
  • Filter : 출력 로그 필터링
  • Formatter : 출력 형태 지정

logger

Logger의 세 가지 역할

  1. 로그를 생성할 수 있는 method 제공 ( Logger.debug(), Logger.info(), Logger.error()...)

  2. Logger객체는 Filter 객체에 따라 어떤 로그 메세지를 처리할 지 결정

  3. 로그 관련 메세지(LoggRecord 인스턴스)를 Handler에 전달

로깅은 Logger 클래스(로거) 인스턴스의 메서드를 호출하여 사용할 수 있다.
각 인스턴스에는 이름(원하는 어떤 것이든)을 부여할 수 있고, 점(마침표)를 사용하여 클래스의 부모-자식 관계를 나타낼 수 있다.
예를 들어, A라는 로거는 A.B, A.C 로거의 부모이다.

logger = logging.getLogger(__name__)

이런 기능은 로거 이름이 패키지/모듈 계층을 추적할 수 있다는 것을 의미하며, 로거 이름으로부터 이벤트가 기록되는 위치를 직관적으로 알 수 있다.

  • name이 전달 되는 경우, 해당 이름에 해당하는 Logger를, 주어지지 않으면 root를 받는다.

  • name은 마침표(.)로 구분되는 계층구조를 가지고 있다.

  • 로거를 생성하고 logger.setLevel()로 레벨을 설정할 수 있다.

  • 레벨이 정해지지 않은 경우, 부모 Logger의 레벨을 사용한다. (끝에 root Logger는 기본값이 WARNING)

  • 자식 Logger는 메세지를 부모 Logger에게 전달한다. 부모 Logger에 handler가 설정되어 있는 경우, 자식 Logger에서 handler를 다시 설정해야 한다.
    ( Logger.propagate = False로 이 과정을 막을 수 있다.)

Logger 일반적인 구성

  • Logger.setLevel() : 로거가 처리할 가장 낮은 수준을 지정한다.
    ( debug -> info -> warning -> error -> critial )

  • Logger.addHandler(), Looger.removeHandler() : 로거 객체에서 handler 객체를 추가하고 제거한다. handler가 없는 경우도 있으며 여러개의 handler가 하나의 로거에 추가되는 경우도 있다.
    (handler에 대해서는 아래에서 자세히 알아본다.)

  • Logger.addFilter(), Logger.removeFilter() : 로거 객체에서 filter 객체를 추가하고 제거한다.

로그 메시지 생성

로거 객체가 구성된 상태에서 로그 메시지를 만드는 방법

  • Logger.debug(),Logger.info(),Logger.warning(), Logger.error(),Logger.critical()

위 방법은 모두 메시지와 메서드 이름에 해당하는 수준으로 로그 레코드를 생성한다.
메시지는 포맷 문자열이며, %s, %d, %f등표준 문자열 치환 문법을 사용할 수 있다.

  • Logger.exception()Logger.error()는 비슷한 메시지를 생성하지만 차이점은 Logger.exception()을 사용하면 스택 트레이스를 포함한 로그를 발생시킨다. 따라서 Logger.exception()은 Exception handler에서만 사용한다.

  • Logger.log()는 사용자 정의 로그 수준으로 로깅하는 방법이다.

Handler

Handler 객체는 로그 메시지의 Level을 기반으로 적절한 로그 메시지를 Handler에 지정된 대상으로 전달하는 역할을 한다.

Logger 객체는 addHandler() 메서드를 사용하여 0개 혹은 그 이상의 Handler 객체를 추가할 수 있다.
예를 들어, 한개의 Handler는 모든 로그 메시지를 로그 파일로 메시지를 보내고, 다른 Handler는 심각한 에러(critical) 메시지를 전자 메일 주소로 보내는 역할을 할 수 있습니다.

다양한 역할을 하는 Handler는 Useful Handler에서 참조할 수 있다.

Handler에는 개발자가 직접 신경 써야 할 메서드가 거의 없다.
사용자 정의 Handler를 만들지 않는 이상, 관련이 있는 메서드는 아래와 같다.

  • setLevel() : 로거 객체에서와 마찬가지로 적절한 목적지로 보내지는 Level을 지정한다.
    Logger 객체에서 설정된 Level은 Handler에 전달할 메시지를 판별하는 역할을 한다.
    각 Handler 객체에서 설정된 Level은 전송할 메시지를 결정한다.

  • setFormatter() : Handler가 사용할 formatter 객체를 선택한다.

  • addFilter(), removeFilter() : 각각 Handler에서 filter 객체를 구성하고 삭제한다.

Formatter

formatter 객체는 로그 메시지의 최종 순서, 구조 및 내용을 구성한다.
logging.basicConfig처럼 기본 fmt, datefmt옵션을 사용할 수 있으며 인스턴스화 시킬 수 있다.

logging.Formatter(fmt=None, datefmt=None, style='%')

style은 <%> , <{>, <$> 중 하나를 선택할 수 있으며 default는 <%>로 설정되어 있다.

<%> : LogRecord
<{> : str.format
<$> : string.Template.substitute

실제 구성

로깅을 구성하는 3가지 방법

  1. 위에 나열된 메서드를 호출하는 코드를 사용하여 만든다.

  2. 로깅 구성 파일을 만들고 fileConfig()함수를 사용하여 파일을 읽어서 만든다.

  3. 구성 정보의 딕셔너리를 만들고, dictConfig()함수에 전달한다.

python code

import logging

#create logger
logger = logging.getLogger('Test')
logger.setLevel(logging.INFO)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create fomatter
formatter = logging.Formatter(
    fmt='%(asctime)s-%(name)s-%(levelname)s-%(message)s',
    datefmt='%Y-%m-%d | %H:%M:%S'
)

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

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

출력 :

2021-10-20 | 10:43:27-Test-INFO-info message
2021-10-20 | 10:43:27-Test-WARNING-warning message
2021-10-20 | 10:43:27-Test-ERROR-error message
2021-10-20 | 10:43:27-Test-CRITICAL-critical message

file handler 추가

import logging

#create logger
logger = logging.getLogger('Test')
logger.setLevel(logging.INFO)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create file handler and set level to error
ch2 = logging.FileHandler(filename='test.log')
ch2.setLevel(logging.ERROR)

# create fomatter
formatter = logging.Formatter(
    fmt='%(asctime)s-%(name)s-%(levelname)s-%(message)s',
    datefmt='%Y-%m-%d | %H:%M:%S'
)

# add formatter to ch
ch.setFormatter(formatter)
ch2.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)
logger.addHandler(ch2)

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

출력 1 ( stream handler )

2021-10-20 | 10:48:23-Test-INFO-info message
2021-10-20 | 10:48:23-Test-WARNING-warning message
2021-10-20 | 10:48:23-Test-ERROR-error message
2021-10-20 | 10:48:23-Test-CRITICAL-critical message

출력 2 ( file handler )

test.log 파일이 생성되며, 그 안에는 아래와 같은 로그 기록이 출력된다.

2021-10-20 | 10:48:23-Test-ERROR-error message
2021-10-20 | 10:48:23-Test-CRITICAL-critical message

dictConfig

  • 딕셔너리 구성
{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "basic":{
            "format" : "%(asctime)s:%(name)s:%(levelname)s:%(message)s",
            "datefmt" : "%Y-%m-%d %H:%M:%S"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.SteramHandler",
            "level": "DEBUG",
            "formatter": "basic"
        },
        "file_error":{
            "class": "logging.FileHandler",
            "level": "ERROR",
            "formatter":"basic"
        }
    },
    "loggers":{
        "__main__":{
            "level": "INFO",
            "handlers":["console", "file_error"]
            "propagate": true
        }
    }

}
  • version - 스키마 버전, 현재 유효한 값은 1이다.

  • disable_existing_loggers - fileConfig(), dictConfig()를 사용하면 기존의 logger들은 비활성화 되기 때문에 이를 방지하기 위한 옵션이다.

  • fomatters - 각 키는 formatter의 ID이고 각 값은 Formatter인스턴스를 구성하는 방법이다.
    foramt
    datefmt
    style
    validate

  • handlers - 각 키는 handler의 ID이고 각 값은 Handler인스턴스를 구성하는 방법이다.
    class(필수) : Handler의 완전한 이름
    level(선택) : Handler의 Level
    foramtter(선택) : Handler에 적용할 formatter ID
    filters(선택) : Handler에 적용할 filter ID list

한번 사용된 설정들을 다른 모듈에서 다시 사용하고 싶을 경우, 위와 같이 딕셔너리 형태의 파일을 통해 적용할 수 있다. 실제로 적용하는 방법은 아래와 같다.

import logging
import logging.config
import json

config = json.load(open('./config.json'))
logging.config.dictConfig(config)

logger = logging.getLogger(__name__)

출처

profile
CS | ML | DL

0개의 댓글