[JavaScript] ウェブシステムのランタイムエラーを集積する仕組み作り

概要

大まかにやる事は以下の通り。

  1. 集積用のデータベースを用意する
  2. 共通ページにエラー集積スクリプトを仕込む
  3. 訪問者の操作でエラーが発生するのを待つ

集積用データベースを用意する

このあたりは環境に依ると思う。単なるランタイムエラーと言えど外部にデータを投げられない制約があれば自前のサーバに蓄積すれば良いし、今は有償・無償どれでも選択肢はあるので、制約や利便性を考えて組み合わせると良い。

個人的に JavaScript でそのままデータを DB に投げたかったので(JS から情報を受け取るバックエンドを実装するのは不毛だと感じた)、情報や運用実績のあったスプレッドシートかトレジャーデータが選択肢に挙がった。

思いついた選択肢:

  • 自前のサーバ
  • Heroku
  • Google スプレッドシート
  • Firebase
  • トレジャーデータ(有償)

元々、別の情報集積にトレジャーデータを使用していた事もあり、トレジャーデータに情報を蓄積して、そこからスプレッドシートに可視化する方が良さそうだったので、トレジャーデータに情報を蓄積する方法を選択した。(可視化については、また別の機会で)

トレジャーデータを JS だけで利用する

トレジャーデータは、リファレンスの更新頻度も高く、SDK のソースなども GitHub に公開しているので、開発者には使い勝手が良いように感じた。
Treasure Data JavaScript SDK | Treasure Data

使い方もシンプルで以下のようにインスタンスを作ってtrackEventを実行するだけで OK だった。

var td = new Treasure({
  host: "in.treasuredata.com",
  writeKey: "YOUR_WRITE_ONLY_APIKEY_IS_HERE",
  database: "DATABASE_NAME",
});
var data = "送信するデータ";

td.trackEvent("hogehoge", data);

エラー集積スクリプトを仕込む

JS のランタイムエラーは、windowerrorイベントをハンドリングすればいい。

大まかな実装は以下の通り。

window.addEventListener("error", function (e) {
  // エラー内容を処理
  // ...
  // データを送信する処理
  // ...
});

参考: Error - JavaScript | MDN


仕様と実装

大まかには先述の実装で良い。

システム要件として IE 対応は IE11 だけだったので、レガシー IE は除外したい(そもそもレガシー IE だとエラー出る)。また、bot が発するエラーを集積してもあまり旨味はなさそうなので出来る限り排除したい。

window.addEventListener("error", function (e) {
  var message = e.message;
  var fileName = e.filename;
  var lineNumber = e.lineno;
  var url = location.href;
  var userAgent = navigator.userAgent;

  // 特定のブラウザはさよなら
  if (is_bot(userAgent.toLowerCase()) || is_legacyIE(userAgent.toLowerCase())) {
    return;
  }

  // データ送信処理
  treasure_data.trackEvent("runtime_error", {
    message: message,
    fileName: fileName,
    lineNumber: lineNumber,
    url: url,
    userAgent: userAgent,
  });
});

function is_bot(userAgent) {
  var botPattern =
    "(googlebot/|Googlebot-Mobile|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)";
  var reg = new RegExp(botPattern, "i");
  return reg.test(userAgent);
}

function is_legacyIE(userAgent) {
  return userAgent.indexOf("msie") >= 0;
}
  1. エラーをキャッチ
  2. 送信するデータを処理
  3. DB へ送信

実際の運用コードは少し違うが、これで運用は回せていけている。

Google Tag Manager を使って配信

前述の集積用コードの配信は、Google Tag Managerを利用した。

採用理由は、単純に Google Tag Manager でタグ配信を既に行っていた為、合わせてしまうのが得策だったからである。発火トリガーや配信開始・停止が通常のリリースと比べて柔軟に行える点はかなり利点であった。

しばらく運用してみて…

フロントエンド関連のエラーは検知が難しいケースが多い。単純な実装ミスであれば、静的解析・レビュー・テストなどで表面化させる事ができる。ただ、エンドユーザの特異な環境や操作で発生するエラーを検知するに対しては、こうした仕組みづくりが必要不可欠となってくる。

今回の施策によって、単純なエラー集積だけではなく、UI 面やバックエンド側の問題も浮き彫りにする事が出来た。また、エンドユーザの環境で発生するエラーを事前に検知する事で、顧客への問い合わせを未然に防ぐ事ができたことは運用面でも精神衛生上でも効果をもたらした。品質向上にも繋がるので大きなウェブシステムを運用している場合はエラー集積は入れておくと良い。