JavaScript
Rails
keen.io

Keen IOによるやさしいユーザー行動ログ収集

この記事はCrowdWorks Advent Calendar 2017の19日目の記事です。

ユーザー行動ログの収集にKeen IOというサービスがとても便利だったため、今回はこのサービスについて選定の理由と簡単な使い方について紹介していきます。

行動ログの収集の目的

ひとことでいえば、「ユーザーの嗜好を汲み取ってサービスを改善するため」です。

行動ログを分析することによって、ユーザーからのフィードバックを得ることができます。この行動ログによるフィードバックは、直接的なフィードバックよりも取得できるデータの量が多く幅も広いのがメリットです。

また、直接的なフィードバックでは「声の大きい人の意見の印象が強くなる」や「背景に真の目的があるにも関わらずフィードバックをそのまま受け取ってしまう」といったバイアスに注意する必要がありますが、行動ログではデータという事実から分析することができます。

CrowdWorksではこの行動ログを用い、推薦アルゴリズムによって提示した内容をチューニングし、マッチングのユーザー体験を改善していこうとしています。マッチングに関するCrowdWorksの取り組みについては、次の記事に記載されていますのでご覧ください。

もちろん行動ログだけに頼るのではなく、状況に応じて直接的なフィードバックを踏まえながらバランスよくサービス改善に役立てることが重要です。

サービスの選定

行動ログはページビュー数以上のデータ量になるため、それに耐えうるインフラを設計してメンテナンスしなければなりません。そのためのコストを考えると内製は避け、外部サービスを検討することにしました。

検討は主に「16 best alternatives to Mixpanel as of 2017 - Slant」に挙がっているサービスを対象にしました。追加でSegmentというサービスも検討しました。

選定基準

検討にあたって以下の基準を設けました。

必要な言語に対応したSDKが存在すること

CrowdWorksはWebアプリケーション(Rails)だけでなく、スマートフォン向けのアプリケーション(iOS / Android)も提供しています。これらのプラットフォームに対応するために、JavaScriptとRuby、Swift、JavaのSDKが欲しいです。

外部データストアへのストリーミングができること

その外部サービスにだけでなく、信頼性のあるS3やBigQueryなどにも永続化しておきたいです。CrowdWorks本体のデータはRedshiftに同期しており、そのデータと組み合わせて分析したいため、同じAWSサービスであるS3の方が望ましいです。

また、自前で定期的にエクスポートなんてことはしたくないため、ストリーミングにも対応していて欲しいです。

価格が妥当であること

既にGoogle Analyticsによってページビューは分かるため、それに対して取得したいイベントの種類の数を掛け算してイベント数を見積もります。サービスによってはイベント数ではなく、月次トラッキングユーザー数で料金計算する場合があります。

公式ドキュメントが充実していること

これは実際にSDKを利用するときも重要なのですが、それ以前に上記の条件を満たしているのかを調べるにあたっても大事な要素になります。

Keen IOの選定理由

Keen IOは上記の条件に加え、必要な機能を備えつつ不要な機能が少なく、シンプルでわかりやすいという特徴があります。

他のサービスは分析までサービス内で完結できるようにビジュアライゼーションの機能が充実しているものが多いように見受けられました。今回の我々のニーズは分析を既存のRedashの基盤に統一したかったため、サービス側の豊富なビジュアライゼーション機能は不要でした。

それに対し、Keen IOはビジュアライゼーションの機能はシンプルなのに対し、Web Auto-Collection(後述)といったデータ収集に便利な機能を備えています。

また、Keen IOがドキュメントがとてもわかりやすいです。他のサービスは何ができるかを探すために読み解くのが結構大変でした。

Keen IOを使ってみる

ここからは実際にKeen IOをRailsアプリケーションに組み込んで使う例を紹介していきます。今回はJavaScriptによるWeb Auto-Collectionをカスタマイズして利用してみます。

また、アカウントの作成手順については省略します。以下は既にアカウントを作成してある前提で話を進めていきますので、必要に応じてKeen IOのサイトでアカウントを作成してください。

$20を超えない使用量であれば無料ですので、ぜひ試してみてください。

Web Auto-Collectionとは

Web Auto-Collectionでは次のイベントを自動的に収集することができます。

  • ページビュー
  • クリック (ボタンやリンクだけでなくすべてのクリック)
  • フォーム送信 (送信された内容も含む)

JavaScriptの定義を数行入れるだけで動くようになるため手軽ではあるのですが、クリックはリンクだけでよかったり、追加でスクロールのイベントを取得したかったりします。

嬉しいことにこの機能には柔軟性があり、少し設定してあげるだけでWeb Auto-Collectionの恩恵を受けながらカスタマイズできます。

アクセスキーの作成

Rails側にコードを書いていく前に、まずはアプリケーションからKeen IOにイベントを記録するためのアクセスキーを発行します。

新規にプロジェクトを作成するか、アカウント登録時に作成されたプロジェクトを利用します。

New Project

プロジェクトの「Access」タブからアクセスキーの発行が行えます。

Access Tab

「Project Details」に各種キー情報が載っていますが、これは何かあったときに丸ごと再生成しなければならないため、用途に応じてアクセスキーを個別に発行するのがよいです。

Project Details

アクセスキーは「Project Details」の下にある「Access Keys」から作成できます。

Access Keys

アクセスキーには細かい権限が設定できます。

New Access Key

デフォルトでは色々書かれていて「うっ」となる人もいるかもしれませんが、アプリケーションから利用する分には書き込みさえできればよいので次のようにします。

{
  "is_active": true,
  "name": "Sample Access Key",
  "options": {
    "writes": {
      "autofill": {}
    }
  },
  "permitted": [
    "writes"
  ]
}

Railsへの設定

まずは上記で作成したキーを設定ファイルに追加しておきましょう。config/environments配下の各環境用のファイルに次のように記述します。

config/environments/development.rb
Sample::Application.configure do
  ...

  # Keen IO
  config.keen_tracking_enabled = true # あとでKeen IOの有効・無効を切り替えられるように実装します
  config.keen_project_id = '********************' # 「Project Details」の「Project ID」を設定します
  config.keen_write_key = '*********************' # 上記で作成したアクセスキーを設定します
end

次にKeen IOを利用するためのJavaScriptの定義をビューに実装していきます。

keen-tracking.js/auto-tracking.mdを参考にして設定します。

app/views/shared_partials/_keen_tracking_tag.html.erb
<script>
  <%# Keen IOのライブラリを非同期に取得する。 %>
  // Loads the library asynchronously from any URI
  !function(name,path,ctx){
    var latest,prev=name!=='Keen'&&window.Keen?window.Keen:false;ctx[name]=ctx[name]||{ready:function(fn){var h=document.getElementsByTagName('head')[0],s=document.createElement('script'),w=window,loaded;s.onload=s.onreadystatechange=function(){if((s.readyState&&!(/^c|loade/.test(s.readyState)))||loaded){return}s.onload=s.onreadystatechange=null;loaded=1;latest=w.Keen;if(prev){w.Keen=prev}else{try{delete w.Keen}catch(e){w.Keen=void 0}}ctx[name]=latest;ctx[name].ready(fn)};s.async=1;s.src=path;h.parentNode.insertBefore(s,h)}}
  }('Keen','https://d26b395fwzu5fz.cloudfront.net/keen-tracking-1.4.0.min.js',this);

  <%# Keen IOのライブラリ取得が完了した後に処理を実行する。Keenオブジェクトはグローバルに参照可能。 %>
  Keen.ready(function (){
    <%# 他の箇所でKeenに認証情報がセットされたクライアントを利用できるようにグローバルに配置する %>
    window.KeenClient = new Keen({
      projectId: '<%= @keen_project_id %>',
      writeKey: '<%= @keen_write_key %>'
    });

    <%# イベントに付加情報を追加する %>
    KeenClient.extendEvents(function (){
      return {
        http_request_uuid: '<%= @keen_request_uuid %>',
        user: <%= raw @keen_user_attributes.to_json %>
      };
    });

    <%# Auto Trackingのどの機能を有効にするかを制御する %>
    KeenClient.initAutoTracking({
      ignoreDisabledFormFields: false,
      ignoreFormFieldTypes: ['password'],
      recordClicks: true,
      recordFormSubmits: true,
      recordPageViews: true,
      recordScrollState: true
    });

    <%# 少なくとも一度だけスクロールしたらイベントを飛ばすサンプル %>
    Keen.utils.listener('window').once('scroll', function (_event){
      KeenClient.recordEvent('scroll', {
        scroll: {
          leastOnce: true
        }
      });
    });
  });
</script>

Web Auto-Collectionではkeen-web-autocollector-1.0.8.min.jsというJavaScriptをロードするのですが、カスタマイズする場合はkeen-tracking-1.4.0.min.jsを利用します。

こちらのコードを利用した場合はクリックイベントの収集の対象がデフォルトでa要素に限定されます。

また、イベントリスナーの設定を便利にしてくれるKeen.utils.listenerを利用することにより、「少なくとも一度だけスクロールした」という行動も取得できるため設定しています。

そしてこのビューをレンダリングするためのヘルパーを用意します。設定が有効なときのみにビューをレンダリングするようにして機能の有効・無効を制御しています。

app/helpers/application_helper.rb
module ApplicationHelper
  ...

  # Keen IOのトラッキングタグを表示
  def keen_tracking_tag
    return unless keen_tracking_tag_available?

    @keen_request_uuid = request.uuid
    @keen_project_id = Rails.application.config.keen_project_id
    @keen_write_key = Rails.application.config.keen_write_key

    # イベントに付属させるデータを作る
    @keen_user_attributes = keen_user_attributes

    render 'shared_partials/keen_tracking_tag'
  end

  ...

  private

  # Keen IOのトラッキングタグを出すか否かを判定する
  def keen_tracking_tag_available?
    Rails.application.config.keen_tracking_enabled
  end

  # Keen IOに送信するユーザ属性情報を組み立てる
  def keen_user_attributes
    {
      user_id: current_user.try(:id),
      ...
    }
  end

  ...
end

あとはapplication.html.erbhead要素に埋め込んであげるだけです。

app/views/layouts/application.html.erb
...
<head>
  ...
  <%= keen_tracking_tag %>
</head>
...

Railsアプリケーションを起動していろいろ触ってみましょう。その後、Keen IOプロジェクトページの「Streams」タブを開きます。

Streams Tab

すると次のような画面が現れます。

※ グラフの描画はリアルタイムではないようなので初回アクセスだと何もない状態かもしれません。

Event Streams

イベント名がリンクになっているので選択するとイベントの内容を参照することができます。

Streams clicks

この画面を見ながらRailsアプリケーションを操作するとイベントが飛んでくる様子がわかるので面白いです。

S3へのストリーミング

おまけでS3へのイベントデータのストリーミング設定も紹介します。

Amazon S3 Integration | Keen IO

S3へのストリーミング設定は驚くほど簡単です。あらかじめバケットを作成しておいてください。

「Streams」タブを開きます。

Streams Tab

ここに「Amazon S3」の設定があります。

「Configure S3 Streaming」ボタンを選択するとバケット名の入力フォームと設定手順が表示されます。

設定の流れとしては対象のバケット名を入力し、設定手順に記載されているIDを使ってKeen IOのAWSアカウントのアクセスをバケットに指定するだけです。

AWS Management Consoleに移動し、作成したバケットの「Permissions」タブを開きます。

「Access for other AWS accounts」から「Add account」でアクセスを許可するAWSアカウントを追加します。

手順に記載されているIDを入力し、すべての権限にチェックを入れます。

入力内容を保存したらアカウント名が「dan」になっていることを確認してください。これがKeen IOのAWSアカウント名です。

ストリーミングの設定をすると、次の形式でオブジェクトが5分ごとに保存されていきます。

<Bucket名>/<プロジェクトID>/<ISO-8601形式のタイムスタンプ>/<イベント名>/<プロジェクトID>-<イベント名>-<ISO-8601形式のタイムスタンプ>.json.gz

このJSONファイルはStreamタブで参照可能な形式に、タイムスタンプとプロジェクトIDが格納されたkeenキーが付与された形式となっています。

{
  "keen": {
    "timestamp": "2017-12-18T08:56:26.968000+00:00",
    "created_at": "2017-12-18T08:56:27.092133+00:00",
    "id": "<プロジェクトID>"
  },
  "tracked_by": "keen-tracking-1.4.0",
  "geo": {
    "city": "Tokyo",
    "province": "Tokyo",
    "country": "Japan",
    "country_code": "JP",
    "continent": "Asia",
    "postal_code": "100-0001",
    "coordinates": [
      139.7677,
      35.6427
    ]
  },
  ...
}

Q&A

他のJavaScriptのイベントを妨害したりしない?

JavaScriptによるイベントの記録はEventTarget.addEventListenerによってリスナーが設定されるため、基本的に副作用はありません。(lib/utils/listener.js)

しかし、問題が発生するパターンは存在します。

自分が遭遇したパターンではjQueryプラグインによってクリックイベントのリスナーが仕掛けられたdiv要素の子要素としてa要素が存在していました。Keen IOをロードするとa要素にクリックイベントのリスナーが仕掛けられるため、次のようになります。

<div class="plugin-target"><!-- <= プラグインによってクリックイベントリスナーが仕掛けられる -->
  <a>Sample</a><!-- <= Keen IOによってクリックイベントリスナーが仕掛けられる -->
</div>

この状態でこの要素が表示されている部分をクリックすると、div要素のクリックイベントではなくa要素のクリックイベントが発動し、href属性がないため現在のURLがリロードされるという動きをしてしまいます。

プラグイン側でイベントリスナーを仕掛ける対象の要素を操作できる場合はa要素に向ければよいのですが、今回は操作できなかったため、a要素に対してevent.preventDefaultを呼び出すことによって回避しました。

  $('.plugin-target a').on('click', (e) ->
    e.preventDefault()
  )

このようにもし問題が発生した場合は、Keen IOの呼び出し側で除外コードを実装するのではなく、元のアプリケーションコードを修正したほうがよいでしょう。除外ルールの実装とルール自体が増えていくことによりコードが複雑化しますし、問題が発生するのは元の構造に原因がある可能性が高いためです。

clicks event collectionでdata属性の値を取得できる?

イベントを区別するためにa要素のdata属性に識別子を埋め込んでおきたかったのですが、現時点では残念ながらdata属性の値はイベントに含まれません。取得する属性が限定されているためです。

Auto-Collectionの機構に乗っかるのであればclass属性を利用するのが安い代替案かと思います。

おわりに

サービスの改善をしようと思ったときに蓄積されたデータがあるととても有益です。

完全にWeb Auto-Collectionに任せるのであればさらに簡単に実装することができますが、データは取得したいけど本番環境となるとカスタマイズしたくなるニーズは高いと思います。そんな方にこの記事がお役に立てば幸いです。