サイトは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 | サイズの目安 |
|---|---|---|
| 実在する ID | 200 | 約 445,000〜490,000 B(プロパティ設定が埋め込まれる) |
| 存在しない ID | 200 | 約 416,000 B(汎用スタブ) |
| 削除/無効化された ID | 404 | 約 1,600 B(Google のエラーページ) |
番犬はスタブのサイズを毎回実測して基準値を動的に取得し、「実在 ID はスタブより十分大きいか」で判定する。定数で閾値を固定すると Google がサイズを更新したときに壊れるため、毎回引き直す設計にした。
3層で検査する
| 層 | 何を見るか | 捕まえる事故 |
|---|---|---|
① tag_on_site | 公開 HTML に期待する測定 ID が載っているか | タグの消失・別 ID への差し替え(事故 A/B 型) |
② id_alive | gtag/js?id=<ID> が実在 ID のレスポンスを返すか | 削除/無効化された死んだ ID(事故 B 型) |
③ data_flowing | GA4 に直近データが届いているか | ①②が通っても実際に収集されていない場合 |
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.com と analyticsdata.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件同時発覚を受けて作成)