← Back to Blog

[Python] Logging

computer science > programming

2026-07-064 min read

#computer-science #programming #python #logging

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)slog 시간
%(levelname)slog level 이름
%(name)slogger 이름
%(message)slog message
%(filename)s파일 이름
%(lineno)dline 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")

구성 요소는 다음과 같다.

요소역할
Loggerlog를 남기는 객체
Handlerlog를 어디로 보낼지 결정
Formatterlog를 어떤 형식으로 출력할지 결정
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 상태를 기록하기 위한 표준 도구이다.

핵심은 다음과 같다.

  1. print() 대신 logging을 사용하면 level, format, output을 관리할 수 있다.
  2. log level은 DEBUG, INFO, WARNING, ERROR, CRITICAL이 있다.
  3. project에서는 logging.getLogger(__name__) 패턴을 사용한다.
  4. exception은 logger.exception()으로 traceback과 함께 기록한다.
  5. file logging은 filename 또는 FileHandler를 사용한다.
  6. 파일 크기 관리는 RotatingFileHandler로 처리할 수 있다.
  7. logging 설정은 entry point에서 한 번만 하는 것이 좋다.
  8. library code에서는 basicConfig()를 호출하지 않는 것이 좋다.

작은 script에서는 basicConfig()로 충분하다. 하지만 project가 커지면 logger, handler, formatter를 분리해서 관리하는 것이 좋다.