共通行動ログフォーマットを定義してWebアプリケーションの行動解析が捗る話

こんにちは、浅井です。この写真は海鮮丼です。とても美味しそうですね。

それはさておき、弊社では様々なWebサービスを運営しています。Javaで書かれているものもあればPHPで書かれているものもあります。が、当然ユーザー行動の集計・解析にあたっては共通の行動ログフォーマットを定めています。当然ですね。
共通ログフォーマットと言えば、NCSAログフォーマットとかCLFとかW3C拡張ログフォーマットがあります。いわゆるApacheログです。こんなやつ。
0.0.0.0 - - [18/Apr/2013:12:40:03 +0900] "GET /mode/pics/detail/eXB5c HTTP/1.1" 200 4334 "http://coordisnap.com/mode/mypage/detail/eXB5c" "Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_3 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10B329 Safari/8536.25" 215425 0.0.0.0 - - [18/Apr/2013:12:40:05 +0900] "GET /mode/list/index/%EF%BE%81%EF%BD%AA%EF%BD%AF%EF%BD%B8%E6%9F%84%EF%BE%8A%EF%BE%9F%EF%BE%9D%EF%BE%82%C2%A0%EF%BE%92%EF%BE%9D%EF%BD%BD%EF%BE%9E HTTP/1.1" 200 3275 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" 160586
もともと社内でUrchinが使われていたこともあり、主要なWebアプリケーションではこの形式でログを出力するようになっています。(Urchinがこの形式をベースに解析するため)
自前のアクセス解析の仕組みも、最初はこの形式をカスタマイズして利用していました(そのままUrchinでも利用できるように)。しかし、この形式にはいろいろと問題があります。
- 1アクセス1行に制限される
- IPやUAが記録されていて扱いにくい
- ユニークユーザーを識別できない
- アプリケーションレイヤの情報をログに出しにくい
- 拡張しにくい
- パースしにくい
5, 6は言うまでもないのですが、頑張って拡張を施すと更にパースが苦しくなります。
3, 4はアプリケーションレイヤでログを出力していればやりやすいのですが、ミドルウェアレイヤ(ApacheやJettyなど)に出力を任せていると苦戦します。
1もアプリケーションレイヤで出力していれば都合がつきますが、ミドルウェアに1アクセスで複数行のログを出力させるのは面倒です。また、UrchinでのPV集計に影響を及ぼしてしまうという問題もあります。
2も問題です。非常に扱いづらい。社内で保持しておくぶんにはまだ(アクセス権など気を配れば)良いのですが、外部の解析サービスを使おうとすると気軽にはpostできません。ナマのIPやUAはアクセス解析には不要なので、ハッシュにするなりマスクするなりしてから扱うことになります。(めんどくさい)
独自フォーマットの定義
Apacheログの問題を解決するため独自フォーマットを定義して、Apacheログ形式とは別にアプリケーションレイヤから出力しています。
パースの楽さを考えてTSVにし、項目の並びを固定化しています。今にして思えば構造化すればよかったな・・・とも思いますが。ちなみに、Urchinはメンテされなくなったため、弊社でも利用されなくなりました。
項目は下記の通り。
- 日時
- ユーザー識別子
- ユーザーログイン情報
- ユーザー属性情報
- 国
- デバイス種別
- ネットワーク種別
- OS種別
- ブラウザ種別
- 行動識別子
- 行動に付随する内容
タブ区切りでログに出すとこんな感じになります。
2013-04-17 13:33:18.985 1366173198 5737426 - - newbie JP s au Android - requested mode/coordisnap.com/mode/list/index 2013-04-17 13:33:18.986 1366173198 5737426 - - newbie JP s au Android - landing mode/coordisnap.com/mode/list/index http://www.google.co.jp/search?hl=ja&redir_esc=&client=ms-android-sonyericsson&source=android-launcher-widget&v=133247963&qsubts=1366173182643&action=devloc&q=%E7%B7%91%E3%82%AB%E3%83%BC%E3%83%87%E3%82%A3%E3%82%AC%E3%83%B3&v=133247963 2013-04-17 13:33:18.986 1366173198 5737426 - - newbie JP s au Android - first_access - 2013-04-17 13:33:19.206 1366173199 5737426 - - newbie JP s au Android - search_pics カーディガン 緑 2013-04-17 13:33:19.207 1366173199 5737426 - - newbie JP s au Android - search_from google:::緑カーディガン:::カーディガン 緑 2013-04-17 13:33:19.254 1366173199 5737426 - - newbie JP s au Android - request_done 315577 mode/coordisnap.com/mode/list/index
項目を固定してしまうと拡張に弱くなるわけですが、1アクセス複数行を許容することで横ではなく縦の拡張に持って行くようにしています。
例えば上の例では、ブラウザからの1アクセスに対して以下のような情報を記録しています。
- list/indexページにアクセスした
- 初回アクセスである
- Google検索から来た
- Googleで「緑カーディガン」と検索してた
- サイト内の「カーディガン 緑」という検索ワードの結果ページを見た
- 315577μsecかかった
この他にも、ログインした、登録した、投稿した、などを必要に応じて異なる行として出力します。まさに行動ログですね。
ここ1年ほど、この行動ログフォーマットに沿ったログを実際に複数のWebアプリケーションで出力しています。
集計
自前の集計には従来からApache Pigを使っています。たとえばPV, UU, ログインUUの集計は以下のような感じ。
(gistさん、pig対応してませんね・・・)
入力にデフォルトで使われるPigStorageがデフォルトでタブ区切りを想定しているので、このあたりシンプルになります。ここが拡張Apacheログフォーマットだと、REGEX_EXTRACT_ALLを使って見苦しい正規表現を書くことになってしまいます。
また、Apacheログとは異なりユーザーを識別する情報を持っているため、ユーザーの行動を追跡することができるようになります。これにより、ユニークユーザー数や離脱率、再訪問率の集計ができたり、初回訪問から次の訪問までの行動パターンの統計をとるなどもでき、アプリケーションの改善に活かすことができます。
まとめ
Apacheログを拡張して頑張るのをやめて、独自かつ複数のアプリケーションで共通の行動ログを出力して集計解析で楽をしたよ、というお話でした。今回は以上です。Cloudera某氏に「Pigのブログ書け」と言われ続けているので、そのうちもうちょっと書きたいと思います。では。