Python 3에서 로깅 사용하는 방법

소개

logging 모듈은 표준 Python 라이브러리의 일부이며, 소프트웨어가 실행되는 동안 발생하는 이벤트를 추적합니다. 코드에 로깅 호출을 추가하여 어떤 이벤트가 발생했는지 나타낼 수 있습니다.

logging 모듈은 응용 프로그램 작동과 관련된 이벤트를 기록하는 진단 로깅과 사용자의 트랜잭션 이벤트를 기록하는 감사 로깅을 모두 허용합니다. 주로 파일에 이벤트를 기록하는 데 사용됩니다.

필수 조건

컴퓨터 또는 서버에 Python 3가 설치되어 있고 프로그래밍 환경이 설정되어 있어야 합니다. 프로그래밍 환경이 설정되어 있지 않은 경우, 운영 체제(Ubuntu, CentOS, Debian 등)에 적합한 로컬 프로그래밍 환경 또는 서버에 프로그래밍 환경을 설치하는 방법을 설치 및 설정 가이드를 참조할 수 있습니다.

logging 모듈을 사용하는 이유

logging 모듈은 프로그램 내에서 발생하는 이벤트를 기록하여 소프트웨어 실행 중에 발생하는 이벤트와 관련된 출력을 볼 수 있게 합니다.

여러분은 코드 내에서 print() 문을 사용하여 이벤트가 발생하는지 확인하는 것에 더 익숙할 수 있습니다. print() 문은 코드 디버깅을 위해 기본적인 방법을 제공합니다. 코드 전체에 print() 문을 삽입함으로써 실행 흐름과 프로그램의 현재 상태를 추적할 수 있지만, 이 방법은 logging 모듈을 사용하는 것보다 유지 관리하기 어렵다는 몇 가지 이유로 유지할 수 없습니다:

  • 디버깅 출력과 일반 프로그램 출력을 구분하기 어려워집니다
  • 코드 전체에 흩어져 있는 print() 문을 사용할 때, 디버깅 출력을 제공하는 것을 비활성화하는 효율적인 방법이 없습니다
  • 디버깅이 끝난 후 모든 print() 문을 제거하는 것이 어려워집니다
  • 즉시 사용 가능한 진단 정보가 포함된 로그 레코드가 없습니다

코드에서 logging 모듈을 사용하는 것이 좋은 습관이며, 이는 작은 Python 스크립트를 넘어서는 응용 프로그램에 더 적합하며 디버깅에 지속 가능한 접근 방식을 제공합니다.

로그는 시간이 지남에 따라 동작 및 오류를 보여주기 때문에 응용 프로그램 개발 프로세스에서 무슨 일이 일어나고 있는지에 대한 더 나은 전반적인 그림을 제공할 수 있습니다.

콘솔에 디버그 메시지 출력하기

정보: 이 자습서의 예제 코드를 따라 하려면 로컬 시스템에서 python3 명령을 실행하여 Python 대화형 셸을 엽니다. 그런 다음 >>> 프롬프트 뒤에 예제를 추가하여 복사, 붙여 넣기 또는 편집할 수 있습니다.

프로그램에서 발생하는 내용을 보기 위해 print() 문을 사용하는 데 익숙하다면 무언가를 생성하는 클래스를 정의하고 객체를 인스턴스화하는 프로그램을 보는 데 익숙할 것입니다. 이는 다음과 같은 내용을 생성합니다:

pizza.py
class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

위 코드에는 Pizza 클래스의 객체의 nameprice를 정의하는 __init__ 메서드가 있습니다. 그런 다음 피자를 만드는 make() 및 피자를 먹는 eat()이라는 두 가지 메서드가 있습니다. 이 두 메서드는 quantity 매개변수를 사용하며, 이는 1로 초기화됩니다.

이제 프로그램을 실행해 봅시다:

  1. python pizza.py

다음 출력을 받게 됩니다:

Output
Pizza created: artichoke ($15) Made 1 artichoke pizza(s) Ate 1 pizza(s) Pizza created: margherita ($12) Made 2 margherita pizza(s) Ate 1 pizza(s)

print() 문을 사용하여 코드가 작동하는지 확인할 수 있지만, 대신에 logging 모듈을 사용할 수 있습니다.

코드 전체에서 print() 문을 제거하거나 주석 처리하고 파일 맨 위에 import logging을 추가합시다.

pizza.py
import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

logging 모듈은 기본적으로 WARNING 수준을 가지고 있으며, 이는 DEBUG보다 높은 수준입니다. 이 예에서 디버깅에 logging 모듈을 사용할 것이므로 설정을 수정하여 logging.DEBUG 수준이 콘솔에 정보를 반환하도록해야 합니다. 다음 줄을 import 문 아래에 추가하여 수행할 수 있습니다:

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

logging.DEBUG 수준은 코드에서 임계값을 설정하기 위해 참조하는 상수 정수값을 나타냅니다. DEBUG 수준은 10입니다.

이제 모든 print() 문을 logging.debug() 문으로 대체합니다. logging.DEBUG는 상수이지만, logging.debug()logging 모듈의 메소드입니다. 이 메소드를 사용할 때, 다음과 같이 동일한 문자열을 전달할 수 있습니다:

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

이 시점에서 python pizza.py 명령으로 프로그램을 실행하면 다음 출력이 표시됩니다:

Output
DEBUG:root:Pizza created: artichoke ($15) DEBUG:root:Made 1 artichoke pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: margherita ($12) DEBUG:root:Made 2 margherita pizza(s) DEBUG:root:Ate 1 pizza(s)

로그 메시지에는 Python 모듈의 수준을 나타내는 DEBUG 심각도 수준과 함께 root라는 단어가 포함되어 있습니다. logging 모듈은 서로 다른 이름을 가진 로거의 계층 구조와 함께 사용할 수 있으므로 각 모듈에 대해 다른 로거를 사용할 수 있습니다.

예를 들어, 다른 이름과 다른 출력을 가진 다른 로거에 로거를 설정할 수 있습니다:

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
Output
DEBUG:module_1:Module 1 debugger DEBUG:module_2:Module 2 debugger

logging 모듈을 사용하여 콘솔에 메시지를 출력하는 방법을 이해했으므로, 이제 logging 모듈을 사용하여 메시지를 파일에 출력하는 방법으로 넘어가겠습니다.

파일에 로그 메시지 기록하기

logging 모듈의 주요 목적은 콘솔이 아닌 파일에 메시지를 기록하는 것입니다. 메시지 파일을 유지하면 코드에 필요한 변경 사항을 파악하고 양적으로 평가할 수 있는 데이터가 시간에 따라 제공됩니다.

파일에 로깅을 시작하려면 logging.basicConfig() 메서드를 수정하여 filename 매개변수를 포함시킬 수 있습니다. 이 경우 파일 이름을 test.log로 지정해보겠습니다:

pizza.py
import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

위 코드는 이전 섹션과 동일하지만 이제 로그가 출력될 파일 이름을 추가했습니다. python pizza.py 명령으로 코드를 실행하면 디렉토리에 test.log라는 새 파일이 생성됩니다.

test.log 파일을 nano(또는 원하는 텍스트 편집기)로 엽니다:

  1. nano test.log

파일이 열리면 다음과 같은 내용을 받게 됩니다:

test.log
DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

이전 섹션에서 만난 콘솔 출력과 유사하지만 이제는 test.log 파일에 있습니다.

CTRL + x를 사용하여 파일을 닫고 코드를 수정할 수 있도록 다시 pizza.py 파일로 이동합시다.

코드의 많은 부분은 유지한 채로 두 피자 인스턴스인 pizza_01pizza_02의 매개변수를 수정해 봅시다:

pizza.py
import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# pizza_01 객체의 매개변수 수정
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# pizza_02 객체의 매개변수 수정
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

이러한 변경 사항으로 다시 python pizza.py 명령으로 프로그램을 실행합시다.

프로그램이 실행된 후에 nano로 다시 test.log 파일을 열 수 있습니다:

  1. nano test.log

파일을 검토하면 여러 개의 새로운 줄이 추가되고, 이전에 프로그램이 실행된 마지막 시간의 줄이 유지된 것을 볼 수 있습니다:

test.log
DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

이 정보는 유용하지만 추가적인 LogRecord 속성을 추가하여 로그를 더 유익하게 만들 수 있습니다. 주로 LogRecord가 생성된 시간을 나타내는 사람이 읽기 쉬운 타임 스탬프를 추가하고 싶습니다.

우리는 format이라는 매개변수에 그 속성을 추가할 수 있습니다. 테이블에서 문자열 %(asctime)s을 참조합니다. 또한 DEBUG 레벨 이름을 유지하려면 문자열 %(levelname)s을 포함해야 하며, 로거가 출력하도록 요청한 문자열 메시지를 유지하려면 %(message)s를 포함해야 합니다. 이러한 속성 각각은 colon으로 구분되며, 추가된 코드에 표시된 대로 분리됩니다:

pizza.py
import logging

logging.basicConfig(
    filename="test.log",
    level=logging.DEBUG,
    format="%(asctime)s:%(levelname)s:%(message)s"
    )


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

위의 코드를 추가 속성과 함께 실행할 때 python pizza.py 명령으로, 인간이 읽을 수 있는 타임 스탬프와 함께 test.log 파일에 새 줄이 추가됩니다. 또한 DEBUG 레벨의 이름과 로거로 전달된 연관 메시지도 포함됩니다.

test.log
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: Sicilian ($18)
2021-08-19 23:31:34,484:DEBUG:Made 5 Sicilian pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 4 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: quattro formaggi ($16)
2021-08-19 23:31:34,484:DEBUG:Made 2 quattro formaggi pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 2 pizza(s)

귀하의 요구에 따라 코드에서 추가적인 LogRecord 속성을 사용하여 프로그램 파일의 로그를 귀하에게 관련성 있게 만들 수 있습니다.

디버그 및 기타 메시지를 별도의 파일에 기록하면 시간이 지남에 따라 Python 프로그램에 대한 종합적인 이해를 제공하여 프로그램에 투입된 역사적인 작업 및 발생하는 이벤트 및 트랜잭션을 바탕으로 코드를 문제 해결하고 수정할 수 있는 기회를 제공합니다.

로그 레벨 테이블

개발자로서, 로거에 캡처된 이벤트에 중요도 수준을 지정할 수 있습니다. 심각도 수준은 아래 표에 나와 있습니다.

로그 기록 수준은 기술적으로 정수(상수)이며, 0의 숫자 값에서 시작하여 10씩 증가합니다. NOTSET은 로거를 숫자 값 0으로 초기화합니다.

또한 미리 정의된 수준과 관련하여 사용자 정의 수준을 정의할 수도 있습니다. 동일한 숫자 값으로 수준을 정의하면 해당 값과 관련된 이름이 덮어쓰여집니다.

다음 표는 다양한 수준 이름, 숫자 값, 해당 수준을 호출하는 데 사용할 수 있는 함수 및 해당 수준의 용도를 보여줍니다.

Level Numeric Value Function Used to
CRITICAL 50 logging.critical() Show a serious error, the program may be unable to continue running
ERROR 40 logging.error() Show a more serious problem
WARNING 30 logging.warning() Indicate something unexpected happened, or could happen
INFO 20 logging.info() Confirm that things are working as expected
DEBUG 10 logging.debug() Diagnose problems, show detailed information

logging 모듈은 기본 수준을 WARNING으로 설정하므로 WARNING, ERROR, 및 CRITICAL은 기본적으로 모두 로깅됩니다. 위 예에서는 다음 코드를 사용하여 DEBUG 수준을 포함한 구성을 수정했습니다:

logging.basicConfig(level=logging.DEBUG)

더 많은 명령 및 디버거 사용에 관한 정보는 공식 logging 문서에서 확인할 수 있습니다.

결론

디버깅은 모든 소프트웨어 개발 프로젝트의 중요한 단계입니다. logging 모듈은 표준 Python 라이브러리의 일부로, 소프트웨어가 실행되는 동안 발생하는 이벤트를 추적하고 이러한 이벤트를 별도의 로그 파일에 출력하여 코드가 실행되는 동안 발생하는 사항을 추적할 수 있습니다. 이를 통해 프로그램을 실행하는 동안 발생하는 다양한 이벤트를 이해하여 코드를 디버그할 수 있는 기회를 제공합니다.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-logging-in-python-3