Datadog 로그 검색을 위한 자연어 인터페이스
Source: Dev.to
소개
오전 2시. PagerDuty가 울린다. 결제 서비스에 문제가 생겼다.
Log Explorer를 열고 쿼리 바를 바라본다. service:payment이어야 할까 @service:payment이어야 할까? 부정은 NOT을 써야 할까 -를 써야 할까? 인증 실패에 대한 facet은 뭐였지?
로그에 답이 있지만, 구문이 걸림돌이다. 이 글에서는 자연어를 Datadog Log Search 쿼리로 변환하는 도구를 만드는 과정을 살펴보고, 이 문제를 흥미롭게 만드는 Datadog‑특화 함정들을 분석한다.
로그 검색 구문이 사람들을 헷갈리게 하는 이유
@ 접두사 규칙
예약된 속성은 @를 사용하지 않는다. 이것들은 Datadog이 기본 제공하는 핵심 필드다:
service:payment-service
status:error
host:web-server-01
사용자 정의 facet 및 로그 속성은 @가 필요하다. 직접 인덱싱한 모든 항목:
@http.status_code:500
@duration:>2000000000
@error.message:*timeout*
접두사를 뒤바꾸면 결과가 전혀 나오지 않는다—오류는 없고 빈 집합만 반환된다. 압박 속에서 디버깅할 때 특히 답답하다.
Duration 함정
Datadog은 나노초 단위로 duration을 저장한다. 초나 밀리초가 아니다.
2 초 이상 요청을 필터링하려면:
@duration:>2000000000
0을 하나 빼면 200 ms를, 하나 더 추가하면 20 초 요청을 찾게 된다. 사고 발생 시 이 실수는 시간을 낭비한다.
덜 흔한 Facet
Cloud SIEM이나 감사 로그를 다룰 때는 다음과 같은 facet을 마주한다:
@evt.name:authentication
@evt.outcome:failure
@network.client.geoip.country_name:*
일상적으로 사용되지 않기 때문에 기억에 남지 않지만, 의심스러운 활동을 조사할 때 정확히 필요한 쿼리이다.
LLM을 위한 프롬프트 엔지니어링
LLM은 학습 데이터에 Datadog 쿼리를 본 적이 있지만, 신뢰하기엔 충분하지 않다. 겉보기엔 타당해 보이지만 미묘하게 틀린 구문을 생성하곤 한다. 해결책은 시스템 프롬프트에 규칙을 명시적으로 적는 것이다:
예약된 속성 (@ 접두사 없음)
service:payment-service
status:error
host:web-server-01
Facet 및 사용자 정의 속성 (@ 접두사 필요)
@http.status_code:500
@duration:>1000000000 # 나노초
@error.message:*timeout*
피해야 할 흔한 실수
@service:payment– 잘못된 사용 (예약된 속성)@duration:>2– 잘못된 사용 (나노초가 아님)
나노초에 대한 언급은 핵심이다; 모델이 “2초 이상 요청”에 대해 @duration:>2를 생성하면 완전히 틀린 결과가 된다.
보안 패턴
추측하기 어려운 facet에 대해서는 명시적인 예시가 필요하다:
# 인증 실패
@evt.name:authentication @evt.outcome:failure
# CloudTrail 콘솔 로그인
source:cloudtrail @evt.name:ConsoleLogin
# 외부 IP만
NOT @network.client.ip:10.* NOT @network.client.ip:192.168.*
이러한 프롬프트는 대략 80 % 정확도를 제공한다. 남은 20 %는 엣지 케이스, 희귀 facet 이름, 통합‑특화 속성, 그리고 정적 프롬프트로는 다루기 힘든 구문 변형이다.
Retrieval‑Augmented Generation
엣지 케이스를 처리하기 위해 Datadog 문서를 색인하고, 쿼리 시 관련 섹션을 검색해 프롬프트에 삽입한다.
이중 검색 방법
- Dense embeddings (예: OpenAI
text-embedding-3-large)는 의미적 유사성을 포착한다. - Sparse embeddings (예: SPLADE)는 정확한 키워드 겹침을 포착해
@evt.outcome같은 문자열을 찾는다.
두 결과 집합을 Reciprocal Rank Fusion (RRF) 으로 병합한다:
results = qdrant_client.query_points(
collection_name=collection,
prefetch=[
Prefetch(query=dense_vector, using="dense", limit=limit * 2),
Prefetch(query=sparse_vector, using="sparse", limit=limit * 2),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=limit,
)
이 조합은 개념적 매치와 정확한 구문 매치를 모두 제공해, 정적 프롬프트가 놓친 엣지 케이스를 커버한다.
시스템 사용법
검증 도구로서의 설명
복잡한 쿼리를 가진 대시보드를 인수받을 때:
@evt.name:authentication @evt.outcome:failure \
NOT @network.client.ip:10.* NOT @network.client.ip:192.168.* \
NOT @network.client.ip:172.16.*
어시스턴트에게 “이 쿼리는 무엇을 하는가?”라고 물으면 다음과 같은 답을 얻는다:
“내부 네트워크 범위를 벗어난 IP에서 발생한 인증 실패 시도.”
이를 통해 이해 속도를 높이고, 모델에게 쿼리를 설명하도록 요청해 생성된 쿼리를 검증할 수 있다.
자연어 → 로그 검색 예시
| 입력 (자연어) | 생성된 Log Search 쿼리 |
|---|---|
| “결제 서비스에서 발생한 오류” | service:payment-service status:error |
| “2초 이상 걸리는 느린 요청” | @duration:>2000000000 |
| “외부 IP에서 발생한 로그인 실패” | @evt.name:authentication @evt.outcome:failure NOT @network.client.ip:10.* NOT @network.client.ip:192.168.* |
보안 쿼리가 가장 큰 가치를 제공한다: 엔지니어는 관측성 쿼리(service:api status:error)는 외우지만, SIEM‑스타일 facet은 거의 사용하지 않기 때문에 이 도구가 특히 유용하다.
결론
이 프로젝트의 흥미로운 부분은 LLM 통합 자체가 아니라, Datadog‑특화 세부 사항을 충분히 학습해 모델에 가르치는 과정이었다. @ 접두사 규칙, 나노초 함정, 보안 facet 패턴이 작동하는 쿼리와 조용히 실패하는 쿼리를 구분한다. 이러한 지식을 명시적으로 인코딩하고, 문서 검색을 통해 보강하면 도구를 신뢰할 수 있게 만든다.
구현을 살펴보고 싶다면 코드는 GitHub에 있다.
표현된 의견은 개인적인 것이며, 고용주를 대변하지 않는다.