Python Decorator를 이용한 API 요청 로깅
문제 상황
회사 레거시 API 서버(Flask 기반)에서 간헐적으로 500 에러가 발생했다. 에러 추적을 위해 요청 파라미터와 응답을 로깅해야 했는데, 각 엔드포인트마다 로깅 코드를 추가하기엔 중복이 너무 많았다.
Decorator를 이용한 해결
Python의 decorator를 활용하면 기존 함수를 수정하지 않고 로깅 기능을 추가할 수 있다.
import functools
import logging
import time
from flask import request, jsonify
logger = logging.getLogger(__name__)
def log_api_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
# 요청 정보 로깅
logger.info(f"[REQUEST] {request.method} {request.path}")
logger.info(f"[PARAMS] {request.args.to_dict()}")
try:
result = func(*args, **kwargs)
elapsed = time.time() - start_time
logger.info(f"[RESPONSE] {func.__name__} - {elapsed:.2f}s")
return result
except Exception as e:
logger.error(f"[ERROR] {func.__name__}: {str(e)}")
raise
return wrapper
적용은 간단했다.
@app.route('/api/users/<user_id>')
@log_api_call
def get_user(user_id):
user = User.query.get(user_id)
return jsonify(user.to_dict())
개선 사항
실제로 운영해보니 몇 가지 문제가 있었다.
- POST 요청의 body 로깅 누락 →
request.get_json()추가 - 민감정보(password 등) 로깅 → 특정 필드 마스킹 처리
- 로그 파일 비대화 → logrotate 설정으로 해결
특히 민감정보 처리를 위해 decorator에 옵션을 추가했다.
def log_api_call(mask_fields=None):
if mask_fields is None:
mask_fields = ['password', 'token']
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 로깅 로직...
pass
return wrapper
return decorator
결과
일주일간 로그를 수집한 결과, 특정 쿼리 파라미터 조합에서 DB 타임아웃이 발생하는 것을 확인했다. Decorator 패턴 덕분에 전체 API에 일관된 로깅을 빠르게 적용할 수 있었다.