26F26h

Young-Kyoo Kim·2026년 2월 25일
# scripts/inspect_milvus.py

from pymilvus import connections, Collection, utility
from config import settings
import json


def connect():
    connections.connect(
        host=settings.MILVUS_HOST,
        port=settings.MILVUS_PORT,
    )
    print(f"✓ 연결: {settings.MILVUS_HOST}:{settings.MILVUS_PORT}\n")


def show_collections():
    """전체 컬렉션 목록 및 기본 정보"""
    print("=" * 55)
    print("📦 컬렉션 목록")
    print("=" * 55)

    cols = utility.list_collections()
    if not cols:
        print("  컬렉션 없음")
        return

    for name in cols:
        col = Collection(name)
        col.load()
        count = col.num_entities
        schema_fields = [f.name for f in col.schema.fields]
        print(f"\n  이름     : {name}")
        print(f"  데이터 수 : {count}건")
        print(f"  필드     : {schema_fields}")
        col.release()


def show_incident_history(limit: int = 5):
    """인시던트 히스토리 조회"""
    print("\n" + "=" * 55)
    print("🚨 인시던트 히스토리")
    print("=" * 55)

    col = Collection(settings.MILVUS_INCIDENT_COLLECTION)
    col.load()

    if col.num_entities == 0:
        print("  저장된 인시던트 없음")
        col.release()
        return

    results = col.query(
        expr="id >= 0",
        output_fields=[
            "id", "title", "severity", "service",
            "namespace", "occurred_at", "root_cause",
            "resolution", "tags",
        ],
        limit=limit,
    )

    for r in results:
        print(f"\n  [{r.get('severity', '').upper()}] {r.get('title')}")
        print(f"  ID         : {r.get('id')}")
        print(f"  서비스     : {r.get('service')} / {r.get('namespace')}")
        print(f"  발생일시   : {r.get('occurred_at')}")
        print(f"  근본원인   : {r.get('root_cause', '')[:100]}")
        print(f"  해결방법   : {r.get('resolution', '')[:100]}")
        print(f"  태그       : {r.get('tags')}")
        print(f"  {'─' * 50}")

    col.release()


def show_runbook_knowledge(limit: int = 5):
    """Runbook 지식베이스 조회"""
    print("\n" + "=" * 55)
    print("📖 Runbook 지식베이스")
    print("=" * 55)

    col = Collection(settings.MILVUS_RUNBOOK_COLLECTION)
    col.load()

    if col.num_entities == 0:
        print("  저장된 Runbook 없음")
        col.release()
        return

    results = col.query(
        expr="id >= 0",
        output_fields=["id", "title", "category", "source_file", "chunk_index", "content"],
        limit=limit,
    )

    for r in results:
        print(f"\n  [{r.get('category')}] {r.get('title')} (chunk #{r.get('chunk_index')})")
        print(f"  소스   : {r.get('source_file')}")
        print(f"  내용   : {r.get('content', '')[:150]}...")
        print(f"  {'─' * 50}")

    col.release()


def show_normal_log_baseline(limit: int = 5):
    """정상 로그 베이스라인 조회"""
    print("\n" + "=" * 55)
    print("📊 정상 로그 베이스라인")
    print("=" * 55)

    col = Collection(settings.MILVUS_NORMAL_LOG_COLLECTION)
    col.load()

    if col.num_entities == 0:
        print("  저장된 베이스라인 없음")
        col.release()
        return

    results = col.query(
        expr="id >= 0",
        output_fields=["id", "log_message", "container", "namespace", "sampled_at"],
        limit=limit,
    )

    for r in results:
        print(f"\n  컨테이너 : {r.get('container')} / {r.get('namespace')}")
        print(f"  수집일시 : {r.get('sampled_at')}")
        print(f"  로그     : {r.get('log_message', '')[:150]}")
        print(f"  {'─' * 50}")

    col.release()


def search_incident_by_keyword(keyword: str, limit: int = 5):
    """인시던트를 키워드로 필터링 조회 (scalar 검색)"""
    print(f"\n🔍 인시던트 키워드 검색: '{keyword}'")
    print("=" * 55)

    col = Collection(settings.MILVUS_INCIDENT_COLLECTION)
    col.load()

    # 서비스명 또는 태그 기준 필터
    results = col.query(
        expr=f'service == "{keyword}" or tags like "%{keyword}%"',
        output_fields=["id", "title", "severity", "service", "occurred_at", "root_cause"],
        limit=limit,
    )

    if not results:
        print(f"  '{keyword}' 관련 인시던트 없음")
    else:
        for r in results:
            print(f"\n  [{r.get('severity')}] {r.get('title')}")
            print(f"  서비스   : {r.get('service')}")
            print(f"  발생일시 : {r.get('occurred_at')}")
            print(f"  원인     : {r.get('root_cause', '')[:100]}")

    col.release()


def delete_incident_by_id(incident_id: int):
    """인시던트 ID로 삭제"""
    col = Collection(settings.MILVUS_INCIDENT_COLLECTION)
    col.delete(f"id == {incident_id}")
    col.flush()
    print(f"✓ 인시던트 ID {incident_id} 삭제 완료")


def show_stats():
    """전체 통계 요약"""
    print("\n" + "=" * 55)
    print("📈 전체 통계")
    print("=" * 55)

    for col_name in [
        settings.MILVUS_INCIDENT_COLLECTION,
        settings.MILVUS_RUNBOOK_COLLECTION,
        settings.MILVUS_NORMAL_LOG_COLLECTION,
    ]:
        if utility.has_collection(col_name):
            col = Collection(col_name)
            col.load()
            print(f"  {col_name:35s}: {col.num_entities:>6}건")
            col.release()
        else:
            print(f"  {col_name:35s}: 컬렉션 없음")


# ── 메인 인터랙티브 메뉴 ──────────────────────────────
if __name__ == "__main__":
    import sys

    connect()
    show_stats()

    # 인자 없으면 전체 조회
    if len(sys.argv) == 1:
        show_collections()
        show_incident_history(limit=5)
        show_runbook_knowledge(limit=5)
        show_normal_log_baseline(limit=5)

    # 인자 있으면 키워드 검색
    elif sys.argv[1] == "search" and len(sys.argv) > 2:
        keyword = sys.argv[2]
        search_incident_by_keyword(keyword)

    # 삭제
    elif sys.argv[1] == "delete" and len(sys.argv) > 2:
        delete_incident_by_id(int(sys.argv[2]))

0개의 댓글