LearningCollector: Promtail을 위한 구조화된 로깅 시스템 도입
이번 커밋에서는 기존의 `print()` 기반 로그 출력 방식을 유지하면서, Promtail → Loki → Grafana 파이프라인을 위한 구조화된 로그를 `log/promtail_feed.jsonl` 파일에 병행 기록하는 시스템을 구현했습니다. 이를 통해 로그의 가시성과 분석 용이성을 크게 향상시켰습니다.
LearningCollector: Promtail을 위한 구조화된 로깅 시스템 도입
이번 커밋에서는 기존의 print() 기반 로그 출력 방식을 유지하면서, Promtail → Loki → Grafana 파이프라인을 위한 구조화된 로그를 log/promtail_feed.jsonl 파일에 병행 기록하는 시스템을 구현했습니다. 이를 통해 로그의 가시성과 분석 용이성을 크게 향상시켰습니다.
요약
이번 작업은 core/structured_logger.py 모듈을 새로 추가하여 Promtail에서 활용 가능한 JSONL 형식의 로그를 생성하는 기능입니다. 파이프라인 시작/종료, 데이터 수집, API 호출, 초안 생성, 블로그 포스팅 등 다양한 이벤트에 대한 상세한 정보를 구조화된 형태로 기록하며, 이를 통해 로그 분석 및 문제 해결 과정을 효율화하는 것을 목표로 합니다. core/github_collector.py, core/gemini_draft_generator.py, core.orchestrator.py, api/gemini_client.py 등 여러 모듈에서 이 새로운 로깅 시스템을 활용하도록 수정되었습니다.
배경 및 목적
기존 프로젝트는 print() 함수를 사용하여 콘솔에 로그를 출력했습니다. 이는 개발 과정에서 디버깅을 용이하게 하지만, 프로젝트 규모가 커지고 여러 컴포넌트가 복잡하게 얽히면서 로그의 가독성과 관리의 어려움이 발생했습니다. 특히, 대규모 데이터를 처리하거나 여러 API를 호출하는 과정에서 발생하는 오류를 추적하거나 성능 병목 지점을 파악하는 데 한계가 있었습니다.
Promtail, Loki, Grafana와 같은 로깅 스택을 도입하여 이러한 문제를 해결하고자 했습니다. Promtail은 로그를 수집하고 Loki로 전달하며, Loki는 로그를 저장하고 Grafana는 이를 시각화하여 보여줍니다. 이 파이프라인에서 로그가 효과적으로 작동하기 위해서는 구조화된 형식, 특히 JSONL(JSON Lines) 형식이 필수적입니다. 따라서 이번 작업은 이러한 구조화된 로깅 시스템을 구축하여 로그 분석 능력을 강화하고, 운영 효율성을 높이는 것을 목적으로 합니다.
구현 내용
이번 커밋의 핵심은 core/structured_logger.py 파일에 새로운 구조화된 로깅 모듈을 추가한 것입니다. 이 모듈은 싱글톤 패턴을 사용하여 전역적으로 접근 가능한 로거 인스턴스를 제공하며, 다양한 이벤트 유형에 맞는 헬퍼 함수들을 제공합니다.
주요 변경사항 상세 설명
core/structured_logger.py:- JSONL 형식의 로그를
log/promtail_feed.jsonl파일에 기록하는 로직을 구현했습니다. info,warn,error와 같은 기본 로그 레벨 함수를 제공합니다.- 파이프라인 시작/종료 (
pipeline_start,pipeline_end), 데이터 수집 시작/종료 (collection_start,collection_end), JSON 저장 (json_saved,json_save_summary), 초안 생성 시작/성공/실패/요약 (draft_start,draft_success,draft_failure,draft_summary), 블로그 포스팅 성공/실패 (blog_post_success,blog_post_failure), API 호출 (api_call,api_error) 등 프로젝트의 핵심 흐름에 대한 이벤트를 기록할 수 있는 함수들을 추가했습니다. - API 호출 시 엔드포인트, 상태 코드, 소요 시간, 성공/실패 여부, 모델 정보, 재시도 횟수 등의 상세 정보를 기록합니다.
- 시간 측정 유틸리티 (
start_timer,elapsed_ms)를 포함하여 각 작업의 소요 시간을 밀리초 단위로 기록할 수 있도록 했습니다.
- JSONL 형식의 로그를
core/github_collector.py:core/structured_logger를 임포트하고,collect및collect_interactive메서드 내에서slog.info,slog.json_save_summary,slog.api_call,slog.api_error등을 사용하여 GitHub API 호출 및 데이터 수집 과정을 로깅하도록 수정했습니다.- 특히,
_fetch_baekjoon_rest_info와_fetch_changed_files메서드 내에서 GitHub REST API 호출 시마다slog.api_call을 사용하여 상세 정보를 기록하고, 예외 발생 시slog.api_error를 호출하도록 변경했습니다.
core/gemini_draft_generator.py:core/structured_logger를 임포트하고, 초안 생성 프로세스의 각 단계를slog.draft_start,slog.draft_success,slog.draft_failure,slog.draft_summary등을 사용하여 기록하도록 수정했습니다._post_to_blog메서드에서 블로그 포스팅 실패 시slog.blog_post_failure를 호출하도록 추가했습니다.
core/orchestrator.py:core/structured_logger를 임포트하고, 전체 파이프라인 실행 흐름을slog.info,slog.collection_start,slog.collection_end등을 사용하여 로깅하도록 수정했습니다.- 데이터 수집 단계와 자동 처리 단계의 총 소요 시간을 기록하도록 타이머를 추가하고
slog.info로 기록합니다.
api/gemini_client.py:core/structured_logger를 임포트하고, Gemini API 호출 시slog.api_call,slog.api_error,slog.warn등을 사용하여 API 호출 결과, 소요 시간, 오류 정보 등을 기록하도록 수정했습니다.- 재시도 로직에서도 API 호출 정보를 상세하게 로깅하여 실패 원인 분석에 도움을 주도록 했습니다.
변경된 파일 목록
.gitignoreapi/blog_api.pyapi/gemini_client.pyapi/github_graphql.pyapi/perplexity_client.pycore/ai_chat_collector.pycore/gemini_draft_generator.pycore/github_collector.pycore/orchestrator.pycore/structured_logger.pymain.py
추가/삭제된 코드 라인 수
- 추가: 357 라인
- 삭제: 4 라인
핵심 코드 설명
core/structured_logger.py의 _write 함수는 모든 로그 레코드를 JSON 형식으로 변환하고 타임스탬프를 추가한 뒤 log/promtail_feed.jsonl 파일에 한 줄씩 기록하는 역할을 합니다.
def _write(record: dict):
\"\"\"JSONL 한 줄 기록\"\"\"
record[\"ts\"] = datetime.now(timezone.utc).isoformat()
with open(_get_log_file(), \"a\", encoding=\"utf-8\") as f:
f.write(json.dumps(record, ensure_ascii=False) + \"\\n\")
slog.api_call 함수는 API 요청의 상세 정보를 구조화하여 로그 파일에 기록합니다.
def api_call(api: str, method: str, endpoint: str, status_code: int,
duration_ms: int, success: bool, **kwargs):
\"\"\"API 호출 로그\"\"\"
level_fn = info if success else warn
level_fn(\"api_call\", \"api\", api=api, method=method,
endpoint=endpoint, status_code=status_code,
duration_ms=duration_ms, success=success, **kwargs)
기술적 의사결정
Promtail 호환 JSONL 로깅 채택
- 선택 이유: Promtail, Loki, Grafana 스택에서 표준적으로 사용되는 로그 형식이 JSONL입니다. 구조화된 데이터를 각 줄마다 JSON 객체로 표현하여 파싱이 용이하며, Promtail이 이를 효과적으로 처리하고 Loki로 전송할 수 있습니다.
- 대안:
- 단순 텍스트 로그: 기존
print()방식과 유사하지만, 구조화되지 않아 분석 및 검색이 어렵습니다. - Syslog: 표준적인 로그 프로토콜이지만, JSONL만큼 유연하거나 특정 애플리케이션 이벤트에 대한 상세 정보를 담기에는 제약이 있을 수 있습니다.
- 단순 텍스트 로그: 기존
- 장단점:
- 장점: 로그 분석 및 시각화 도구(Loki, Grafana)와의 완벽한 호환성, 상세한 이벤트별 정보 기록 가능, 오류 추적 및 성능 분석 용이성 증대.
- 단점: 초기 구현 및 설정에 추가적인 노력이 필요합니다. 로그 파일의 크기가 증가할 수 있습니다.
싱글톤 패턴 사용
- 선택 이유: 로거 인스턴스는 애플리케이션 전반에 걸쳐 단일하게 유지되어야 일관된 로그 파일에 기록할 수 있습니다. 싱글톤 패턴은 이를 보장하며, 외부에서 로거 인스턴스를 쉽게 접근할 수 있도록 합니다.
- 대안:
- 전역 변수: 간단하지만, 네임스페이스 충돌의 위험이 있고 객체 생명주기 관리가 어렵습니다.
- 의존성 주입: 더 견고한 방법이지만, 단순 로깅 시스템 구현에는 다소 과할 수 있습니다.
- 장단점:
- 장점: 간결한 구현, 쉬운 접근성, 일관된 로깅.
- 단점: 테스트 시 Mocking이 조금 더 복잡해질 수 있습니다.
배운 점 및 개선점
배운 점
- 구조화된 로깅의 중요성을 실제 프로젝트에 적용하며 체감했습니다. 단순히 콘솔에 출력하는 것보다 훨씬 체계적인 데이터 관리가 가능하며, 문제 발생 시 원인 분석에 걸리는 시간을 크게 단축할 수 있다는 것을 알게 되었습니다.
- Promtail, Loki, Grafana와 같은 오픈소스 로깅 도구들이 어떻게 상호 작용하는지에 대한 이해를 높일 수 있었습니다.
- API 호출 시 상세한 메타데이터(응답 코드, 소요 시간, 재시도 여부 등)를 함께 기록하는 것이 문제 해결에 얼마나 큰 도움이 되는지 알게 되었습니다.
개선점 및 다음 단계 계획
- 로그 레벨 관리: 현재는
info,warn,error만 있지만,debug와 같은 상세 레벨을 추가하여 필요에 따라 더 많은 정보를 기록할 수 있도록 확장할 수 있습니다. - 로거 설정: 로그 파일 경로, 최대 파일 크기, 보관 정책 등을 설정 파일로 관리하여 유연성을 높일 수 있습니다.
- 비동기 로깅: 대량의 로그를 기록할 때 메인 스레드를 차단하지 않도록 비동기 로깅 방식을 고려해볼 수 있습니다.
- LogQL 쿼리 예시 추가:
core/structured_logger.py파일의 docstring에 실제 LogQL 쿼리 예시를 포함하여 사용자가 로그를 쉽게 검색하고 분석할 수 있도록 안내했습니다. (예:{job="learningcollector"} | json | level="ERROR") .gitignore업데이트: 새로 생성된log디렉토리를.gitignore에 추가하여 불필요한 파일이 Git 저장소에 포함되지 않도록 했습니다.