Python 개발자라면 누구나 로깅의 중요성을 알고 있지만, 표준 logging 모듈의 복잡한 설정에 지쳐본 경험이 있을 것입니다. Loguru는 이러한 문제를 해결하기 위해 탄생한 혁신적인 로깅 라이브러리로, “단 한 줄의 import로 완벽한 로깅을”이라는 철학을 바탕으로 설계되었습니다.
Loguru란 무엇인가?
Loguru는 Python의 서드파티 로깅 라이브러리로, 복잡한 설정 없이도 강력하고 아름다운 로그를 제공합니다. 2018년에 처음 릴리스된 이후 GitHub에서 19,000개 이상의 스타를 받으며 개발자들 사이에서 빠르게 인기를 얻고 있습니다.
설치
pip install loguru
기본 사용법: 단 한 줄로 시작하기
Loguru의 가장 큰 매력은 복잡한 설정 없이 바로 사용할 수 있다는 점입니다.
from loguru import logger
logger.debug("디버그 메시지입니다")
logger.info("정보성 메시지입니다")
logger.warning("경고 메시지입니다")
logger.error("에러가 발생했습니다")
logger.critical("심각한 오류입니다")
실행 결과 (터미널에서 색상으로 표시됨):
2024-05-29 14:23:45.123 | DEBUG | __main__:<module>:3 - 디버그 메시지입니다
2024-05-29 14:23:45.124 | INFO | __main__:<module>:4 - 정보성 메시지입니다
2024-05-29 14:23:45.125 | WARNING | __main__:<module>:5 - 경고 메시지입니다
2024-05-29 14:23:45.126 | ERROR | __main__:<module>:6 - 에러가 발생했습니다
2024-05-29 14:23:45.127 | CRITICAL | __main__:<module>:7 - 심각한 오류입니다
자동 색상화: 가독성의 혁신
Loguru의 가장 눈에 띄는 특징 중 하나는 터미널에서의 자동 색상화입니다. 각 로그 레벨마다 다른 색상이 적용되어 로그를 훨씬 쉽게 읽을 수 있습니다.
- DEBUG: 회색 (차분한 느낌)
- INFO: 파란색 (정보성)
- WARNING: 노란색 (주의)
- ERROR: 빨간색 (위험)
- CRITICAL: 굵은 빨간색 (매우 위험)
문자열 포매팅: 직관적이고 강력하게
Loguru는 두 가지 문자열 포매팅 방식을 지원합니다.
1. 위치 기반 포매팅 (str.format 스타일)
from loguru import logger
name = "김철수"
age = 30
score = 95.5
logger.info("사용자 {}는 {}세입니다", name, age)
logger.info("사용자 {name}의 점수는 {score}점입니다", name=name, score=score)
출력:
2024-05-29 14:25:10.456 | INFO | __main__:<module>:6 - 사용자 김철수는 30세입니다
2024-05-29 14:25:10.457 | INFO | __main__:<module>:7 - 사용자 김철수의 점수는 95.5점입니다
2. f-string과 함께 사용
from loguru import logger
user_id = 12345
action = "로그인"
timestamp = "2024-05-29 14:30:00"
logger.info(f"사용자 {user_id}가 {timestamp}에 {action}했습니다")
파일 로깅: 간단하지만 강력하게
기본 파일 로깅
from loguru import logger
# 파일에 로그 추가 (콘솔 출력도 유지됨)
logger.add("application.log")
logger.info("이 메시지는 콘솔과 파일 모두에 기록됩니다")
logger.error("에러 메시지도 동일하게 처리됩니다")
고급 파일 로깅 설정
from loguru import logger
# 기존 핸들러 제거 (콘솔 출력 제거)
logger.remove()
# 상세한 설정으로 파일 핸들러 추가
logger.add(
"logs/app_{time:YYYY-MM-DD}.log", # 날짜별 파일 생성
rotation="00:00", # 매일 자정에 로테이션
retention="30 days", # 30일간 보관
compression="zip", # 압축 저장
level="INFO", # INFO 레벨 이상만 기록
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} - {message}"
)
# 콘솔 출력도 추가 (색상 포함)
logger.add(
sys.stderr,
colorize=True,
format="<green>{time:HH:mm:ss}</green> | <level>{level}</level> | <cyan>{message}</cyan>"
)
logger.info("애플리케이션이 시작되었습니다")
logger.warning("메모리 사용률이 높습니다")
로그 로테이션: 유연하고 직관적
Loguru는 다양한 로테이션 옵션을 제공합니다.
from loguru import logger
# 크기 기반 로테이션
logger.add("app.log", rotation="500 MB")
# 시간 기반 로테이션
logger.add("app.log", rotation="1 day") # 매일
logger.add("app.log", rotation="1 week") # 매주
logger.add("app.log", rotation="1 month") # 매월
# 특정 시간에 로테이션
logger.add("app.log", rotation="00:00") # 매일 자정
logger.add("app.log", rotation="monday") # 매주 월요일
# 조건부 로테이션
logger.add("app.log", rotation=lambda message, file: file.stat().st_size > 1e6)
예외 처리: 상세하고 아름다운 트레이스백
Loguru는 예외 발생 시 매우 상세하고 읽기 쉬운 트레이스백을 제공합니다.
from loguru import logger
def divide_numbers(a, b):
try:
result = a / b
logger.info(f"계산 결과: {a} ÷ {b} = {result}")
return result
except Exception:
logger.exception("계산 중 오류가 발생했습니다")
return None
def process_data():
try:
numbers = [10, 5, 0, 2]
for i, num in enumerate(numbers):
result = divide_numbers(100, num)
logger.debug(f"Step {i+1}: 100 ÷ {num} = {result}")
except Exception:
logger.exception("데이터 처리 중 오류 발생")
# 테스트 실행
process_data()
실행 결과에는 매우 상세한 컨텍스트 정보가 포함됩니다:
2024-05-29 14:35:22.789 | ERROR | __main__:divide_numbers:8 - 계산 중 오류가 발생했습니다
Traceback (most recent call last):
File "test.py", line 5, in divide_numbers
result = a / b
ZeroDivisionError: division by zero
구조화된 로깅: JSON과 키-값 쌍
JSON 형태로 로그 저장
from loguru import logger
# JSON 직렬화 활성화
logger.add("structured.log", serialize=True)
# 구조화된 데이터와 함께 로깅
logger.info("사용자 로그인", user_id=12345, ip="192.168.1.100", user_agent="Chrome/91.0")
logger.error("API 호출 실패", endpoint="/api/users", status_code=500, response_time=1.5)
structured.log 파일 내용:
{"text": "2024-05-29 14:40:15.123 | INFO | __main__:<module>:5 - 사용자 로그인\n", "record": {"elapsed": {"repr": "0:00:00.001234", "seconds": 0.001234}, "exception": null, "extra": {"user_id": 12345, "ip": "192.168.1.100", "user_agent": "Chrome/91.0"}, "file": {"name": "test.py", "path": "/Users/test.py"}, "function": "<module>", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 5, "message": "사용자 로그인", "module": "test", "name": "__main__", "process": {"id": 12345, "name": "MainProcess"}, "thread": {"id": 67890, "name": "MainThread"}, "time": {"repr": "2024-05-29 14:40:15.123456+09:00", "timestamp": 1716965215.123456}}}
키-값 쌍으로 컨텍스트 추가
from loguru import logger
# 컨텍스트 정보 바인딩
logger_with_context = logger.bind(request_id="req-123", user_id=456)
logger_with_context.info("API 요청 시작")
logger_with_context.info("데이터베이스 쿼리 실행")
logger_with_context.error("권한 없음 오류")
필터링과 레벨 관리
동적 레벨 변경
from loguru import logger
import sys
# 개발 환경과 프로덕션 환경에 따른 설정
DEBUG_MODE = True
if DEBUG_MODE:
logger.add(sys.stderr, level="DEBUG")
logger.add("debug.log", level="DEBUG")
else:
logger.add(sys.stderr, level="INFO")
logger.add("production.log", level="WARNING")
logger.debug("이 메시지는 DEBUG_MODE일 때만 표시됩니다")
logger.info("일반 정보 메시지")
logger.warning("경고 메시지")
커스텀 필터
from loguru import logger
import sys
def my_filter(record):
# 특정 모듈의 로그만 허용
return record["name"] == "__main__"
def sensitive_filter(record):
# 민감한 정보가 포함된 로그 필터링
return "password" not in record["message"].lower()
logger.add(sys.stderr, filter=my_filter)
logger.add("filtered.log", filter=sensitive_filter)
logger.info("일반 로그 메시지")
logger.info("사용자 password 변경 시도") # filtered.log에 기록되지 않음
비동기 로깅: 성능 최적화
고성능 애플리케이션에서는 비동기 로깅이 중요합니다.
from loguru import logger
import asyncio
# 비동기 로깅 활성화
logger.add("async.log", enqueue=True)
async def process_requests():
for i in range(1000):
logger.info(f"Processing request #{i}")
await asyncio.sleep(0.001) # 비동기 작업 시뮬레이션
# 비동기 실행
asyncio.run(process_requests())
실제 프로젝트 활용 예시
다음은 FastAPI 웹 애플리케이션에서 Loguru를 활용하는 실제적인 예시입니다.
from loguru import logger
from fastapi import FastAPI, Request
import sys
import time
# 로깅 설정
logger.remove() # 기본 핸들러 제거
# 개발용 콘솔 출력
logger.add(
sys.stderr,
colorize=True,
format="<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
level="DEBUG"
)
# 프로덕션용 파일 로깅
logger.add(
"logs/app_{time:YYYY-MM-DD}.log",
rotation="00:00",
retention="30 days",
compression="zip",
level="INFO",
serialize=True # JSON 형태로 저장
)
# 에러 전용 로그
logger.add(
"logs/errors_{time:YYYY-MM-DD}.log",
rotation="00:00",
retention="90 days",
level="ERROR",
backtrace=True,
diagnose=True
)
app = FastAPI()
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
# 요청 로깅
logger.info(
"HTTP 요청 시작",
method=request.method,
url=str(request.url),
client=request.client.host if request.client else None
)
response = await call_next(request)
# 응답 로깅
process_time = time.time() - start_time
logger.info(
"HTTP 요청 완료",
method=request.method,
url=str(request.url),
status_code=response.status_code,
process_time=round(process_time, 4)
)
return response
@app.get("/users/{user_id}")
async def get_user(user_id: int):
logger.info(f"사용자 정보 조회 요청: {user_id}")
try:
# 비즈니스 로직
if user_id <= 0:
logger.warning(f"잘못된 사용자 ID: {user_id}")
return {"error": "Invalid user ID"}
# 데이터베이스 조회 시뮬레이션
user_data = {"id": user_id, "name": f"User{user_id}"}
logger.info(f"사용자 정보 조회 성공: {user_id}")
return user_data
except Exception as e:
logger.exception(f"사용자 정보 조회 실패: {user_id}")
return {"error": "Internal server error"}
class UserService:
def __init__(self):
self.logger = logger.bind(service="UserService")
async def create_user(self, user_data: dict):
self.logger.info("새 사용자 생성 시작", user_data=user_data)
try:
# 유효성 검사
if not user_data.get("email"):
self.logger.warning("이메일 누락", user_data=user_data)
return None
# 사용자 생성 로직
user_id = hash(user_data["email"]) % 10000
self.logger.info("사용자 생성 완료", user_id=user_id, email=user_data["email"])
return {"id": user_id, **user_data}
except Exception:
self.logger.exception("사용자 생성 실패", user_data=user_data)
return None
# 사용 예시
user_service = UserService()
성능 고려사항
지연 평가 (Lazy Evaluation)
from loguru import logger
# 비효율적인 방식
expensive_data = get_expensive_computation() # 항상 실행됨
logger.debug(f"Debug info: {expensive_data}")
# 효율적인 방식 (로그 레벨이 DEBUG가 아니면 실행되지 않음)
logger.debug("Debug info: {}", lambda: get_expensive_computation())
배치 로깅
from loguru import logger
# 대량의 로그를 효율적으로 처리
logger.add("batch.log", enqueue=True, backtrace=False, diagnose=False)
# 대량 데이터 처리
for i in range(10000):
logger.info(f"Processing item {i}")
Loguru vs 표준 logging 모듈 비교
Loguru의 장점
간편한 사용법: 복잡한 설정 없이 바로 사용 가능합니다.
자동 색상화: 터미널에서 로그를 더 쉽게 읽을 수 있습니다.
직관적인 API: 메서드 체이닝과 자연스러운 문법을 제공합니다.
풍부한 기본 기능: 로테이션, 압축, 필터링 등이 기본 제공됩니다.
아름다운 예외 처리: 상세하고 읽기 쉬운 트레이스백을 제공합니다.
Loguru의 단점
서드파티 의존성: 별도 설치가 필요합니다.
호환성 고려: 기존 logging 모듈과의 호환성을 고려해야 할 수 있습니다.
학습 비용: 팀 전체가 새로운 라이브러리를 학습해야 합니다.
언제 Loguru를 선택해야 할까?
Loguru가 적합한 경우
새로운 프로젝트: 처음부터 시작하는 프로젝트에서는 Loguru의 장점을 최대한 활용할 수 있습니다.
개인 프로젝트나 소규모 팀: 빠른 개발과 간편함이 중요한 환경에 적합합니다.
개발자 경험 중시: 로깅 설정에 시간을 쓰기보다는 핵심 기능 개발에 집중하고 싶은 경우입니다.
모던한 개발 환경: 최신 Python 기능과 패러다임을 적극 활용하는 프로젝트에 어울립니다.
표준 logging이 더 적합한 경우
기존 대규모 시스템: 이미 표준 logging으로 구축된 시스템의 경우 일관성 유지가 중요합니다.
엔터프라이즈 환경: 외부 의존성을 최소화해야 하는 보수적인 환경입니다.
라이브러리 개발: 다른 개발자들이 사용할 라이브러리를 만들 때는 표준 라이브러리 사용을 권장합니다.
마무리
Loguru는 Python 로깅의 새로운 패러다임을 제시하는 혁신적인 라이브러리입니다. 복잡한 설정 없이도 아름답고 강력한 로깅을 구현할 수 있어, 특히 모던 Python 개발 환경에서 개발자 경험을 크게 향상시킵니다.
기존의 표준 logging 모듈이 제공하는 안정성과 호환성도 중요하지만, Loguru가 제공하는 편의성과 현대적인 기능들은 많은 프로젝트에서 생산성 향상에 기여할 수 있습니다. 프로젝트의 요구사항과 팀의 상황을 고려하여 적절한 선택을 하시기 바랍니다.