21.Python logging — print() 대신 로그를 남겨야 하는 이유와 사용법

 저도 처음엔 디버깅할 때마다 코드 여기저기에 print('여기까지 옴'), print(data) 같은 코드를 수십 개씩 집어넣곤 했습니다. 에러를 잡고 나면 다시 코드를 뒤져가며 일일이 지워야 했는데요. 어느 날 미처 지우지 못한 print('여기가 터졌네 시X') 같은 부끄러운 개발용 낙서가 실제 서비스 서버 로그에 그대로 찍혀 상사에게 호출당하는 낭패를 겪었습니다. 그때 비로소 logging 라이브러리를 배웠고, 코드 수정 없이 출력 레벨(Level)만 변경해 로그를 제어하는 신세계를 맛보았습니다.


Python으로 프로그램을 만들다 보면 누구나 print() 함수로 값을 확인하며 디버깅한 경험이 있을 것입니다. vscode에서 코드를 실행할 때마다 터미널에 출력되는 그 익숙한 문장 말입니다. 하지만 프로그램이 커지고 운영 단계에 들어서면 print()만으로는 한계에 부딪힙니다. 이 글에서는 Python의 표준 logging 모듈이 무엇인지부터, 로그 레벨 설정, 파일 저장, 그리고 실전 프로젝트에 적용하는 방법까지 단계별로 안내해 드리겠습니다.


print() 대신 logging을 써야 하는 이유

일기장과 액자의 차이

print()는 화면에 즉시 출력되고 사라지는 일회용 메모와 같습니다. 프로그램을 끄는 순간 그 내용은 영영 사라집니다. 반면 logging날짜와 시간이 자동으로 기록되는 일기장입니다. 언제 무슨 일이 있었는지, 그 일이 단순한 정보였는지 심각한 문제였는지까지 구분해서 차곡차곡 쌓아둡니다.

새벽 3시에 서버에서 오류가 발생했다고 가정해 보겠습니다. print()만 사용했다면 그 순간 화면을 보고 있지 않은 이상 어떤 문제가 있었는지 알 방법이 없습니다. 하지만 logging으로 파일에 기록을 남겨두었다면, 다음 날 아침 로그 파일을 열어 정확히 몇 시 몇 분에 어떤 오류가 발생했는지 확인할 수 있습니다.

print()의 세 가지 명확한 한계

  • 레벨 구분이 불가능합니다 — 단순 정보인지 심각한 오류인지 코드만 봐서는 구분되지 않습니다.
  • 흔적이 남지 않습니다 — 프로그램 종료와 동시에 출력 내용이 사라집니다.
  • 운영 환경에서 끄고 켜기가 어렵습니다 — 디버깅용 print()를 지우거나 주석 처리하는 작업을 반복해야 합니다.

logging은 이 세 가지 문제를 모두 해결하면서도, Python 표준 라이브러리에 포함되어 있어 별도 설치 없이 바로 사용할 수 있습니다.


logging 모듈 기본 사용법

VS Code에서 첫 로그 출력해보기

가장 단순한 형태부터 시작하겠습니다. vscode에서 새 파일을 만들고 아래 코드를 입력합니다.

import logging

logging.basicConfig(level=logging.INFO)

logging.debug("디버그 메시지입니다.")
logging.info("정보성 메시지입니다.")
logging.warning("경고 메시지입니다.")
logging.error("오류 메시지입니다.")
logging.critical("심각한 오류 메시지입니다.")

실행 결과를 보면 debug 메시지는 출력되지 않고, info 이상의 메시지만 화면에 나타납니다.

INFO:root:정보성 메시지입니다.
WARNING:root:경고 메시지입니다.
ERROR:root:오류 메시지입니다.
CRITICAL:root:심각한 오류 메시지입니다.


로그 레벨 — 경비실의 출입 등급에 비유하기

logging에는 다섯 가지 레벨이 있고, 숫자가 클수록 더 심각한 상황을 의미합니다. 이를 건물 경비실의 출입 등급에 비유할 수 있습니다.

  • DEBUG (10) — 개발자만 보는 내부 메모, 평소에는 굳이 확인하지 않는 세부 정보
  • INFO (20) — 정상적인 출입 기록, "누가 언제 들어왔다"는 일반적인 흐름 정보
  • WARNING (30) — 주의가 필요한 상황, 당장 문제는 아니지만 눈여겨봐야 하는 신호
  • ERROR (40) — 실제 문제가 발생한 사건, 특정 기능이 정상 작동하지 않음
  • CRITICAL (50) — 건물 전체가 멈출 수준의 비상 상황, 프로그램 전체에 영향을 주는 심각한 오류

level=logging.INFO로 설정하면 INFO 이상의 등급만 통과시키고, 그보다 낮은 DEBUG는 걸러냅니다. 평소에는 INFO로 운영하다가, 문제가 생기면 DEBUG로 낮춰서 더 자세한 정보를 확인하는 방식으로 활용합니다.


로그를 파일로 저장하기

화면 출력만으로는 프로그램이 종료된 후 기록을 확인할 수 없습니다. 파일로 저장하는 방법을 살펴보겠습니다.

기본 파일 저장 설정

import logging

logging.basicConfig(
    filename="app.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    encoding="utf-8"
)

logging.info("프로그램이 시작되었습니다.")
logging.warning("설정 파일에서 일부 값이 누락되었습니다.")
logging.error("데이터베이스 연결에 실패했습니다.")

코드 실행 후 프로젝트 폴더를 보면 app.log 파일이 생성되어 있고, 내용은 이런 형태로 기록됩니다.

2026-06-30 14:23:11,452 - INFO - 프로그램이 시작되었습니다.
2026-06-30 14:23:11,453 - WARNING - 설정 파일에서 일부 값이 누락되었습니다.
2026-06-30 14:23:11,454 - ERROR - 데이터베이스 연결에 실패했습니다.


format 옵션 — 로그에 담길 정보 커스터마이징

format 문자열에 자주 쓰이는 항목들입니다.

포맷 코드 의미
%(asctime)s 로그가 기록된 시각
%(levelname)s 로그 레벨 (INFO, ERROR 등)
%(message)s 실제 로그 메시지
%(filename)s 로그가 발생한 파일명
%(lineno)d 로그가 발생한 코드 줄 번호
logging.basicConfig(
    filename="app.log",
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s",
    encoding="utf-8"
)

이렇게 설정하면 오류가 정확히 어느 파일, 몇 번째 줄에서 발생했는지까지 로그에 함께 기록되어 디버깅 시간을 크게 단축할 수 있습니다.


실전 패턴 — Logger 객체로 제대로 관리하기

logging.info() 같은 방식은 간단한 스크립트에는 충분하지만, 여러 파일로 구성된 프로젝트에서는 Logger 객체를 직접 만들어 관리하는 방식을 권장합니다.

모듈별 로거 생성하기

import logging

def get_logger(name: str) -> logging.Logger:
    """모듈 이름을 받아 설정이 완료된 로거를 반환합니다."""

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)

    # 이미 핸들러가 설정되어 있다면 중복 추가 방지
    if logger.handlers:
        return logger

    formatter = logging.Formatter(
        "%(asctime)s [%(levelname)s] %(name)s - %(message)s"
    )

    # 콘솔 출력 핸들러
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)

    # 파일 저장 핸들러
    file_handler = logging.FileHandler("app.log", encoding="utf-8")
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)

    logger.addHandler(console_handler)
    logger.addHandler(file_handler)

    return logger


# 사용 예시
logger = get_logger(__name__)

logger.debug("파일에만 기록되는 상세 정보입니다.")
logger.info("콘솔과 파일 모두에 기록되는 일반 정보입니다.")
logger.error("심각한 오류가 발생했습니다.")

이 방식의 핵심은 핸들러를 두 개로 분리한 것입니다. 콘솔에는 INFO 이상만 보여 화면이 깔끔하게 유지되고, 파일에는 DEBUG 이상 모든 내용이 저장되어 나중에 상세히 추적할 수 있습니다.

[app.log]

[console log]



여러 파일에서 같은 로거 재사용하기

# utils.py
from logger_config import get_logger

logger = get_logger(__name__)

def fetch_data(url: str):
    logger.info(f"데이터 요청 시작: {url}")
    try:
        # 데이터 요청 로직
        logger.info("데이터 요청 성공")
    except Exception as e:
        logger.error(f"데이터 요청 실패: {e}")

각 파일마다 get_logger(__name__)을 호출하면, 로그 메시지에 어느 모듈에서 발생한 기록인지가 자동으로 함께 표시되어 추적이 쉬워집니다.


로그 파일이 무한정 커지는 것을 막는 방법

오랫동안 실행되는 프로그램은 로그 파일이 계속 쌓여 용량 문제를 일으킵니다. RotatingFileHandler를 사용하면 일정 크기를 넘으면 자동으로 새 파일을 만들어 관리합니다.

from logging.handlers import RotatingFileHandler
import logging

logger = logging.getLogger("my_app")
logger.setLevel(logging.INFO)

# 파일이 5MB를 넘으면 새 파일로 교체, 최대 3개까지만 보관
handler = RotatingFileHandler(
    "app.log",
    maxBytes=5 * 1024 * 1024,  # 5MB
    backupCount=3,
    encoding="utf-8"
)
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))

logger.addHandler(handler)
logger.info("로테이팅 로그 설정이 완료되었습니다.")

이렇게 설정하면 app.log, app.log.1, app.log.2 형태로 자동 관리되어, 디스크 공간이 가득 차는 사고를 사전에 방지할 수 있습니다.


자주 발생하는 오류와 해결법

오류 1 — basicConfig() 설정이 적용되지 않는 경우

원인: basicConfig()는 프로그램 안에서 단 한 번만 적용되며, 이미 로거가 설정된 이후에는 무시됩니다. 해결: import logging 직후, 다른 코드보다 먼저 basicConfig()를 호출하거나 force=True 옵션을 추가합니다.

logging.basicConfig(level=logging.DEBUG, force=True)

오류 2 — 같은 로그가 두 번씩 출력되는 경우

원인: 같은 이름의 로거에 핸들러가 중복으로 추가된 경우입니다. 해결: 위 get_logger() 예제처럼 if logger.handlers: 조건으로 중복 추가를 막습니다.

오류 3 — 한글 로그가 깨지는 경우

원인: 파일 핸들러에 인코딩이 지정되지 않은 경우입니다. 해결: FileHandler 또는 basicConfig()encoding="utf-8"을 반드시 추가합니다.


마무리

✅ Python logging 핵심 요약

  • print()는 일회성 출력, logging은 기록이 남는 영구적인 로그입니다.
  • 로그 레벨은 DEBUG → INFO → WARNING → ERROR → CRITICAL 순으로 심각도가 높아집니다.
  • basicConfig()로 간단히 시작할 수 있지만, 실전 프로젝트는 getLogger()로 모듈별 로거를 구성하는 것을 권장합니다.
  • 콘솔용 핸들러와 파일용 핸들러를 분리하면 화면은 깔끔하게, 기록은 상세하게 유지할 수 있습니다.
  • RotatingFileHandler로 로그 파일 용량을 자동 관리하면 장기간 운영하는 프로그램에서도 안정적입니다.

logging을 도입하는 것은 단순히 코드 한 줄을 바꾸는 작업이 아니라, 문제가 생겼을 때 추적할 수 있는 안전망을 갖추는 일입니다. vscode에서 작은 프로젝트를 만들 때부터 print() 대신 logging을 쓰는 습관을 들여두면, 프로젝트 규모가 커졌을 때 그 차이를 분명히 체감하게 될 것입니다.



댓글