feat(phase-5-3): Logger, Metrics, 알림 시스템 통합

Phase 5-3 모니터링 강화 작업의 통합을 완료했습니다.

변경사항:
- Logger 통합: console.log를 구조화된 로깅으로 전환 (9개 파일)
  - JSON 기반 로그, 환경별 자동 전환 (개발/프로덕션)
  - 타입 안전성 보장, 성능 측정 타이머 내장

- Metrics 통합: 실시간 성능 모니터링 시스템 연결 (3개 파일)
  - Circuit Breaker 상태 추적 (api_call_count, error_count, state)
  - Retry 재시도 횟수 추적 (retry_count)
  - OpenAI API 응답 시간 측정 (api_call_duration)

- 알림 통합: 장애 자동 알림 시스템 구현 (2개 파일)
  - Circuit Breaker OPEN 상태 → 관리자 Telegram 알림
  - 재시도 실패 → 관리자 Telegram 알림
  - Rate Limiting 적용 (1시간에 1회)

- 문서 업데이트:
  - CLAUDE.md: coder 에이전트 설명 강화 (20년+ 시니어 전문가)
  - README.md, docs/: 아키텍처 문서 추가

영향받은 파일: 16개 (수정 14개, 신규 2개)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-19 21:23:38 +09:00
parent 410676e322
commit eee934391a
16 changed files with 675 additions and 777 deletions

View File

@@ -177,6 +177,95 @@
background: linear-gradient(180deg, transparent 60%, #ffc078 60%);
padding: 0 4px;
}
/* 채팅 위젯 스타일 */
.chat-widget-container {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 100;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 1rem;
}
.chat-frame-wrapper {
width: 380px;
height: 600px;
background: white;
border: 2px solid #1e1e1e;
border-radius: 4px;
box-shadow: 6px 6px 0 rgba(0,0,0,0.2);
transform: scale(0);
transform-origin: bottom right;
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
overflow: hidden;
display: flex;
flex-direction: column;
}
.chat-frame-wrapper.open {
transform: scale(1);
}
.chat-header {
background: #1971c2;
color: white;
padding: 0.75rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #1e1e1e;
font-family: 'Caveat', cursive;
font-size: 1.25rem;
}
.chat-iframe {
flex: 1;
width: 100%;
height: 100%;
border: none;
}
.chat-toggle-btn {
width: 64px;
height: 64px;
background: #1971c2;
color: white;
border: 2px solid #1e1e1e;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 4px 4px 0 #1e1e1e;
transition: all 0.2s ease;
font-size: 1.5rem;
position: relative;
}
.chat-toggle-btn:hover {
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 #1e1e1e;
}
.chat-toggle-btn:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0 #1e1e1e;
}
/* 모바일 대응 */
@media (max-width: 480px) {
.chat-widget-container {
bottom: 1rem;
right: 1rem;
}
.chat-frame-wrapper {
width: calc(100vw - 2rem);
height: 80vh;
}
}
</style>
</head>
<body class="font-sans text-sketch-line">
@@ -881,5 +970,54 @@
</div>
</footer>
<!-- Chat Widget -->
<div class="chat-widget-container">
<!-- Chat Frame (Hidden by default) -->
<div id="chatFrame" class="chat-frame-wrapper">
<div class="chat-header">
<div class="flex items-center gap-2">
<span>💬 실시간 상담</span>
</div>
<button onclick="toggleChat()" class="hover:text-cream transition">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<iframe src="https://bd46a903.chat-frontend-4wf.pages.dev" class="chat-iframe" allow="microphone;"></iframe>
</div>
<!-- Toggle Button -->
<button id="chatBtn" onclick="toggleChat()" class="chat-toggle-btn group">
<!-- Chat Icon -->
<svg class="group-[.open]:hidden transition-transform duration-300" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<!-- Close Icon (Hidden initially) -->
<svg class="hidden group-[.open]:block transition-transform duration-300" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
<!-- New Message Badge (Optional) -->
<span class="absolute -top-1 -right-1 flex h-4 w-4">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-sketch-red opacity-75"></span>
<span class="relative inline-flex rounded-full h-4 w-4 bg-sketch-red"></span>
</span>
</button>
</div>
<script>
function toggleChat() {
const frame = document.getElementById('chatFrame');
const btn = document.getElementById('chatBtn');
frame.classList.toggle('open');
btn.classList.toggle('open');
// Update ARIA
const isOpen = frame.classList.contains('open');
btn.setAttribute('aria-expanded', isOpen);
}
</script>
</body>
</html>