Skip to content

dotnetpower/sk-appinsights

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

71 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ETF Agent

Deploy to Azure Container App CI - Build and Test Python Version Node Version FastAPI Semantic Kernel React License Azure Application Insights Cosmos DB

ETF 및 주식 μ’…λͺ© 데이터 뢄석 μ—μ΄μ „νŠΈ ν”„λ‘œμ νŠΈ, 주식 λ°μ΄ν„°λŠ” μ‹€μ œ λ°μ΄ν„°μ΄μ§€λ§Œ Application Insights 의 λͺ¨λ‹ˆν„°λ§ κΈ°λŠ₯ μ‹œμ—°μ„ μœ„ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€.

alt text

alt text

alt text

alt text

μ‹€μŠ΅

ν”„λ‘œμ νŠΈ ꡬ쑰

sk-appinsights/
β”œβ”€β”€ src/                          # Backend μ†ŒμŠ€ μ½”λ“œ
β”‚   β”œβ”€β”€ agent/                    # Semantic Kernel μ—μ΄μ „νŠΈ
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ agent_service.py      # μ—μ΄μ „νŠΈ μ„œλΉ„μŠ€
β”‚   β”‚   └── stock_plugin.py       # 주식 데이터 ν”ŒλŸ¬κ·ΈμΈ
β”‚   β”œβ”€β”€ api/                      # FastAPI λΌμš°ν„°
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ analytics.py          # μ‚¬μš©μž 행동 뢄석 API
β”‚   β”‚   β”œβ”€β”€ chat.py               # AI μ±„νŒ… API
β”‚   β”‚   β”œβ”€β”€ etf.py                # ETF 데이터 API
β”‚   β”‚   β”œβ”€β”€ news.py               # λ‰΄μŠ€ API
β”‚   β”‚   └── stocks.py             # 주식 데이터 API
β”‚   β”œβ”€β”€ observability/            # Application Insights ν…”λ ˆλ©”νŠΈλ¦¬
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ middleware.py         # HTTP μš”μ²­ 좔적 미듀웨어
β”‚   β”‚   β”œβ”€β”€ telemetry.py          # ν…”λ ˆλ©”νŠΈλ¦¬ μ„€μ • 및 좔적 ν•¨μˆ˜
β”‚   β”‚   └── utils.py              # μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜
β”‚   β”œβ”€β”€ services/                 # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 μ„œλΉ„μŠ€
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ alphavantage_service.py    # Alpha Vantage API
β”‚   β”‚   β”œβ”€β”€ cosmos_service.py          # Cosmos DB μ„œλΉ„μŠ€
β”‚   β”‚   β”œβ”€β”€ rss_news_service.py        # RSS λ‰΄μŠ€ μ„œλΉ„μŠ€
β”‚   β”‚   β”œβ”€β”€ totalrealreturns_service.py # TotalRealReturns API
β”‚   β”‚   └── yfinance_service.py        # Yahoo Finance API
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ config.py                 # μ„€μ • 관리
β”‚   └── main.py                   # FastAPI μ„œλ²„ μ§„μž…μ 
β”‚
β”œβ”€β”€ frontend/                     # React λŒ€μ‹œλ³΄λ“œ
β”‚   β”œβ”€β”€ public/                   # 정적 파일
β”‚   β”‚   β”œβ”€β”€ index.html
β”‚   β”‚   β”œβ”€β”€ manifest.json
β”‚   β”‚   └── robots.txt
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/           # React μ»΄ν¬λ„ŒνŠΈ
β”‚   β”‚   β”‚   β”œβ”€β”€ ChatInterface.tsx      # AI μ±„νŒ… μΈν„°νŽ˜μ΄μŠ€
β”‚   β”‚   β”‚   β”œβ”€β”€ Dashboard.tsx          # 메인 λŒ€μ‹œλ³΄λ“œ
β”‚   β”‚   β”‚   β”œβ”€β”€ ETFList.tsx            # ETF λͺ©λ‘
β”‚   β”‚   β”‚   β”œβ”€β”€ NewsFeed.tsx           # λ‰΄μŠ€ ν”Όλ“œ
β”‚   β”‚   β”‚   └── StockDetail.tsx        # 주식 상세 정보
β”‚   β”‚   β”œβ”€β”€ hooks/                # μ»€μŠ€ν…€ ν›…
β”‚   β”‚   β”‚   └── usePageTracking.ts     # νŽ˜μ΄μ§€ 좔적 ν›…
β”‚   β”‚   β”œβ”€β”€ services/             # API ν΄λΌμ΄μ–ΈνŠΈ
β”‚   β”‚   β”‚   β”œβ”€β”€ analytics.ts           # 뢄석 API ν΄λΌμ΄μ–ΈνŠΈ
β”‚   β”‚   β”‚   └── api.ts                 # λ°±μ—”λ“œ API ν΄λΌμ΄μ–ΈνŠΈ
β”‚   β”‚   β”œβ”€β”€ App.tsx               # 메인 μ•± μ»΄ν¬λ„ŒνŠΈ
β”‚   β”‚   β”œβ”€β”€ index.tsx             # μ§„μž…μ 
β”‚   β”‚   └── setupTests.ts         # ν…ŒμŠ€νŠΈ μ„€μ •
β”‚   β”œβ”€β”€ package.json              # ν”„λ‘ νŠΈμ—”λ“œ μ˜μ‘΄μ„±
β”‚   └── tsconfig.json             # TypeScript μ„€μ •
β”‚
β”œβ”€β”€ .github/                      # GitHub μ„€μ •
β”‚   └── copilot-instructions.md   # Copilot μ§€μΉ¨
β”‚
β”œβ”€β”€ .vscode/                      # VSCode μ„€μ •
β”‚   β”œβ”€β”€ launch.json               # 디버그 μ„€μ •
β”‚   └── tasks.json                # νƒœμŠ€ν¬ μ„€μ •
β”‚
β”œβ”€β”€ λ¬Έμ„œ/                         # ν”„λ‘œμ νŠΈ λ¬Έμ„œ
β”‚   β”œβ”€β”€ TELEMETRY_TABLES.md       # ν…”λ ˆλ©”νŠΈλ¦¬ ν…Œμ΄λΈ” κ°€μ΄λ“œ
β”‚   β”œβ”€β”€ USER_BEHAVIOR_ANALYTICS.md # μ‚¬μš©μž 행동 뢄석 κ°€μ΄λ“œ
β”‚   β”œβ”€β”€ LIVE_METRICS_GUIDE.md     # Live Metrics κ°€μ΄λ“œ
β”‚   β”œβ”€β”€ DASHBOARD_SETUP.md        # Azure λŒ€μ‹œλ³΄λ“œ μ„€μ •
β”‚   β”œβ”€β”€ COSMOS_DB_NETWORK_SETUP.md # Cosmos DB λ„€νŠΈμ›Œν¬ μ„€μ •
β”‚   β”œβ”€β”€ GUIDE.md                  # 개발 κ°€μ΄λ“œ
β”‚   └── WSL_NETWORK_SETUP.md      # WSL λ„€νŠΈμ›Œν¬ μ„€μ •
β”‚
β”œβ”€β”€ ν…ŒμŠ€νŠΈ 파일/
β”‚   β”œβ”€β”€ test_chat.py              # μ±„νŒ… API ν…ŒμŠ€νŠΈ
β”‚   β”œβ”€β”€ test_cosmos.py            # Cosmos DB ν…ŒμŠ€νŠΈ
β”‚   β”œβ”€β”€ test_fallback.py          # 폴백 둜직 ν…ŒμŠ€νŠΈ
β”‚   β”œβ”€β”€ test_live_metrics.py      # Live Metrics ν…ŒμŠ€νŠΈ
β”‚   β”œβ”€β”€ test_observability.py     # ν…”λ ˆλ©”νŠΈλ¦¬ ν…ŒμŠ€νŠΈ
β”‚   └── test_rss_news.py          # RSS λ‰΄μŠ€ ν…ŒμŠ€νŠΈ
β”‚
β”œβ”€β”€ Azure μ„€μ • 파일/
β”‚   β”œβ”€β”€ azure-dashboard.json      # Azure Portal λŒ€μ‹œλ³΄λ“œ
β”‚   β”œβ”€β”€ azure-workbook.json       # Azure Workbook
β”‚   └── *.example.json            # μ„€μ • ν…œν”Œλ¦Ώ
β”‚
β”œβ”€β”€ .env                          # ν™˜κ²½λ³€μˆ˜ (μ‹€μ œ κ°’, git μ œμ™Έ)
β”œβ”€β”€ .env.example                  # ν™˜κ²½λ³€μˆ˜ ν…œν”Œλ¦Ώ
β”œβ”€β”€ .gitignore                    # Git μ œμ™Έ 파일
β”œβ”€β”€ pyproject.toml                # Python ν”„λ‘œμ νŠΈ μ„€μ • (uv)
β”œβ”€β”€ uv.lock                       # μ˜μ‘΄μ„± 잠금 파일
β”œβ”€β”€ verify.sh                     # μ‹œμŠ€ν…œ 검증 슀크립트
β”œβ”€β”€ LICENSE                       # λΌμ΄μ„ μŠ€
└── README.md                     # ν”„λ‘œμ νŠΈ μ„€λͺ…μ„œ

기술 μŠ€νƒ

Backend

  • Python 3.13+: μ΅œμ‹  Python λŸ°νƒ€μž„
  • uv: 고속 Python νŒ¨ν‚€μ§€ κ΄€λ¦¬μž
  • FastAPI: κ³ μ„±λŠ₯ 비동기 REST API ν”„λ ˆμž„μ›Œν¬
  • Uvicorn: ASGI μ„œλ²„
  • Semantic Kernel 1.14+: Microsoft AI μ—μ΄μ „νŠΈ ν”„λ ˆμž„μ›Œν¬
  • 주식 데이터 μ†ŒμŠ€:
    • yfinance: Yahoo Finance 데이터 (μ£Όμš” μ†ŒμŠ€)
    • Alpha Vantage API: 보쑰 데이터 μ†ŒμŠ€
    • TotalRealReturns API: ETF 수읡λ₯  데이터
    • RSS Feeds: μ‹€μ‹œκ°„ λ‰΄μŠ€
  • Azure Cosmos DB: NoSQL λ°μ΄ν„°λ² μ΄μŠ€ (캐싱 및 μ €μž₯)
  • Application Insights: μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λͺ¨λ‹ˆν„°λ§ 및 ν…”λ ˆλ©”νŠΈλ¦¬
  • OpenTelemetry: λΆ„μ‚° 좔적 및 λ©”νŠΈλ¦­ μˆ˜μ§‘
    • azure-monitor-opentelemetry: Azure Monitor 톡합
    • opentelemetry-instrumentation-fastapi: FastAPI μžλ™ 계츑
    • opentelemetry-instrumentation-httpx: HTTP ν΄λΌμ΄μ–ΈνŠΈ 좔적
  • applicationinsights SDK: pageViews, customEvents 전솑

Frontend

  • React 18: μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€ 라이브러리
  • TypeScript: 정적 νƒ€μž… JavaScript
  • Material-UI (MUI): UI μ»΄ν¬λ„ŒνŠΈ 라이브러리
  • Axios: HTTP ν΄λΌμ΄μ–ΈνŠΈ
  • μ‚¬μš©μž 행동 뢄석: νŽ˜μ΄μ§€ λ·° 좔적 및 이벀트 λ‘œκΉ…

μ£Όμš” κΈ°λŠ₯

  • πŸ“Š ETF μ’…λͺ© λͺ©λ‘: μ‹€μ‹œκ°„ ETF 데이터 쑰회
  • πŸ“ˆ 주식 상세 정보: κ°œλ³„ μ’…λͺ© 뢄석 (가격, κ±°λž˜λŸ‰, λ‰΄μŠ€)
  • πŸ“° λ‰΄μŠ€ ν”Όλ“œ: RSS 기반 μ‹€μ‹œκ°„ 주식 λ‰΄μŠ€
  • πŸ’¬ AI μ±„νŒ…: Semantic Kernel 기반 주식 μ§ˆμ˜μ‘λ‹΅
  • πŸ“‰ 데이터 μ‹œκ°ν™”: 차트 및 κ·Έλž˜ν”„
  • πŸ” μ‚¬μš©μž 행동 뢄석: Application Insights 톡합
  • πŸ”Ž App Insights: KQL 쿼리λ₯Ό ν†΅ν•œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 원격 뢄석 뢄석

μ„€μΉ˜ 및 μ‹€ν–‰

사전 μš”κ΅¬μ‚¬ν•­

  • Python 3.13 이상
  • Node.js 18 이상
  • uv νŒ¨ν‚€μ§€ κ΄€λ¦¬μž
  • Azure 계정 (Application Insights, Cosmos DB)

1. ν”„λ‘œμ νŠΈ 클둠 및 μ΄ˆκΈ°ν™”

# μ €μž₯μ†Œ 클둠
git clone https://github.com/dotnetpower/sk-appinsights.git
cd sk-appinsights

# Python κ°€μƒν™˜κ²½ 생성 및 ν™œμ„±ν™”
python3 -m venv .venv
source .venv/bin/activate  # Linux/Mac
# .venv\Scripts\activate   # Windows

# Python μ˜μ‘΄μ„± μ„€μΉ˜
uv sync --prerelease=allow

# Frontend μ˜μ‘΄μ„± μ„€μΉ˜
cd frontend
npm install
cd ..

2. ν™˜κ²½λ³€μˆ˜ μ„€μ •

.env νŒŒμΌμ„ μƒμ„±ν•˜κ³  ν•„μš”ν•œ 값을 μž…λ ₯ν•˜μ„Έμš”:

cp .env.example .env

ν•„μˆ˜ ν™˜κ²½λ³€μˆ˜:

# Deployment Environment
ENVIRONMENT=development  # development, staging, production

# Azure Container Registry (배포용)
CONTAINER_REGISTRY_NAME=crskappinsights
RESOURCE_GROUP=rg-sk-appinsights
LOCATION=koreacentral
CONTAINER_APP_NAME=ca-sk-appinsights

# Application Insights (ν•„μˆ˜)
APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=xxx;IngestionEndpoint=https://koreacentral-0.in.applicationinsights.azure.com/;LiveEndpoint=https://koreacentral.livediagnostics.monitor.azure.com/;ApplicationId=xxx"
APPLICATIONINSIGHTS_WORKSPACE_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"  # KQL 쿼리용

# Azure Cosmos DB (ν•„μˆ˜)
COSMOS_ENDPOINT="https://xxx.documents.azure.com:443/"
# Azure AD (RBAC) 인증 μ‚¬μš© μ‹œ - COSMOS_KEY μƒλž΅ κ°€λŠ₯ (ꢌμž₯)
# COSMOS_KEY="your-cosmos-key"
COSMOS_DATABASE_NAME="etf-agent"
COSMOS_CONTAINER_NAME="etf-data"  # partition key = /symbol
COSMOS_ACCOUNT_NAME="cosmosskappinsights"  # GitHub Actions용

# AI μ„œλΉ„μŠ€ - Azure OpenAI (ꢌμž₯) λ˜λŠ” OpenAI
# μ˜΅μ…˜ 1: Azure OpenAI (ꢌμž₯)
AZURE_OPENAI_ENDPOINT="https://xxx.openai.azure.com/"
AZURE_OPENAI_API_KEY="xxx"
AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"
AZURE_OPENAI_API_VERSION="2024-08-01-preview"

# μ˜΅μ…˜ 2: OpenAI (Azure OpenAI μ‚¬μš© μ‹œ λΆˆν•„μš”)
OPENAI_API_KEY="sk-xxx"
OPENAI_ORG_ID=""

# 주식 데이터 API (선택적, yfinance fallback)
ALPHA_VANTAGE_KEY="your-alpha-vantage-key"  # alphavantage.co

# FastAPI (둜컬 개발용)
API_HOST=0.0.0.0
API_PORT=8000

# React Frontend (둜컬 개발용)
REACT_APP_API_URL=http://localhost:8000

ν™˜κ²½λ³€μˆ˜ μš°μ„ μˆœμœ„

  • AI μ„œλΉ„μŠ€: Azure OpenAI > OpenAI
    • Azure OpenAIκ°€ μ„€μ •λ˜μ–΄ 있으면 OPENAI_API_KEYλŠ” λ¬΄μ‹œλ©λ‹ˆλ‹€
  • Cosmos DB 인증: Azure AD (DefaultAzureCredential) > COSMOS_KEY
    • Azure AD 인증 μ‚¬μš© μ‹œ COSMOS_KEYλŠ” λΆˆν•„μš”ν•©λ‹ˆλ‹€ (ꢌμž₯)

선택적 ν™˜κ²½λ³€μˆ˜

  • OPENAI_API_KEY: Azure OpenAI μ‚¬μš© μ‹œ λΆˆν•„μš”
  • OPENAI_ORG_ID: OpenAI Organization μ‚¬μš© μ‹œμ—λ§Œ ν•„μš”
  • COSMOS_KEY: Azure AD 인증 μ‚¬μš© μ‹œ λΆˆν•„μš” (ꢌμž₯)
  • ALPHA_VANTAGE_KEY: yfinance μš°μ„  μ‚¬μš©, Alpha VantageλŠ” fallback

### 3. Backend μ‹€ν–‰

```bash
# κ°€μƒν™˜κ²½ ν™œμ„±ν™” (아직 μ•ˆ ν–ˆλ‹€λ©΄)
source .venv/bin/activate

# 방법 1: Uvicorn으둜 직접 μ‹€ν–‰ (개발 λͺ¨λ“œ, μžλ™ μž¬μ‹œμž‘)
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000

# 방법 2: Python λͺ¨λ“ˆλ‘œ μ‹€ν–‰
python -m src.main

# 방법 3: VSCode Task μ‚¬μš©
# Ctrl+Shift+B β†’ "Backend: Start Server" 선택

확인:

4. Frontend μ‹€ν–‰

cd frontend

# 개발 μ„œλ²„ μ‹œμž‘
npm start

# λ˜λŠ” VSCode Task μ‚¬μš©
# Ctrl+Shift+B β†’ "Frontend: Start Dev Server" 선택

확인:

5. λͺ¨λ“  μ„œλΉ„μŠ€ λ™μ‹œ μ‹€ν–‰ (VSCode)

# VSCodeμ—μ„œ Ctrl+Shift+B
# "Start All Services" 선택 β†’ Backend + Frontend λ™μ‹œ μ‹€ν–‰

ν™˜κ²½λ³€μˆ˜ μ „νŒŒ ν”„λ‘œμ„ΈμŠ€

둜컬 개발 ν™˜κ²½

.env 파일
    β”‚
    β”œβ”€β”€β†’ Python Backend (src/config.py)
    β”‚    └── os.getenv() β†’ λͺ¨λ“  λ°±μ—”λ“œ μ„œλΉ„μŠ€
    β”‚
    └──→ React Frontend (process.env)
         └── REACT_APP_* β†’ npm start β†’ 개발 μ„œλ²„

ν”„λ‘œλ•μ…˜ 배포 (GitHub Actions β†’ Azure)

.env 파일
    β”‚
    └──→ setup-github-secrets.sh
              β”‚
              β–Ό
         GitHub Secrets
              β”‚
              β”œβ”€β”€β†’ GitHub Actions Workflow
              β”‚    β”œβ”€β”€ Docker build-arg (Frontend λΉŒλ“œ νƒ€μž„)
              β”‚    β”‚   └── REACT_APP_VERSION, REACT_APP_GIT_COMMIT
              β”‚    └── az containerapp secret set
              β”‚
              β–Ό
         Container App Secrets
              β”‚
              └──→ Container Environment Variables
                        β”‚
                        └──→ Running Container
                             β”œβ”€β”€ Python: os.getenv()
                             └── React: λΉŒλ“œ νƒ€μž„ μ£Όμž… κ°’ μ‚¬μš©

μ£Όμ˜μ‚¬ν•­:

  • React ν™˜κ²½λ³€μˆ˜λŠ” λΉŒλ“œ νƒ€μž„μ— λ²ˆλ“€μ— ν¬ν•¨λ©λ‹ˆλ‹€
  • Container App의 λŸ°νƒ€μž„ ν™˜κ²½λ³€μˆ˜λŠ” Reactμ—μ„œ μ ‘κ·Όν•  수 μ—†μŠ΅λ‹ˆλ‹€
  • REACT_APP_API_URL을 빈 κ°’μœΌλ‘œ μ„€μ •ν•˜μ—¬ μƒλŒ€κ²½λ‘œλ₯Ό μ‚¬μš©ν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€

개발 μ›Œν¬ν”Œλ‘œμš°

Backend 개발

# νŒ¨ν‚€μ§€ μΆ”κ°€
uv add <package-name>

# 개발 μ˜μ‘΄μ„± μΆ”κ°€
uv add --dev <package-name>

# μ˜μ‘΄μ„± 동기화
uv sync

# μ½”λ“œ ν¬λ§·νŒ…
black src/

# 린트 검사
ruff check src/

# νƒ€μž… 체크
mypy src/

# ν…ŒμŠ€νŠΈ μ‹€ν–‰
pytest -v

# νŠΉμ • ν…ŒμŠ€νŠΈ μ‹€ν–‰
python test_chat.py
python test_cosmos.py

Frontend 개발

cd frontend

# νŒ¨ν‚€μ§€ μΆ”κ°€
npm install <package-name>

# 개발 μ˜μ‘΄μ„± μΆ”κ°€
npm install --save-dev <package-name>

# 린트 검사
npm run lint

# λΉŒλ“œ (ν”„λ‘œλ•μ…˜)
npm run build

# ν…ŒμŠ€νŠΈ
npm test

VSCode Tasks

ν”„λ‘œμ νŠΈμ—λŠ” λ‹€μŒ VSCode Tasksκ°€ μ„€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€:

  • Backend: Start Server: Backend μ„œλ²„ μ‹€ν–‰
  • Frontend: Start Dev Server: Frontend 개발 μ„œλ²„ μ‹€ν–‰
  • Start All Services: Backend + Frontend λ™μ‹œ μ‹€ν–‰ (κΈ°λ³Έ)
  • Python: Install Dependencies: uv sync μ‹€ν–‰
  • Python: Run Tests: pytest μ‹€ν–‰
  • Python: Format Code: black ν¬λ§·νŒ…
  • Python: Lint Code: ruff 린트
  • Verify System: μ‹œμŠ€ν…œ 검증 슀크립트

μ‹€ν–‰ 방법: Ctrl+Shift+B β†’ Task 선택

디버깅

Backend 디버깅 (VSCode)

  1. .vscode/launch.json에 디버그 μ„€μ • 포함됨
  2. F5 ν‚€ λ˜λŠ” "Run and Debug" νŒ¨λ„ μ‚¬μš©
  3. Breakpoint μ„€μ • κ°€λŠ₯

Application Insights 확인

# Live Metrics ν…ŒμŠ€νŠΈ
python test_live_metrics.py

# ν…”λ ˆλ©”νŠΈλ¦¬ ν…ŒμŠ€νŠΈ
python test_observability.py

# Azure Portal β†’ Application Insights β†’ Logs
# KQL 쿼리둜 데이터 확인

Application Insights ν…Œμ΄λΈ” λ§€ν•‘

Application Insights의 Log Analytics workspaceμ—μ„œλŠ” ν‘œμ€€ ν…Œμ΄λΈ”λͺ…을 μ‚¬μš©ν•©λ‹ˆλ‹€. KQL 쿼리 μž‘μ„± μ‹œ λ‹€μŒ 맀핑을 μ°Έκ³ ν•˜μ„Έμš”:

κΈ°μ‘΄ ν…Œμ΄λΈ”λͺ… (Classic) Log Analytics ν…Œμ΄λΈ”λͺ… μ„€λͺ… μ£Όμš” 컬럼
requests AppRequests HTTP μš”μ²­ 좔적 TimeGenerated, Name, ResultCode, DurationMs, Success
dependencies AppDependencies μ™ΈλΆ€ μ„œλΉ„μŠ€ 호좜 TimeGenerated, Name, Type, Target, ResultCode, Success
traces AppTraces 둜그 λ©”μ‹œμ§€ TimeGenerated, Message, SeverityLevel
exceptions AppExceptions μ˜ˆμ™Έ 및 였λ₯˜ TimeGenerated, ProblemId, OuterMessage, Type
pageViews AppPageViews νŽ˜μ΄μ§€ λ·° 좔적 TimeGenerated, Name, Url, DurationMs
customEvents AppEvents μ»€μŠ€ν…€ 이벀트 TimeGenerated, Name, Properties
customMetrics AppMetrics μ»€μŠ€ν…€ λ©”νŠΈλ¦­ TimeGenerated, Name, Sum, Count
availabilityResults AppAvailabilityResults κ°€μš©μ„± ν…ŒμŠ€νŠΈ TimeGenerated, Name, Success, DurationMs

μ£Όμš” 컬럼 변경사항:

  • timestamp β†’ TimeGenerated
  • name β†’ Name
  • resultCode β†’ ResultCode
  • duration β†’ DurationMs
  • success β†’ Success
  • customDimensions β†’ Properties
  • customMeasurements β†’ Measurements

KQL 쿼리 μ˜ˆμ‹œ:

// ❌ 잘λͺ»λœ 쿼리 (Classic ν…Œμ΄λΈ”λͺ…)
requests
| where timestamp > ago(1h)
| summarize count() by name

// βœ… μ˜¬λ°”λ₯Έ 쿼리 (Log Analytics ν…Œμ΄λΈ”λͺ…)
AppRequests
| where TimeGenerated > ago(1h)
| summarize count() by Name

// μ»€μŠ€ν…€ 이벀트 쑰회
AppEvents
| where TimeGenerated > ago(24h)
| where Name == "page_view"
| extend page_name = tostring(Properties.page_name)
| summarize visit_count = count() by page_name
| order by visit_count desc

// μ„±λŠ₯ 뢄석
AppRequests
| where TimeGenerated > ago(24h)
| summarize avg_duration = avg(DurationMs), request_count = count() by bin(TimeGenerated, 1h)
| render timechart

Application Insights ν…”λ ˆλ©”νŠΈλ¦¬

πŸ“Š 데이터 μˆ˜μ§‘ ꡬ쑰

Application InsightsλŠ” λ‹€μŒ 7κ°€μ§€ ν…Œμ΄λΈ”μ— μžλ™/μˆ˜λ™μœΌλ‘œ 데이터λ₯Ό μˆ˜μ§‘ν•©λ‹ˆλ‹€:

1. requests ν…Œμ΄λΈ” (μžλ™ μˆ˜μ§‘)

μˆ˜μ§‘ μ‹œμ : FastAPI HTTP μš”μ²­ 처리 μ‹œ
μˆ˜μ§‘ 방식: OpenTelemetry μžλ™ 계츑
μ €μž₯ 데이터:

- name: "GET /api/etf", "POST /api/chat"
- url: 전체 μš”μ²­ URL
- duration: μš”μ²­ 처리 μ‹œκ°„ (λ°€λ¦¬μ΄ˆ)
- resultCode: HTTP μƒνƒœ μ½”λ“œ (200, 404, 500)
- success: 성곡/μ‹€νŒ¨ μ—¬λΆ€
- customDimensions: μš”μ²­ νŒŒλΌλ―Έν„°, 헀더 λ“±

μ˜ˆμ‹œ:

# FastAPI μ—”λ“œν¬μΈνŠΈ 호좜 μ‹œ μžλ™ 기둝
@app.get("/api/etf")
async def get_etf_list():
    # 이 ν•¨μˆ˜κ°€ 호좜되면 requests ν…Œμ΄λΈ”μ— μžλ™ μ €μž₯
    pass

2. dependencies ν…Œμ΄λΈ” (μžλ™ μˆ˜μ§‘)

μˆ˜μ§‘ μ‹œμ : μ™ΈλΆ€ API 호좜, DB 쿼리 μ‹€ν–‰ μ‹œ
μˆ˜μ§‘ 방식: HTTPX, Cosmos DB SDK μžλ™ 계츑
μ €μž₯ 데이터:

- name: API 호좜 이름
- type: "HTTP", "Azure Cosmos DB"
- target: λŒ€μƒ μ„œλ²„/μ„œλΉ„μŠ€
- data: SQL 쿼리, API URL
- duration: 호좜 μ‹œκ°„ (λ°€λ¦¬μ΄ˆ)
- success: 성곡/μ‹€νŒ¨
- resultCode: 응닡 μ½”λ“œ

μ˜ˆμ‹œ:

# yfinance API 호좜 μ‹œ μžλ™ 기둝 (HTTPX 계츑)
import httpx
async with httpx.AsyncClient() as client:
    response = await client.get("https://api.example.com/stock")
    # dependencies ν…Œμ΄λΈ”μ— μžλ™ μ €μž₯

# Cosmos DB 쿼리 μ‹œ μžλ™ 기둝
container.query_items(
    query="SELECT * FROM c WHERE c.type = @type",
    parameters=[{"name": "@type", "value": "ETF"}]
)
# dependencies ν…Œμ΄λΈ”μ— μžλ™ μ €μž₯

3. traces ν…Œμ΄λΈ” (μžλ™ μˆ˜μ§‘)

μˆ˜μ§‘ μ‹œμ : Python logger μ‚¬μš© μ‹œ
μˆ˜μ§‘ 방식: Python logging μžλ™ 연동
μ €μž₯ 데이터:

- message: 둜그 λ©”μ‹œμ§€
- severityLevel: 0=Verbose, 1=Info, 2=Warning, 3=Error, 4=Critical
- timestamp: 둜그 λ°œμƒ μ‹œκ°„
- customDimensions: μΆ”κ°€ μ»¨ν…μŠ€νŠΈ

μ˜ˆμ‹œ:

import logging
logger = logging.getLogger(__name__)

# λͺ¨λ“  λ‘œκ·Έκ°€ traces ν…Œμ΄λΈ”μ— μžλ™ μ €μž₯
logger.info("ETF 데이터 쑰회 μ‹œμž‘")  # severityLevel=1
logger.warning("μΊμ‹œ 만료됨")        # severityLevel=2
logger.error("API 호좜 μ‹€νŒ¨")        # severityLevel=3

4. pageViews ν…Œμ΄λΈ” (μˆ˜λ™ μˆ˜μ§‘)

μˆ˜μ§‘ μ‹œμ : track_page_view() ν•¨μˆ˜ 호좜 μ‹œ
μˆ˜μ§‘ 방식: TelemetryClient λͺ…μ‹œμ  호좜
μ €μž₯ 데이터:

- name: νŽ˜μ΄μ§€ 이름 ("Dashboard", "ETF List")
- url: νŽ˜μ΄μ§€ URL
- customDimensions.duration_ms: νŽ˜μ΄μ§€ 체λ₯˜ μ‹œκ°„ (λ°€λ¦¬μ΄ˆ)
- customDimensions.user_id: μ‚¬μš©μž ID
- customDimensions.session_id: μ„Έμ…˜ ID

μ˜ˆμ‹œ:

# Backend API
from src.observability.telemetry import track_page_view

@router.post("/api/analytics/page-view")
async def log_page_view(event: PageViewEvent):
    track_page_view(
        name=event.page_name,
        url=f"/{event.page_name}",
        properties={"user_id": event.user_id, "session_id": event.session_id},
        duration_ms=event.duration_ms
    )
    # pageViews ν…Œμ΄λΈ”μ— μ €μž₯

Frontend 톡합:

// React μ»΄ν¬λ„ŒνŠΈ 마운트/μ–Έλ§ˆμš΄νŠΈ μ‹œ
useEffect(() => {
    const entryTime = Date.now();
    
    return () => {
        const duration = Date.now() - entryTime;
        trackPageView({
            page_name: "Dashboard",
            duration_ms: duration,
            user_id: getUserId(),
            session_id: getSessionId()
        });
    };
}, []);

5. customEvents ν…Œμ΄λΈ” (μˆ˜λ™ μˆ˜μ§‘)

μˆ˜μ§‘ μ‹œμ : track_user_event() ν•¨μˆ˜ 호좜 μ‹œ
μˆ˜μ§‘ 방식: TelemetryClient λͺ…μ‹œμ  호좜
μ €μž₯ 데이터:

- name: 이벀트 이름 ("button_click", "search", "tab_changed")
- customDimensions.event_category: 이벀트 μΉ΄ν…Œκ³ λ¦¬
- customDimensions.user_id: μ‚¬μš©μž ID
- customDimensions.*: μ΄λ²€νŠΈλ³„ μΆ”κ°€ 속성
- customMeasurements: 숫자 μΈ‘μ •κ°’

μ˜ˆμ‹œ:

# Backend API
from src.observability.telemetry import track_user_event

@router.post("/api/analytics/event")
async def log_user_event(event: UserEvent):
    track_user_event(
        name=event.event_name,
        properties={
            "event_category": event.event_category,
            "user_id": event.user_id,
            "query": event.query  # 검색 이벀트의 경우
        }
    )
    # customEvents ν…Œμ΄λΈ”μ— μ €μž₯

Frontend 이벀트 좔적:

// νƒ­ λ³€κ²½ μ‹œ
trackEvent({
    event_name: "tab_changed",
    event_category: "navigation",
    properties: { from_tab: "Dashboard", to_tab: "ETF List" }
});

// 검색 μ‹œ
trackEvent({
    event_name: "search",
    event_category: "interaction",
    properties: { query: "AAPL", results_count: 5 }
});

6. customMetrics ν…Œμ΄λΈ” (μžλ™ μˆ˜μ§‘)

μˆ˜μ§‘ μ‹œμ : OpenTelemetry Metrics 기둝 μ‹œ
μˆ˜μ§‘ 방식: Meter API μ‚¬μš©
μ €μž₯ 데이터:

- name: λ©”νŠΈλ¦­ 이름 ("app.requests.total", "app.page_views.duration")
- value: λ©”νŠΈλ¦­ κ°’
- valueCount: μΈ‘μ • 횟수
- valueSum: 합계
- customDimensions: λ©”νŠΈλ¦­ 속성 (page_name, endpoint λ“±)

μ˜ˆμ‹œ:

# μ΄ˆκΈ°ν™” μ‹œ λ©”νŠΈλ¦­ 생성
from src.observability.telemetry import initialize_metrics

initialize_metrics()  # μ•± μ‹œμž‘ μ‹œ ν•œ 번 호좜

# μžλ™μœΌλ‘œ λ‹€μŒ λ©”νŠΈλ¦­ μˆ˜μ§‘:
# - app.requests.total: μš”μ²­ μΉ΄μš΄ν„°
# - app.requests.duration: μš”μ²­ 처리 μ‹œκ°„ νžˆμŠ€ν† κ·Έλž¨
# - app.errors.total: μ—λŸ¬ μΉ΄μš΄ν„°
# - app.page_views.total: νŽ˜μ΄μ§€ λ·° μΉ΄μš΄ν„°
# - app.page_views.duration: νŽ˜μ΄μ§€ 체λ₯˜ μ‹œκ°„ νžˆμŠ€ν† κ·Έλž¨
# - app.user_events.total: μ‚¬μš©μž 이벀트 μΉ΄μš΄ν„°

7. exceptions ν…Œμ΄λΈ” (μžλ™ + μˆ˜λ™)

μˆ˜μ§‘ μ‹œμ : μ˜ˆμ™Έ λ°œμƒ μ‹œ λ˜λŠ” track_exception() 호좜 μ‹œ
μˆ˜μ§‘ 방식: OpenTelemetry span.record_exception() μžλ™ + μˆ˜λ™ 호좜
μ €μž₯ 데이터:

- type: μ˜ˆμ™Έ νƒ€μž… (ValueError, HTTPException)
- outerMessage: μ˜ˆμ™Έ λ©”μ‹œμ§€
- problemId: 같은 μ˜ˆμ™Έ κ·Έλ£Ήν™” ID
- customDimensions: μ˜ˆμ™Έ λ°œμƒ μ»¨ν…μŠ€νŠΈ (endpoint, user_id λ“±)

μ˜ˆμ‹œ:

# μžλ™ μˆ˜μ§‘ - μ²˜λ¦¬λ˜μ§€ μ•Šμ€ μ˜ˆμ™Έ
@app.get("/api/data")
async def get_data():
    result = 10 / 0  # ZeroDivisionError μžλ™ 기둝
    
# μˆ˜λ™ μˆ˜μ§‘ - λͺ…μ‹œμ  좔적
from src.observability.telemetry import track_exception

try:
    risky_operation()
except Exception as e:
    track_exception(e, {
        "operation": "risky_operation",
        "user_id": current_user_id
    })
    raise

πŸ”„ ν…”λ ˆλ©”νŠΈλ¦¬ μˆ˜μ§‘ 흐름

μ‹œμž‘ μ‹œ (μ„œλ²„ λΆ€νŒ…)

# src/main.py
from src.observability.telemetry import setup_telemetry, initialize_metrics

app = FastAPI()

# 1. ν…”λ ˆλ©”νŠΈλ¦¬ μ„€μ •
setup_telemetry(app)  
# - FastAPI μžλ™ 계츑 ν™œμ„±ν™” (requests)
# - HTTPX μžλ™ 계츑 ν™œμ„±ν™” (dependencies)
# - Cosmos DB μžλ™ 계츑 ν™œμ„±ν™” (dependencies)
# - TelemetryClient μ΄ˆκΈ°ν™” (pageViews, customEvents)

# 2. μ»€μŠ€ν…€ λ©”νŠΈλ¦­ μ΄ˆκΈ°ν™”
initialize_metrics()
# - customMetrics ν…Œμ΄λΈ”μš© Meter 생성

HTTP μš”μ²­ 처리 μ‹œ

1. μš”μ²­ μˆ˜μ‹ 
   ↓
2. requests ν…Œμ΄λΈ”μ— μžλ™ 기둝 (OpenTelemetry)
   ↓
3. λ―Έλ“€μ›¨μ–΄μ—μ„œ 처리 μ‹œκ°„ μΈ‘μ •
   ↓
4. μ™ΈλΆ€ API 호좜 μ‹œ dependencies ν…Œμ΄λΈ”μ— μžλ™ 기둝
   ↓
5. logger μ‚¬μš© μ‹œ traces ν…Œμ΄λΈ”μ— μžλ™ 기둝
   ↓
6. μ˜ˆμ™Έ λ°œμƒ μ‹œ exceptions ν…Œμ΄λΈ”μ— μžλ™ 기둝
   ↓
7. 응닡 λ°˜ν™˜

μ‚¬μš©μž 행동 좔적 μ‹œ (Frontend β†’ Backend)

1. μ‚¬μš©μžκ°€ νŽ˜μ΄μ§€ λ°©λ¬Έ
   ↓
2. React useEffect ν›… μ‹€ν–‰
   ↓
3. νŽ˜μ΄μ§€ μ§„μž… μ‹œκ°„ 기둝
   ↓
4. μ‚¬μš©μž μƒν˜Έμž‘μš© (클릭, 검색 λ“±)
   β†’ POST /api/analytics/event
   β†’ customEvents ν…Œμ΄λΈ”μ— μ €μž₯
   ↓
5. νŽ˜μ΄μ§€ μ΄νƒˆ μ‹œ
   β†’ 체λ₯˜ μ‹œκ°„ 계산
   β†’ POST /api/analytics/page-view
   β†’ pageViews ν…Œμ΄λΈ”μ— μ €μž₯

πŸ“ KQL 쿼리 예제

// 1. 졜근 1μ‹œκ°„ λͺ¨λ“  μš”μ²­
requests
| where timestamp > ago(1h)
| project timestamp, name, duration, resultCode

// 2. μ™ΈλΆ€ API 호좜 좔적
dependencies
| where type == "HTTP"
| summarize count(), avg(duration) by target

// 3. μ—λŸ¬ 둜그 쑰회
traces
| where severityLevel >= 3
| project timestamp, message

// 4. νŽ˜μ΄μ§€λ³„ λ°©λ¬Έ 횟수
pageViews
| summarize view_count = count() by name

// 5. μ‚¬μš©μž 이벀트 뢄석
customEvents
| where name == "search"
| extend query = tostring(customDimensions["query"])
| project timestamp, query

// 6. μ„±λŠ₯ λ©”νŠΈλ¦­
customMetrics
| where name == "app.requests.duration"
| summarize avg(value) by bin(timestamp, 5m)

// 7. μ˜ˆμ™Έ 좔적
exceptions
| summarize count() by type
| order by count_ desc

πŸ“š 상세 λ¬Έμ„œ

Application Insights λͺ¨λ‹ˆν„°λ§ 및 뢄석을 μœ„ν•œ 심화 κ°€μ΄λ“œ:


🐳 Docker 및 Azure Container App 배포

ν™˜κ²½λ³€μˆ˜ μ„€μ •

.env νŒŒμΌμ— Container Registry 정보가 μ„€μ •λ˜μ–΄ μžˆλŠ”μ§€ 확인:

# .env 파일 확인
cat .env | grep -E "CONTAINER_REGISTRY_NAME|RESOURCE_GROUP|LOCATION"

# μ˜ˆμƒ 좜λ ₯:
# CONTAINER_REGISTRY_NAME=crskappinsights
# RESOURCE_GROUP=rg-sk-appinsights
# LOCATION=koreacentral

둜컬 Docker ν…ŒμŠ€νŠΈ

# Docker 이미지 λΉŒλ“œ 및 ν…ŒμŠ€νŠΈ (μžλ™ν™” 슀크립트)
./test-docker.sh

# λ˜λŠ” μˆ˜λ™μœΌλ‘œ
docker build -t etf-agent:local .
docker run -d --name etf-agent-test --env-file .env -p 8000:8000 etf-agent:local

# 둜그 확인
docker logs -f etf-agent-test

# 쀑지 및 제거
docker stop etf-agent-test
docker rm etf-agent-test

Docker Compose μ‹€ν–‰

# λͺ¨λ“  μ„œλΉ„μŠ€ μ‹œμž‘
docker-compose up -d

# 둜그 확인
docker-compose logs -f

# 쀑지
docker-compose down

Azure Container App 배포

μžλ™ 배포 (μΆ”μ²œ):

# 배포 슀크립트 μ‹€ν–‰
./deploy-containerapp.sh

# ν™˜κ²½ λ³€μˆ˜ μ‹œν¬λ¦Ώ μ„€μ •
source .env
az containerapp secret set \
  --name etf-agent-app \
  --resource-group etf-agent-rg \
  --secrets \
    appinsights-connection-string="$APPLICATIONINSIGHTS_CONNECTION_STRING" \
    cosmos-endpoint="$COSMOS_ENDPOINT" \
    cosmos-key="$COSMOS_KEY" \
    cosmos-database-name="$COSMOS_DATABASE_NAME" \
    cosmos-container-name="$COSMOS_CONTAINER_NAME" \
    openai-api-key="$OPENAI_API_KEY" \
    alphavantage-api-key="$ALPHA_VANTAGE_API_KEY" \
    finnhub-api-key="$FINNHUB_API_KEY"

상세 κ°€μ΄λ“œ: Container App 배포 κ°€μ΄λ“œ


πŸ”„ GitHub Actions CI/CD

μžλ™ 배포 μ„€μ •

μ½”λ“œλ₯Ό main λΈŒλžœμΉ˜μ— ν‘Έμ‹œν•˜λ©΄ μžλ™μœΌλ‘œ λΉŒλ“œ 및 λ°°ν¬λ©λ‹ˆλ‹€.

1. GitHub Secrets μ„€μ •

Repository β†’ Settings β†’ Secrets and variables β†’ Actions

ν•„μˆ˜ Secrets:

  • AZURE_CREDENTIALS - Azure μ„œλΉ„μŠ€ 주체 인증 정보
  • APPLICATIONINSIGHTS_CONNECTION_STRING
  • COSMOS_ENDPOINT, COSMOS_KEY, COSMOS_DATABASE_NAME, COSMOS_CONTAINER_NAME
  • OPENAI_API_KEY
  • ALPHA_VANTAGE_API_KEY, FINNHUB_API_KEY (선택)

2. Azure μ„œλΉ„μŠ€ 주체 생성

# Service Principal 생성 및 JSON 좜λ ₯
az ad sp create-for-rbac \
  --name "github-actions-etf-agent" \
  --role contributor \
  --scopes /subscriptions/{SUBSCRIPTION_ID}/resourceGroups/rg-sk-appinsights \
  --sdk-auth

# 좜λ ₯된 전체 JSON을 AZURE_CREDENTIALS Secret에 μ €μž₯

3. μžλ™ 배포

# main λΈŒλžœμΉ˜μ— ν‘Έμ‹œν•˜λ©΄ μžλ™ 배포
git add .
git commit -m "feat: μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€"
git push origin main

# GitHub Actionsμ—μ„œ μžλ™ μ‹€ν–‰:
# 1. Docker 이미지 λΉŒλ“œ
# 2. Azure Container Registry ν‘Έμ‹œ
# 3. Container App 배포

4. μˆ˜λ™ 배포

GitHub Repository β†’ Actions β†’ "Deploy to Azure Container App" β†’ Run workflow

상세 κ°€μ΄λ“œ: GitHub Actions μ„€μ • κ°€μ΄λ“œ

μ›Œν¬ν”Œλ‘œμš°

  • CI (ci.yml): Pull Request μ‹œ 린트, ν…ŒμŠ€νŠΈ, Docker λΉŒλ“œ
  • CD (deploy-containerapp.yml): main 브랜치 ν‘Έμ‹œ μ‹œ μžλ™ 배포

배포된 μ•± 확인

# App URL κ°€μ Έμ˜€κΈ°
APP_URL=$(az containerapp show \
  --name etf-agent-app \
  --resource-group etf-agent-rg \
  --query properties.configuration.ingress.fqdn -o tsv)

echo "🌐 App URL: https://$APP_URL"
echo "πŸ“Š Health: https://$APP_URL/health"
echo "πŸ“š API Docs: https://$APP_URL/docs"

# Health check
curl https://$APP_URL/health

VSCode μ„€μ •

Ctrl + / 둜 μ§€μΉ¨ 파일 λͺ…μ‹œμ μœΌλ‘œ μ§€μ •

λΌμ΄μ„ μŠ€

MIT License

Releases

No releases published

Packages

 
 
 

Contributors