logging이란?
logging은 Python 표준 라이브러리에서 제공하는 log 출력 도구이다.
개발할 때는 print()로 값을 확인해도 되지만, 실제 application에서는 logging을 사용하는 것이 좋다.
print()는 단순히 화면에 문자열을 출력한다.
반면 logging은 다음을 관리할 수 있다.
log level
출력 형식
출력 위치
파일 저장
module별 logger
운영 환경별 log 설정
즉 logging은 단순 출력이 아니라 application 상태를 기록하는 체계이다.
print와 logging의 차이
print()는 빠르게 확인할 때 편하다.
print("user created")
하지만 log level, 시간, 파일명, line number, 저장 위치 등을 관리하기 어렵다.
logging을 사용하면 다음처럼 작성한다.
import logging
logging.info("user created")
기본 설정만으로는 info log가 보이지 않을 수 있다.
그래서 보통 basicConfig()로 level과 format을 설정한다.
import logging
logging.basicConfig(level=logging.INFO)
logging.info("user created")
log level
logging에는 중요도에 따라 level이 있다.
| level | 의미 |
|---|---|
DEBUG | 개발 중 상세한 정보 |
INFO | 정상적인 흐름에서 의미 있는 정보 |
WARNING | 문제 가능성이 있지만 실행은 계속 가능 |
ERROR | 특정 작업 실패 |
CRITICAL | 프로그램 전체에 치명적인 문제 |
사용 예시는 다음과 같다.
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("debug message")
logging.info("info message")
logging.warning("warning message")
logging.error("error message")
logging.critical("critical message")
level=logging.INFO로 설정하면 DEBUG는 출력되지 않는다.
즉 설정한 level보다 낮은 중요도의 log는 무시된다.
DEBUG < INFO < WARNING < ERROR < CRITICAL
basicConfig
가장 간단한 설정은 logging.basicConfig()를 사용하는 것이다.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logging.info("server started")
출력 예시는 다음과 같다.
2026-07-06 12:00:00,000 [INFO] server started
format에서 자주 쓰는 값은 다음과 같다.
| 값 | 의미 |
|---|---|
%(asctime)s | log 시간 |
%(levelname)s | log level 이름 |
%(name)s | logger 이름 |
%(message)s | log message |
%(filename)s | 파일 이름 |
%(lineno)d | line number |
%(funcName)s | 함수 이름 |
예를 들어 module 이름과 line number까지 보고 싶다면 다음처럼 설정한다.
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
)
module별 logger
실제 project에서는 logging.info()를 직접 쓰기보다 module별 logger를 만든다.
import logging
logger = logging.getLogger(__name__)
그리고 다음처럼 사용한다.
logger.info("user created")
logger.warning("invalid request")
logger.error("failed to save user")
__name__을 사용하면 현재 module 이름이 logger 이름이 된다.
예를 들어 파일 구조가 다음과 같다고 하자.
my_app/
main.py
users/
service.py
users/service.py에서 다음처럼 logger를 만들면
logger = logging.getLogger(__name__)
logger 이름은 보통 users.service가 된다.
이렇게 하면 어느 module에서 log가 발생했는지 알기 쉽다.
log message 작성 방식
logging에서는 f-string보다 % style lazy formatting을 사용하는 경우가 많다.
logger.info("user_id=%s created", user_id)
다음처럼 f-string을 써도 동작은 한다.
logger.info(f"user_id={user_id} created")
하지만 f-string은 log level 때문에 실제 출력되지 않아도 문자열이 먼저 만들어진다. 반면 logging의 인자 방식은 필요한 경우에만 formatting된다.
권장 형태는 다음과 같다.
logger.debug("request payload=%s", payload)
exception logging
예외를 기록할 때는 logger.exception()을 사용할 수 있다.
이 method는 stack trace를 함께 출력한다.
try:
result = 10 / 0
except ZeroDivisionError:
logger.exception("failed to divide")
출력에는 error message와 traceback이 함께 포함된다.
logger.exception()은 except block 안에서 사용하는 것이 일반적이다.
비슷하게 logger.error(..., exc_info=True)를 사용할 수도 있다.
try:
result = 10 / 0
except ZeroDivisionError:
logger.error("failed to divide", exc_info=True)
file logging
log를 파일에 저장하려면 filename을 지정한다.
import logging
logging.basicConfig(
level=logging.INFO,
filename="app.log",
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
)
logger = logging.getLogger(__name__)
logger.info("application started")
이제 log는 terminal이 아니라 app.log 파일에 기록된다.
파일에 계속 append하지 않고 실행할 때마다 새로 쓰고 싶다면 filemode="w"를 지정한다.
logging.basicConfig(
level=logging.INFO,
filename="app.log",
filemode="w",
)
Handler와 Formatter
basicConfig()는 간단하지만, console과 file에 동시에 출력하려면 handler를 직접 구성하는 편이 좋다.
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s"
)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
file_handler = logging.FileHandler("app.log")
file_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.info("application started")
구성 요소는 다음과 같다.
| 요소 | 역할 |
|---|---|
| Logger | log를 남기는 객체 |
| Handler | log를 어디로 보낼지 결정 |
| Formatter | log를 어떤 형식으로 출력할지 결정 |
| Level | 어떤 중요도 이상의 log를 남길지 결정 |
StreamHandler는 console로 출력하고, FileHandler는 파일로 출력한다.
RotatingFileHandler
운영 환경에서 하나의 log 파일이 계속 커지면 관리하기 어렵다.
이때 RotatingFileHandler를 사용할 수 있다.
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(
"app.log",
maxBytes=1024 * 1024,
backupCount=3,
)
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("application started")
위 설정은 app.log가 1MB를 넘으면 파일을 회전시키고, backup 파일을 3개까지 유지한다.
app.log
app.log.1
app.log.2
app.log.3
logging 설정 함수 만들기
project에서는 logging 설정을 함수로 분리하는 것이 좋다.
import logging
def setup_logging() -> None:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
)
entry point에서 한 번만 호출한다.
from logging_config import setup_logging
def main():
setup_logging()
...
if __name__ == "__main__":
main()
각 module에서는 설정을 반복하지 않고 logger만 만든다.
import logging
logger = logging.getLogger(__name__)
이 패턴을 사용하면 logging 설정이 한 곳에 모인다.
중복 log가 찍힐 때
가끔 같은 log가 두 번씩 출력될 때가 있다. 대부분 handler가 중복으로 추가되었기 때문이다.
예를 들어 설정 함수가 여러 번 호출되면서 같은 logger에 handler가 계속 붙을 수 있다.
logger.addHandler(console_handler)
logger.addHandler(console_handler)
방지하려면 handler가 이미 있는지 확인한다.
if not logger.handlers:
logger.addHandler(console_handler)
또는 application 시작 지점에서 logging 설정을 한 번만 호출하도록 구조를 정리한다.
library code에서 주의할 점
library를 만들 때는 basicConfig()를 library 내부에서 호출하지 않는 것이 좋다.
나쁜 예시는 다음과 같다.
# my_library/client.py
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
library가 logging 설정을 바꿔버리면, 이 library를 사용하는 application의 logging 정책과 충돌할 수 있다.
library에서는 logger만 만들고 사용한다.
import logging
logger = logging.getLogger(__name__)
logging 설정은 application entry point에서 담당하는 것이 좋다.
좋은 log 작성 기준
좋은 log는 나중에 문제를 추적할 수 있어야 한다.
예를 들어 다음 log는 정보가 부족하다.
logger.error("failed")
다음처럼 맥락을 함께 남기는 것이 좋다.
logger.error("failed to create user: user_id=%s", user_id)
기준은 다음과 같다.
무슨 일이 일어났는가?
어떤 대상에서 발생했는가?
실패 원인은 무엇인가?
다음 조사에 필요한 id가 있는가?
다만 password, token, 주민등록번호 같은 민감한 값은 log에 남기면 안 된다.
정리
Python logging은 application 상태를 기록하기 위한 표준 도구이다.
핵심은 다음과 같다.
print()대신logging을 사용하면 level, format, output을 관리할 수 있다.- log level은
DEBUG,INFO,WARNING,ERROR,CRITICAL이 있다. - project에서는
logging.getLogger(__name__)패턴을 사용한다. - exception은
logger.exception()으로 traceback과 함께 기록한다. - file logging은
filename또는FileHandler를 사용한다. - 파일 크기 관리는
RotatingFileHandler로 처리할 수 있다. - logging 설정은 entry point에서 한 번만 하는 것이 좋다.
- library code에서는
basicConfig()를 호출하지 않는 것이 좋다.
작은 script에서는 basicConfig()로 충분하다.
하지만 project가 커지면 logger, handler, formatter를 분리해서 관리하는 것이 좋다.