Claude Codeのログとメトリクスをさくらのクラウド「モニタリングスイート」に送る

Claude Codeには OpenTelemetry (OTel) のサポートがある。

これを使ってログやメトリクスを収集し、さくらのクラウドの「モニタリングスイート」に送信する試みをした。

Claude CodeのOpenTelemetry設定

ほぼすべてのことがここに書いてある。

docs.anthropic.com

最も簡単には、以下の設定を .claude/settings.local.json などに書けば確認できる。

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_LOGS_EXPORTER": "console",
    "OTEL_METRICS_EXPORTER": "console"
  }
}

通常の入出力の他に様々な情報がターミナルに現れるようになる。

otel-collectorで受ける

console出力ではなく、外部システムへの送信を目的としてOpenTelemetry Collectorにデータを流すようにしてみる。

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc"
  }
}

これにより、ログ・メトリクスそれぞれが OTEL_EXPORTER_OTLP_ENDPOINT (デフォルトで http://localhost:4317) に対し grpc で送信されることになる。

これを受け取るOpenTelemetry collectorには、後述する「モニタリングスイート」への中継を前提として以下のものを使用する。

github.com

必要最低限の機能は十分に揃っているはず。ビルド済みバイナリをダウンロードするなりdockerで起動するなり自前でビルドするなりして、repositoryに含まれている config.yaml を使って起動するだけでまずはotel-collectorの出力としてデバッグログが流れるのを確認できる。

./sacloud-otel-collector --config=./config.yaml

verbositydetailed にするとより詳細な内容を見ることができる。

exporters:
  debug:
    verbosity: detailed

ログ:

2025-06-30T12:04:04.885+0900    info    ResourceLog #0
Resource SchemaURL:
Resource attributes:
     -> service.name: Str(claude-code)
     -> service.version: Str(1.0.35)
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope com.anthropic.claude_code.events 1.0.35
LogRecord #0
ObservedTimestamp: 2025-06-30 03:04:03.135 +0000 UTC
Timestamp: 2025-06-30 03:04:03.135 +0000 UTC
SeverityText:
SeverityNumber: Unspecified(0)
Body: Str(claude_code.user_prompt)
Attributes:
     -> user.id: Str(***)
     -> session.id: Str(c1acf7b7-d1b2-497b-9512-d8b2728259c4)
     -> organization.id: Str(***)
     -> user.email: Str(***@gmail.com)
     -> user.account_uuid: Str(***)
     -> terminal.type: Str(tmux)
     -> event.name: Str(user_prompt)
     -> event.timestamp: Str(2025-06-30T03:04:03.135Z)
     -> prompt_length: Str(5)
     -> prompt: Str(<REDACTED>)
Trace ID:
Span ID:
Flags: 0
        {"otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "logs"}

メトリクス:

2025-06-30T12:01:56.754+0900    info    ResourceMetrics #0
Resource SchemaURL:
Resource attributes:
     -> service.name: Str(claude-code)
     -> service.version: Str(1.0.35)
ScopeMetrics #0
ScopeMetrics SchemaURL:
InstrumentationScope com.anthropic.claude_code 1.0.35
Metric #0
Descriptor:
     -> Name: claude_code.session.count
     -> Description: Count of CLI sessions started
     -> Unit:
     -> DataType: Sum
     -> IsMonotonic: true
     -> AggregationTemporality: Delta
NumberDataPoints #0
Data point attributes:
     -> user.id: Str(***)
     -> session.id: Str(5d1084d1-f6f1-4feb-b5fc-4081104e9d6e)
     -> organization.id: Str(***)
     -> user.email: Str(***@gmail.com)
     -> user.account_uuid: Str(***)
     -> terminal.type: Str(tmux)
StartTimestamp: 2025-06-30 03:01:46.884 +0000 UTC
Timestamp: 2025-06-30 03:01:56.662 +0000 UTC
Value: 1.000000
        {"otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics"}

ここで注目すべきところは

  • ログは Bodyclaude_code.user_prompt といったevent名だけが入り、その他の情報は Attributes に含まれる
    • promptの内容は <REDACTED> になっている
  • メトリクスは AggregationTemporality: Delta で送られる

というところ。

otel-collectorからモニタリングスイートへ送る

ここからモニタリングスイートの話。「モニタリングスイート」のスイートは「スイートプリキュア♪」のスイートと同じスイートです。

manual.sakura.ad.jp

上記マニュアルにある通り、さくらのクラウド「モニタリングスイート」では、少なくとも2025年6月現在では対応プロトコル

  • メトリクス: Prometheus Remote Write
  • ログ: OpenTelemetry Protocol (OTLP/HTTP)

となっている。 上述のotel-collectorからそれぞれ対応したexporterを設定することで、それぞれ送信できる。

コントロールパネル上でそれぞれ「ログストレージ」「メトリクスストレージ」を作成する。

各ストレージで対応するアクセスキーを作成。

これらで得たエンドポイントとBasic認証ヘッダを使って、以下のような感じで設定していく。 メトリクスに関しては自分で usernamepasswordからbase64エンコードしてヘッダの値を作る必要がある…。

# 例: username: `m123456`, password: `12345678-1234-5678-9abc-123456789abc`
$ echo -n "m123456:12345678-1234-5678-9abc-123456789abc" | base64
bTEyMzQ1NjoxMjM0NTY3OC0xMjM0LTU2NzgtOWFiYy0xMjM0NTY3ODlhYmM=

exportersを追加した config.yaml は以下のようになる:

exporters:
  debug:
    verbosity: detailed

  prometheusremotewrite:
    endpoint: https://************.metrics.monitoring.global.api.sacloud.jp/prometheus/api/v1/write
    headers:
      Authorization: Basic **********************************************************==

  otlphttp:
    endpoint: https://************.logs.monitoring.global.api.sacloud.jp
    headers:
      Authorization: Basic ********************************************************

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, prometheusremotewrite]

    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, otlphttp]

メトリクスの設定

上記の基本設定だけでは、メトリクスは正常に送信されない。DEBUGログを出力するようにすると以下のようなエラー文言が出る。

service:
  telemetry:
    logs:
      level: DEBUG
        {"otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics"}
2025-06-30T23:38:39.874+0900    debug   prometheusremotewriteexporter@v0.125.0/exporter.go:219  failed to translate metrics, exporting remaining metrics        {"otelcol.component.id": "prometheusremotewrite", "otelcol.component.kind": "exporter", "otelcol.signal": "metrics", "error": "invalid temporality and type combination for metric \"claude_code.session.count\"", "translated": 1}

先述の通り、Claude Codeからは AggregationTemporality: Delta で送信されるが、これは prometheusremotewrite exporterでは扱えない、とのこと。

これに対応するための一つの方法として、 deltatocumulative processorが使用できる。

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  
  deltatocumulative: {}

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch, deltatocumulative]
      exporters: [debug, prometheusremotewrite]

こうすることで、otel-collector側で累計値を保持して処理してcumulativeの値で書き込んでくれるようになる。

または、Claude Code側で以下の環境変数を設定しておけば、processorは使わなくてもClaude Code側で毎sessionでの累積値を送信してくれるようにはなる。

{
  "env": {
    "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
  }
}

ログの設定

ログは特別な設定なしでも一応送信はできるが、そのままではイベント名しか記録されず、情報量が極めて乏しい。

debug 出力で見た通り、多くの情報は Attributes の中に含まれていて、モニタリングスイートにはこれらの情報を Body に載せる必要がありそう。

ここでは transform processorを使う。

processors:
  transform/add_attrs_into_body:
    log_statements:
      - 'set(log.body, {"message": log.body})'
      - merge_maps(log.body, log.attributes, "upsert")
      - set(log.attributes, {})

service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch, transform/add_attrs_into_body]
      exporters: [otlphttp]

log.bodyMap として持つようにし、元々入っていた Str のBodyは "message" というフィールドで持つよう変更する。そしてすべての log.attributes をその log.body にmergeする。元々の log.attributes は含めていても無駄になるだけなので空にしてしまう。

こうしてやることで、 json_payload として Attributes に含まれていた情報をモニタリングスイートで閲覧することができるようになる。

user_promptprompt はデフォルトで <REDACTED> と記録されるが、これはClaude Code側の OTEL_LOG_USER_PROMPTS の設定で変えることはできる。

{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_LOG_USER_PROMPTS": 1
  }
}

さらにHooksも

Claude Codeの最近の更新(v1.0.38以降)では、各イベント時にカスタム処理を実行できるHooks機能が追加されている。

これらをログとして取得して送信したい場合は以下のようなスクリプトを用意しておくと良さそう。

#!/bin/bash

HOOK_NAME="$1"
OTEL_ENDPOINT="${OTEL_ENDPOINT:-http://localhost:4318/v1/logs}"

if [ -z "$HOOK_NAME" ]; then
  echo "Usage: $0 <hook_name>" >&2
  exit 1
fi

jq -n --argjson body "$(cat)" --arg hook "$HOOK_NAME" '{
  resourceLogs: [{
    resource: {attributes: [{key: "service.name", value: {stringValue: "log-sender"}}]},
    scopeLogs: [{
      logRecords: [{
        timeUnixNano: (now * 1000000000 | tostring),
        body: {kvlistValue: {values: ([$body | to_entries[] | {key: .key, value: {stringValue: (.value | tostring)}}] + [{key: "hook.event", value: {stringValue: $hook}}])}},
        attributes: []
      }]
    }]
  }]
}' | curl -X POST \
  -H "Content-Type: application/json" \
  -d @- \
  "$OTEL_ENDPOINT"

otel-collector では receivershttp も指定してあればこのように curl でPOSTすることもできる。 あとは、Claude Code側で hooks 設定をそれぞれ指定する。

{
  "env": {  ...  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [ { "type": "command", "command": "./otel-log-sender.sh PreToolUse" } ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [ { "type": "command", "command": "./otel-log-sender.sh PostToolUse" } ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [ { "type": "command", "command": "./otel-log-sender.sh Notification" } ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [ { "type": "command", "command": "./otel-log-sender.sh Stop" } ]
      }
    ]
  }
}

こうして様々な情報をログとして残しておくことができるようになる。

メトリクス可視化

メトリクスが正常に保存されていれば、モニタリングスイートの可視化機能を使ってダッシュボードを作成できる。

クエリ例:

sum(claude_code_token_usage_tokens_total) by (type, model)

これらの値を使ってアラートを設定して通知送信する、といったこともできるがここでは割愛…

まとめ

Claude CodeのOpenTelemetryサポートを活用して、ログとメトリクスをさくらの「モニタリングスイート」に送信する環境を構築した。

主なポイント:

  • Claude Code側でOTelの設定を行い、otel-collectorに送信
  • メトリクスはDelta形式で送られるため、deltatocumulative processorまたは累積モード設定が必要
  • ログはAttributesの情報をBodyに含めるためtransform processorで変換が必要
  • Hooksを活用することで独自のイベントログも収集可能

これにより、Claude Codeの使用状況を詳細に監視・分析できる環境が整った。メトリクスの可視化やアラート設定により、使用パターンの把握やトークン消費量の管理も行える。