サイトは200を返したまま「計測だけ」死ぬ — 測定タグの生死をHTTPステータスで判断してはいけない

  • #GA4
  • #計測
  • #監視
  • #番犬
  • #運用
サイトは200を返したまま「計測だけ」死ぬ — 測定タグの生死をHTTPステータスで判断してはいけない

今回やったこと

自社の複数サイトで GA4 計測の断絶が同時発覚した。いずれもサイト自体は正常に HTTP 200 を返しており、通常の死活監視では一切捕まらないタイプの障害だった。「計測そのものを監視する」番犬(scripts/measurement_watchdog.py)を作って日次で走らせるようにした記録。

2件同時発覚した事故

事故 A(約28日間の未計測): あるサイトを Astro に移行した際、新しい Layout コンポーネントに GA4 タグを実装するのを忘れた。サイトは問題なく動いていたが、その間の全トラフィックが GA4 に記録されていなかった。移行が 2026-06-27 で発覚が 2026-07-26 だったため、約 1 ヶ月分のデータが欠落している。

事故 B(計測ID の差し替え残留): 別のサイトで Google 関連のプラグインを整理した際、「2つのタグが二重出力されていた」状態を修正しようとして、生きている側 のタグを消してしまった。残ったのは Cocoon の設定に残っていた古い測定 ID で、この ID に対応する gtag/js は HTTP 404 を返す。サイト自体のトラフィックは平常で、GA4 だけが収集を停止していた。

どちらの事故も共通しているのは、サイトが元気に 200 を返し続けるという点だ。通常の死活監視(サイトが 200 を返すか)では絶対に捕まらない。

なぜ HTTP 200 で判断できないか

測定 ID の生死を確認するために https://www.googletagmanager.com/gtag/js?id=<ID> を叩く方法が思い浮かぶが、これも HTTP ステータスだけでは不十分だ。Google は存在しない測定 ID に対しても HTTP 200 で汎用スタブを返す

実測(2026-07-26)したところ、レスポンスのサイズに明確な差がある:

状態HTTPサイズの目安
実在する ID200約 445,000〜490,000 B(プロパティ設定が埋め込まれる)
存在しない ID200約 416,000 B(汎用スタブ)
削除/無効化された ID404約 1,600 B(Google のエラーページ)

番犬はスタブのサイズを毎回実測して基準値を動的に取得し、「実在 ID はスタブより十分大きいか」で判定する。定数で閾値を固定すると Google がサイズを更新したときに壊れるため、毎回引き直す設計にした。

3層で検査する

何を見るか捕まえる事故
tag_on_site公開 HTML に期待する測定 ID が載っているかタグの消失・別 ID への差し替え(事故 A/B 型)
id_alivegtag/js?id=<ID> が実在 ID のレスポンスを返すか削除/無効化された死んだ ID(事故 B 型)
data_flowingGA4 に直近データが届いているか①②が通っても実際に収集されていない場合

3層すべてを通さないと、上の2件を同時に捕まえることはできない。事故 A は①で捕まる(HTML に ID がない)。事故 B は②で捕まる(ID が 404)。①②が通っていても実際にデータが送れていないケースが ③ で拾える。

③ データの到達確認は「0なら異常」とは判定しない

直近の N 日間のセッション数が 0 だったとしても、それが「異常」かどうかは媒体の規模に依存する。1 日あたり 1.6 セッション程度の低トラフィック媒体では、2 日間が 0 件になることは確率的にそこそこあり得る。この水準で FAIL を出し続けると番犬が「狼少年」になり、本物の断絶が来たときに無視される。

そこで GA4 Reporting API で過去 30 日間のセッション数を基準値として取り、直近 2 日間の期待値を推定する。期待値 10 件以上で実績 0 なら FAIL(偶然が約 4.5e-5 の確率 = セッション数がポアソン分布に従うと仮定した場合の e^{-10})、期待値 3〜10 は WARN(記録には残すが人を起こさない)、期待値 3 未満は低トラフィックとして判定を保留する。

実行機の回線が落ちているときは「判定しない」

番犬は外部(Google のサーバ)に到達できることが前提だ。実行機がネットワーク障害から復帰した直後に launchd が発火すると、全サイトへの疎通が一瞬失敗して「全 6 媒体で計測異常」という誤報が出る(2026-07-27 に実際に発生)。

番犬が誤報を出し続けると通知が無視されるようになり、本物の断絶が見逃される。そのため、検査の前に www.googletagmanager.comanalyticsdata.googleapis.com の 2 ホストに最大 10 分間接続を試みて、それでも届かない場合は 判定を出さずに保留 する。「今日は測れなかった」という終了コード 2 を返し、Discord には「判定を保留(計測異常ではない)」のメッセージを送る。

使い方

# 全媒体・人間向け出力(結果確認)
python3 scripts/measurement_watchdog.py

# 特定媒体のみ確認
python3 scripts/measurement_watchdog.py --brand <site>

# 異常時のみ Discord 通知(日次 launchd から呼ぶ形式)
python3 scripts/measurement_watchdog.py --notify

# 結果を JSON で保存
python3 scripts/measurement_watchdog.py --json /tmp/watchdog_result.json

終了コードは 0(全媒体 OK)、1(1件以上異常)、2(回線障害で判定保留)の 3 値になっている。

試した環境

  • Python 3.12
  • requests ライブラリ(HTTP 取得)
  • google-analytics-data ライブラリ(GA4 Reporting API)
  • macOS launchd(日次 09:XX に自動実行)
  • 確認日: 2026-07-26〜27

更新履歴

  • 2026-07-27: 初稿(2026-07-26 の2件同時発覚を受けて作成)