로깅 레벨 (Logging Level)
로깅 레벨은 로그 한 줄 한 줄에 "얼마나 중요한 일인지" 등급을 붙여서, 상황에 따라 필요한 등급 이상의 로그만 골라 남기게 하는 기준입니다.
한눈에 보기
- 프로그램은 실행 중에 "무엇을 했고, 무엇이 잘못됐는지"를 로그로 남깁니다. 그런데 모든 것을 다 남기면 정작 중요한 줄을 찾기 어렵고 저장 공간도 금방 찹니다.
- 그래서 로그마다 등급을 붙이고, 설정한 등급보다 낮은 로그는 출력하지 않는 방식으로 양을 조절합니다.
- 비유: 병원 응급실의 환자 분류와 같습니다.
- DEBUG: 진료 기록의 세세한 메모 (의사 본인만 봄)
- INFO: "환자 접수", "진료 완료" 같은 일상 기록
- WARN: "혈압이 조금 높아요. 지켜봅시다"
- ERROR: "검사 장비가 고장 났어요. 지금 조치해야 해요"
- FATAL: "정전입니다. 응급실 운영 중단"
- 개발할 때는 세세한 메모까지 다 보고(DEBUG), 실제 서비스(운영)에서는 일상 기록 이상만 보는(INFO) 식으로 같은 코드에서 설정만 바꿔 로그 양을 조절할 수 있습니다.
레벨은 기준선처럼 동작한다
레벨에는 순서가 있고, 설정한 레벨을 기준선으로 그 이상만 출력됩니다.
낮음(자세함) ──────────────────────────────────────▶ 높음(심각함)
TRACE DEBUG INFO WARN ERROR FATAL
설정 = INFO 이면:
TRACE DEBUG │ INFO WARN ERROR FATAL
(출력 안 함) │ (출력함)- 기준선을 낮추면(DEBUG) 로그가 많아지고 자세해집니다.
- 기준선을 높이면(ERROR) 심각한 일만 남습니다.
레벨별 의미와 예시
TRACE
DEBUG보다 더 자세한, 함수에 들어가고 나오는 흐름 하나하나를 남기는 레벨입니다. Java의 Log4j 2, Logback 같은 라이브러리에 있고, Python 기본 logging 모듈에는 없습니다. 아주 까다로운 버그를 추적할 때만 잠깐 켭니다.
DEBUG
개발자가 문제를 찾을 때 필요한 자세한 정보입니다. 변수 값, 받은 요청의 원본, 실행한 SQL 등을 남깁니다. 운영 환경에서는 보통 꺼 둡니다.
- 예:
장바구니 원본 데이터: {'items': 3, 'coupon': None}
INFO
정상적으로 일어난 중요한 사건을 남깁니다. 서버 시작과 종료, 중요한 업무 처리의 시작과 끝처럼 "무슨 일이 있었는지" 나중에 흐름을 따라갈 수 있을 정도로 남깁니다.
- 예:
서버가 8000번 포트에서 시작되었습니다,주문 #1001 결제를 시작합니다
WARN (WARNING)
당장 문제는 아니지만 주의해야 하는 상황입니다. 프로그램은 계속 동작합니다. 크게 두 가지 경우가 있습니다.
- 명확한 문제가 있지만 대신할 방법으로 버티는 경우: 외부 서버 응답이 느려 캐시된 값을 대신 사용, 재시도 끝에 성공
- 지금은 괜찮지만 나중에 문제가 될 수 있는 경우: 운영 서버가 개발 모드로 켜짐, 관리자 비밀번호가 기본값 그대로, 디스크 사용량 85% 돌파, 곧 지원이 끝나는 기능 사용
원문은 "WARN에서도 두 가지 경우에는 종료가 일어난다"고 적었는데, WARN은 프로그램을 멈추지 않는 레벨입니다. 원래 뜻은 "WARN에 해당하는 상황이 두 종류(명확한 문제, 잠재적 문제)로 나뉜다"에 가깝습니다. 프로그램이 멈춰야 할 정도라면 ERROR나 FATAL입니다.
ERROR
기능이 실패해서 조치가 필요한 상황입니다. 해당 요청이나 작업은 실패했지만, 프로그램 전체는 계속 돌아가며 다른 요청은 처리할 수 있습니다. 보통 ERROR 로그가 생기면 담당자에게 알림이 가도록 설정합니다.
- 예: 결제 서버 연결 실패, DB 쿼리 실패, 처리하지 못한 예외
FATAL (CRITICAL)
프로그램 전체가 더 이상 동작할 수 없는 상황입니다. 이 로그를 남기고 곧 프로그램이 종료되는 경우가 많습니다. Python에서는 CRITICAL이라고 부릅니다.
- 예: 시작할 때 DB에 아예 연결할 수 없음, 필수 설정 파일이 없음, 메모리 부족
레벨별 요약표
| 레벨 | 언제 | 예 | 운영 환경에서 | 누가 봐야 하나 |
|---|---|---|---|---|
| TRACE | 실행 흐름 하나하나 | 함수 진입과 종료 | 끔 | 개발자 (특수한 디버깅) |
| DEBUG | 문제 찾기용 자세한 정보 | 변수 값, 요청 원본, SQL | 보통 끔 | 개발자 |
| INFO | 정상적인 중요 사건 | 서버 시작, 주문 접수 | 켬 | 운영자 (흐름 파악) |
| WARN | 지금은 괜찮지만 주의 | 캐시로 대체, 디스크 85% | 켬 | 운영자 (주기적으로 확인) |
| ERROR | 기능 실패, 조치 필요 | 결제 실패, 예외 발생 | 켬 + 알림 | 담당자 (빠르게 확인) |
| FATAL | 프로그램 전체 중단 | DB 연결 불가로 종료 | 켬 + 긴급 알림 | 담당자 (즉시 대응) |
라이브러리마다 이름이 조금 다르다
| 라이브러리 | 레벨 (낮음 → 높음) | 비고 |
|---|---|---|
Python logging | DEBUG(10), INFO(20), WARNING(30), ERROR(40), CRITICAL(50) | TRACE 없음, 기본값 WARNING |
| Java SLF4J + Logback | TRACE, DEBUG, INFO, WARN, ERROR | FATAL 없음, Spring Boot 기본 조합 |
| Java Log4j 2 | TRACE, DEBUG, INFO, WARN, ERROR, FATAL | |
| 리눅스 syslog | debug, info, notice, warning, err, crit, alert, emerg | 8단계 |
브라우저, Node.js console | console.debug, console.info, console.warn, console.error | 개발자 도구에서 레벨별로 걸러 볼 수 있음 |
원문은 "보통 log4j 라이브러리를 활용한다"고 적었는데, 예전 Log4j 1.x는 2015년에 지원이 끝났고, Log4j 2는 2021년 말에 원격으로 코드를 실행할 수 있는 심각한 취약점(Log4Shell)이 발견되었습니다. 지금 Java에서는 SLF4J + Logback(Spring Boot 기본) 또는 취약점이 고쳐진 최신 Log4j 2를 씁니다. 레벨 개념 자체는 어느 라이브러리든 같습니다.
환경별로 레벨을 다르게 쓰기
| 환경 | 보통 설정 | 이유 |
|---|---|---|
| 내 컴퓨터 (개발) | DEBUG | 무슨 일이 일어나는지 자세히 봐야 함 |
| 테스트 서버 | DEBUG 또는 INFO | 문제 재현과 흐름 확인 |
| 운영 서버 | INFO 또는 WARN | 로그 양과 비용을 줄이고, 중요한 사건만 남김 |
코드 안에 레벨을 박아 두지 말고 환경 변수나 설정 파일로 바꿀 수 있게 만들어 두면, 운영 중 문제가 생겼을 때 코드를 고치지 않고 잠깐 DEBUG로 낮춰 원인을 찾을 수 있습니다.
좋은 로그를 남기는 요령
- 비밀번호, 토큰, 카드 번호, 주민등록번호 같은 민감 정보는 남기지 않습니다. 로그 파일은 생각보다 많은 사람이 보고, 오래 보관됩니다.
- 맥락을 함께 남깁니다. "결제 실패"보다 "주문 #1001 결제 실패 (금액 5000원, 사유: 타임아웃)"가 훨씬 쓸모 있습니다.
- 예외는 스택 트레이스와 함께 남깁니다. 어느 줄에서 실패했는지 알 수 있습니다. (Python의
log.exception()) - 반복문 안에서 INFO를 남발하지 않습니다. 1만 건을 처리할 때 1만 줄을 남기기보다 "1만 건 처리 완료, 실패 3건"처럼 요약합니다.
- 레벨을 정직하게 고릅니다. 별일 아닌 것을 ERROR로 남기면 알림이 쏟아져서 진짜 ERROR를 놓치게 됩니다.
- 시간, 레벨, 어디서 남겼는지가 같은 형식으로 들어가게 합니다. 나중에 검색하고 모으기 쉽습니다.
로그를 파일로 쌓고 오래된 로그를 정리하는 방법은 로그 관리에서 다룹니다.
코드로 감 잡기: Python logging으로 레벨 바꿔 보기
레벨에 따라 출력이 달라진다
같은 코드를 실행하면서 기준 레벨만 바꿔 보겠습니다.
# log_levels.py
import logging
import sys
level_name = sys.argv[1] if len(sys.argv) > 1 else "INFO"
logging.basicConfig(
level=getattr(logging, level_name),
format="%(levelname)-8s %(name)s: %(message)s",
)
log = logging.getLogger("order")
log.debug("장바구니 원본 데이터: %s", {"items": 3, "coupon": None})
log.info("주문 #1001 결제를 시작합니다")
log.warning("재고 서버 응답이 느려 캐시된 재고 값을 사용합니다")
log.error("결제 서버에 연결하지 못했습니다")
log.critical("DB에 연결할 수 없어 주문 서비스를 멈춥니다")$ python3 log_levels.py DEBUG
DEBUG order: 장바구니 원본 데이터: {'items': 3, 'coupon': None}
INFO order: 주문 #1001 결제를 시작합니다
WARNING order: 재고 서버 응답이 느려 캐시된 재고 값을 사용합니다
ERROR order: 결제 서버에 연결하지 못했습니다
CRITICAL order: DB에 연결할 수 없어 주문 서비스를 멈춥니다
$ python3 log_levels.py INFO
INFO order: 주문 #1001 결제를 시작합니다
WARNING order: 재고 서버 응답이 느려 캐시된 재고 값을 사용합니다
ERROR order: 결제 서버에 연결하지 못했습니다
CRITICAL order: DB에 연결할 수 없어 주문 서비스를 멈춥니다
$ python3 log_levels.py WARNING
WARNING order: 재고 서버 응답이 느려 캐시된 재고 값을 사용합니다
ERROR order: 결제 서버에 연결하지 못했습니다
CRITICAL order: DB에 연결할 수 없어 주문 서비스를 멈춥니다
$ python3 log_levels.py ERROR
ERROR order: 결제 서버에 연결하지 못했습니다
CRITICAL order: DB에 연결할 수 없어 주문 서비스를 멈춥니다코드는 한 줄도 바꾸지 않았는데, 기준선을 올릴수록 아래쪽(덜 중요한) 로그가 하나씩 사라집니다.
log.debug("... %s", 값)처럼 값을 바로 문자열에 끼워 넣지 않고 따로 넘기면, 해당 레벨이 꺼져 있을 때는 문자열을 만드는 작업 자체를 건너뛰어 조금 더 효율적입니다.getLogger("order")처럼 이름을 붙이면 어느 기능에서 남긴 로그인지 바로 보입니다.
레벨 숫자와 기본 설정
import logging
for name in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
print(name, getattr(logging, name))
logging.warning("basicConfig 없이 바로 쓰면")
logging.info("이 줄은 안 보입니다")DEBUG 10
INFO 20
WARNING 30
ERROR 40
CRITICAL 50
WARNING:root:basicConfig 없이 바로 쓰면레벨은 사실 숫자이고, 숫자가 클수록 심각합니다. 아무 설정 없이 쓰면 기본 기준이 WARNING(30)이라서 INFO 로그가 출력되지 않습니다. "로그를 남겼는데 안 보여요"의 흔한 원인입니다.
예외를 스택 트레이스와 함께 남기기
# log_exc.py
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("payment")
def pay(amount):
log.info("결제 요청: %d원", amount)
try:
return 10000 / amount
except ZeroDivisionError:
log.exception("결제 금액 계산 실패 (amount=%d)", amount)
return None
pay(5000)
pay(0)2026-10-04 05:59:46 [INFO] payment - 결제 요청: 5000원
2026-10-04 05:59:46 [INFO] payment - 결제 요청: 0원
2026-10-04 05:59:46 [ERROR] payment - 결제 금액 계산 실패 (amount=0)
Traceback (most recent call last):
File "/private/tmp/demo/log_exc.py", line 13, in pay
return 10000 / amount
~~~~~~^~~~~~~~
ZeroDivisionError: division by zerolog.exception()은 ERROR 레벨로 남기면서, 어느 파일 몇 번째 줄에서 무슨 예외가 났는지까지 함께 기록합니다. 시간과 레벨이 같은 형식으로 들어가 있어서 나중에 grep ERROR로 실패만 골라 보기도 쉽습니다.
면접에서 자주 나오는 질문
Q. 로그 레벨에는 무엇이 있고 각각 언제 쓰나요? 보통 DEBUG, INFO, WARN, ERROR (라이브러리에 따라 TRACE, FATAL 추가)를 씁니다. DEBUG는 개발용 자세한 정보, INFO는 정상적인 중요 사건, WARN은 당장 문제는 아니지만 주의할 상황, ERROR는 기능이 실패해 조치가 필요한 상황, FATAL은 프로그램 전체가 멈추는 상황입니다.
Q. 운영 환경에서는 어떤 레벨로 설정하나요? 보통 INFO나 WARN으로 설정합니다. DEBUG는 로그 양이 많아 저장 비용과 성능 부담이 크고, 민감한 값이 섞여 남을 위험도 있기 때문입니다. 문제가 생기면 설정으로 잠시 낮춰서 원인을 찾습니다.
Q. WARN과 ERROR는 어떻게 구분하나요? 요청이나 작업이 결국 성공했거나 대체 방법으로 처리됐으면 WARN, 실패해서 누군가 조치해야 하면 ERROR입니다. 예를 들어 외부 API 호출이 한 번 실패했지만 재시도로 성공했다면 WARN, 끝내 실패했다면 ERROR입니다.
Q. 로그를 남길 때 주의할 점은 무엇인가요? 비밀번호, 토큰 같은 민감 정보를 남기지 않고, 나중에 원인을 찾을 수 있도록 식별자와 맥락을 함께 남기며, 예외는 스택 트레이스와 함께 기록합니다. 레벨을 과장하지 않아야 알림이 의미를 가집니다.
정리
| 개념 | 한 줄 요약 |
|---|---|
| 로깅 레벨 | 로그에 중요도 등급을 붙여, 기준선 이상만 남기게 하는 장치 |
| 순서 | TRACE < DEBUG < INFO < WARN < ERROR < FATAL |
| DEBUG | 개발자용 자세한 정보, 운영에서는 보통 끔 |
| INFO | 정상적인 중요 사건, 운영 기본값 |
| WARN | 버티고 있지만 주의할 상황, 프로그램은 계속 동작 |
| ERROR / FATAL | 기능 실패로 조치 필요 / 프로그램 전체 중단 |
| 운영 팁 | 레벨은 설정으로 바꾸게, 민감 정보 금지, 예외는 스택 트레이스와 함께 |
