요즘 팀에서 Claude Code, Codex 같은 코딩 에이전트를 도입하는 조직이 늘고 있는데, 막상 "누가 얼마나 쓰고 있는지", "어느 팀이 토큰을 많이 소비하는지" 같은 질문에는 답하기 어렵습니다. 개발자 개개인의 로컬 터미널에서 돌아가는 도구라, IT 조직 관점에서는 블랙박스에 가깝습니다. 그러다가 최근 Coding Agent Insights - Amazon CloudWatch 문서를 읽게 됐는데, CloudWatch가 OTLP 엔드포인트로 코딩 에이전트의 OpenTelemetry 메트릭을 받아서 전용 대시보드로 보여준다는 내용이었습니다.
호기심이 생겨서 개인 AWS 계정에 Claude Code를 붙여보고, 팀 시나리오를 흉내내기 위해 EC2 위에 여러 사용자 프로파일을 만들어 테스트해봤습니다. 결론부터 말하면, 대시보드 자체는 CloudWatch 콘솔에 기본 제공되는 리소스지만 실제로 값이 채워지려면 에이전트 쪽에서 OTel 리소스 속성을 정확한 형태로 보내야 하고, 여기서 은근히 삽질 포인트가 많았습니다.
이 글에서는 코딩 에이전트가 어떤 텔레메트리를 어떻게 뽑아내는지, CloudWatch의 네이티브 OTLP 엔드포인트가 그걸 어떻게 받아 처리하는지를 먼저 정리하고, 이어서 EC2 기반 개발 환경에서 어떻게 배포했는지, 그리고 무엇을 취사선택했는지를 공유합니다.
범위 명시: 이 글은 Coding Agent Insights의 metrics 경로에 집중합니다. tool 실행 결과 같은 events/logs나 traces는 별도 exporter와 opt-in이 필요하며, 여기서는 다루지 않습니다(뒤의 "시그널 구분" 참고).
(A) 설계 의도와 아키텍처
Claude Code, OpenAI Codex, GitHub Copilot 같은 최신 코딩 에이전트는 이미 내부에 OpenTelemetry SDK를 내장하고 있습니다. 사용자가 프롬프트를 입력할 때마다 세션 단위로 토큰 소비량, 턴당 latency, API 요청 수, 세션과 코드 변경 카운트 같은 것을 방출합니다. 다만 신호(signal)가 하나로 뭉뚱그려지는 게 아니라 metrics / events(logs) / traces로 구분되어 나오는데, Coding Agent Insights 대시보드가 사용하는 것은 이 중 metrics입니다. 즉, 이 도구들은 CLI 겉모양은 단순해 보여도, 안쪽에서는 이미 관측 가능한(observable) 애플리케이션처럼 동작하도록 만들어져 있습니다. 환경 변수 몇 개만 세팅해주면 OTLP 프로토콜로 원하는 collector 혹은 백엔드로 신호를 보내줍니다.
CloudWatch가 이 데이터를 받아들이는 방식이 흥미로웠습니다. 별도의 에이전트 없이 https://monitoring.<region>.amazonaws.com/v1/metrics 형태의 네이티브 OTLP HTTP 엔드포인트를 열어두고, Bearer 토큰(AWS SigV4로도 가능) 인증만 통과하면 CloudWatch OTel Metrics로 수집합니다. 내부적으로는 이 메트릭이 CloudWatch의 OTel Metrics(신형 저장소)로 들어가서 PromQL로 조회 가능한 상태가 됩니다. 대시보드는 정해진 metric과 attribute shape을 기반으로 제공되는 managed dashboard로, 이 저장소를 미리 정의된 PromQL 쿼리로 훑어서 그려줍니다.
Coding Agent Insights 대시보드가 "기본 제공된다"는 말에는 트릭이 있습니다. 대시보드가 기대하는 메트릭 이름(예: claude_code.token.usage, claude_code.cost.usage 등)과 차원(dimension)이 있고, 조직적 속성인 user.email, team.id, department, cost_center 같은 값은 반드시 OTel resource attribute로 실려 와야 합니다. Metric attribute로 넣으면 대시보드의 슬라이스 필터에 잡히지 않습니다. 이 부분이 문서에서도 "Important"로 강조돼 있었는데, 실제로 처음에 metric attribute로 넣었다가 대시보드가 비어 보여서 한참 헤맸습니다.
AWS 측 구성은 단순합니다. Node.js와 Claude Code CLI가 도는 환경(로컬 노트북이든 EC2든 무관)에서, IAM 역할 대신 CloudWatch Metrics API Key(cloudwatch:PutMetricData 등 지표 전송 권한을 가진 IAM 사용자로부터 생성) 형태의 Bearer 토큰을 발급해 사용했습니다. 이 토큰은 모델 추론에 쓰는 Bedrock API Key와는 별개이며, 텔레메트리 전송 경로 인증에만 쓰입니다. 팀 단위로 사용자 프로파일을 나눠서 각기 다른 resource attribute를 세팅한 뒤 세션을 돌려봤습니다. Collector를 별도로 두지 않고 CLI가 직접 CloudWatch OTLP 엔드포인트로 전송하도록 만든 것이 핵심 결정이었고, 그 이유는 뒤에서 다시 설명하겠습니다.
(B) 주요 이점과 고려 사항
가장 먼저 눈에 띈 것은 대시보드가 이미 "쓸 만한" 카테고리로 나뉘어 있다는 점이었습니다. 토큰 사용량은 input/output/cacheRead/cacheCreation 네 가지 type으로 자동 분리되고, 모델별(model 차원), 사용자별, 팀별로 자유롭게 grouping이 됩니다. 테스트 계정에서 세 개의 가짜 팀(platform, data, frontend)을 만들어 돌려봤더니, 어느 팀이 Sonnet 대비 Opus를 많이 쓰고 있는지, cache hit ratio가 어느 수준인지 그래프로 바로 보였습니다. 별도 ETL이나 대시보드 코딩이 전혀 필요 없었습니다. claude_code.cost.usage 같은 비용 metric이 기본 제공되는 것도 FinOps 관점에서 유용했습니다.
실제로 세 개의 가짜 팀(platform, data, frontend)으로 세션을 돌려보니, 단발 세션에서는 Total Tokens 33.9K 수준이던 것이 팀별로 나눠 세 세션을 돌리자 99.1K / 3 users / 3 sessions로 집계됐고, Group by Team으로 바꾸자 팀별 토큰 사용량이 색깔별 그래프로 바로 갈렸습니다. cache hit ratio는 서로 다른 짧은 프롬프트라 이번 테스트에선 0으로 나왔습니다(같은 긴 컨텍스트를 반복하면 올라갑니다).
프롬프트 스타일과 사용량의 관계도 흥미로운 단서였습니다. 같은 리팩터링 요청이라도 "관련 파일을 먼저 찾아본 뒤 수정해줘"처럼 탐색을 유도하면 세션당 토큰과 turn 수가 늘고, 컨텍스트를 미리 붙여주면 줄어드는 경향이 metric에서 드러났습니다. 즉, 대시보드로 프롬프트 작성 방식과 사용량의 관계를 엿볼 수 있었습니다.
한편 운영상 고려할 점도 몇 가지 있었습니다. 첫째, 개발자 로컬 머신에서 직접 CloudWatch로 전송하게 하려면 Bearer 토큰(CloudWatch Metrics API Key)을 배포해야 하는데, 이 장기 자격증명이 로컬 셸 환경에 남는 것을 어떻게 관리할지가 이슈입니다. 개인 실험은 단순히 ~/.zshrc에 넣었지만, 팀 규모라면 뒤에서 설명할 Collector + Instance Role(SigV4) 구조나 SSO 기반 enterprise 롤아웃 경로를 쓰는 게 맞습니다.
비용 측면은 예상보다 신경 쓸 부분이 있었습니다. CloudWatch OTel Metrics는 unique time series 개수가 아니라 수집한 비압축 OTLP payload 용량(GB)을 기준으로 과금됩니다. 따라서 user.email, team.id, model 같은 attribute가 늘어도 시계열별 저장 요금이 직접 부과되지는 않습니다. 다만 attribute가 많아질수록 datapoint 크기가 커지고, PromQL query가 스캔하는 sample 수도 늘어날 수 있어, 카디널리티는 여전히 비용과 query 성능 측면에서 관리해야 합니다. 참고로 PromQL query 중 콘솔과 CloudWatch 대시보드에서 실행되는 것은 무료이고, PromQL alarm은 alarm 비용과 query 비용이 발생합니다.
마지막으로 대시보드가 만능은 아니라는 점을 짚어두고 싶습니다. 예를 들어 "어떤 파일을 주로 수정했는가", "어떤 언어에서 tool 실행이 실패하는가" 같은 세밀한 분석은 기본 대시보드에서는 불가능하고, 애초에 tool 실행 결과는 metrics가 아니라 event/log 신호라 별도 수집이 필요합니다. 다만 metrics 원 데이터가 PromQL로 그대로 열려 있기 때문에, Query Studio에서 커스텀 쿼리를 짜거나 CloudWatch dashboard에 붙이면 확장이 가능합니다. 정리하면, 기본 대시보드는 조직 전반의 adoption과 cost overview에 적합했고, PromQL 커스텀 뷰는 엔지니어링 리드용으로 나눠 쓰는 게 자연스러웠습니다.
시그널 구분: metrics / events / traces
Claude Code의 OTel 신호는 섞어서 이해하면 안 됩니다. 다음처럼 구분됩니다.
| Signal | 내용 | 활성화 |
|---|---|---|
| Metrics | 토큰, 비용, 세션, 코드 변경 등 | OTEL_METRICS_EXPORTER |
| Events / Logs | API 요청, tool result, tool decision 등 | OTEL_LOGS_EXPORTER |
| Traces | Prompt → API → Tool 실행 관계 | 별도 beta opt-in 필요 |
이 글의 설정은 metrics exporter만 활성화하므로 tool result event나 trace는 CloudWatch로 전송되지 않습니다. Coding Agent Insights 대시보드도 metrics 기반이라, tool 성공/실패율 같은 지표를 보려면 OTEL_LOGS_EXPORTER와 CloudWatch Logs 수집을 추가로 구성해야 합니다.
프라이버시 기본값도 짚어둘 만합니다. Claude Code 공식 문서 기준 기본적으로 프롬프트 원문, 어시스턴트 응답 원문, tool input과 파일 내용은 텔레메트리에 수집되지 않습니다. 상세 tool 내용과 raw body는 별도 opt-in이 필요합니다. 다만 OAuth 인증 시 user.email은 telemetry에 포함될 수 있으니, 개인 식별자 노출을 원치 않으면 team.id까지만 붙이는 절충안을 고려할 수 있습니다.
(C) 아키텍처 트레이드오프
첫 번째 결정은 collector를 중간에 둘지, 에이전트가 직접 CloudWatch로 전송하게 할지였습니다. 표준 OTel 프랙티스에 따르면 OpenTelemetry Collector를 EC2나 ECS Fargate에 띄워두고 각 개발자 머신은 그쪽으로 로컬 네트워크 송신을 하는 게 정석입니다. 하지만 코딩 에이전트의 실행 위치가 개인 노트북, 원격 devcontainer, GitHub Codespaces 등으로 분산돼 있어서 공통 접점을 만들기 어려웠습니다. 결국 이번 PoC에서는 CLI가 CloudWatch OTLP 엔드포인트로 직접 전송하는 방식을 택했고, 대신 개발자 조직 안에서 collector를 운영할 여력이 있다면 sampling/필터링을 collector에 넣는 게 장기적으로 낫다고 봤습니다.
두 번째는 인증 방식입니다. Bearer 토큰과 SigV4(IAM) 두 가지 옵션이 있는데, 로컬 CLI 환경에서는 Bearer 토큰이 훨씬 다루기 쉽습니다. IAM은 EC2/ECS 같은 AWS 내부 리소스에서 실행할 때 자연스럽지만, 개인 노트북에서 IAM 자격증명을 관리하려면 aws-vault 같은 도구가 추가로 필요합니다. 대신 Bearer 토큰(CloudWatch Metrics API Key)은 유효기간 관리와 회수 정책을 따로 세워야 한다는 부담이 남습니다. 이번 실험은 개인 계정이라 단순 Bearer로 처리했지만, 실제 조직 배포라면 SSO 기반 enterprise 경로를 강력히 추천합니다.
세 번째는 resource attribute를 어디까지 표준화할지입니다. 처음에는 user.email, team.id, cost_center만 넣으려 했는데, 나중에 "특정 프로젝트 리포지토리에서 사용량이 얼마나 나오는지" 궁금해질 것 같아 project.repo도 추가했습니다. 문제는 이런 커스텀 attribute는 기본 대시보드에는 노출되지 않고 PromQL 커스텀 쿼리에서만 쓸 수 있다는 점입니다. 대시보드 표준 슬라이스를 최대한 활용하려면 문서에 명시된 attribute set만 넣고, 그 외는 label sprawl로 이어질 수 있으니 자제하는 게 낫다는 결론이었습니다.
네 번째는 개발자 프라이버시와 관측 가능성 사이의 균형이었습니다. user.email을 붙이면 개인 단위 분석이 가능해지지만, 조직에 따라서는 이걸 부담스러워하는 문화도 있습니다. 대안으로 team.id까지만 붙이고 개인 식별자는 제외하는 방식도 테스트해봤는데, 개인 단위 랭킹만 포기하면 대시보드 대부분은 여전히 잘 동작했습니다. "누가 많이 쓰는가"보다 "어느 팀이 어떻게 쓰는가"에 초점을 맞춘다면 이 절충안이 실용적이었습니다.
코드 예시
Claude Code가 CloudWatch OTLP 엔드포인트로 메트릭을 전송하도록 만드는 최소 환경 변수 세팅입니다. 핵심은 OTEL_RESOURCE_ATTRIBUTES에 조직 속성을 실어 보내는 것, 그리고 Claude Code는 metrics 전용 환경 변수에 /v1/metrics까지 전체 경로를 명시해야 한다는 점입니다. (범용 OTEL_EXPORTER_OTLP_ENDPOINT에 base URL만 넣는 패턴은 Copilot 방식이고, Claude Code에는 맞지 않습니다.)
# Claude Code OTel 메트릭을 CloudWatch로 직접 전송
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# metrics 전용 변수 + /v1/metrics 전체 경로 (Claude Code는 경로를 자동으로 붙이지 않음)
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="https://monitoring.ap-northeast-2.amazonaws.com/v1/metrics"
export OTEL_EXPORTER_OTLP_METRICS_HEADERS="Authorization=Bearer ${CW_OTLP_TOKEN}"
# (선택) export 주기 단축 - 짧은 세션에서 값이 빨리 뜨게
export OTEL_METRIC_EXPORT_INTERVAL=2000
# 리소스 속성 - 대시보드 슬라이스에 반드시 resource로 실려야 함
# 주의: 값 사이에 공백이 들어가면 안 됨
export OTEL_RESOURCE_ATTRIBUTES="service.name=claude-code,user.email=hong@example.com,team.id=platform,department=engineering,cost_center=cc-1024,organization=acme"
EC2에서 여러 개발자 프로파일을 흉내내기 위해, 사용자별로 별도 셸 프로파일을 만들었습니다. 아래는 팀별 프로파일을 자동 생성하는 스크립트 일부입니다. (실제 Unix 사용자 계정이나 container를 생성하는 완전한 재현 스크립트는 아니고, resource attribute 분리 방식을 보여주기 위한 발췌입니다.)
#!/bin/bash
# 팀별 개발자 프로파일 생성 (PoC 시뮬레이션용 - 발췌)
for TEAM in platform data frontend; do
for USER in alice bob; do
PROFILE_FILE="/home/${USER}-${TEAM}/.claude_env"
mkdir -p "$(dirname "$PROFILE_FILE")"
cat > "$PROFILE_FILE" <<EOF
export OTEL_RESOURCE_ATTRIBUTES="service.name=claude-code,user.email=${USER}@acme.test,team.id=${TEAM},department=engineering,cost_center=cc-${TEAM},organization=acme"
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="https://monitoring.ap-northeast-2.amazonaws.com/v1/metrics"
export OTEL_EXPORTER_OTLP_METRICS_HEADERS="Authorization=Bearer ${CW_OTLP_TOKEN}"
EOF
done
done
보안 주의: 공용 EC2 환경에서 장기 CloudWatch Metrics API Key를
/etc/profile.d/같은 world-readable 평문 파일에 심는 방식(예전 v0.1 예제)은 권장하지 않습니다. 그 인스턴스에 로그인하는 모든 사용자가 토큰을 읽을 수 있기 때문입니다. 공용 서버라면 아래처럼 Collector가 EC2 Instance Role로 SigV4 인증하고, 사용자에게는 장기 키를 노출하지 않는 구조가 더 안전합니다.
Direct 전송을 유지해야 한다면 사용자별 credential 발급, 최소 권한, 정기 회전 및 폐기 절차를 반드시 갖춰야 합니다.
PromQL로 팀별 시간당 토큰 소비량을 직접 조회하는 예시입니다. 기본 대시보드로 부족할 때 Query Studio에서 이렇게 확장합니다. 두 가지를 주의해야 합니다. (1) CloudWatch PromQL은 OTel metric 이름의 점(.) 표기를 그대로 유지하므로 큰따옴표로 감싼 셀렉터를 씁니다. (2) resource attribute는 @resource. 접두사 레이블로 매핑됩니다(예: @resource.team.id). 언더스코어 형태의 team_id로는 조회되지 않습니다.
# 팀별 최근 1시간 output 토큰 소비 총량 (Query Studio에서 실행)
# rate()는 초당 평균 증가율이라 "시간당 총량"과 안 맞음 → increase() 사용
sum by ("@resource.team.id", model) (
increase({
"claude_code.token.usage",
type="output"
}[1h])
)
참고: tool 실행 성공/실패율은 metric이 아니라
claude_code.tool_resultevent/log로 제공됩니다. metrics-only 설정에서는 조회되지 않으므로, 이 글에서는 tool 실패율 예제를 다루지 않습니다. 필요하면OTEL_LOGS_EXPORTER+ CloudWatch Logs 수집을 별도 절로 구성하세요.
CloudWatch alarm으로 특정 팀의 토큰 소비가 급증할 때 알림을 받는 설정입니다. PromQL alarm은 --metrics(Metric Math용) 필드가 아니라 PromQLCriteria를 사용하며, 비교 조건을 query 안에 넣습니다.
# PromQL 기반 CloudWatch alarm 생성
aws cloudwatch put-metric-alarm \
--alarm-name "claude-code-token-surge-platform" \
--alarm-description "Platform 팀의 최근 1시간 토큰 사용량 급증 감지" \
--evaluation-criteria '{
"PromQLCriteria": {
"Query": "sum(increase({\"claude_code.token.usage\", \"@resource.team.id\"=\"platform\"}[1h])) > 500000",
"PendingPeriod": 600,
"RecoveryPeriod": 300
}
}' \
--evaluation-interval 300 \
--alarm-actions arn:aws:sns:ap-northeast-2:123456789012:cost-alerts \
--region ap-northeast-2
참고: PromQL alarm 문법은 AWS CLI 버전에 민감하므로, 재현 시 최신 CLI 사용을 권장합니다(아래 테스트 환경표의 AWS CLI 버전 참고).
Environment & Final Checklist
| 항목 | 값 |
|---|---|
| Region | ap-northeast-2 |
| Claude Code | 2.1.220 |
| AWS CLI | 2.25.14 |
| Compute | Local |
| Model 추론 | kiro-gateway 경유, 관측 경로와 독립 |
| Teams | platform, data, frontend |
| Telemetry | Metrics only, direct OTLP |
| Auth | CloudWatch Metrics API Key |
| Test period | 1h |
재현하려는 분들을 위한 체크리스트입니다.
- [ ] CloudWatch OTLP 엔드포인트용 Bearer 토큰(CloudWatch Metrics API Key) 또는 IAM 자격증명 준비
- [ ]
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT에https://monitoring.<region>.amazonaws.com/v1/metrics전체 경로 세팅 - [ ]
OTEL_EXPORTER_OTLP_METRICS_HEADERS에Authorization=Bearer <토큰>세팅 - [ ]
OTEL_RESOURCE_ATTRIBUTES에user.email,team.id,department,cost_center,organization포함 (값 사이 공백 없이) - [ ] Claude Code라면
CLAUDE_CODE_ENABLE_TELEMETRY=1활성화 - [ ] 콘솔의 GenAI Observability → Coding Agent Insights에서 값 확인
- [ ] Query Studio에서 PromQL(
increase,@resource.접두사)로 커스텀 뷰 시험 - [ ] 시간당 토큰 alarm 하나 정도는 걸어두기 (비용 급증 방지)
- [ ] 테스트 후 API Key 폐기 절차 수행
Lessons Learned
가장 인상적이었던 것은 "관측 가능성 자체를 도구 벤더가 담당하는" 흐름이었습니다. Claude Code나 Codex 같은 도구가 OTel을 기본 탑재하는 시대에는, 이걸 어떻게 계측할지 고민할 필요 없이 "어디로 보낼 것인가"만 결정하면 됩니다. AWS는 여기서 표준 OTLP 엔드포인트를 그대로 열어두는 방식으로 참여했습니다. 다만 전송 규격은 OTLP를 쓰더라도 dashboard와 저장, query는 CloudWatch에 결합되므로, "완전한 벤더 중립"이라기보다 "전송 계층은 표준, 관측 계층은 관리형"이라는 균형으로 이해하는 게 정확합니다.
한편, 조직 속성을 resource attribute로 넣어야 한다는 규약은 처음에는 사소해 보였지만, 실제로는 팀 전체가 지켜야 할 스키마 계약(schema contract)이라는 점이 인상 깊었습니다. 각 개발자가 임의로 attribute를 붙이기 시작하면 payload가 커지고 query 성능과 비용에 영향을 주므로, .claude_env 같은 공용 프로파일을 조직 차원에서 관리하는 편이 안전합니다. 다음에 팀에 도입한다면 이 프로파일을 SSO 로그인 훅과 연결해서 자동 주입하는 방식을 시도해보고 싶습니다.
마지막으로, 코딩 에이전트 관측 데이터는 단순한 비용 리포팅 이상의 가치가 있었습니다. 토큰 사용 패턴이나 cache hit ratio를 보고 있으면, 개발자가 프롬프트를 어떻게 구성하는지에 대한 힌트가 나옵니다. 이걸 조직 내 프롬프트 가이드나 코칭 자료로 되먹임할 수 있다면, 관측 데이터가 곧 생산성 개선 루프의 입력이 되는 그림이 그려집니다. 이 부분은 다음 번에 좀 더 깊게 파볼 예정입니다.









Top comments (0)