본문 바로가기
목차
Python

[Python] logging

by ds31x 2023. 12. 18.
728x90
반응형

logging을 통해 프로그램 동작 상태 등을 로그로 남길 경우,

프로그램의 사후 진단을 보다 효과적으로 할 수 있기 때문에,

문제 분석이나 디버깅 등에 유용하게 사용할 수 있음.

 

logging을 사용할 경우,

  • 소스 코드의 수정 없이
  • 모든 정보를 한꺼번에 출력하는 것이 아닌,
  • 원하는 정보 레벨(log level로 지정) 이상의 로그 메시지를
  • 관련 이벤트 발생 시점과 함께 기록하는 것이 가능함.

장점

print문 을 사용하는 경우보다 다음의 장점을 가짐

  • log 기록을 여러 곳에 동시에 전송가능.
  • 출력 위치별로 다른 포맷을 지정할 수 있음.
  • 프로그램 실행 중에도 설정 변경을 통한 로그 출력방식 제어 가능 (동적 변경). 
  • 계층적 logger를 생성하여 로그 발생위치를 명확하게 파악 가능.
  • Lazy Formatting을 통해 실제 log가 기록되는 경우에만 연산이 수행됨.

log levels

Python의 logging은 다음의 5단계의 levels를 지원함.
낮은 단계의 log level이 지정된 경우 보다 높은(=심각한) log levels가 같이 기록됨.

Level 언제 사용되는가?
DEBUG 가장 낮은 심각도와 가장 상세한 수준의 정보를 가지고 있음.
주로 디버깅 등에서 문제의 원인을 파악하는데 사용됨.
INFO 프로그램이 정상적으로 작동하는지를 tracking하기 위한 메시지들.
WARNING 예상치 못한 event 또는 추후 문제가 될 수 있는 경우에 사용됨.
ERROR 프로그램이 의도한 기능을 수행하지 못하는 수준의 심각한 에러 발생시 사용.
CRITICAL 가장 심각한 수준의 에러가 발생한 경우로 프로그램의 실행이 중단될 수 있는 수준을 의미.

 


간단한 사용법

logging.basicConfig 로 최초로 한번만 설정해주고 난 이후에,

logging.debug, logging.info, logging.warning, logging.error, logging.critical 등을 통해 로깅 메시지를 남김.

import logging

logging.basicConfig(
    format='%(asctime)s %(levename)s:%(message)s', # Formatter생성
    level=logging.INFO,
    datefmt='%m-%d-%Y %I:%M:%S %p',
    # 기본 Handler를 이용(콘솔에 출력하는 Stream Handler)
) # 가장 기본적인 로깅환경을 이용- root logger를 사용함.

logging.debug('debug message example!')
  • root logger를 이용하는 가장 간단한 형태.
  • 내부적으로는 아래 설명하는 핵심 클래스 객체들이 만들어지고 연결되어 동작.
  • logger = logging.getLogger(__name__) 과 같이 모듈(각 py 파일)별로 logger를 생성하기도 함.
  • 이는 root logger의 자식 로거임.

핵심 클래스들

https://www.codemotion.com/magazine/ai-ml/big-data/logging-in-python-a-broad-gentle-introduction/

  • Logger: 로깅 모듈의 중심. 이 클래스의 인스턴스를 통해 로깅 시스템을 제어함. 무엇을 기록할지를 결정함.
  • Handler: 어디에 기록할지를 결정하는 역할. 파일에 기록할지 콘솔에 출력할지 네트워크로 전송할 지 등등
  • Formatter: 어떻게 기록할지를 결정함. what, where, when 등의 내용을 어떤 형식으로 기록할지를 결정.

앞서의 예제 코드는 다음과 같음:

import logging

# ----------------- [설정 단계] -----------------
# 1. 로거 객체 가져오기
# 이름을 비워두면 'root logger'를 반환.
# Application의 main logger를 설정하기 위해 사용하는 방식.
logger = logging.getLogger() 
logger.setLevel(logging.INFO) # 로거의 레벨 설정

# 2. Handler 생성하기 (로그를 어디로 보낼지)
stream_handler = logging.StreamHandler() # 콘솔(화면)으로 보낼 핸들러

# 3. Formatter 생성하기 (로그를 어떤 형식으로 남길지)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# 4. Handler와 Formatter 연결하기
stream_handler.setFormatter(formatter)

# 5. Logger와 Handler 연결하기
logger.addHandler(stream_handler)
# -----------------------------------------------

logging.debug('debug message example!')

2개의 handler와 다른 레벨를 이용한  경우는 다음과 같음:

import logging


# log msg를 어떻게 출력할지를 Formatter객체로 설정 (다음 절 참고).
formatter = logging.Formatter('%(asctime)s~%(levelname)s~%(message)s~module:%(module)s')

# File Handler 객체 생성.
file_handler = logging.FileHandler("logfile.log")
file_handler.setLevel(logging.WARN) # WARN 이상의 log를 파일로 저장.
file_handler.setFormatter(formatter)

# Console의 출력위한 StreamHandler 객체 생성.
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG) # DEBUG이상의 log를 콘솔로 출력
console_handler.setFormatter(formatter)

# Root Logger생성하고 서로 연결.
logger = logging.getLogger()
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.setLevel(logging.DEBUG) # Logger가 우선 DEBUG이상의 log를 handler에 전달.

# 다양한 레벨의 로그 남기기.
logger.critical("Something critical")
logger.error("An error")
logger.warning("A warning")
logger.info("My info is that you are here")
logger.debug("I'm debugging")

logging message format attributes

로그 레코드의 format에 사용되는 attributes 중 많이 사용되는 것을 간략히 정리함.

Attributer Name Format Description
asctime %(asctime)s 사람이 읽을 수 있는 로그 레코드가 생성된 시간
2023-12-18 18:20:55, 810 형태임 (쉼표 뒤의 숫자는 밀리세컨드)
created %(created)f time.time()이 반환하는 시간으로
로그 레코드가 생성된 시간
filename %(filename)s pathname 파일명 부분
funcName %(funcName)s 로깅 호출을 포함하는 function의 이름
levelname %(levelname)s Logging Level
lineno %(lineno)d 로깅 호출이 일어난 소스의 line number
message %(message)s 로깅 메시지. msg % args 의 형태임.
module %(module)s 모듈 (filename 에서 확장자 뺀 이름)
name %(name)s 사용된 logger의 이름.
pathname %(pathname)s 로깅호출이 일어난 소스 파일의 전체 경로명
process %(process)d process ID
processName %(processName)s process name
thread %(thread)d thread ID
threadName %(threadName)s thread name

예제: 동적인 로깅 레벨 변경

import logging
import time

def setup_logger():
    """초기 로거 설정을 담당하는 함수"""
    # 1. 로거 생성 (이름을 지정하여 루트 로거와 분리)
    logger = logging.getLogger("my_app")
    
    # 중복 추가를 방지하기 위해, 핸들러가 이미 있는지 확인
    if logger.hasHandlers():
        return logger

    # 2. 초기 레벨 설정 (INFO로 시작)
    logger.setLevel(logging.INFO)
    print("--- 초기 로거 레벨을 INFO로 설정! ---")

    # 3. 핸들러 및 포매터 설정
    handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)

    # 4. 로거에 핸들러 추가
    logger.addHandler(handler)
    
    return logger

def some_business_logic(logger):
    """애플리케이션의 핵심 로직을 흉내 내는 함수"""
    logger.info("핵심 로직을 시작!.")
    
    # 이 부분은 현재 로거 레벨에서는 출력되지 않을 것입니다.
    logger.debug("상세 디버그 정보: 데이터 처리 준비 완료.")
    
    time.sleep(1) # 무언가 작업하는 것처럼 잠시 대기
    
    logger.info("핵심 로직을 성공적으로 수행함.")

# --- 메인 실행 부분 ---
if __name__ == "__main__":
    # 1. 초기 로거 설정
    my_logger = setup_logger()

    # 2. 초기 상태에서 로직 실행
    print("\n[1. 초기 상태에서 로직 실행]")
    some_business_logic(my_logger)

    # 3. 사용자 입력을 받아 동적으로 레벨 변경
    print("\n" + "="*40)
    print("이제 로거 레벨을 DEBUG로 변경하여 더 상세한 로그를 확인함!!!.")
    user_input = input("디버그 모드를 활성화하려면 'd'를 누르고 Enter 키를 입력할 것: ")

    if user_input.lower() == 'd':
        # --- 여기가 동적 설정 변경의 핵심 ---
        print("\n--- 로거 레벨을 DEBUG로 동적으로 변경되었음! ---")
        my_logger.setLevel(logging.DEBUG)
        
        # 4. 변경된 상태에서 로직 다시 실행
        print("\n[2. DEBUG 모드로 변경 후 로직 재실행]")
        some_business_logic(my_logger)
        
        # 레벨을 다시 원래대로 돌릴 수도 있습니다.
        print("\n--- 로거 레벨을 다시 INFO로 복구함. ---")
        my_logger.setLevel(logging.INFO)
    else:
        print("\n디버그 모드를 활성화하지 않고 종료.")

보다 자세한 건 다음 URL 참고

https://docs.python.org/3/library/logging.html#logrecord-attributes

 

logging — Logging facility for Python

Source code: Lib/logging/__init__.py Important: This page contains the API reference information. For tutorial information and discussion of more advanced topics, see Basic Tutorial, Advanced Tutor...

docs.python.org

https://www.codemotion.com/magazine/ai-ml/big-data/logging-in-python-a-broad-gentle-introduction/

 

Logging in Python: a broad, gentle introduction

A fundamental step in writing production-quality code is the ability to log properly. In this article, I will explain how to log in Python.

www.codemotion.com

 


References

https://docs.python.org/3/library/logging.html#module-logging

 

logging — Logging facility for Python

Source code: Lib/logging/__init__.py Important: This page contains the API reference information. For tutorial information and discussion of more advanced topics, see Basic Tutorial, Advanced Tutor...

docs.python.org

https://hwangheek.github.io/2019/python-logging/

 

조금 더 체계적인 Python Logging

IntroductionPython으로 코드를 짤 때, 로그를 띄우는 방법으로 print('[*] Message')를 정말 많이 써 왔습니다. 군더더기 없고, 유연하고, dependency 없이 아무 위치에나 넣을 수 있다는 점이 좋았습니다. ‘

hwangheek.github.io

https://www.codemotion.com/magazine/ai-ml/big-data/logging-in-python-a-broad-gentle-introduction/

 

Logging in Python: a broad, gentle introduction

A fundamental step in writing production-quality code is the ability to log properly. In this article, I will explain how to log in Python.

www.codemotion.com

 

728x90