モバイルファースト室の @rejasupotaroです。
クックパッドでは、サービスをリリースしてログを収集して分析して改善してまたリリースして、というサイクルを素早く回すことでより良いものを作るということをウェブではやってきました。 クックパッドのサービス開発のフレームワークをモバイルアプリでも適用したいのですが、モバイルアプリにはウェブアプリと違ったロギングの難しさがあります。
今回はモバイルアプリのロギングの問題点とPureeというログ収集ライブラリについて話します。
モバイルアプリのロギングの難しさ
ウェブアプリでは、基本的にはサーバー側でログを収集することができますが、モバイルアプリの場合は画面の制御はアプリ側で行われ、APIを介してデータを受け取るため、クライアント側でログを収集して送信する必要があります。
アプリのログを収集するのに、画面遷移をしたりタップするたびにサーバーにログを送るということも考えられますが、パフォーマンスやデータ使用量やバッテリーのことを考えるとあまり現実的ではありません。
また、モバイルアプリは動的に接続状況が変わるので送信に失敗したログをケアしなければなりませんし、そのためにはログをバッファリングする必要があります。
そこでクックパッドではPureeというライブラリを作って、モバイルアプリからはPureeを介してログを送信しています。
ログ収集ライブラリPureeについて
Pureeは以下の機能を備えています。
- フィルタリング: 共通のパラメータを付与したり、サンプリングを行ったりすることができます。
- バッファリング: ログを一時的に貯めて、送信に失敗したときや端末の再起動などでログが消えないようにする役割を持っています。
- バッチ: 複数のログを一つにまとめて送ることができます。
- リトライ: ログの送信に失敗した場合は、一定時間経過後に自動で再送信を試みます。
また、Pureeはアウトプットプラグインという形でログの送信先の切り替えたり、フィルターを適用したりすることができます。
これらの実装は Fluentdを参考にしています。
Pureeの使い方
ログを送る
例として、タップのログを定義します。 ログはJsonConvertibleクラスを継承します。
publicclass ClickLog extends JsonConvertible { @SerializedName("page") private String page; @SerializedName("label") private String label; public ClickLog(String page, String label) { this.page = page; this.label = label; } }
ログを定義したら任意の箇所で Puree.send
メソッドにログと送り先を渡します。
Puree.send(new ClickLog("MainActivity", "track"), OutLogcat.TYPE);
アウトプットプラグインを定義する
アウトプットプラグインにはPureeOutputとPureeBufferedOutputの二種類があります。 PureeOutputはバッファリングは行わず、ただちにログの出力を行います。Google AnalyticsやMixpanelのようなライブラリ内にバッファのしくみがあるようなものに出力する場合はこちらを使います。
例えば、ログをLogcatに出力するプラグインは下のようになります。
publicclass OutLogcat extends PureeOutput { publicstaticfinal String TYPE = "logcat"; @Overridepublic String type() { return TYPE; } @Overridepublic OutputConfiguration configure(OutputConfiguration conf) { return conf; } @Overridepublicvoid emit(JSONObject jsonLog) { Log.d(TYPE, jsonLog.toString()); } }
PureeBufferedOutputを継承すると、バッファリングとリトライを自動で行うようになります。
デフォルトのインターバルは2分になっていて、現状ではログの送信に失敗すると baseInterval * (retryCount + 1)
後に再送信を試みます。また、デフォルトの最大リトライ回数は5回に設定されていて、5回連続で失敗した場合は次回のイベント発火時に送られます。
一度に送るログの量も制限することができます。
publicclass OutBufferedLogcat extends PureeBufferedOutput { publicstaticfinal String TYPE = "buffered_logcat"; @Overridepublic String type() { return TYPE; } @Overridepublic OutputConfiguration configure(OutputConfiguration conf) { return conf; } @Overridepublicvoid emit(JSONArray jsonLogs, AsyncResult asyncResult) { Log.d(TYPE, jsonLogs.toString()); asyncResult.success(); } }
emitメソッドに出力するログが入ってくるので、そこでGoogle AnalyticsやMixpanelに送ったり、APIを叩いたりします。
実際にPureeBufferedOutputを使う場合には非同期で結果を受け取ることになると思うので、ログの送信に成功したか失敗したかを知らせるためにコールバックで asyncResult#success
か asyncResult#fail
を呼ぶ必要があります。
@Overridepublicvoid emit(JSONArray jsonLogs, final AsyncResult asyncResult) { client.sendLogs(jsonLogs, new Callback() { @Overridepublicvoid success() { asyncResult.success(); } @Overridepublicvoid fail() { asyncResult.fail(); } }); }
フィルターの定義
Pureeのsendが呼ばれたあとに、対応したフィルターのapplyメソッドが呼ばれます。 そこでログに共通するパラメータを付けたり、特定の条件のときには送らないようにするなどができます。 たとえばイベントの起こった時間を付与するフィルターは下のようになります。
publicclass AddEventTimeFilter implements PureeFilter { public JSONObject apply(JSONObject jsonLog) throws JSONException { jsonLog.put("event_time", DateUtils.getTimestamp()); return jsonLog; } }
Pureeの初期化
Pureeでログを送るには前もってプラグインを登録する必要があります。 初期化はApplicationクラスの中などで行います。
publicclass MyApplication extends Application { @Overridepublicvoid onCreate() { Puree.initialize(buildConf(context)); } publicstatic PureeConfiguration buildConf(Context context) { PureeFilter addEventTimeFilter = new AddEventTimeFilter(); returnnew PureeConfiguration.Builder(context) .registerOutput(new OutLogcat(), addEventTimeFilter) .registerOutput(new OutBufferedLogcat(), addEventTimeFilter) .registerOutput(new OutDisplay(), new SamplingFilter(0.5F)) .registerOutput(new OutBufferedDisplay()) .build(); }
第一引数にアウトプットプラグインを渡して、第二引数以降にオプションでフィルターを渡します。
Pureeの導入方法
Pureeはjcenterで公開していますので、build.gradleのrepositoriesにjcenterを追加した上で、dependenciesにpureeを追加してください。
// build.gradle buildscript { repositories { jcenter() } ... // app/build.gradle compile 'com.cookpad:puree:1.0.0'
ソースコードは GitHubで公開しています。
まとめ
Pureeを作る前はログの出力先が増えたり、プロジェクトが変わったりするたびに独自にログを収集するしくみを作っていました。 ログはサービスを成長させるためには重要です。Pureeを導入することでログを収集するしくみを実装する時間を短縮して、その分「どのログを取るか」「どうやってログを活かすか」を考える時間に使えればいいなと思います。
Pureeは今のところはAndroid版だけを公開していますが、iOS版の開発も進んでいるので、近いうちに公開されると思います。