
Splunk의 신기능을 검증하다가 우리 팀의 텔레메트리(Telemetry) 기반의 허점을 발견한 이야기
요약
Splunk의 AI Agent Monitoring 기능을 검증하는 과정에서 발생한 데이터 미표시 문제를 분석하고, 이를 통해 팀 내부의 텔레메트리 기반 비용 집계 결함을 발견하여 수정한 사례를 다룹니다.
핵심 포인트
- Splunk AI Agent Monitoring 기능의 데이터 미표시 증상 확인
- 일반 APM과 전용 기능 간의 데이터 속성 차이 가설 수립
- 브라우저 DevTools를 활용한 네트워크 통신 기반의 심층 진단
- 실시간 태그 지정 방식에서 사후 대조 방식으로의 비용 집계 로직 개선
TL;DR
- Splunk Observability Cloud의 「AI Agent Monitoring」 기능(AI 에이전트 모니터링에 특화된 전용 기능)을 검증했으나, 원인 불명인 채로 작동시키지 못했다. 일반적인 APM 경로는 정상적으로 관측되었으므로, 적어도 그 경로에 한해서는 전용 기능 고유의 무언가가 원인이었다.
- 철수하는 과정에서 표준 기능과 전용 기능을 분리한 것을 계기로, 우리 팀의 로컬 텔레메트리 (Telemetry) 기반에도 다른 결함이 발견되었다. 당초 핵심 기능이었던 「태스크별 비용 집계」가 구현 당시부터 한 번도 작동하지 않고 있었다.
- 실시간으로 태그를 지정하는 방식을 포기하고, 나중에 완료 기록과 대조하는 방식으로 전환하여 수정했다. 이 기사 자체도 그 집계에 포함되어 「가장 비용이 많이 든 태스크」로서 기록에 남았다.
전제: 일반적인 OTLP/APM은 작동하고 있었다
Claude Code의 AI 에이전트 텔레메트리 (Telemetry)를 OpenTelemetry로 자체 수집하여, 범용적인 OTLP 메트릭스 (Metrics)로서 Splunk Observability Cloud로 보내는 연결은 이전부터 작동하고 있었다. 이 부분의 설정 상세는 이번 본론이 아니므로 반복하지 않겠다.
Splunk에는 이와 별개로, 「AI Agent Monitoring」이라는 AI 에이전트 모니터링에 특화된 전용 기능이 있다. 이번에 테스트하고 싶었던 것은 이 전용 기능이다. 동일한 텔레메트리 (Telemetry)를 보내고 있음에도, 일반적인 APM 기능에서 보이는 데이터와 이 전용 기능이 기대하는 데이터 사이에 어떤 차이가 있는 것일까. 결론부터 말하자면, 전용 기능 쪽은 잘 되지 않았다. 다만, 잘 되지 않는 과정에서 발견된 것들이 이번 기사로서는 오히려 더 재미있을지도 모르겠다.
증상: 화면이 전부 0이 된다
Splunk 측에서 AI Agent Monitoring 화면을 열면, 요청 수도 에러 수도 토큰 사용량도 비용도 모두 0인 상태로 움직이지 않았다 (화면상의 「비용」은 Splunk 공식 설명에 따르면 추정치이며, 실제 청구 금액 그 자체는 아니다). 반면, 동일한 데이터 소스에서 보내고 있는 일반적인 APM 화면 (서비스 맵)을 보면, 레이턴시 (Latency)도 요청 수도 실제 값이 제대로 표시되고 있다. 즉, 적어도 일반적인 트레이스 (Trace) 경로는 기능하고 있다. 전용 기능 측만 무언가를 포착하지 못하고 있는 상태인 듯하다.
처음에 세운 가설은 단순했다. AI 에이전트 모니터링 전용 기능이므로, gen_ai.operation.name과 같이 해당 에이전트 호출의 종류를 나타내는 속성 (Attribute)이 필요한 것이 아닐까 하는 것이었다. 실제로 전송하고 있던 스팬 (Span)에는 모델명이나 토큰 수는 포함되어 있었지만, 이 속성은 붙어 있지 않았다. 꽤 타당해 보이는 가설이다.

진단: UI 너머를 들여다보기
여기서 조금 곤란한 상황이 발생했다. 이 전용 기능의 화면은 필터 위치나 검색 조건 표시가 알기 어려워, 수동으로 몇 번을 클릭해도 「지금 어떤 조건으로 검색되고 있는지」가 명확하지 않았다. 몇 번이고 왔다 갔다 해보았지만, 솔직히 「이 UI는 사용하기 불편하다」는 인상을 받았다.
그래서 브라우저를 직접 조작하여 확인하는 방법으로 전환했다. Chrome DevTools Protocol (브라우저의 내부 상태나 통신을 외부에서 관측·조작할 수 있는 표준 프로토콜)을 통해 브라우저에 접속하여, 화면을 클릭하는 대신 실제로 브라우저와 Splunk 서버 사이에서 어떤 통신이 이루어지고 있는지를 직접 들여다보았다. 이를 실행해 보니, UI상의 필터 버튼 뒷단에서 실제로는 비동기 검색 작업 (Job)이 서버 측에서 실행되고 있다는 것을 알 수 있었다. 작업 진행 상황을 폴링 (Polling)으로 받으면서 서서히 결과를 좁혀나가는 구조로 되어 있었다. 화면의 「로딩 중」 표시가 좀처럼 끝나지 않았던 것도 이 비동기 처리 때문이 아니었을까 생각한다.
나아가, 이 검색이 기본적으로 어떤 조건으로 설정되어 있는지도 통신 내용을 통해 직접 읽어낼 수 있었다. 에이전트 호출 종류를 나타내는 속성 값으로 필터링된 상태가 기본값으로 설정되어 있었는데, 이는 UI상에서 내가 선택한 기억이 없는 설정이었다. 당초의 가설 (속성이 부족하다)은 여기서 상당히 보강되었다고 할 수 있을 것이다.
검증: 가설을 실제로 시험해 보기
가설이 보강된 시점에서 실제로 확인해 보기로 했다. 운영 파이프라인과는 별개로 테스트용 임시 Collector를 세우고, OpenTelemetry SDK로 합성한 스팬 (Span)을 직접 Splunk로 보내본다. 보내는 스팬은 두 종류를 준비했다. 하나는 지금까지 보내왔던 것과 동일한 속성 구성 (gen_ai.operation.name...
속성 없음). 또 하나는, gen_ai.operation.name="chat"를 추가하고, 스팬 종류(Span Kind)를 CLIENT로, 스팬 이름도 chat으로 변경하여 OpenTelemetry의 생성형 AI(Generative AI)용 시맨틱 컨벤션(Semantic Conventions)이 권장하는 형태에 가깝게 만든 것이다.
결과는 솔직히 말해 기대 이하였다. 속성을 추가한 테스트 데이터도 전용 기능의 검색에는 걸리지 않았다. 일정 시간 관측했지만 변화는 없었다. 반면, 일반적인 APM 기능에서는 테스트용 서비스를 포함하여 올바르게 표시되었다.
즉, 이번 검증을 통해 "속성 추가만으로 표시되게 된다"는 점은 확인할 수 없었다. 단독 원인이라고 간주할 수 있는 가설은 적어도 이번 검증 범위 내에서는 지지되지 않았다는 것이 정확한 상황이다. 일반적인 APM 경로로는 계속해서 정상적으로 관측되고 있다. 전용 기능만이 원인을 알 수 없는 채 반응하지 않는다. 조금 더 오래 기다렸다면 다른 결과가 나왔을까. 아직 시도하지 않은 다른 속성이나 스팬의 계층 구조가 필요한 것인지, 아니면 무료 티어(Free Tier) 고유의 제약인지, 지금도 답은 나오지 않았다.

철수 판단, 그리고 그 범위를 착각할 뻔한 이야기
원인을 모르는 채 검증을 계속하기보다 여기서 손을 떼기로 했다. 다만, 처음에 정했던 철수 범위는 "Splunk로의 송신 전체를 중단한다"는, 조금 너무 큰 범위였을지도 모른다.
여기서 멈출 수 있었던 것은, "작동하지 않은 것은 전용 기능뿐이며, 이전 글에서 썼던 범용적인 송신은 지금도 살아있다"는 사실을 검증 도중에 우리 스스로 확인했기 때문이다. 표준 기능과 전용 기능을 분리하여 다시 생각했고, 철수 대상은 전용 기능 설정으로만 좁혔다. 잘 작동하고 있던 것까지 함께 놓칠 뻔했다는 점은 기록해 둘 가치가 있는 반성점이라고 생각한다.
전환점: "이거, 정말 도움이 되고 있었나요?"
전용 기능의 뒷정리를 하던 중 문득 질문을 받았다. "이 텔레메트리(Telemetry) 기반 자체, 실제로 도움이 되고 있다는 실감이 없다"라고.
뼈아픈 지적이었지만, 감정적으로 반론하기 전에 구현 전후로 정해두었던 효과 측정 기록을 재검토하기로 했다. 그러자 구현 전에 세웠던 5가지 질문 중, 구현 후에도 단 하나만이 계속 "데이터 소스 없음" 상태로 남아 있었다. 게다가 그것은 처음에 가장 기대했던 질문이었다. "어떤 태스크에 가장 많은 비용이 들고 있는가".
원인을 파헤쳐 보니 허무할 정도였다. 이 질문에 답하기 위한 메커니즘은 절반밖에 구현되어 있지 않았다. "어떤 태스크의 작업인가"라는 정보를 읽어내는 메커니즘은 코드 안에 존재했지만, 그 정보를 기록하는 쪽의 코드가 애초에 한 번도 작성되지 않았던 것이다. 실제 데이터를 확인해보니, 기록된 이벤트 전체 중 태스크 번호가 들어있던 것은 0건이었다.
더 골치 아픈 것은 이것이 한 번 "수정되었다"고 보고되었다는 점이었다. 실제로 수정되었던 것은 "태스크 번호가 있을 경우 이를 올바르게 읽어내지 못하던 버그"였고, "태스크 번호 자체가 애초에 기록되지 않고 있다"는 한 단계 더 앞선 결손은 아무도 알아채지 못했다. 부분적인 수정이 "완료" 보고에 섞여 들어갔고, 그대로 몇 주 동안 간과되었다. 절반의 구현을 "완료"라고 읽어버리는 습관이 다른 곳에도 잠재되어 있지는 않을까.

해결 방법: 실시간을 포기하고, 나중에 채워 넣기
솔직하게 고친다면, 태스크에 착수할 때 그 번호를 환경 변수(Environment Variable)로 전달하는 방법이 된다. 다만, 이는 대화형 작업과는 궁합이 좋지 않다. 어떤 태스크를 할지 정해져 있지 않을 때도 있고, 채팅 도중에 처음 알게 되는 경우도 많다. 여기서 "착수 시 반드시 설정한다"라는 규칙을 만들어도, 결국 다시 잊어버려 똑같은 구멍에 빠질 가능성이 높다고 판단했다.
대신 선택한 것은 사후에 대조하는 방식이었다. 작업이 완료될 때마다 어떤 태스크였는지를 기록한 로그는 별도로 존재한다. 텔레메트리 기록에는 작업이 언제부터 언제까지 지속되었는지에 대한 타임스탬프(Timestamp)가 남아 있다. 양쪽을 시간의 근접성으로 대조하면 나중이라도 태스크 번호를 채워 넣을 수 있다.
구현 리뷰에서는 예상보다 고생했다. 과거 완료 로그의 형식이 시기에 따라 몇 가지 방식으로 변해 있었기 때문이다. 처음에는 타이틀 행의 형식만 예상했지만, 실제로는 불렛 포인트(Bullet point) 안에 매립되어 있거나, 마크다운(Markdown)의 굵게(Bold) 장식이 붙어 있거나, 링크 형식으로 되어 있는 등 적어도 5가지 종류의 변형이 발견되었다.
그중에서도 특히 명심해야 할 점은, 단순히 "찾지 못하는" 것뿐만 아니라 "잘못된 번호를 집어내는" 위험이 있었다는 것이다. 어떤 로그는 콜론(:)으로 구분된 태스크 번호를 선언하는 행을 서두에 가지고 있었지만, 그 이후의 본문 중에서 참고용으로 다른 관련 태스크의 번호도 언급하고 있었다. 처음에 작성한 검색 로직은 선언된 번호가 아니라, 본문 중에서 우연히 먼저 나온 다른 번호를 집어내고 있었다. 값이 빈칸이 되는 정도라면 실질적인 피해는 제한적이겠지만, 잘못된 값이 조용히 들어가는 것은 은밀하게 까다로운 종류의 결함이다. 실제 로그를 수십 건씩 대조하며 읽은 끝에 겨우 수렴시킬 수 있었지만, 이와 유사한 서식의 변동(Fluctuation)은 다른 로그에도 아직 숨어 있을지 모른다.
최종적으로 채택한 판정 규칙은 다음과 같다. 선언 행으로서 명시적으로 작성된 번호를 본문에서 언급만 된 번호보다 항상 우선한다. 시간적으로 유일한 후보를 찾을 수 없는 경우에는 억지로 채우지 않고 빈칸으로 남겨둔다. 시간의 근접성만으로 매칭하고 있는 이상, 병행하여 실행 중이던 다른 태스크나 우연히 같은 시간대에 완료된 작업을 착각할 가능성은 이론적으로 남아 있다. 수백 건의 실제 데이터로 확인한 바로는 그러한 오류는 확인되지 않았지만, 이것은 "발생하지 않는다"는 증명이 아니라 "현재로서는 발견되지 않았다"는 뜻일지도 모른다.

결과
수정한 후, 다시 한번 동일한 5가지 질문을 확인했다. 앞서 언급한 판정 규칙으로 다 걸러내지 못한 일부 로그를 제외하고는, 5개 질문 모두에 답할 수 있게 되었다. 계속 빈칸이었던 "가장 비용이 많이 들고 있는 태스크"도 처음으로 구체적인 답이 나왔다. 답은 이번 Splunk 검증 그 자체를 포함한 일련의 조사 태스크였다. 장시간의 브라우저 조작과 수차례의 검증을 반복했다는 점을 고려하면 타당한 결과라고 생각한다.
요약
외부 서비스의 신기능이 제대로 작동하지 않는 원인을 끝까지 추적하려면 시간을 들여 도달할 수 있었을지도 모른다. 하지만 표준 기능은 건전하게 작동하고 있음을 확인한 시점에서, 그 기능을 더 이상 계속 추적하기보다는 발밑에 있는 우리들의 데이터를 바로잡는 데 시간을 쓰는 것이 결과적으로 리턴(Return)이 더 컸다.
잘 풀리지 않았던 검증의 기록을 남기는 것 자체에는, 미미하지만 의미가 있다고 생각한다. 이번 사례로 치면, "전용 기능만 작동하지 않는다"라는 분류(Segmentation)가 없었다면 표준 기능까지 함께 포기할 뻔했을 것이고, "도움이 되었습니까"라는 솔직한 질문이 없었다면 절반밖에 구현되지 않은 기능을 앞으로 몇 달 동안이나 알아차리지 못했을지도 모른다.
외부 서비스의 검증이 잘 풀리지 않을 때, 그 원인을 계속 추적하기 전에 한 번쯤 "우리들의 발밑은 정말 제대로 움직이고 있는가"를 의심해 볼 가치는 있지 않을까.
참고:
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기