본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 22. 21:09

AI 에이전트의 OpenTelemetry는 'other 46.7%'를 발견한 시점부터가 운영이었다

요약

Claude Code의 세션 로그를 OpenTelemetry Span으로 변환하여 AI 에이전트의 실행 과정을 가시화하고 운영하는 방법을 다룹니다. 특히 도구 호출(tool_call) 메타데이터의 분류 스키마 유지보수가 운영의 핵심임을 강조합니다.

핵심 포인트

  • Claude Code 세션 로그를 OpenTelemetry Span으로 변환하여 Jaeger에서 시각화 가능
  • AI 에이전트 운영의 핵심은 변화하는 도구 카테고리(tool.category)의 스키마 유지보수
  • 개인정보 및 보안을 위해 Span 속성에는 대화 본문 대신 메타데이터만 기록 권장
  • session-turn-tool_call으로 이어지는 계층적 Span 설계 구조 활용

계기

이전에 Claude Code의 세션 로그를 OpenTelemetry의 Span으로 변환하여, Jaeger에서 가시화했다.

셋업 절차는 별도의 기사로 나누었다.

그 후, 단 1개의 세션만으로는 결론이 편향될 수 있으므로 39개 세션까지 범위를 넓혔다.

첫 번째 기사에는 Hatena Bookmark에서 237개의 북마크가 달렸다.

댓글을 보니 독자들이 반응한 지점은 크게 3가지가 있었다.

이 반응을 보고, 다음 기사에서는 스코프(Scope)를 명확히 나누는 것이 좋겠다고 생각했다.

이 기사에서 다루는 것은 LLM의 내부 상태가 아니다.

Claude Code의 JSONL에 남는, 도구 실행(tool execution)과 턴(turn)의 메타데이터다.

그와 동시에, 이전 실험에는 아직 미흡한 점이 있었다.

Span을 출력할 수 있게 되더라도, 분류 스키마(classification schema)가 낡아지면 대시보드는 다시 읽기 어려워진다.

웹 서비스라면 HTTP, DB, 외부 API 정도의 분류는 비교적 안정적이다.

AI 에이전트에서는 Bash, Read, Write, WebFetch, TaskOutput 옆에 MCP, 채팅 연동, 스케줄러, 자체 제작 도구 등이 늘어간다.

이 기사에서는 2026년 6월 시점의 수동 로그를 다시 수집하고, 추가로 Claude Code를 4개만 제어 실행했다.

목적은 OpenTelemetry의 계측(instrumentation) 그 자체가 아니라, tool.category의 유지보수가 운영 대상이 되는지를 확인하는 것이다.

전제로 사용 중인 Span 설계

지난번부터 사용 중인 Span 계층은 변경하지 않았다.

session
└─ turn
└─ tool_call

session은 하나의 작업 세션 전체.

turn은 사용자의 발언 1회부터 어시스턴트 응답까지.

tool_callBash, Read, Write, WebFetch 등의 도구 호출.

속성(attribute)은 내용을 담지 않는 방침으로 하고 있다.

# 기록하는 것
"tool.name" # 정규화된 도구 이름
"tool.category" # shell / file_read / file_write / web / agent_task 등
...

AI 에이전트의 로그에는 업무 대화, 코드베이스의 구조, 인증 정보에 가까운 문자열이 섞인다.

OpenTelemetry의 백엔드(backend)는 팀 단위로 보는 경우가 많으므로, 본문을 그대로 Span 속성에 실을 수는 없다.

따라서 이 기사에서도 대화 본문이나 파일 경로는 사용하지 않는다.

집계에 사용하는 것은 이벤트 종류, 도구 이름, 시작/종료 시각, 크기, 에러 유무, 토큰 수뿐이다.

사용한 재료

공개 중인 변환 스크립트는 지난번과 동일한 것을 사용했다.

로컬에서 확인하는 것이라면, Jaeger를 띄운 후 parse_session.py로 OTLP에 전송한다.

git clone https://github.com/mii012345/otel-agent-trace.git
cd otel-agent-trace
python -m venv .venv
...

집계만 하는 것이라면 analyze_session.py를 사용한다.

python analyze_session.py ~/.claude/projects/<project>/<session>.jsonl

이번 기사용으로는 공개 가능한 형태의 집계 증적(evidence)도 별도로 두었다.

기사용으로 남긴 집계 증적

experiments/splunk-opentelemetry-2026/claude_code_2026_06_metadata_summary.json
experiments/splunk-opentelemetry-2026/claude_code_controlled_2026_06_22_summary.json
experiments/splunk-opentelemetry-2026/hatena_feedback_summary.json

splunk-opentelemetry-2026은 기사용 집계 증적을 모아둔 디렉토리 이름이다.

이번 전송 대상을 나타내는 것이 아니다.

제어 실험은 Claude Code를 claude -p로 4회만 구동했다.

작업 디렉토리 내의 작은 CSV, README, Markdown만을 대상으로 파일 처리, 편집, WebFetch, 서브 에이전트(sub-agent) 확인을 혼합했다.

실행과 집계는 이 두 가지로 나누었다.

python experiments/splunk-opentelemetry-2026/scripts/run_controlled_claude.py
python experiments/splunk-opentelemetry-2026/scripts/aggregate_claude_metadata.py

여기에서도 생(raw) 프롬프트, 파일 경로, 명령 본문, 가져온 페이지 본문, raw session id는 집계 JSON에 포함하지 않았다.

우선 2개 세션을 다시 수집했다

가장 먼저 본 것은 2026년 6월의 2개 세션 분량이다.

이는 지난번의 39개 세션 분석과 비교하기에는 적은 양이다.

그럼에도 불구하고 분류 누락은 확인할 수 있었다.

새로운 종류의 도구(tool)가 나오면, 분류의 빈틈은 작은 샘플에서도 보이기 때문이다.

대상: 2세션
이벤트 수: 132
턴 수: 22
...

도구 호출(tool call)의 내역은 다음과 같았다.

mcp__…__reply 7회 평균 0.350s 합계 2.453s error=0
ToolSearch 2회 평균 0.004s 합계 0.008s error=0
Read 2회 평균 0.011s 합계 0.022s error=0
...

지난 기사에서는 BashTaskOutput이 눈에 띄었다.

이번에는 메시징(messaging)을 통한 MCP 도구가 가장 많다.

여기서 문제가 되는 것은 레이턴시(latency)가 아니다.

기존의 분류기(classifier)라면 이 7회가 전부 other로 떨어지는 것이 문제였다.

other가 되었다

기존 분류기에서는 46.7%가 other가 되었다. 지난번에 사용했던 분류는 대략 다음과 같았다.

def classify_tool_v1(name: str) -> str:
    categories = {
        "file_read": ["Read", "Glob", "Grep"],
        ...
    }

이 분류로 2개 세션을 보면 카테고리는 다음과 같다.

other 7회
system 3회
file_read 2회
...

15회 중 7회가 other이므로, 46.7%가 분류 불가능 상태가 된다.

other는 편리한 도피처이지만, 가시성(observability) 화면에서 계속 늘어나기만 하면 읽을 수 없게 된다.

"기타가 많다"는 것을 알아도 무엇을 수정해야 할지 알 수 없기 때문이다.

이번 other는 정체불명의 잡다한 도구가 아니었다.

메시징을 통해 에이전트가 응답하는, 명확한 역할을 가진 도구였다.

MCP를 카테고리로 취급하기

우선 MCP 계열의 도구를 별도 카테고리로 분리했다.

완전한 분류는 아니지만, 첫 단계로서의 유지보수다.

def classify_tool_v2(name: str) -> str:
    if name.startswith("mcp__"):
        if name.endswith("__reply"):
            ...

다시 분류하면 카테고리는 다음과 같이 바뀐다.

messaging_mcp 7회
system 3회
file_read 2회
...

숫자는 같아도 의미가 달라진다.

기존의 관점에서는 "기타가 절반 가까이"였다.

새로운 관점에서는 "이 세션은 메시징을 통한 응답이 많다"가 된다.

이것은 그래프 색상 구분(color coding)의 문제가 아니다.

알람(alert)이나 대시보드에서 확인해야 할 대상이 달라지는 것이다.

Claude Code를 4개만 추가로 실행했다

하지만 MCP를 추가한다고 끝나는 것은 아니었다.

Hatena Bookmark에서도 "Claude Code는 JSONL이라 다루기 쉽다"라는 반응이 있었지만, 다루기 쉬운 로그라 하더라도 분류는 별도로 키워나가야 한다.

그래서 Claude Code 2.1.177을 claude -p로 4개만 제어 실행했다.

파일 집계, README 편집, OpenTelemetry 페이지의 WebFetch, 서브 에이전트 확인의 4가지 시나리오다.

결과는 다음과 같았다.

대상: 4세션
이벤트 수: 95
도구 호출: 14
...

도구 호출의 내역은 다음과 같았다.

Read 4회 평균 0.005s 합계 0.021s error=0
Write 3회 평균 0.038s 합계 0.113s error=0
Bash 2회 평균 0.037s 합계 0.075s error=0
...

여기서 새롭게 보인 것은 TaskCreate였다.

MCP 대응이 완료된 v2 분류에서도, TaskCreateother인 상태였다.

v1 category:
file_read 4회
file_write 4회
...

14회 중 1회이므로, other 비율은 7.1%다.

46.7%보다는 작다.

하지만 의미 있는 도구가 other에 남아 있다는 점은 동일했다.

참고로, 여기서 측정한 TaskCreate의 0.004초는 서브 에이전트(Sub-agent) 전체의 작업 시간이 아니다.

이 값은 Claude Code의 로그상에서 TaskCreate 도구 호출(Tool call)로 보인 부분의 시간이다.

서브 에이전트의 내부 작업이나 품질을 설명하는 수치가 아니다.

이 경계는 본문에 써둘 필요가 있다.

OpenTelemetry로 보고 있는 것은 어디까지나 도구 호출과 턴(Turn)의 메타데이터이며, LLM의 내부 추론 그 자체가 아니다.

agent_task를 나눈 v3에서는 제어 실험을 거쳐, 분류기(Classifier)에 agent_task를 추가했다.

MCP와 마찬가지로 운영상의 의미가 다르기 때문이다.

def classify_tool_v3(name: str) -> str:
    if name.startswith("mcp__"):
        if name.endswith("__reply"):
            ...

v3로 제어 실험의 4개 세션을 재검토하면, other는 0이 되었다.

file_read 4회
file_write 4회
shell 2회
...

여기서 말하고자 하는 것은 v3가 완성형이라는 뜻이 아니다.

오히려 반대로, v3도 다음 도구가 늘어나면 다시 낡게 된다.

따라서 분류기는 한 번 작성하고 끝내는 코드가 아니라, 대시보드와 함께 유지보수하는 운영물로 취급하는 것이 좋다.

tool.name을 그대로 고카디널리티(High cardinality)로 두지 않기

MCP를 분류할 때 한 가지 더 주의할 점이 있었다.

가공되지 않은 도구 이름을 그대로 속성(Attribute)에 담으면 카디널리티가 증가하기 쉽다.

MCP의 도구 이름에는 서버 이름, 플러그인 이름, 구현 이름에 가까운 정보가 포함되기 쉽다.

환경에 따라서는 팀 이름이나 내부 서비스 이름에 가까운 문자열이 섞일 수도 있다.

그러므로 Span에 담는 값은 정규화(Normalization)하는 것이 좋다.

def normalize_tool_name(name: str) -> str:
    if name.startswith("mcp__") and name.endswith("__reply"):
        return "mcp.reply"
    ...

속성으로는 다음과 같이 나눈다.

tool.name = "mcp.reply"
tool.category = "messaging_mcp"
tool.integration_type = "mcp"
...

메시지 본문, 채널 ID, 사용자 ID는 담지 않는다.

필요하다면 로그 측에서 권한을 나누어 관리한다.

OpenTelemetry의 Span에는 운영 판단에 필요한 저카디널리티(Low cardinality) 속성만을 둔다.

대시보드에서 보는 지표가 바뀐다

분류 스키마(Classification schema)를 업데이트하면 보고 싶은 지표도 바뀐다.

이전에는 Bash의 실패율, TaskOutput의 대기 시간, AskUserQuestion의 이상치(Outlier)를 보고 있었다.

그것은 지금도 유효하다.

메시징을 통해 AI 에이전트를 구동한다면 다음 지표들도 필요해진다.

messaging_mcp의 호출 횟수 -
messaging_mcp의 에러율 -
messaging_mcp의 p95 레이턴시(Latency) -
agent_task의 호출 횟수 -
agent_task의 에러율 -
other의 비율 -
미지 도구 이름의 종류 수 -
tool.schema.versionother 비율 -
도구 카테고리별 호출 수 -
도구 카테고리별 합계 시간

이 중에서 미미하지만 효과적인 것은 other의 비율이다.

other

이 비율이 증가했을 때는 "미지의 도구가 늘어났거나" 혹은 "분류기 (Classifier)가 노후화되었음"을 나타낸다.

둘 중 어느 쪽이든 방치해서는 안 된다.

Web 서비스 모니터링에서 미지의 상태 코드 (Status Code)나 미지의 경로 (Route)가 늘어나면 조사한다.

AI 에이전트도 마찬가지로, 미지의 도구 카테고리가 늘어나면 스키마 (Schema)를 재검토한다.

백엔드보다 스키마가 먼저 영향을 미친다

이 기사의 집계는 로컬의 Claude Code JSONL 메타데이터를 통해 확인하고 있다.

Jaeger로는 기존의 OTLP 변환을 통해 보낼 수 있는 구성이지만, 이번에는 Splunk Observability Cloud로는 전송하지 않았다.

이전에 언급한 experiments/splunk-opentelemetry-2026/은 이 검증 작업 디렉터리 이름일 뿐이다.

이번에 확인한 것은 로컬 JSONL로부터 집계 JSON을 생성하고, 분류 스키마의 차이점 (Diff)을 확인하는 단계까지다.

Hatena Bookmark의 댓글에도 있었듯이, OTel의 전송 대상은 Jaeger만이 아니다.

OpenTelemetry Collector나 Grafana Alloy와 같은 수집·전송 파이프라인을 사이에 두고, Grafana Cloud, Grafana Tempo, OpenObserve, Splunk Observability Cloud와 같은 백엔드로 전송 대상을 교체할 수 있는 설계가 가능하다.

단, 백엔드를 바꾸더라도 분류 스키마가 흐릿하다면 보이는 정보도 흐릿하다.

other가 절반인 상태라면, 어떤 백엔드를 사용하더라도 "기타가 많다" 이상의 정보를 읽어내기 어렵다.

반대로 tool.category가 운영상의 용어와 일치한다면, 백엔드를 바꾸더라도 분석 내용을 이식하기 쉽다.

shell
file_read
file_write
...

이 정도의 입도 (Granularity)라면 개인의 로컬 실험에서도, 팀의 대시보드 (Dashboard)에서도 사용하기 쉽다.

세세한 도구 이름은 필요할 때만 본다.

평소에는 카테고리로 본다.

이번에 알게 된 점

이번 샘플은 작다.

2개 세션의 재시도와 4개 세션의 제어 실험뿐이므로, "최근의 Claude Code는 이렇게 동작한다"라고 일반화할 수 있는 자료는 아니다.

그럼에도 운영상의 문제는 보였다.

2개 세션의 재시도:
v1에서는 other가 7/15 = 46.7%
v2에서 MCP reply를 messaging_mcp로 나누면 other는 0%
...

AI 에이전트의 OpenTelemetry는 계측 코드 (Instrumentation Code)를 작성하는 것으로 끝나지 않는다.

도구 분류 스키마를 유지보수하는 운영이 필요해지기 쉽다.

처음에는 Bash, Read, Write, TaskOutput만 보고 있으면 됐다.

하지만 에이전트가 MCP, 채팅, 서브 에이전트 (Sub-agent), 스케줄러 (Scheduler), 자체 제작 도구로 연결되면 분류할 수 없는 스팬 (Span)이 늘어난다.

분류할 수 없는 스팬이 늘어나면, 시각화는 되어 있지만 판단할 수 없는 상태로 되돌아간다.

그 상태를 피하기 위해서는 다음 세 가지를 운영에 포함하는 것이 좋아 보였다.

  • tool.category를 저카디널리티 (Low Cardinality)로 설계한다
  • other의 비율과 미지의 도구 이름 종류 수를 모니터링한다
  • 새로운 도구가 늘어나면 분류기와 대시보드를 업데이트한다

OpenTelemetry는 AI 에이전트의 도구 실행을 보이게 해준다.

단, 보이는 상태를 유지하려면 에이전트의 변화에 맞춰 스키마도 계속해서 바꿔나가야 한다.

이번 작은 로그에서는 그 유지보수 대상이 MCP와 TaskCreate였다.

다음에 다른 도구가 늘어나면 다시 other가 늘어날 것이다.

그때 이를 알아챌 수 있도록 만드는 것까지가 AI 에이전트의 옵저버빌리티 (Observability)라고 생각한다.

Discussion

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0