0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code の使用状況を OpenTelemetry で全社監視する基盤を作った話

0
Posted at

Claude Code の「使い方」を紹介する記事は数多くあります。一方で、組織として「誰がどれくらい使っているか」をどう把握するか、という運用側の記事はあまり見かけません。

業務で Claude Code を社内に広く(エンジニア以外の部署も含めて)に展開する機会があり、その利用状況を可視化する基盤を作りました。備忘録もかねて、構成とハマったポイントをまとめます。少しでも同じような状況の方の参考になれば幸いです。

なぜ可視化したかったか

Claude Code の利用が部署横断で広がってくると、こういった問いに答えづらくなってきます。

  • コスト: 誰がどのモデルをどれだけ使っているのか
  • セキュリティ: 深夜の不審なアクセスがないか。不審なMCPを使っていないか
  • 活用推進: そもそも組織としてどれくらい使えているのか

これらを継続的に把握するために、Claude Code に組み込まれている OpenTelemetry のテレメトリー機能を使って、利用状況を集める仕組みを用意することにしました。

OpenTelemetry(OTel)は、メトリクスやログといった計測データをベンダー非依存の共通フォーマットで送るための標準です。アプリ側は OTel 形式で吐き出すだけでよく、受け口(OTel Collector やバックエンド)を後から差し替えられるのが利点になります。

全体構成

最終的に、次のような構成にしました。

Claude Code(各社員の端末)
   │  OTLP/HTTP (port 4318)
   ▼
OTel Collector(Cloud Run: claude-otel.example.com)
   │  OTLP/gRPC(サービスアカウント / ADC で認証)
   ▼
Google Cloud Telemetry API
   ▼
Cloud Logging + Cloud Monitoring(ダッシュボード)

ポイントは、受け口を自前の OTel Collector にしたことです。Claude Code から直接バックエンドへ送ることもできるのですが、Collector を一段挟むことで、属性名の変換・機密情報のフィルタ・送り先の差し替えといった調整を一箇所に閉じ込められます。

Claude Code 側の設定

Claude Code 側は、環境変数でテレメトリー送信を有効化するだけです。配布用の設定にこれを入れておきます。

export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_ENDPOINT="https://claude-otel.example.com"

これで Claude Code は、メトリクス(コスト・トークン・セッション数など)とログ(各種イベント)を Collector へ送るようになります。

OTel Collector を Cloud Run で動かす

Collector のイメージには、Google 公式の otelcol-google を使いました。設定の肝になるのは receivers / processors / exporters の3つです。順に見ていきます。

receivers:OTLP/HTTP で受ける

まずは Claude Code からの送信を HTTP で受けます。

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

processors:Google Cloud の流儀に合わせる

ここが一番のハマりどころでした。Google Cloud の Telemetry API は、OTel のセマンティック規約とは少し違う属性を要求してきます。

processors:
  resourcedetection:
    detectors: [gcp]
  # Telemetry API は gcp.project_id を必須要求するので cloud.account.id から複製
  resource/gcp_project_id:
    attributes:
      - key: gcp.project_id
        from_attribute: cloud.account.id
        action: insert
  # generic_task リソースの必須ラベル。空だと log-based metric がサイレントに無視される
  resource/service_namespace:
    attributes:
      - key: service.namespace
        value: "claude"
        action: insert

つまり、次の3点を最初に押さえておかないと、送ったはずのデータが静かに消えたりエラーになったりします。

  • gcp.project_id が無いと Telemetry API に弾かれるので、cloud.account.id から複製してやる
  • service.namespace が空だと log-based metric の集計対象から外れてしまうので、固定値を必ず入れる
  • status のような Google Cloud 側で予約されているキーは int64 としてパースされるため、HTTP ステータス文字列を入れるとエラーになる。mcp.connection.status のようにリネームして回避する

このあたりはドキュメントを読んでもなかなか気づきにくく、Cloud Logging に何も出てこない原因を探すのに少し時間を使いました。

exporters:Telemetry API へ送る

最後に Telemetry API へ送ります。

exporters:
  otlp:
    endpoint: telemetry.googleapis.com:443
    auth:
      authenticator: googleclientauth

認証は Cloud Run に割り当てたサービスアカウント(ADC)に任せられるので、鍵ファイルを持ち回らずに済みます。

Cloud Run へのデプロイ

サービスは Knative のマニフェストで定義し、設定ファイルは Secret Manager からマウントしています。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: claude-otel
  annotations:
    run.googleapis.com/ingress: internal-and-cloud-load-balancing
spec:
  template:
    spec:
      serviceAccountName: claude-otel-sa@example-project.iam.gserviceaccount.com
      containers:
        - image: .../otelcol-google:0.144.0
          args: ["--config=/etc/otel/otel-collector-config.yml"]
          ports:
            - containerPort: 4318

ingress は internal-and-cloud-load-balancing にして、インターネットに素で晒さず Load Balancer 経由のみ受けるようにしています。

config に差分が無くても確実に再読み込みさせる

デプロイは GitHub Actions で、main への push をトリガーにしています。Workload Identity Federation で Google Cloud に認証し、Secret を更新してから gcloud run services replace でサービスを更新する、という流れです。

ここで地味に効いたのが、デプロイのたびに毎回ユニークな値(${github.sha}-${run_id}-${run_attempt})をアノテーションへ注入することでした。

gcloud secrets versions add OTEL_COLLECTOR_CONFIG --data-file=otel-collector-config.yml
envsubst '${DEPLOY_STAMP}' < otel-collector-service.yml | gcloud run services replace -

config に差分が無くてもアノテーションが変わるので、新しいリビジョンが必ず作られ、Secret の最新バージョンが確実に読み直されます。これをやっていないと、「Secret は更新したのに古い config のまま動き続ける」という分かりにくい事故が起きます。

何が見えるようになったか

可視化先は Cloud Logging のクエリと、Terraform で管理している Cloud Monitoring のダッシュボードです。

# Claude Code の全イベント
resource.type="generic_task" resource.labels.job="claude-code"

ここから、次のような切り口を見られるようにしています。

  • ユーザー × モデル × プロダクト別のコスト・トークン使用量
  • セッション数の推移
  • ツール(MCP サーバー)の使用ランキング

なお、MCP サーバーは UUID で飛んでくるので、Collector の transform プロセッサで可読名にリマップしています(367bf128-...Slack に変換する、といった具合です)。新しいコネクタが増えたらマッピングを追記する、という運用にしています。

おわりに

Claude Code のテレメトリーは、「環境変数を撒く → Collector で受ける → バックエンドへ流す」という流れで、思ったよりも素直に全社可視化まで持っていけました。Cloud Run + OTel Collector の構成は薄くて運用も軽く、属性名の変換や機密フィルタといった調整を一箇所に閉じ込められるのが気に入っています。

Google Cloud に流す場合は、gcp.project_id / service.namespace / 予約キーの衝突という3点を最初に潰しておくと、原因の分かりにくい事故を避けられるでしょう。同じように Claude Code の利用状況を可視化したい方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?