
【AI】로컬 로그만으로 AI의 실행 시간·토큰·비용을 시각화하는 「aimet」
요약
Claude Code, Codex CLI, GitHub Copilot의 로컬 로그를 분석하여 AI 사용 시간, 토큰 소비량, 비용을 시각화하는 CLI 도구인 'aimet'을 소개합니다. 별도의 관리자 권한이나 팀 계약 없이도 개인 개발자가 로컬 JSONL 로그를 통해 프로젝트 매니지먼트 수치를 확보할 수 있게 해줍니다.
핵심 포인트
- 로컬 세션 로그(JSONL)를 활용해 별도 API 권한 없이 비용 및 토큰 추적 가능
- Claude Code, Codex CLI, GitHub Copilot의 데이터를 정규화하여 SQLite에 저장
- 의존성 없는 설계로 Node.js 표준 라이브러리만 사용하여 가볍고 안전함
- 일별/도구별 세션 수, 작동 시간, 캐시 내역, API 환산 비용 시각화 지원
대응 도구: Claude Code / Codex CLI / GitHub Copilot (VS Code Chat)
태그: Claude, ClaudeCode, OpenAI, GitHubCopilot, TypeScript
Claude Code나 Codex, GitHub Copilot으로 코드를 작성하는 것이 당연해졌습니다. 하지만 막상 "이번 개발에 시간이 얼마나 걸렸는가", "토큰을 얼마나 소비했는가", "종량제라면 비용이 얼마였는가"라고 물으면 정확하게 대답할 수 있는 사람은 적을 것입니다.
팀용 관리 API(조직 기능)를 사용하면 집계할 수 있지만, 이는 관리자 권한과 팀 계약이 전제되어야 합니다. 개인 개발자나 작은 팀에게는 거리가 먼 이야기입니다. 그렇다면 수중에 아무런 기록도 남아 있지 않느냐 하면, 그렇지 않습니다. 각 도구는 세션마다 로컬에 상세한 로그 (JSONL)를 남기고 있습니다. 여기에는 모델명·타임스탬프·실측 토큰 수(캐시 내역 포함)까지 전부 들어 있습니다.
이 "수중의 로그"만을 정보원으로 하여, AI 에이전트 개발의 시간·토큰·API 환산 비용을 채취하는 CLI 도구를 만들었습니다. 그것이 aimet (AI Metrics)입니다. 우선 완성된 형태의 출력을 확인해 보세요.
| period | tool | sessions | turns | active | wall | input | output | cacheR | cacheW | cost($) |
| ---------- | ------- | -------- | ----- | ------ | ------ | ----- | ------ | ------ | ------ | ------ |
| 2026-07-05 | copilot | 1 | 1 | 0.01h | 0.02h | 31.3k | 1.6k | 0 | 0 | 0.06 |
...
일별·도구별로 세션 수·턴 수·실제 작동 시간·토큰 내역·비용을 한눈에 볼 수 있습니다. 채취한 데이터는 실행 시간 파악·안건별 비용 배분·모델 선정의 판단 자료 등 프로젝트 매니지먼트의 수치로 사용할 수 있습니다.
3개 도구의 로컬 세션 로그를 스캔하여 공통 스키마로 정규화하고 SQLite에 축적하며, 기간 집계나 세션 상세를 출력하는 CLI입니다. 대응 현황은 다음과 같습니다.
| 도구 | 로그 위치 | 취득 가능한 토큰 | 상태 |
|---|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl | 실측 (in / out / cacheR / cacheW, 1h/5m 캐시 내역) | ✅ |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl | 실측 (in / cached / out / reasoning) + 레이트 리밋(Rate Limit) 시계열 | ✅ |
| GitHub Copilot (VS Code Chat) | <userData>/User/workspaceStorage/<hash>/chatSessions/*.jsonl | 실측 (prompt / completion) + 소비 크레딧 | ✅ |
설계 시 신경 쓴 점은 4가지입니다.
- 의존 패키지 제로. Node.js 22.5+에 표준 탑재된
node:sqlite를 사용하므로,npm install을 해도 서드파티 라이브러리는 하나도 늘어나지 않습니다. - 로컬 완결·조직 API 불필요. 수중의 로그만 읽기 때문에 권한도 팀 계약도 필요 없습니다. 로그가 로컬 외부로 나가는 일도 없습니다.
- SQLite에 축적.
~/.aimet/metrics.db에 저장되므로 과거 데이터도 모아서 집계할 수 있습니다. - 멱등성(Idempotency) 설계.
(tool, session_id)를 기본 키(Primary Key)로 하여, 최종 이벤트 시간이 진행되었을 때만 업데이트하므로 여러 번 실행해도 중복 계상되지 않습니다.
Copilot의 로그 위치(macOS)는 ~/Library/Application Support/Code/User/workspaceStorage/입니다. 기록되는 것은 Chat/에이전트 모드의 대화뿐이며, 인라인 보완(Inline Completion)은 남지 않습니다.
리포지토리는 여기입니다. 괜찮으시다면 스타(Star)나 피드백을 주시면 큰 힘이 됩니다.
git clone https://github.com/mayochan32/aimet.git && cd aimet
npm install && npm run build
npm link # `aimet` 명령어를 글로벌하게 등록
npm install은 TypeScript 빌드용(devDependencies)일 뿐이며, 실행 시의 의존성은 없습니다. Node.js는 22.5 이상이 필요합니다(node:sqlite를 사용하기 때문).
aimet collect # 모든 로그를 스캔하여 가져오기 (멱등성 및 재실행 안전)
aimet report # 일일 요약 (텍스트 표)
aimet session --tool claude # 최근 세션의 요약
collect가 로그를 DB로 가져오고, report가 집계하며, session / detail이 개별 세션을 심층 분석하는 역할 분담 구조입니다. collect는 이미 가져온 변화 없는 세션은 건너뛰므로(skip), 자주 실행해도 안전합니다.
session 출력 예시 (Claude Code의 1개 세션):
| item | value |
| tool | claude |
| model | claude-sonnet-4-6 |
...
input tokens가 29밖에 없는데 cache read가 292,816이나 있다——이 숫자를 읽는 법은 6장에서 자세히 해설합니다 (이 부분이 본 기사의 기술적인 핵심입니다).
aimet는 "언제 기록할지"를 3가지 방식으로 선택할 수 있습니다.
수동 실행 — 위와 같이 원하는 타이밍에 collect / report를 실행합니다.
자동 실행 (훅, Hook) — aimet init <tool>이 각 개발 환경에 훅을 삽입합니다. Claude Code라면 ~/.claude/settings.json에 SessionEnd 훅을 등록하여, 세션이 끝날 때마다 자동으로 기록되도록 합니다. --dry-run을 통해 작성될 내용을 사전에 확인할 수 있습니다.
aimet init claude # SessionEnd 훅을 등록
대화형 실행 — /metrics 명령어를 설치하면, 에이전트와의 대화 도중에 집계를 호출할 수 있습니다. 기존 설정은 병합(merge)되며, 이미 등록되어 있다면 중복 추가되지 않습니다.
모든 출력은 --md <파일>을 통해 Markdown으로, --json을 통해 JSON으로 정렬할 수 있습니다. 스프레드시트나 BI 도구에 데이터를 넣고 싶을 때 유용합니다.
aimet report --by project --md report.md
aimet report --by model --json # BI·스프레드시트 연동용
aimet의 출력은 입도(granularity)가 다른 3가지 레벨로 볼 수 있습니다.
레벨 1: aimet report (PM용 요약) — 기간 × 축(tool / project / model)으로 집계한 표입니다. 프로젝트 전체의 비용감이나 실행 시간을 조망하는 데 사용합니다. 읽는 법의 힌트로서, active/wall의 비율이 낮을수록 "AI에게 맡겨두고 방치할 수 있었다"는 것을 의미하며, cacheR이 클수록 컨텍스트 재사용이 잘 이루어지고 있음을 의미합니다. cost/turns를 통해 1태스크당 단가도 확인할 수 있습니다.
레벨 2: aimet session (1개 세션의 요약) — 특정 세션의 토큰 내역, 시간, 비용을 한눈에 확인합니다. "그 무거웠던 태스크, 토큰을 얼마나 썼지?"를 조사할 때 사용합니다.
레벨 3: aimet detail (로그 전체 기록) — API 요청 단위까지 전개한 상세 덤프(dump)입니다. 1개 요청마다의 모델, 중단 이유, content 종류(thinking / text / tool_use), 토큰 내역이 나열됩니다.
--raw를 붙이면, 통상적으로는 제외하는 거대 필드(시스템 프롬프트 전문이나 도구 스키마 정의)까지 포함한 완전한 덤프가 됩니다. 이 부분이 본 기사에서 가장 기술적으로 읽어볼 만한 대목입니다. aimet를 사용하면 필연적으로 in / out / cacheR / cacheW라는 숫자와 마주하게 되는데, 처음 보면 in이 1이나 29
와, 극단적으로 작아서 마치 버그처럼 보입니다. 이것은 프롬프트 캐싱 (Prompt Caching)의 사양이며 정상적인 동작입니다. 입력과 출력의 메커니즘이 완전히 다르기 때문에 나누어서 설명하겠습니다.
Claude API의 usage는 1회 요청의 입력 토큰을 3가지로 분류하여 기록합니다. aimet의 각 열은 바로 그 분류 자체입니다.
| 열 | 원본 필드 | 의미 |
|---|---|---|
in | input_tokens | 캐시에서 읽히지도 않았고, 생성에도 사용되지 않은 나머지 입력만 |
cacheR | cache_read_input_tokens | 과거에 캐싱되어 이번에 읽어와 재사용한 입력 (할인 단가) |
cacheW | cache_creation_input_tokens | 이번에 새로 캐시에 기록한 입력 (할증 단가. 1h/5m 내역 포함) |
즉, in은 「입력의 총량」이 아닙니다. 해당 요청에서 모델이 실제로 읽은 입력의 총량은 다음과 같습니다.
실제 입력 토큰 = in + cacheR + cacheW
왜 in이 극단적으로 작아지는 걸까요? 에이전트 대화에서는 시스템 프롬프트, 도구 정의, 과거 대화 이력이라는 거대한 덩어리가 매 턴 거의 동일하게 유지되며, 이 부분이 캐시에 고정됩니다 (→ cacheR). 매 턴 증가하는 신규 콘텐츠(사용자의 한마디나 도구 실행 결과)에도 캐시 표시가 붙기 때문에, 그 대부분은 cacheW로 흡수됩니다. 결과적으로 어떤 캐시 구분에도 속하지 않고 in에 남는 것은, 마지막 캐시 구분 지점보다 뒤로 벗어난 아주 짧은 끝부분의 파편뿐이게 됩니다.
따라서 in이 작다는 것은 캐시가 효율적으로 작동하고 있음을 의미합니다. 대화가 길어짐에 따라 늘어나는 부분은 in이 아니라 cacheR / cacheW 측에 쌓이게 됩니다. 실제로 앞서 본 session 출력에서는 input=29에 대해 cacheR=292,816이었습니다. 거의 전부가 캐시 재사용으로 돌아가고 있는 좋은 상태입니다.
캐시는 입력 전용입니다. 출력은 절대로 캐싱되지 않습니다. 모델의 생성물은 매번 처음부터 만들어지므로, out에는 cacheR / cacheW와 같은 분할이 없으며, 다른 어떤 열과도 가감 관계를 가지지 않는 독립적인 숫자입니다.
out (output_tokens)이 계산하는 것은 해당 응답에서 모델이 생성한 모든 토큰이며, 그 내용은 thinking (추론) + text (본문) + tool_use (도구 호출 JSON)를 모두 합산한 하나의 값입니다. 세 종류 모두 출력 단가로 과금됩니다 (thinking 또한 예외 없이 출력으로 취급됩니다).
detail의 함정: detail은 하나의 AI 응답을 content 블록별(thinking / text / tool_use)로 여러 줄에 걸쳐 전개하지만, in / out / cacheR / cacheW는 턴 단위의 동일한 usage를 각 행에 복사하여 표시할 뿐입니다. thinking 행과 text 행 모두에 out=59라고 적혀 있는 것은 「사고 59 + 본문 59」라는 뜻이 아니라 「이 턴의 생성 합계가 59」라는 의미입니다. 행마다 더하면 중복 계산이 됩니다. (집계 측의 report / session은 messageId로 중복을 제거하므로 합계 값은 정확하게 나옵니다. 중복되어 보이는 것은 detail의 로우 덤프(raw dump)일 뿐입니다.)
출력은 생성되는 순간에는 캐싱되지 않습니다 (→ out에 계상). 하지만 응답이 끝나면 해당 텍스트는 대화 이력에 추가되며, 다음 요청에서는 「입력」으로 변합니다. 그러면 다음 턴에서 cacheW (신규 쓰기)가 되고, 그 이후부터는 cacheR (읽기)로 재사용됩니다.
이번 턴의 out ──(1턴 후)──▶ 다음 턴의 cacheW ──(이후)──▶ cacheR
회의록에 비유하자면, out은 「지금 말한 내용」, cacheW는 「그것을 회의록에 기록하는 것」, cacheR은 「회의록을 할인된 가격으로 다시 읽는 것」입니다. 출력은 1턴 늦게 입력 캐시 파이프라인에 합류합니다. 단, 다음 턴의 cacheW는 이전 턴의 out
그 자체뿐만 아니라, 사이에 끼어든 사용자 입력이나 도구 실행 결과도 포함하기 때문에 수치가 딱 맞아떨어지지는 않습니다. 어디까지나 「출력이 입력 캐시(input cache)로 흘러 들어간다」는 방향성의 관계로 파악해 주세요.
⚠️ 비용은 참고치입니다. 실제 실행 환경에 맞춰 계산해 주세요.
aimet의 cost($)는 **모두 「API 환산 비용」**입니다. 종량제(API 직접 호출)였을 경우 얼마가 되는지를, 로그에 기록된 실측 토큰 수 × 공개 단가로 계산한 이론치이며, 실제 청구 금액이 아닙니다. Claude의 Pro/Max 플랜이나 ChatGPT Plus와 같은 정액 구독 서비스를 사용 중이라면, 실제 한계 비용은 0원입니다.
이 값은 다음 지표로 사용해 주세요.
- 구독 서비스를 통해 얼마나 이득을 보고 있는가
- 종량제로 전환하면 얼마가 되는가
- 태스크당 자원 소비량 (모델 선정의 판단 자료)
계산식은 단순하게 「각 토큰 구분 × 대응하는 단가」의 합산입니다. 여기서 핵심적인 것이 6장에서 본 캐시(cache) 구분으로, in (신규 입력)은 통상 단가, cacheR은 할인 단가, cacheW는 할증 단가와 같이 각각 별도의 단가가 적용됩니다.
구현상 공을 들인 점이 세 가지 있습니다.
캐시 TTL별 정확한 계산. 캐시 쓰기(write)는 TTL에 따라 단가가 다르기 때문에 (5분 = 1.25배, 1시간 = 2.0배), 로그의 cache_creation 내역으로부터 TTL별로 계산합니다.
중복 제거. Claude의 로그는 동일한 API 메시지가 여러 레코드로 나뉘는 경우가 있으므로, messageId로 중복을 제거하여 집계합니다.
단가 불명 시 0원으로 처리하지 않음. 모델명은 접두사(prefix) 최장 일치 방식으로 내장 단가표에서 가져옵니다. 일치하는 것이 없으면 비용은 - (null)가 되며, 0원으로 집계되는 일은 없습니다. 단가가 어긋나면 집계도 어긋나므로, 중요한 수치를 산출하기 전에는 최신 공개 단가와 대조해 주세요 (~/.aimet/pricing.json에서 덮어쓰기 가능).
또한, 도구에 따라 비용의 의미가 달라지는 점에도 주의가 필요합니다. 특히 GitHub Copilot만은 「API 환산값」이 아니라 「실제로 소비한 크레딧」 (copilotCredits × $0.01)으로 계산합니다. 로그에 실비가 기록되어 있기 때문이며, claude / codex의 「종량제였다면 얼마인가」와는 의미가 다릅니다.
마지막으로, 도구로서의 방침을 세 가지만 말씀드리겠습니다.
의존성 제로(Zero Dependency)를 고집한 이유. 메트릭(metrics) 수집 도구는 실행을 위한 설정이 무거우면 결국 사용되지 않습니다. node:sqlite가 표준화됨에 따라 외부 라이브러리 없이도 SQLite를 사용할 수 있게 되었습니다. npm install을 해도 공급망(supply chain)이 늘어나지 않는다는 점은, 로컬 로그를 다루는 도구로서 안심할 수 있는 요소입니다.
멱등성(Idempotency). 후크(hook)의 다중 발동이나 collect의 재실행은 흔히 일어납니다. (tool, session_id)를 기본 키(primary key)로 하여 최종 이벤트 시간이 진행되었을 때만 업데이트하도록 설계함으로써, 「일단 실행해 두기」가 항상 안전하도록 만들었습니다. 메트릭은 신뢰할 수 있어야 의미가 있으므로, 이중 계상이 절대 발생하지 않도록 하는 것을 최우선으로 했습니다.
로컬 완결 (프라이버시). 읽는 것은 로컬의 세션 로그뿐이며, 어디로도 전송하지 않습니다. 집계 결과도 ~/.aimet/metrics.db에 로컬로 저장됩니다. 대화 내용이나 코드를 포함할 수 있는 로그를 다루는 이상, 외부로 내보내지 않는 것은 기능이라기보다 전제 조건이라고 생각합니다.
지금까지 버려졌던 정보인 로컬 로그로부터, AI 개발의 실행 시간·토큰·비용이라는 「보이지 않던 숫자」를 추출할 수 있습니다. 마찬가지로 자신의 AI 개발을 수치로 되돌아보고 싶은 분들에게 도움이 된다면 기쁘겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기