22. 로깅
print디버깅의 한계를 알고 로깅으로 넘어간다.- 5단계 로그 레벨로 메시지의 중요도를 구분한다.
- 로그 형식·파일 출력을 설정한다.
- 로거 객체로 체계적인 로깅을 한다.
먼저: print 대신 로깅을 쓰는 이유
코드가 잘 도는지 확인할 때 우리는 print를 뿌립니다. 작은 스크립트에선 괜찮지만, 프로그램이 커지면 문제가 생깁니다.
- 어디서 출력됐는지, 언제인지 알기 어렵다.
- 중요도 구분이 없다(에러와 단순 정보가 똑같이 보임).
- 배포할 때
print를 일일이 지워야 한다. - 파일에 남기거나 필터링하기 번거롭다.
로깅(logging)은 이 문제들을 해결하는 체계적인 기록 방식입니다. 메시지에 중요도(레벨)를 붙이고, 시간·위치를 자동 기록하며, 한 번 설정으로 화면·파일에 보내고 필터링까지 합니다.
import logging logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") logging.info("프로그램 시작") logging.warning("디스크 공간 부족") logging.error("파일을 찾을 수 없음") # INFO: 프로그램 시작 # WARNING: 디스크 공간 부족 # ERROR: 파일을 찾을 수 없음
print를 대체하는 체계적 기록 도구다. 중요도·시간·위치를 자동으로 붙이고, 설정 한 번으로 화면·파일 출력과 필터링을 제어한다. 실전 프로그램에서는 print 대신 로깅을 쓴다.5단계 로그 레벨
로깅의 핵심은 레벨(level)입니다. 메시지의 중요도를 다섯 단계로 나눕니다.
| 레벨 | 함수 | 언제 쓰나 |
|---|---|---|
| DEBUG | logging.debug() | 개발 중 상세 추적 정보 |
| INFO | logging.info() | 정상 동작 확인 ("시작됨", "처리 완료") |
| WARNING | logging.warning() | 문제는 아니나 주의할 상황 |
| ERROR | logging.error() | 기능이 실패한 오류 |
| CRITICAL | logging.critical() | 프로그램이 멈출 수준의 치명적 문제 |
위로 갈수록 덜 중요하고, 아래로 갈수록 심각합니다.
import logging logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s") logging.debug("변수 x = 42") # DEBUG: 변수 x = 42 logging.info("사용자 로그인") # INFO: 사용자 로그인 logging.warning("응답 느림") # WARNING: 응답 느림 logging.error("저장 실패") # ERROR: 저장 실패 logging.critical("DB 연결 끊김") # CRITICAL: DB 연결 끊김
⭐ 레벨로 필터링하기
basicConfig(level=...)로 설정한 레벨 이상만 출력됩니다. 이게 로깅의 강력한 점입니다 — 코드를 지우지 않고 출력량을 조절합니다.
import logging logging.basicConfig(level=logging.WARNING) # WARNING 이상만! logging.debug("안 보임") # 출력 안 됨 (WARNING보다 낮음) logging.info("안 보임") # 출력 안 됨 logging.warning("보임") # 출력됨 logging.error("보임") # 출력됨
flowchart TD
Msg["로그 메시지 발생"]:::data --> Check{"레벨 >=<br/>설정 레벨?"}:::proc
Check -->|예| Out["출력 ✅"]:::result
Check -->|아니오| Skip["무시 (안 보임)"]:::data
classDef data fill:#a8dadc,stroke:#457b9d,color:#1d3557
classDef proc fill:#fff3b0,stroke:#e0a800,color:#5c4500
classDef result fill:#b8e6c1,stroke:#34a853,color:#14532d
level=logging.DEBUG로 모든 걸 보고, 배포할 땐 level=logging.WARNING으로 바꾸면 됩니다. debug 로그를 코드에 남겨둔 채 출력만 끄는 것이죠. print를 일일이 지우던 수고가 사라집니다.print에는 없는 로깅의 결정적 장점이다.로그 형식 꾸미기
format으로 각 로그에 시간·레벨·위치 등을 붙입니다. 자주 쓰는 항목입니다.
| 형식 | 의미 |
|---|---|
%(asctime)s | 시간 |
%(levelname)s | 레벨 이름 (INFO 등) |
%(message)s | 메시지 본문 |
%(name)s | 로거 이름 |
%(filename)s | 파일명 |
%(lineno)d | 줄 번호 |
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S", ) logging.info("처리 시작") # 14:30:05 [INFO] 처리 시작
datefmt로 시간 표시 형식을 정합니다(다음 장 datetime 형식과 같은 기호).
파일에 로그 남기기
basicConfig에 filename을 주면 화면 대신 파일에 기록됩니다. 프로그램이 꺼진 뒤에도 기록이 남아, 나중에 문제를 추적할 수 있습니다.
import logging logging.basicConfig( filename="app.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", filemode="w", # "w"=새로 쓰기, "a"=이어 쓰기(기본) ) logging.info("서버 시작") logging.error("요청 실패")
app.log 파일 내용:
2025-12-31 14:30:05,123 INFO 서버 시작 2025-12-31 14:30:06,456 ERROR 요청 실패
filemode의 기본값은 "a"(추가)라, 실행할 때마다 로그가 쌓입니다. 매 실행마다 새로 쓰려면 "w"로 지정하세요(16장 파일 모드와 같은 의미).⭐ 예외와 함께 로깅하기
에러를 처리할 때, logging.exception()을 쓰면 트레이스백 전체를 자동으로 기록합니다. 디버깅에 결정적입니다.
import logging logging.basicConfig(level=logging.ERROR, format="%(levelname)s: %(message)s") try: result = 10 / 0 except ZeroDivisionError: logging.exception("계산 중 오류 발생") # ERROR: 계산 중 오류 발생 # Traceback (most recent call last): # File "...", line 6, in <module> # result = 10 / 0 # ZeroDivisionError: division by zero
logging.exception()은 except 블록 안에서만 쓰며, 메시지와 함께 그 예외의 트레이스백을 자동으로 붙여줍니다. "무슨 에러가 어디서 났는지"가 로그에 고스란히 남습니다.
except 블록에서는 logging.error()보다 logging.exception()을 써라. 트레이스백까지 기록되어 나중에 원인을 추적하기 쉽다.로거 객체: 체계적인 로깅
지금까지는 logging.info()처럼 모듈 함수를 직접 썼습니다. 규모가 커지면 로거 객체를 만들어 쓰는 게 좋습니다. 각 모듈이 자기 이름의 로거를 가져, 어디서 나온 로그인지 구분됩니다.
import logging # 이 모듈 전용 로거 생성 (관습: __name__ 사용) logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # 출력 방식(핸들러)과 형식 설정 handler = logging.StreamHandler() handler.setFormatter(logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s", datefmt="%H:%M:%S")) logger.addHandler(handler) logger.info("애플리케이션 시작") logger.warning("설정 파일 없음, 기본값 사용") # 14:30:05 [__main__] INFO: 애플리케이션 시작 # 14:30:05 [__main__] WARNING: 설정 파일 없음, 기본값 사용
구성 요소를 정리하면 이렇습니다.
| 요소 | 역할 |
|---|---|
| Logger | 로그를 만드는 주체 (getLogger) |
| Handler | 로그를 어디로 보낼지 (화면=StreamHandler, 파일=FileHandler) |
| Formatter | 로그를 어떤 형식으로 |
flowchart LR
Code["logger.info(...)"]:::user --> Logger["Logger<br/>(레벨 필터)"]:::proc
Logger --> Handler["Handler<br/>(화면/파일)"]:::proc
Handler --> Format["Formatter<br/>(형식 적용)"]:::data
Format --> Out["출력 ✅"]:::result
classDef user fill:#fff3b0,stroke:#e0a800,color:#5c4500
classDef proc fill:#7fd8d8,stroke:#2a9d8f,color:#14532d
classDef data fill:#a8dadc,stroke:#457b9d,color:#1d3557
classDef result fill:#b8e6c1,stroke:#34a853,color:#14532d
getLogger(__name__)은 모듈 이름을 로거 이름으로 씁니다(15장의 __name__). 큰 프로젝트에서 "어느 모듈이 낸 로그인지"가 자동으로 표시되어 매우 유용합니다.나쁜 예 ❌ vs 좋은 예 ✅
# ❌ 나쁜 예: print로 디버깅 def process(data): print("처리 시작") # 배포 때 다 지워야 함 print("데이터:", data) # 중요도 구분 없음 if not data: print("데이터 없음!") # 에러인지 정보인지 모호 # ✅ 좋은 예: 로깅 import logging logger = logging.getLogger(__name__) def process(data): logger.info("처리 시작") logger.debug("데이터: %s", data) # 상세는 DEBUG if not data: logger.warning("데이터가 비어 있음") # 적절한 레벨
logger.debug("x = %s", x)처럼 %s와 인자를 따로 주는 방식이 권장됩니다. f-string보다 약간의 성능 이점이 있고(필터링되면 문자열 조합을 건너뜀), 로깅의 관용적 방식입니다.이 장에서 배운 것
- 로깅은
print를 대체하는 체계적 기록 도구다. 중요도·시간·위치를 자동으로 붙인다. - 5단계 레벨: DEBUG < INFO < WARNING < ERROR < CRITICAL. 설정 레벨 이상만 출력된다.
- 코드를 그대로 두고 설정 한 줄로 출력량을 조절한다 — 이것이
print에 없는 장점이다. format으로 형식을,filename으로 파일 출력을 정한다.except에서는logging.exception()으로 트레이스백까지 기록한다.- 규모가 크면 로거 객체(
getLogger(__name__)) + 핸들러 + 포매터로 체계적으로 구성한다.
🧪 실습 문제
문제 1. logging.basicConfig(level=logging.WARNING)으로 설정했을 때, 다음 중 화면에 출력되는 것은?
logging.debug("A") logging.info("B") logging.warning("C") logging.error("D")
문제 2. 로그 레벨 5가지를 덜 심각한 것부터 심각한 순으로 나열하세요.
문제 3. 로그를 error.log 파일에 ERROR 레벨 이상만, 시간 레벨 메시지 형식으로 기록하는 basicConfig 설정을 작성하세요.
문제 4. 다음 코드에서 트레이스백까지 로그에 남기려면 어느 함수를 써야 할까요?
try: int("abc") except ValueError: logging.???("변환 실패")
문제 5. print("처리 완료") 같은 디버깅 출력을 로깅으로 바꾼다면, 어떤 레벨이 적절할까요? 그리고 왜 print보다 로깅이 나은지 한 가지 이유를 적으세요.
<details>
<summary>✅ 정답·해설 보기</summary>
1. C(warning)와 D(error). WARNING 이상만 출력되므로 debug·info는 안 보입니다.
2. DEBUG → INFO → WARNING → ERROR → CRITICAL.
3.
import logging logging.basicConfig( filename="error.log", level=logging.ERROR, format="%(asctime)s %(levelname)s %(message)s", )
4. logging.exception("변환 실패"). except 블록에서 호출하면 메시지와 함께 트레이스백을 자동 기록합니다.
5. logging.info("처리 완료")가 적절합니다(정상 동작 정보). 로깅이 나은 이유 예: 배포 시 코드를 지우지 않고 레벨 설정만으로 출력을 끌 수 있다 / 시간·위치가 자동 기록된다 / 파일에 남길 수 있다 (하나만 적으면 됨).
</details>
◀️ 이전 장: 21. 정규표현식 | ▶️ 다음 장: 23. 날짜와 시간