現代的なWebサービスを構築する場合、避けては通れないのが各種ログの収集と活用です。 HTTPサーバの各種ログ (e.g. アクセスログ、エラーログ) を始めとして、サーバサイドのアプリケーションログなどもよく収集されています。 サーバサイドのログについては、fluentdなど、収集するための手法はある程度確立しているように思いますが、フロントエンドでのログ収集については、あまり議論されていないように思います。
先日、お仕事でWebサービスを作成したのですが、そのときに改めてブラウザ上でのエラーログの収集について本気出して考えてみたので、ここに記事としてまとめてみようと思います。
ブラウザ上のエラーログ収集の目的
まず収集する目的についてですが、大きなところで以下のようになるかと思います。
- 継続的な不具合修正のため (サービス改善)
- ユーザからの問い合わせ時に調査するため (サポート改善)
特に強調したいのは、サポートの改善という側面です。 Webサービスを継続的に運用していく場合、どうしてもサポートに時間を割かざるを得ません。
サポート対象外のブラウザでサービスを利用された場合でも、『動かない』という問い合わせを受ける可能性は大いにあります。 このようなケースでは、以下のフローで問い合わせ対応を行うでしょう。
- ユーザからの問い合わせを受ける
- サポートから利用ブラウザとバージョンについて、ユーザに質問する
- ユーザからの回答を得、サポート対象外である旨を伝える
しかし、ユーザのITリテラシによっては、利用しているブラウザとバージョンをサポートが知るために、あれこれと受け答えしなければならないことも多くあります。 この手間が、そのままサポートのための費用に直結し、さらにはエンジニアを疲弊させることにも繋がります。
他にも、本ケースにおいては、サポート対象のブラウザを利用しているにも関わらず、不具合が発生している可能性もあります。 この場合、再現性のあるものであれば良いですが、必ずしもそうとも限りません。 もしそのときのエラーログがあれば、かなり調査の助けになるのではないでしょうか。 他、IEの互換モードを利用していたために発生した不具合だとか、考えるとキリがありません。
結果的に、可能な限り多くのブラウザ環境において、可能な限り確実にログを収集したいという欲求が生まれます。 これが収集の目的となります。
手法
ここでは、色々考えた中から、なるべく目的に沿う形で2つの手法について考察します。 どちらの場合でも、サーバサイドではログを受け付けるREST APIのエンドポイントが存在する前提とします。
XMLHttpRequestを用いる手法
Ajaxで有名なXMLHttpRequestを利用する方法です。 モダンブラウザにおいては、XMLHttpRequestの仕様は共通化されてきているため、比較的多くのブラウザで簡単に利用することが可能です。 RESTful APIのお作法に準じるような、HTTP POSTによってエンドポイントにログを投げつけることができます。
利点
他のREST APIを呼ぶときと同様の実装をすることができ、全体として統一感を持たせられます。
欠点
ブラウザごとの実装差異をどうにかする必要があります。 また、古めのブラウザではまともに動作しません。
IMGタグを用いる手法
昔から存在するIMGタグのsrc属性を利用する方法です。 私は今回、この方法が一番ベターだと判断しました。 IE5互換モードでも安定して動作したため、ほとんどのブラウザで動くのではないでしょうか。
new Image().src= loggingEndpoint + '?' + encodeURIComponent(message);
利点
かなり広範なブラウザで安定して動作します。 また、実装もかなり単純です。
欠点
ブラウザによって、URLの長さに制約があることが欠点として挙げられます。 またHTTP GETでログを投げることになるため、キャッシュ切りをする必要があるかもしれません。
実環境への適用
実際にログを投げつける方法にどれを選んだとしても、JavaScript上でエラーが発生したことを補足しなければ、永遠にログを収集することはできません。
エラーを捕捉するための基本的な戦略としては、 window.onerror
や window.addEventListener
を用いるようにします。
JavaScriptはどうしてもページ単位でグローバルな領域であれこれする必要があるため、エラーイベントのハンドリングにも気を遣う必要が出てきます。
比較的最近のブラウザであれば、 window.addEventListener('error', function(evt){})
によって特に考慮なくエラーを捕捉することができます。
しかし古めのブラウザになると、 window.addEventListener
は実装されていないため、 window.onerror
を利用する必要がありますが、これが結構曲者です。
window.onerror
は、ただ1つの関数しか持つことができず、意図せず上書きしてしまうとアプリケーションコードを破壊してしまいます。
そのため、以下のフローにより上書きを行います。
window.onerror
に関数が登録されているかをチェック- 登録されていれば変数に退避させる
window.onerror
に関数を登録する- 退避させた関数を、今回登録した関数内で呼ぶ
具体的には、以下のような実装となるはずです。
var myErrorHandler= function(message, url, ex){ /* ... */ };
if(window.addEventListener)
{
window.addEventListener('error', function(evt){
myErrorHandler.call(this, evt.message, evt.filename, evt);
}, false);
}
else
{
var next= window.onerror || function(){};
window.onerror= function(){
myErrorHandler.apply(this, arguments);
next.apply(this, arguments);
};
}
まとめ
JavaScriptのエラーログを収集する目的について述べ、実現するための2通りの手法について比較しました。 改めて考えると、本記事の文脈においてはXMLHttpRequestを使う意義は薄いを言わざるを得ず、IMGタグによる収集しかないかな、と思います。
実際、ブラウザのエラーログを収集して損はないと思うので、今後Webサービスを作るのであれば必ず実装したほうが良いと思います。 ログを収集しておけば、サポート対応時にアクセス時刻さえ聞けば問題の切り分けが大分捗ると思うので、開発時に多少手間でも実装したほうが後が楽です。