Chrome AppsでSentryを組み込んでみた時の話

僕はChrome OS(Chromebook/boxに搭載されているOS)向けに、いくつかのChrome Appsをリリースしています。そのほとんどは、Chrome OSにてリモートにあるファイルシステムを、あたかもローカルにあるようにマウントすることができるchrome.fileSystemProvider APIの実装です。Dropbox、SFTP、OneDrive、SMB、WebDAVの4つの実装を作りました。その中で、最もユーザ数の多い実装が、Dropboxです。

File System for Dropbox

少し前に、DropboxのAPIはVersion 1が廃止の方向となり、Version 2に移行することを余儀なくされました。Dropbox向けの実装はユーザ数が当時は15万人を突破していて、他の開発者による別の実装があるわけでもないので、使えなくなってしまうと単純に15万人以上の方々が困ってしまう状況になります。しばらく安定稼働していたので放置していたのですが、Dropbox API Version 2への移行作業を行い、バージョンアップしました。

Migrate Dropbox API from v1 to v2. by yoichiro · Pull Request #70 · yoichiro/chromeos-filesystem-dropbox

しかし、上記の差分をリリースしてから、ユーザ数が急落します。数日間で一気に3万人以上が減りました。これはおかしい。なぜかSFTPなどの別の実装も減っていたのですが、それにしてはDropbox実装の減り具合だけ激しく、Migrationに失敗したかも?と不安になりました。

sentry_5.png

chrome.fileSystemProvider APIの実装は、基本的にユーザのChrome OS側で動作が完結します。つまり、ユーザ数の減り具合はわかりますが、実際にユーザ側で何が起きているのか、そのとき僕は知る術を何も持っていなかったんです。Dropbox側の開発者ページを見ても、各種Statisticsは見れますが、エラーに関する情報は何一つ提供されていません。Chrome Appsにクラッシュレポートのような機能はありません。つまり、自分で作り込まなければ、ユーザ側で何が起きているのか、知ることができないのです。

そして、ユーザからのフィードバックもいくつか届き始めました。そのほとんどは、マウントした直後にエラーになって使えなくなる、というものでした。これはマズい。早急に何が起きているのかを把握して、手を打たなければなりません。

Increments社に在籍していた時に、Sentryを使ってエラーレポートを見ていました。基本的に有料サービス(自分でサーバ立てれば無償だけど大量リクエストを捌くための環境準備が必要になってどっちにしてもお金がかかる)ですが、最初の無料枠で何とかなるかな、と思い、Sentryの組み込みにチャレンジすることにしました。

raven.jsの読み込みと初期化

Dropbox実装ではbowerを使っているので、bower.jsonファイルに以下を追記して、bower installしてraven.jsが使えるようにします。

"raven-js": "^3.16.1"

そして、raven.jsをChrome AppsのBackground Scriptとして読み込みます。

"app": {
  "background": {
    "scripts": [
      ...
      "bower_components/raven-js/dist/raven.js"
      ]
  }
}

Chrome Appsの起動時に、raven.jsの初期化を行います。初期化の際には、Sentryで作ったプロジェクトにデータを送るための送信先URLを指定します。

Raven.config('https://8f30bd158dea44d2ad5dbce094b67274@sentry.io/189250').install();

とても簡単です。この初期化の時点で、Chrome Appsのバージョン番号を以下のように指定しておくこともできます。

Raven.config('https://8f30bd158dea44d2ad5dbce094b67274@sentry.io/189250', {
  release: chrome.runtime.getManifest().version
}).install();

これにより、Sentry側に以下のような表示がされます。Releaseごとに、発生したエラーを見ることができるようになります。

sentry_1.png

エラー発生時のメッセージ送信

Dropbox実装において、Sentryに送りたくなるタイミングは、Dropbox API Serverから期待したレスポンスが来なかった時です。jQueryの$.ajax()関数を使っているので、そのタイミングは以下のような感じになります。

$.ajax(...).done(function(result) {
  // Do something...
}).fail(function(error, textStatus, errorThrown) {
  // ここ!
});

Sentryに送り込む時は、まずraven.jsの初期化が完了していることを確認します。そして、Raven.captureMessage()関数を使って、メッセージを送り込みます。

var message = "..."; // エラーメッセージ
if (Raven.isSetup()) {
  Raven.captureMessage(new Error(message), {
    extra: {
      foo: bar
    },
    tags: {
      funcname: "readFile"
    }
  });
}

例えば、messageに”readFile - 400”と指定していれば、Sentry上に以下のようなIssue登録がされます。

sentry_2.png

そして、extraには、任意の情報を入れることができます。そのエラーが発生した原因を見つけるために役立つ情報、例えばAjaxでのリクエストの内容などを入れるとかすると良いでしょう。Sentry上では、extraで指定した情報は、ADDITIONAL DATAとして表示されます。以下の図を見ると、dataとして指定されたJSON文字列が壊れている(pathプロパティに値がない)ために、Dropbox API Serverが400を返したであろうことがわかります。

sentry_3.png

tagsは、captureMessage()関数で送信される情報に目印をつけるためのものです。Sentry上で検索条件に使うことができます。

sentry_4.png

まとめ

Sentryを今回組み込んでみて、なんで今まで組み込んでなかったんだろう、と本当に後悔してしまうほど、簡単に組み込むことができました。そして、トラブルシューティングに活かすことができる貴重な情報を得ることができ、その結果Chrome Appsの安定稼働率を向上させることに成功しました。

具体的には、以下の致命的な状況を回避することに成功しています。

  • Dropbox API Version 2から、単位時間あたりのAPI呼び出し回数に制限が入りました。ユーザがDropbox内に多量のファイルやディレクトリがフラットに存在していた時に、一気に情報取得のAPIが呼び出され、結果としてDropbox API Serverは429を返していました。これに気づき、Retry-Afterレスポンスヘッダを使った再試行処理を実装することで、この問題を回避しました。
  • Dropboxからファイルの中身を読み出す時に、pathの値が指定されていない場合があることに気がつきました。特に、一度Dropbox実装のBackground Scriptが無効化されて、そこからResumeした直後にonReadFileRequestedイベントハンドラが呼び出されていた時に発生していることがわかりました。pathの値を自前管理でメモリに持っていたのが原因で、chrome.fileSystemProvider.get()関数からpathの値を取得するように変更することで、この問題を回避しました。

Sentryが適用可能なシーンは本当に無限だと思います。ユーザからフィードバックが来たのに何が起きているか分からない、と困っている方は、積極的にSentryを組み込んで見れば良いと思います。

このエントリーをはてなブックマークに追加

Yoichiro

(よういちろう)