프로젝트 로깅 설정
프로젝트 로깅 인프라를 자동으로 구성하는 스킬.
목적
- 로깅 모듈 자동 생성
- 환경별 설정 분리 (개발/프로덕션)
- JSON 포맷터 및 컨텍스트 필터 제공
- 로그 파일 로테이션 설정
사용법
bash
1/setup-logging # 대화형 설정
2/setup-logging --minimal # 최소 설정 (콘솔만)
3/setup-logging --standard # 표준 설정 (콘솔 + 파일)
4/setup-logging --json # JSON 로깅 (프로덕션용)
AskUserQuestion 활용 지점
지점 1: 로깅 수준 선택
플래그가 없을 때 로깅 수준을 선택한다:
yaml
1AskUserQuestion:
2 questions:
3 - question: "로깅 수준을 선택해주세요"
4 header: "로깅 수준"
5 multiSelect: false
6 options:
7 - label: "standard - 텍스트 로깅 (권장)"
8 description: "콘솔 + 파일 로테이션 | 개발/일반 애플리케이션"
9 - label: "json - JSON 로깅"
10 description: "구조화 로그 | 프로덕션, 로그 분석 파이프라인"
11 - label: "minimal - 최소 로깅"
12 description: "콘솔만 | 빠른 시작, 스크립트"
13 - label: "verbose - 상세 로깅"
14 description: "디버그 정보 포함 | 문제 진단"
지점 2: 민감정보 필터 활성화
보안을 위해 민감정보 자동 필터링 여부를 확인한다:
yaml
1AskUserQuestion:
2 questions:
3 - question: "민감정보 자동 필터를 활성화할까요?"
4 header: "보안 필터"
5 multiSelect: false
6 options:
7 - label: "예 - 필터 활성화 (권장)"
8 description: "비밀번호, API 키, 토큰 자동 마스킹"
9 - label: "아니오 - 필터 없음"
10 description: "개발 환경에서만 사용"
11 - label: "커스텀 규칙"
12 description: "필터링할 패턴 직접 지정"
설정 프로파일
Minimal (최소)
/setup-logging --minimal
|
+-- src/{project}/utils/logging.py (기본 설정)
+-- 콘솔 출력만
용도: 빠른 시작, 스크립트, CLI 도구
Standard (표준)
/setup-logging --standard
|
+-- src/{project}/utils/logging.py (확장 설정)
+-- src/{project}/utils/log_context.py (컨텍스트 관리)
+-- 콘솔 + 파일 로테이션
+-- 환경별 설정 분리
용도: 일반 애플리케이션, API 서버
JSON (프로덕션)
/setup-logging --json
|
+-- src/{project}/utils/logging.py (JSON 포맷터)
+-- src/{project}/utils/log_context.py (컨텍스트 관리)
+-- src/{project}/utils/log_filters.py (민감정보 필터)
+-- JSON 포맷 출력
+-- 로그 수집 시스템 연동 최적화
용도: 프로덕션 환경, 로그 분석 파이프라인
생성 파일
1. logging.py (핵심 모듈)
python
1"""
2프로젝트 로깅 설정 모듈.
3
4사용법:
5 from {project}.utils.logging import setup_logging, get_logger
6
7 # 애플리케이션 시작 시 한 번 호출
8 setup_logging()
9
10 # 각 모듈에서 로거 획득
11 logger = get_logger(__name__)
12 logger.info("Application started")
13"""
14import logging
15import logging.handlers
16import sys
17from pathlib import Path
18from typing import Optional
19
20# 환경 변수
21import os
22
23LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
24LOG_FORMAT = os.getenv("LOG_FORMAT", "text") # text or json
25LOG_DIR = os.getenv("LOG_DIR", "logs")
26APP_NAME = os.getenv("APP_NAME", "{project}")
27
28
29# 포맷 정의
30TEXT_FORMAT = (
31 "%(asctime)s | %(levelname)-8s | "
32 "%(name)s:%(funcName)s:%(lineno)d | %(message)s"
33)
34
35TEXT_FORMAT_SIMPLE = "%(asctime)s | %(levelname)-8s | %(message)s"
36
37
38def setup_logging(
39 level: Optional[str] = None,
40 log_format: Optional[str] = None,
41 log_dir: Optional[str] = None,
42 console: bool = True,
43 file: bool = True,
44) -> None:
45 """
46 로깅 시스템 초기화.
47
48 Args:
49 level: 로그 레벨 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
50 log_format: 로그 포맷 ("text" 또는 "json")
51 log_dir: 로그 파일 디렉토리
52 console: 콘솔 출력 여부
53 file: 파일 출력 여부
54 """
55 level = level or LOG_LEVEL
56 log_format = log_format or LOG_FORMAT
57 log_dir = log_dir or LOG_DIR
58
59 # 루트 로거 설정
60 root_logger = logging.getLogger()
61 root_logger.setLevel(getattr(logging, level))
62
63 # 기존 핸들러 제거
64 root_logger.handlers.clear()
65
66 # 포맷터 선택
67 if log_format == "json":
68 from {project}.utils.log_formatters import JSONFormatter
69 formatter = JSONFormatter()
70 else:
71 formatter = logging.Formatter(TEXT_FORMAT)
72
73 # 콘솔 핸들러
74 if console:
75 console_handler = logging.StreamHandler(sys.stdout)
76 console_handler.setFormatter(formatter)
77 console_handler.setLevel(getattr(logging, level))
78 root_logger.addHandler(console_handler)
79
80 # 파일 핸들러 (로테이션)
81 if file:
82 log_path = Path(log_dir)
83 log_path.mkdir(parents=True, exist_ok=True)
84
85 file_handler = logging.handlers.RotatingFileHandler(
86 log_path / f"{APP_NAME}.log",
87 maxBytes=10 * 1024 * 1024, # 10MB
88 backupCount=5,
89 encoding="utf-8",
90 )
91 file_handler.setFormatter(formatter)
92 file_handler.setLevel(getattr(logging, level))
93 root_logger.addHandler(file_handler)
94
95 # 에러 전용 파일
96 error_handler = logging.handlers.RotatingFileHandler(
97 log_path / f"{APP_NAME}.error.log",
98 maxBytes=10 * 1024 * 1024,
99 backupCount=5,
100 encoding="utf-8",
101 )
102 error_handler.setFormatter(formatter)
103 error_handler.setLevel(logging.ERROR)
104 root_logger.addHandler(error_handler)
105
106 # 외부 라이브러리 로그 레벨 조정
107 logging.getLogger("urllib3").setLevel(logging.WARNING)
108 logging.getLogger("httpx").setLevel(logging.WARNING)
109 logging.getLogger("httpcore").setLevel(logging.WARNING)
110
111
112def get_logger(name: str) -> logging.Logger:
113 """
114 모듈별 로거 획득.
115
116 Args:
117 name: 로거 이름 (보통 __name__ 사용)
118
119 Returns:
120 logging.Logger: 설정된 로거
121 """
122 return logging.getLogger(name)
python
1"""JSON 로그 포맷터."""
2import json
3import logging
4import traceback
5from datetime import datetime, timezone
6from typing import Any
7
8
9class JSONFormatter(logging.Formatter):
10 """
11 구조화된 JSON 로그 포맷터.
12
13 로그 수집 시스템(ELK, CloudWatch, Datadog 등)과 호환.
14 """
15
16 def format(self, record: logging.LogRecord) -> str:
17 log_data: dict[str, Any] = {
18 "timestamp": datetime.now(timezone.utc).isoformat(),
19 "level": record.levelname,
20 "logger": record.name,
21 "message": record.getMessage(),
22 "module": record.module,
23 "function": record.funcName,
24 "line": record.lineno,
25 }
26
27 # 프로세스/스레드 정보
28 log_data["process_id"] = record.process
29 log_data["thread_name"] = record.threadName
30
31 # 예외 정보
32 if record.exc_info:
33 log_data["exception"] = {
34 "type": record.exc_info[0].__name__ if record.exc_info[0] else None,
35 "message": str(record.exc_info[1]) if record.exc_info[1] else None,
36 "traceback": traceback.format_exception(*record.exc_info),
37 }
38
39 # extra 필드 추가 (request_id, user_id 등)
40 reserved_attrs = {
41 "name", "msg", "args", "created", "filename", "funcName",
42 "levelname", "levelno", "lineno", "module", "msecs",
43 "pathname", "process", "processName", "relativeCreated",
44 "stack_info", "exc_info", "exc_text", "thread", "threadName",
45 "message", "asctime",
46 }
47
48 for key, value in record.__dict__.items():
49 if key not in reserved_attrs and not key.startswith("_"):
50 log_data[key] = value
51
52 return json.dumps(log_data, ensure_ascii=False, default=str)
3. log_context.py (컨텍스트 관리)
python
1"""로그 컨텍스트 관리."""
2import contextvars
3import logging
4import uuid
5from typing import Any, Optional
6
7# 컨텍스트 변수 정의
8request_id_var: contextvars.ContextVar[str] = contextvars.ContextVar(
9 "request_id", default=""
10)
11user_id_var: contextvars.ContextVar[str] = contextvars.ContextVar(
12 "user_id", default=""
13)
14extra_context_var: contextvars.ContextVar[dict] = contextvars.ContextVar(
15 "extra_context", default={}
16)
17
18
19class ContextFilter(logging.Filter):
20 """로그 레코드에 컨텍스트 정보를 자동 추가하는 필터."""
21
22 def filter(self, record: logging.LogRecord) -> bool:
23 record.request_id = request_id_var.get()
24 record.user_id = user_id_var.get()
25
26 # 추가 컨텍스트 병합
27 for key, value in extra_context_var.get().items():
28 setattr(record, key, value)
29
30 return True
31
32
33def set_request_context(
34 request_id: Optional[str] = None,
35 user_id: Optional[str] = None,
36 **extra: Any,
37) -> None:
38 """
39 현재 요청의 로그 컨텍스트 설정.
40
41 Args:
42 request_id: 요청 ID (없으면 자동 생성)
43 user_id: 사용자 ID
44 **extra: 추가 컨텍스트 필드
45 """
46 request_id_var.set(request_id or str(uuid.uuid4()))
47
48 if user_id:
49 user_id_var.set(user_id)
50
51 if extra:
52 current = extra_context_var.get()
53 extra_context_var.set({**current, **extra})
54
55
56def clear_request_context() -> None:
57 """요청 컨텍스트 초기화."""
58 request_id_var.set("")
59 user_id_var.set("")
60 extra_context_var.set({})
61
62
63def get_request_id() -> str:
64 """현재 요청 ID 반환."""
65 return request_id_var.get()
66
67
68# 컨텍스트 필터를 기본으로 적용
69def install_context_filter() -> None:
70 """루트 로거에 컨텍스트 필터 설치."""
71 root_logger = logging.getLogger()
72 context_filter = ContextFilter()
73
74 for handler in root_logger.handlers:
75 handler.addFilter(context_filter)
4. log_filters.py (민감정보 필터)
python
1"""민감 정보 마스킹 필터."""
2import logging
3import re
4from typing import Pattern, Set
5
6
7class SensitiveDataFilter(logging.Filter):
8 """
9 민감 정보를 자동으로 마스킹하는 필터.
10
11 로그 메시지에서 비밀번호, API 키, 토큰 등을 마스킹.
12 """
13
14 SENSITIVE_PATTERNS: list[tuple[Pattern, str]] = [
15 # 비밀번호
16 (re.compile(r'password["\']?\s*[:=]\s*["\']?[^"\'}\s,]+', re.I), 'password=***MASKED***'),
17 # API 키
18 (re.compile(r'api[_-]?key["\']?\s*[:=]\s*["\']?[^"\'}\s,]+', re.I), 'api_key=***MASKED***'),
19 # 토큰
20 (re.compile(r'token["\']?\s*[:=]\s*["\']?[^"\'}\s,]+', re.I), 'token=***MASKED***'),
21 # Bearer 토큰
22 (re.compile(r'Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+', re.I), 'Bearer ***MASKED***'),
23 # 이메일 (부분 마스킹)
24 (re.compile(r'([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'), r'\1[at]\2'),
25 # 신용카드 번호
26 (re.compile(r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b'), '****-****-****-****'),
27 ]
28
29 SENSITIVE_KEYS: Set[str] = {
30 "password", "passwd", "secret", "token", "api_key", "apikey",
31 "authorization", "auth", "credential", "credit_card", "ssn",
32 "social_security", "private_key", "secret_key",
33 }
34
35 def filter(self, record: logging.LogRecord) -> bool:
36 # 메시지 마스킹
37 record.msg = self._mask_message(str(record.msg))
38
39 # args 마스킹
40 if record.args:
41 record.args = tuple(
42 self._mask_message(str(arg)) if isinstance(arg, str) else arg
43 for arg in record.args
44 )
45
46 return True
47
48 def _mask_message(self, message: str) -> str:
49 """메시지 내 민감 정보 마스킹."""
50 for pattern, replacement in self.SENSITIVE_PATTERNS:
51 message = pattern.sub(replacement, message)
52 return message
53
54
55def install_sensitive_filter() -> None:
56 """루트 로거에 민감정보 필터 설치."""
57 root_logger = logging.getLogger()
58 sensitive_filter = SensitiveDataFilter()
59
60 for handler in root_logger.handlers:
61 handler.addFilter(sensitive_filter)
프로세스
단계 1: 프로젝트 분석
1. 프로젝트 구조 확인
- src/ 또는 {project}/ 디렉토리
- pyproject.toml에서 프로젝트명 추출
2. 기존 로깅 설정 확인
- utils/logging.py 존재 여부
- 설정 파일 내 logging 섹션
단계 2: 파일 생성
/setup-logging --standard
생성할 파일:
├── src/{project}/utils/logging.py
├── src/{project}/utils/log_formatters.py
├── src/{project}/utils/log_context.py
└── config/logging.yaml (선택)
단계 3: 통합 가이드 제공
python
1# main.py 또는 __init__.py에 추가
2from {project}.utils.logging import setup_logging
3
4# 애플리케이션 시작 시
5setup_logging()
6
7# FastAPI 미들웨어 예시
8from {project}.utils.log_context import set_request_context, clear_request_context
9
10@app.middleware("http")
11async def logging_middleware(request: Request, call_next):
12 request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
13 set_request_context(request_id=request_id)
14
15 response = await call_next(request)
16
17 clear_request_context()
18 return response
환경 변수
| 변수 | 설명 | 기본값 |
|---|
LOG_LEVEL | 로그 레벨 | INFO |
LOG_FORMAT | 포맷 (text 또는 json) | text |
LOG_DIR | 로그 파일 디렉토리 | logs |
APP_NAME | 애플리케이션 이름 | 프로젝트명 |
환경별 권장 설정
| 환경 | LOG_LEVEL | LOG_FORMAT |
|---|
| 개발 | DEBUG | text |
| 테스트 | DEBUG | text |
| 스테이징 | INFO | json |
| 프로덕션 | INFO | json |
pyproject.toml 설정
toml
1[tool.{project}]
2# 로깅 기본 설정
3log_level = "INFO"
4log_format = "text"
5log_dir = "logs"
통합
convention-logging 연동
이 스킬은 [@skills/reference/conventions/convention-logging/SKILL.md] 컨벤션을 준수하는 설정을 생성한다:
- 로그 레벨 기준 준수
- 표준 포맷 필드 포함
- 민감 정보 마스킹 적용
- 성능 최적화 패턴 적용
setup-quality 연동
/setup-quality --standard
|
+-- 품질 도구 설정
+-- /setup-logging --standard (자동 호출 가능)
출력 예시
--minimal
/setup-logging --minimal
✅ 로깅 설정 완료 (Minimal)
생성된 파일:
- src/myapp/utils/logging.py
사용법:
from myapp.utils.logging import setup_logging, get_logger
setup_logging()
logger = get_logger(__name__)
--standard
/setup-logging --standard
✅ 로깅 설정 완료 (Standard)
생성된 파일:
- src/myapp/utils/logging.py
- src/myapp/utils/log_formatters.py
- src/myapp/utils/log_context.py
환경 변수:
- LOG_LEVEL=INFO
- LOG_FORMAT=text
- LOG_DIR=logs
다음 단계:
1. main.py에 setup_logging() 호출 추가
2. 필요 시 미들웨어에 컨텍스트 설정 추가
--json
/setup-logging --json
✅ 로깅 설정 완료 (JSON)
생성된 파일:
- src/myapp/utils/logging.py
- src/myapp/utils/log_formatters.py
- src/myapp/utils/log_context.py
- src/myapp/utils/log_filters.py
프로덕션 권장 환경 변수:
- LOG_LEVEL=INFO
- LOG_FORMAT=json
- LOG_DIR=/var/log/myapp
민감정보 필터 활성화됨
모범 사례
DO
- 애플리케이션 시작 시 한 번만
setup_logging() 호출
- 각 모듈에서
get_logger(__name__) 사용
- 환경 변수로 로그 레벨 제어
- 프로덕션에서 JSON 포맷 사용
- 요청 컨텍스트 설정으로 추적성 확보
DON'T
- 여러 곳에서 로깅 설정 중복
- 하드코딩된 로그 레벨
- 프로덕션에서 DEBUG 레벨 사용
- 민감 정보 필터 없이 배포
Changelog
| 날짜 | 버전 | 변경 내용 |
|---|
| 2026-01-21 | 1.0.0 | 초기 생성 - Python 표준 logging 기반 설정 자동화 |