記事一覧に戻る
ベストプラクティス2026年5月29日10分で読めます

Aikidoで、AIのトークン使用量を取得するのに苦労した話

LLMのトークン消費量はAikido ZenのAI Usageタブで無料可視化できる。だが翻訳を走らせているのにずっと空——Outboundには出るのにAI Usageは空、という謎を追い、真因(Next.jsの自動バンドルがZenの計装フックを無効化)に到達。next.config.jsの1行で解決した実話。

AikidoNext.jsセキュリティ

Aikidoで、AIのトークン使用量を取得するのに苦労した話

LLMを使っていると、どうしても知りたくなるのが、トークン消費量。

このコスト、馬鹿にならないからね。使い方によっては、とんでもないことになる。

特に、基盤の上で複数のアプリが動いていると、どれがどれだけ勝手にトークンを使っているのか、把握しておく必要がある。コストが積み上がるからね。

だから普通は、ログをきちんと取って、整形して、計測する。使い方を見て、エンハンスが必要なら早急に対応しないと、これまた、とんでもないことになる。

——その、トークン消費量。

Aikidoで取得できるって、知ってました?

しかも、無料のフリーバージョンで。

えっ?トークン、可視化できるの?

Aikido のファイアウォール部品、ZEN。

先日この記事でも挙げたとおり、うちのサイトにインストールしてみた。

そのタブに、見慣れないものがあった。

AI Usage。

えっ?トークン消費量、可視化できるの? うち、毎日 Claude で翻訳してるけど。

これは見るしかない。期待して開いた。

……空だった。

No AI Usage。

おかしいな。翻訳、毎日走ってるはずなんだけど。

しばらく待った。翌日また見た。やっぱり空。

同じ通信なのに、片方だけ見えない

念のため、隣の Outbound タブを開いてみた。

こっちには、api.anthropic.com への通信が、ちゃんと出ている。Allowed。Zen は、Claude を呼んでいることを認識している。

なのに——

AI Usage は、いつまで経っても空。

同じ1本の通信なのに、片方(Outbound)には出て、もう片方(AI Usage)には出ない。

これは気持ち悪い。気持ち悪いので、ソースコードを調べてみた。

犯人探し

Zen のドキュメントによると、AI Usage が拾うのは SDK 経由の呼び出しだけ。生の fetch は SSRF 検知で見張られるが、使用量までは取らないらしい。

うちのコードを見たら、案の定、生 fetch で Claude を叩いていた。これだ、と思って公式 SDK に書き換えた。

// before:生 fetch
const res = await fetch("https://api.anthropic.com/v1/messages", { ... });
// after:公式 SDK
import Anthropic from "@anthropic-ai/sdk";
const message = await client.messages.create({ ... });

ビルドして再起動。翌朝、翻訳が走った。満を持して AI Usage を開く。

……空。

え。書き換えたのに。

ここから、思いついた仮説を一つずつ潰していった。

「古いビルドが動いてるのでは?」 → ビルドとソースの更新時刻を stat で比較。ビルドの方が新しかった。シロ。

「翻訳コードが二重で、旧 fetch が残ってるのでは?」 → ルートを grep。ちゃんと SDK 版の関数を呼んでいた。生 fetch の直書きなんて、どこにもない。シロ。

なのに——コンパイル済みのファイルには、手書き fetch のヘッダ anthropic-version が、なぜか残っている。SDK 版を呼んでるはずなのに。なんで。

——ここで、気づいた。

真犯人:Next.js が、SDK を「埋め込んで」いた

その anthropic-version、手書き fetch の痕跡じゃない。SDK 自身が内部で送っているヘッダだ。

つまり、コンパイル済みファイルに SDK そのものが丸ごと埋め込まれている(バンドルされている)ということ。

ここで、全部つながった。

Next.js は、ルートが使うライブラリを、ビルド時に自動で1ファイルに埋め込む。一方、Zen が使用量を取る仕組みは、require('@anthropic-ai/sdk') という呼び出しを横取りして、中を覗く方式。

埋め込まれて中身が直接展開されると、require(...) という呼び出し自体が消える。横取りする対象がない。だから、SDK に書き換えても AI Usage は空のまま。import しただけじゃダメだったのは、これが理由。

最後の謎、「なんで Outbound には出るのか」も解けた。SDK は内部でグローバルの fetch を使って通信する。Zen はその通信を、ネットワークのレベルで別途見張っている。だから埋め込まれていても、「外に出る通信」だけは捕まえられる。

同じ1本の通信が、見張り方の種類で見え方が変わっていた。 ネットワークの見張り → Outbound に出る。ライブラリの見張り → 埋め込まれて空振り → AI Usage が空。

気づいてしまえば、なんてことはない。でも、ここに辿り着くまでが長かった。

修正は、たった1行

やることは決まった。SDK を「埋め込まない」ようにして、実行時にちゃんと require させればいい。

Next.js では、next.config.js にこう書く。

const nextConfig = {
  serverExternalPackages: ["@aikidosec/firewall", "@anthropic-ai/sdk"], // ← これ
  // ...
};

Next.js 14 以前は experimental.serverComponentsExternalPackages。

1行。たった1行だ。

ビルドして、再起動。コンパイル済みファイルから anthropic-version(=埋め込まれた SDK の痕跡)が消えたことを確認した。

grep -rl 'anthropic-version' /workspace/.next/server | grep -v node_modules
# (何も出ない=埋め込みが解けた)

翻訳を、1回走らせた。

そして、AI Usage を開いた。

出た

Aikido Zen の AI Usage タブ。claude-sonnet-4 / Calls 1 / 推定コスト $0.02

claude-sonnet-4 / Calls 1 / Input 3.2K / Output 601 / 推定コスト $0.02。

出た。

しかも、Calls が「1」なのがいい。

修正する前の同じ週、Outbound には Claude への通信が何回も記録されていた。でも AI Usage に刻まれたのは、修正後の、たった1回の呼び出しだけ。

この「1」が、1行の修正がちゃんと効いた、何よりの証拠になっている。

(ちなみに Zen は、攻撃以外の記録を10分ごとにまとめて送る。だから呼んだ直後じゃなく、ちょっと待つと出てくる。「出ない出ない」と焦らないように。)

学んだこと

ひとことで言うと、これ。

監視ツールは、「入れただけ・import しただけ」で動くとは限らない。

Zen や OpenTelemetry のような「ライブラリを横取りして覗く」タイプの監視は、バンドラがライブラリを埋め込んだ瞬間、静かに息を止める。エラーも出ない。ただ、データが来ないだけ。だから気づきにくい。

そして、こうも思った。

「同じものが、片方では見えて、片方では見えない」とき、見張り方の違いを疑え。

今回まさにそれだった。Outbound(ネットワークの見張り)と AI Usage(ライブラリの見張り)は、別の仕組み。片方だけ見えるのには、ちゃんと理由がある。

切り分けの途中で、思い込み(旧ビルド・コード重複)を、その都度コマンドで否定していったのも効いた。stat で時刻、grep で痕跡。仮説を3つ潰して、ようやく真犯人に辿り着いた。

修正は1行。でもその1行に辿り着くまでに、「監視の粒度」と「バンドルの副作用」という、なかなか他では味わえない実践知が詰まっていた。

Aikido Zen を Next.js で使う人。もし AI Usage が空なら、serverExternalPackages を疑ってみて。

それと——トークン消費量、無料で可視化できるの、ほんとに便利です。

キーワード

  • Aikido
  • Next.js
  • セキュリティ
A

Aikido Community Japan

公開日: 2026/5/29

最終更新: 2026/5/29

シェア:

ニュースを購読

セキュリティの最新情報をお届けします。

Aikido Community Japan からのお知らせの受信に同意します。詳しくはプライバシーポリシーをご確認ください。