레슨 4: 트레이싱: 한 번의 실행을 트리로 꿰기
학습 목표:
- 상관관계 ID로 흩어진 기록을 '프롬프트 하나가 촉발한 전부'로 묶고, 세션 ID와 어떻게 분업하는지 설명하기
- 루트 스팬, 모델 요청, 도구 호출, 도구의 두 국면(권한 대기와 실행), 서브에이전트가 부모 에이전트의 도구 스팬 아래에 중첩되는 방식까지 스팬과 트레이스의 계층 읽기
- 대시보드가 비었을 때 에이전트보다 파이프라인을 먼저 의심하기: 내보내기가 조용히 실패하는 이유, 배치 내보내기가 데이터를 흘리는 조건, 계측 자체를 검증하는 방법 알기
전제: 레슨 1–3(비결정성이 재현을 어떻게 무너뜨리는지, 원본 기록이 왜 일차 증거인지, 구조화 로그와 메트릭으로 하네스를 계측하는 법) | << 레슨 3 | 레슨 5 >>
400개의 기록 — 그중 어느 12개가 그 한 번의 실행인가
지난 레슨을 마칠 무렵 여러분의 하네스는 runs.jsonl 에 구조화된 기록을 쓰고 있었습니다. 모델 요청마다 하나, 도구 호출마다 하나씩, 소요 시간과 토큰과 에러를 달아서요. 로그는 '커다란 텍스트 덩어리 하나'에서 '한 줄에 JSON 객체 하나'로 바뀌었습니다. 만족스러웠죠.
그런데 사용자가 문제를 제보해 옵니다. "어제 오후에 로그인 페이지 문구 좀 고쳐 달라고 했는데 테스트 파일도 같이 바꿔 놨더라고요. 그건 시키지 않았는데요."
로그를 열고 날짜로 grep 하면 기록 400개가 여러분을 마주 봅니다. 도구 호출 200개, 모델 요청 100개, 거기에 서브에이전트가 남긴 기록 수십 개가 섞여 있습니다. 사용자가 말하는 그 프롬프트에 대응하는 것은 아마 열두어 개일 겁니다. 어느 열두 개일까요?
단서는 두 개 있지만, 어느 쪽도 충분하지 않습니다.
- 세션 ID. 지난 레슨에서 모든 기록에
session_id 를 쓰긴 했습니다. 그런데 어제 오후 사용자는 같은 세션 안에서 일고여덟 번을 주고받았습니다. 세션으로 거르면 400이 210이 됩니다. 범위는 줄었지만 성질은 그대로입니다.
- 타임스탬프. 시간 구간을 짐작해서 잘라낼 수는 있습니다. 하지만 서브에이전트가 동시에 돌았기 때문에 그 기록이 타임라인에서 메인 루프의 기록과 뒤엉켜 있고, 사용자가 말한 '오후'는 2시일 수도 4시일 수도 있습니다. 본인도 기억하지 못합니다.
문제는 기록이 덜 상세해서가 아닙니다. 기록에 관계가 없기 때문입니다. 레슨 3은 모든 단계를 데이터로 바꿔 놨지만, 그 데이터는 나란히 놓인 행 더미입니다. 어느 행이 어느 행을 불렀는지, 누가 누구의 자식인지에 대해서는 한마디도 없습니다. 예쁘게 포맷된 JSON 객체 수백 개, 여전히 모래 더미입니다. 이번엔 좀 더 반듯한 모래일 뿐이죠.
이번 레슨은 그 빠진 계층을 채웁니다.
상관관계 ID: 프롬프트에 붙이는 이름표
가장 단순한 한 걸음은 이렇습니다. '촉발 사건 하나'에 ID를 주고, 그 촉발이 낳은 모든 사건이 그 ID를 복사해 가지게 하는 것. 그것이 상관관계 ID입니다. 인프라가 전혀 필요 없습니다. 그냥 필드 하나입니다.
Claude Code 의 퍼스트파티 설계가 정확히 이렇게 합니다. 사용자가 프롬프트를 제출하면 Claude Code 는 여러 번의 API 호출을 하고 도구를 몇 개 돌릴 수 있는데, prompt.id 속성이 그 모든 사건을 그것을 촉발한 단 하나의 프롬프트로 되묶어 줍니다1. 문서가 주는 디버깅 레시피도 그만큼 직접적입니다. 프롬프트 하나가 촉발한 활동 전부를 추적하려면 특정 prompt.id 값으로 이벤트를 거르라는 것입니다1.
이것과 세션 ID는 서로 다른 두 개의 입도이고, 각자 맡은 일이 있습니다.
서두의 400개 기록으로 돌아가 봅시다. 모든 기록이 prompt_id 를 달고 있었다면, 세션 트랜스크립트에서 '로그인 페이지 문구 수정'에 해당하는 id 를 찾아 한 번 거르는 것으로 400이 12로 줄어듭니다. 모래에 처음으로 경계선이 생긴 겁니다.
하지만 경계선은 구조가 아닙니다. 그 12개는 여전히 평평한 12행입니다. 잘못된 테스트 파일 편집이 메인 루프에서 바로 나온 것인지, 메인 루프가 띄운 서브에이전트에서 나온 것인지 여전히 알 수 없습니다. 40초 걸린 그 도구가 여러분이 '승인'을 누르기를 기다리느라 40초를 썼는지도 알 수 없습니다.
스팬과 트레이스: 사건을 트리로 배열하기
여기서부터 계속 쓸 용어라서 먼저 평범한 말로 옮겨 두겠습니다.
- 스팬(span): '시작과 끝이 있는 작업 한 조각'의 기록입니다. 이름(
llm_request 같은), 시작 시각, 종료 시각, 그리고 거기 매달린 속성 몇 개(모델 이름, 도구 이름, 토큰 수)를 가집니다. 스팬은 자기 부모 스팬을 지목할 수 있습니다.
- 트레이스(trace): 부모-자식 관계로 이어진 스팬 트리 전체입니다. 완전한 요청 하나가 시작부터 끝까지 겪은 모든 일을 트리로 읽는 것입니다.
- 익스포터(exporter): 스팬을 포장해 밖으로 보내는 일을 맡은, 프로세스 안쪽의 코드입니다.
- 컬렉터(collector): 그 스팬을 받는 중계소 또는 백엔드 서비스입니다. 익스포터가 여기로 데이터를 보내고, 여러분은 그 대시보드에서 트리를 봅니다.
Claude Code 의 분산 트레이싱은 각 사용자 프롬프트를 그것이 촉발한 API 요청 및 도구 실행과 이어 주는 스팬을 내보내므로, 트레이싱 백엔드에서 완전한 요청 하나를 단일 트레이스로 볼 수 있습니다1. 구체적인 계층은 이렇습니다. 사용자 프롬프트마다 claude_code.interaction 루트 스팬이 하나 시작되고, API 호출과 도구 호출과 훅 실행이 그 자식으로 기록되며, 도구 스팬은 다시 자기 자식 스팬을 둘 가집니다. 하나는 권한 결정을 기다린 시간, 다른 하나는 실행 자체입니다1.
도구 아래 달린 그 자식 스팬 둘은 잠시 멈춰 볼 값어치가 있습니다. 지난 레슨에서 여러분은 duration_ms 를 기록했습니다. 도구가 40초 걸렸다고요. 그런데 '40초 중 38초를 누군가 승인을 누르기를 기다린 것'과 '40초 중 38초를 명령 실행에 쓴 것'은 완전히 다른 두 문제입니다. 앞쪽은 권한 설정을 고치거나 상호작용 방식을 바꾸라는 뜻이고, 뒤쪽은 도구 구현을 고치라는 뜻입니다. 같은 40초가 두 구간으로 쪼개지면 처방이 둘로 갈립니다. 평평한 필드가 주지 못하는 것을 트리 구조가 주는 지점이 바로 이것입니다.
Agent SDK 는 더 직접적으로 말합니다. 트레이스는 에이전트 실행에 대해 얻을 수 있는 가장 상세한 뷰이고, CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 을 설정하면 에이전트 루프의 각 단계가 트레이싱 백엔드에서 들여다볼 수 있는 스팬이 됩니다2. CLI 에는 OpenTelemetry 계측이 내장돼 있습니다. 모델 요청과 도구 실행마다 스팬을 기록하고, 토큰과 비용 카운터에 대한 메트릭을 내보내며, 프롬프트와 도구 결과에 대한 구조화 로그 이벤트를 냅니다2.
이 시리즈 7번째 코스('에이전트 하네스 기초: 루프와 제어')에서 쓴 하네스와 비교해 보세요. 여러분의 루프에는 이미 뚜렷한 자리들이 있습니다. '요청 보내기 / tool_use 받기 / 도구 실행 / tool_result 돌려주기'. 각 자리가 자연스럽게 스팬 하나에 대응합니다. 자리가 없는 게 아니라 부모-자식 관계가 없는 겁니다.
경계를 넘는 전파: 서브에이전트, 여러분의 애플리케이션, Bash 서브프로세스
트리는 보기 좋지만, 실제 실행은 프로세스 경계를 여러 번 넘습니다. 넘고 나서도 트리가 이어질 수 있을까요? 됩니다. '내 부모가 누구인지'를 아래로 계속 넘겨 주면 됩니다.
한 층 아래로: 서브에이전트. 에이전트가 Agent 도구를 통해 서브에이전트를 띄우면 서브에이전트의 llm_request 와 tool 스팬이 부모 에이전트의 claude_code.tool 스팬 아래로 중첩되므로, 위임 사슬 전체가 하나의 트레이스로 나타납니다2. 트리 없이는 답할 수 없는 질문 하나가 이걸로 풀립니다. 서브에이전트의 토큰은 누구의 토큰인가? 그 도구 호출 아래에 중첩돼 있고 그 도구 호출은 그 프롬프트 아래에 중첩돼 있으니, 그 프롬프트의 것입니다. 별도의 이어 붙이기가 필요 없습니다.
한 층 위로: 여러분의 애플리케이션. SDK 는 W3C 트레이스 컨텍스트를 CLI 서브프로세스로 자동 전파합니다. W3C 트레이스 컨텍스트란 트레이스 id 와 현재 스팬 id 를 담은 표준화된 문자열일 뿐입니다. 받는 쪽은 자기가 어디에 붙어야 하는지 알게 되죠. 애플리케이션에서 OpenTelemetry 스팬이 활성인 상태로 query() 를 호출하면 SDK 가 자식 프로세스 환경에 TRACEPARENT 와 TRACESTATE 를 주입하고, CLI 가 그것을 읽어 자신의 claude_code.interaction 스팬을 여러분 스팬의 자식으로 만듭니다. 에이전트 실행이 끊어진 루트가 아니라 여러분 애플리케이션의 트레이스 안에 나타나는 것입니다2.
이 차이는 프로덕션 문제 해결에서 대단히 실용적입니다. 사용자가 "그 버튼을 눌렀는데 페이지가 20초를 돌았어요"라고 합니다. HTTP 요청의 트레이스로 들어가면 시스템을 갈아타거나 타임스탬프를 맞춰 볼 필요 없이, 에이전트 안에서 14초를 쓴 그 도구 호출까지 그대로 따라갈 수 있습니다.
더 아래로: 에이전트 자신이 실행하는 명령. 트레이싱이 활성이면 Bash 와 PowerShell 서브프로세스는 활성 도구 실행 스팬의 W3C 트레이스 컨텍스트를 담은 TRACEPARENT 환경 변수를 자동으로 물려받습니다1. Bash 도구를 통해 띄운 명령이 자기 자신의 OpenTelemetry 스팬을 낸다면, 그 스팬은 명령을 감싸는 claude_code.tool.execution 스팬 아래로 중첩됩니다2.
세 구간을 이으면 트리 하나가 여러분의 웹 요청에서 출발해 CLI 를 지나고 서브에이전트를 지나, 에이전트가 실행한 npm run build 안의 컴파일 단계까지 뻗어 내려갈 수 있습니다.
내용이 아니라 구조만
이런 의문이 들 수 있습니다. 에이전트가 밟는 모든 단계가 외부 백엔드로 보내진다면, 사용자가 한 말이나 읽고 쓴 파일 내용도 거기 포함되는 걸까요?
멀티 에이전트 리서치 시스템에 대한 Anthropic 의 사후 분석은 나란히 놓인 결론 두 개를 줍니다. 하나는 얻은 것입니다. 완전한 트레이싱을 가동한 뒤 에이전트가 왜 실패했는지 진단하고 문제를 체계적으로 고칠 수 있게 됐습니다3. 다른 하나는 경계입니다. 사용자 프라이버시를 지키려고 개별 대화의 내용은 모니터링하지 않으면서 에이전트의 의사결정 패턴과 상호작용 구조를 모니터링했고, 이 정도의 상위 수준 관측 가능성만으로도 근본 원인을 진단하고 예상 밖의 동작을 발견하고 흔한 실패를 고치는 데 도움이 됐습니다3.
퍼스트파티 도구들의 기본 자세가 그 원칙과 정확히 맞물립니다. 텔레메트리는 기본적으로 구조적입니다. 소요 시간, 모델 이름, 도구 이름은 모든 스팬에 기록되고, 토큰 수는 밑에 깔린 API 요청이 usage 데이터를 반환할 때 기록되므로 실패했거나 중단된 요청의 스팬에는 빠져 있을 수 있으며, 에이전트가 읽고 쓰는 내용은 기본적으로 기록되지 않습니다2. 사용자 프롬프트 내용도 기본으로는 수집되지 않고 길이만 기록됩니다. 내용을 포함하려면 OTEL_LOG_USER_PROMPTS=1 을 명시적으로 설정해야 합니다1. Agent SDK 페이지도 자기 쪽 내용 수집 스위치 옆에 같은 주의를 답니다. 여러분의 관측 가능성 파이프라인이 에이전트가 다루는 데이터를 저장해도 된다고 승인받은 게 아니라면 이 값들은 설정하지 말고 두라는 것입니다2.
많은 팀에게 이건 좋은 소식입니다. '사용자 내용을 서드파티 백엔드로 보내도 되는가'라는 컴플라이언스 싸움을 이기고 나서야 에이전트가 뭘 하는지 보기 시작할 수 있는 게 아니라는 뜻이니까요. 많은 문제가 구조 계층에서 보입니다.
자기 하네스의 트레이스를 설계할 때도 이걸 기본값으로 삼으세요. 스팬에는 이름, 소요 시간, 도구 이름, 토큰 수, 에러 종류를 기록하고, 파라미터와 반환 내용은 (레슨 2의) 로컬 원본 기록에 남겨 둔 뒤 필요할 때 id 로 꺼내 오는 것입니다.
관측 가능성 파이프라인 자체가 여러분에게 거짓말을 할 수 있다
여기까지는 '트리가 세워진 다음에 볼 수 있는 것'에 대한 이야기였습니다. 이 절은 그보다 먼저 일어나고 더 쉽게 데는 이야기입니다. 데이터를 보고 있다고 생각하지만 실은 빈 패널을 보고 있는 경우요.
먼저 기억할 것. 내보내기 실패는 기본적으로 조용합니다. 엔드포인트에 닿지 못하거나 백엔드가 데이터를 거부해도 에이전트는 정상적으로 계속 돌고, CLI 는 여러분 애플리케이션에 에러를 드러내지 않은 채 텔레메트리를 버립니다2. 이 설계는 옳습니다. 관측 가능성 파이프라인이 본류를 쓰러뜨리면 안 되니까요. 다만 대가가 있습니다. 파이프라인이 망가진 상태와 전부 잘 돌아가는 상태가 여러분 쪽에서는 똑같아 보입니다.
두 번째. 배치 내보내기는 특정 조건에서 데이터를 흘립니다. CLI 는 텔레메트리를 모아 두었다가 일정 간격으로 내보냅니다. 프로세스가 깨끗하게 종료될 때는 남은 데이터를 플러시하려 시도하지만, 그 플러시에는 짧은 타임아웃 상한이 걸려 있어서 컬렉터의 응답이 느리면 스팬이 여전히 버려질 수 있습니다. CLI 가 셧다운을 마치기 전에 프로세스가 죽임을 당하면 배치 버퍼에 남아 있던 것은 전부 잃습니다2. 기본값으로 메트릭은 60초마다, 트레이스와 로그는 5초마다 내보냅니다2. 이 문장들을 붙여 놓고 보세요. CI 에서 3~5초 만에 끝나는 짧은 에이전트 실행은 꼬리 쪽 텔레메트리를 지키기 위해 '타임아웃 안에 플러시가 끝나고 그리고 프로세스가 일찍 죽임당하지 않는다'에 기대고 있고, 내보내기 간격은 버퍼에 쌓여 있는 양을 키웁니다. 이런 시나리오에서는 실행 시간보다 충분히 짧은 내보내기 간격이 필요하고, 프로세스가 깨끗하게 끝나도록 보장해야 합니다.
세 번째, 그리고 문제를 풀 때 가장 먼저 할 일. 계측 자체를 검증하세요(계측이란 여러분이 설치한 탐침을 부르는 다른 이름일 뿐입니다). 메트릭을 내보내는 설정을 검증하려면 백엔드에서 claude_code.session.count 메트릭을 확인하세요. Claude Code 가 세션이 시작될 때 내보내는 값입니다1. 아무것도 도착하지 않으면 claude --debug 를 실행하고 디버그 로그에서 OTel 내보내기 에러를 확인합니다1. 이 두 단계의 가치는 '에이전트에 문제가 있는가'와 '파이프라인이 동작하는가'를 따로 답할 수 있는 두 질문으로 갈라놓는다는 데 있습니다.
밟기 쉬운 설정 함정 두 개 더.
- CLI 는 기본적으로
service.name 을 claude-code 로 보고합니다. 에이전트를 여러 개 돌리거나 같은 컬렉터로 내보내는 다른 서비스와 나란히 SDK 를 돌린다면, 백엔드에서 에이전트별로 거를 수 있도록 서비스 이름을 덮어쓰고 리소스 속성을 추가하세요2. 그러지 않으면 에이전트 셋의 스팬이 같은 서비스 이름 아래 섞이고, 여러분은 죽을 들여다보게 됩니다.
- SDK 를 통해 실행할 때는
console 을 익스포터 값으로 설정하지 마세요2. 문서는 이유를 말하지 않습니다. SDK 와 CLI 가 통신하는 방식에 비춰 보면, SDK 는 stdout 으로 CLI 와 이야기하는데 거기에 스팬을 찍으면 그 채널이 뒤엉킬 것입니다.
시그널 셋, 필요한 것만 켜도 됩니다
전부를 한꺼번에 켤 필요는 없습니다. CLI 는 독립적인 OpenTelemetry 시그널 세 개(메트릭, 로그 이벤트, 트레이스)를 내보내고, 각각 자기 활성화 스위치와 자기 익스포터를 가지므로 필요한 것만 켤 수 있습니다2.
여기서 자연스러운 도입 순서가 나옵니다.
- 메트릭부터 켭니다. 비용과 토큰 사용량은 사람들이 가장 먼저 묻는 질문이고, 메트릭이 가장 값싸며, 기본 60초 내보내기 간격은 오래 도는 서비스에는 문제없습니다.
- 그다음 로그 이벤트를 켭니다. 도구 결과와 권한 결정 같은 구조화 이벤트는 레슨 3의 진단적 읽기 패턴에 쓰이는 원재료입니다.
- 설명되지 않는 문제를 만나면 트레이스를 켭니다. 가장 비싸고 가장 상세합니다. '한 번의 실행이 어떤 모양인지 봐야만 하는' 상황에서 필요합니다.
내보내는 대상은 OpenTelemetry 프로토콜(OTLP)을 받는 백엔드라면 무엇이든 됩니다. 문서는 몇 개를 이름으로 듭니다. Honeycomb, Datadog, Grafana, Langfuse, 또는 자체 호스팅 컬렉터입니다2. 어느 것을 고를지는 이 코스의 범위가 아닙니다. 한마디만 하자면, 시그널 셋이 독립이라는 것은 인프라가 전부 준비되기를 기다리지 않고 가장 작은 조각으로 물을 떠 볼 수 있다는 뜻입니다.
곁다리로: 같은 이벤트 묶음이 감사 추적이기도 합니다
구조화 이벤트에는 디버깅과 무관한 쓸모가 하나 더 있습니다. 존재한다는 것만 알아 두세요.
최종 사용자 신원을 붙이면 claude_code. 접두사가 붙은 로그 레코드로 내보내지는 tool_decision, tool_result, mcp_server_connection, permission_mode_changed 이벤트가 SIEM(Security Information and Event Management) 플랫폼으로 전달할 수 있는 사용자별 감사 추적이 됩니다2. 모든 이벤트는 도구 호출과 MCP 활동과 권한 결정을 그것을 촉발한 사용자로 되묶는 신원 속성을 지니고 있습니다1.
같은 데이터 묶음을 다르게 읽으면 다른 일의 원재료가 됩니다. 디버깅할 때는 prompt.id 로 가로로 자르고, 감사할 때는 사용자로 세로로 자릅니다. 보안 주제는 이 코스에서 확장하지 않습니다.
이 코스가 주지 않을 답
몇 가지는 경계를 분명히 해 둬야 여러분이 다른 데서 기성 레시피를 찾아 헤매지 않습니다.
- 샘플링 비율과 보존 기간: 트레이스를 전량 저장하면 비싸지고, 얼마나 샘플링하고 얼마나 오래 둘지는 실제 문제입니다. 하지만 일차 자료에 지침이 없으므로 이 코스는 숫자를 지어내지 않습니다. 데이터 양이 실제로 문제가 될 때, 그건 여러분과 백엔드 청구서 사이의 일입니다.
- 알림 임계값: 마찬가지로 숫자를 주지 않습니다.
- 훅: 생명주기 검문소에 탐침을 설치하는 법,
PreToolUse 와 PostToolUse 가 각각 무엇에 접근할 수 있는지는 레슨 5입니다. 인터페이스 하나만 여기 미리 심어 둡니다. 훅의 입력은 지금 처리 중인 사용자 프롬프트의 UUID 를 담고 있고, 그것은 텔레메트리 이벤트의 prompt.id 속성과 같은 값이라서 같은 프롬프트에 대한 훅 출력과 텔레메트리를 상관지을 수 있습니다4. 이번 레슨에서 세운 상관관계 ID 가 다음 레슨에서 바로 쓰이게 됩니다.
- 여러분의 하네스에는 완전한 OTel 이 필요 없습니다. 이번 레슨은 있어야 할 구조를 전부 펼쳐 보여 주기 때문에 퍼스트파티 설계를 교재로 삼았습니다. 하지만 여러분이 실제로 원하는 것은 트리뿐입니다. 레슨 6에서는 실행마다
trace_id 를 만들고 기록마다 span_id 와 부모 포인터 필드를 붙인 다음, 열댓 줄짜리 코드로 들여쓰기해서 출력합니다. 같은 부모-자식 메커니즘으로 세운 트리를 얻게 되죠(레슨 6은 도구에 대해 다른 부모를 골랐다는 점을 설명할 겁니다). 컬렉터도, 백엔드도, 의존성도 필요 없습니다. 언젠가 OTLP 를 연결해야 할 때가 오면 필드는 이미 거기 있습니다.
💻 연습
정리
- 구조화 로그는 '기록이 충분히 상세한가'를 풀었지만 '기록에 어떤 관계가 있는가'는 풀지 못했다. 상관관계 ID 가 관계를 붙이는 첫 걸음이다. 프롬프트 하나가 여러 API 호출과 도구 몇 개를 촉발할 수 있고,
prompt.id 가 그 모든 사건을 그것을 촉발한 단 하나의 프롬프트로 되묶으며, 디버깅의 첫 수는 이 값으로 거르는 것이다1
- 스팬은 시작과 끝이 있는 작업 한 조각의 기록이고, 트레이스는 부모-자식 관계로 꿴 트리다. 분산 트레이싱은 각 사용자 프롬프트를 그것이 촉발한 API 요청 및 도구 실행과 스팬으로 이어 주므로, 완전한 요청이 트레이싱 백엔드에서 하나의 트레이스로 읽힌다1
- 계층은 정해져 있다. 프롬프트마다
claude_code.interaction 루트 스팬이 열리고 API 호출과 도구 호출과 훅 실행이 그 자식이며, 도구 스팬은 권한을 기다린 시간과 실제로 실행한 시간을 따로 기록하는 자식 스팬 둘을 가진다1. 향상된 텔레메트리를 켜면 에이전트 루프의 각 단계가 들여다볼 수 있는 스팬이 되고, 트레이스는 실행에 대한 가장 상세한 뷰다2
- 트리는 프로세스 경계를 넘어서도 이어질 수 있다. 서브에이전트 스팬은 부모 에이전트의 도구 스팬 아래 중첩돼 위임 사슬 전체가 하나의 트레이스로 읽히고, SDK 는 CLI 서브프로세스에
TRACEPARENT 와 TRACESTATE 를 주입해 에이전트 실행이 끊어진 루트가 아니라 애플리케이션의 트레이스 안에 나타나게 하며2, 더 아래로는 Bash 서브프로세스가 TRACEPARENT 를 물려받고1 명령이 낸 스팬이 그 도구 실행의 스팬 아래로 중첩된다2
- 완전한 트레이싱을 가동하면 실패를 체계적으로 진단할 수 있게 되고3, 대화 내용을 보지 않고 의사결정 패턴과 상호작용 구조만 모니터링해도 근본 원인을 진단하고 예상 밖의 동작을 발견하기에 충분하다3. 메커니즘도 정확히 맞물린다. 텔레메트리는 기본적으로 구조적이어서 소요 시간과 모델 이름과 도구 이름은 모든 스팬에 기록되지만 내용은 기본적으로 기록되지 않고2, 프롬프트 내용도 기본은 길이만 기록하며 내용을 포함하려면 스위치를 명시적으로 켜야 한다1
- 메트릭, 로그 이벤트, 트레이스는 독립적인 시그널 셋이고 각자 활성화 스위치와 익스포터를 가지므로 필요한 것만 켤 수 있다2. 점진적 도입이 가능하고 한 번에 전부 갈 필요가 없다
- 관측 가능성 파이프라인 자체가 거짓말을 할 수 있다. 내보내기 실패는 기본적으로 조용해서 에이전트는 정상적으로 도는 동안 텔레메트리가 버려지고 에러도 드러나지 않는다2. 배치 내보내기는 깨끗한 종료에서 플러시를 시도하지만 짧은 타임아웃 상한이 걸려 있고, 프로세스가 죽임당하면 버퍼를 통째로 잃는다2. 기본값은 메트릭 60초, 트레이스와 로그 5초이므로2 짧게 사는 실행에는 더 짧은 간격이 필요하다. 문제 해결의 첫 순서는 계측 자체를 검증하는 것이다. 백엔드에서 그 세션 카운트 메트릭을 확인하고1, 없으면
--debug 를 켜서 내보내기 에러를 본다1
- 설정 함정 둘. 여러 에이전트가 하나의 백엔드를 공유하면 구별을 위해
service.name 을 덮어쓰고 리소스 속성을 추가해야 하고2, SDK 를 통해 실행할 때는 console 을 익스포터로 설정하지 않는다2
- 같은 구조화 이벤트 묶음을 다르게 읽으면 감사 자료가 된다. 신원 속성이 붙으면 도구 결정, 도구 결과, MCP 연결, 권한 모드 변경이 SIEM 으로 전달할 수 있는 사용자별 감사 추적이 되고2, 각 이벤트의 신원 속성이 도구 호출을 그것을 촉발한 사람으로 되묶는다1
- 샘플링 비율과 보존 기간에는 일차 지침이 없으므로 이 코스는 숫자를 주지 않는다. 자기 하네스에도 완전한 OTel 은 필요 없다. 레슨 6은
trace_id 와 부모 포인터 필드와 들여쓰기 출력만으로 같은 부모-자식 메커니즘의 트리를 얻는다
>> 레슨 5: 검문소에 탐침 놓기: 훅과 디버깅 워크플로