Staccato を使ってサーバーサイドからGoogleAnalyticsに直接送信する


こんにちは、Misocaの @lulu-ulul です。
[Misoca Advent Calendar 2017](https://qiita.com/advent-calendar/2017/misoca) の12日目の記事になります。

GoogleAnalytics に関する記事になります。

ちょっと前まではGAとは代行で設置して初期設定して終わり！くらいの付き合い方くらいしかしてませんでした。
最近はちょこちょこ弄る様になり、数字の増減やチャートの動きを見てニヤニヤする人達の気持ちが少し分かってきた今日この頃です。
同僚から Staccato っての使うと良さそうって教えてもらったので試してみました。

# GoogleAnalytics によるトラッキング
通常はトラッキング用のスクリプトを埋め込まみ、クライアントサイドでページビューやイベントをGAのサーバに送信させます。
ただ view が無いactionでは取れないですし、処理終了後にリダイレクトでダッシュボード等に飛ばしているとそれぞれを識別するのが困難ですよね。
そこで今回はサーバーサイドから直接送信してトラッキングする方法を試してみます。

## Google Analytics Measurement Protocol(以下GAMP)

Google が提供しているHTTPリクエストで直接送信するためのプロトコルです。
詳しくはGoogle先生が丁寧なドキュメントを提供してくれているのでそちらを見て下さい。
[Google Analytics Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1/)



## Staccato

GAMPを扱うためのgemである [staccato](https://github.com/tpitale/staccato/) を使いました。
同じ作者の方が [staccato-rails](https://github.com/tpitale/staccato-rails/) も公開しているので、そちらを利用すると手軽にGETリクエストをpageviewとしてサーバサイドから送る事ができる様になります。

私の場合は以下の理由で staccato を使い送信する形にしました。

- Tracker のcreate時にパラメータを指定したい
- pageviewについては複数のTrackerを使いたい
- view 側で送る必要があるので結局スクリプトの埋め込みが必要
- GAに送りたくないGETリクエストがあるが除外するのが面倒

# :muscle: 手を動かす
## gateway の作成

Gemfile 周りや `application.rb` へのpathの追加等は割愛します。

controller で直接Staccatoを呼び出してtracker 作るのはアレなので以下の様なgatewayを定義しました。

```ruby:app/gateways/google_analytics_tracking_gateway.rb
# frozen_string_literal: true

class GoogleAnalyticsTrackingGateway
  def initialize(client_id: nil, user_id: nil, user_ip: nil)
    @staccato_tracker = Staccato.tracker('UA-XXXXXXXX-X', client_id, user_id: user_id, user_ip: user_ip)
  end
  
  def tracker
    @staccato_tracker
  end
end

```

今回はお試しなのと`Staccato`の`pageview`メソッドが直感的だったので `Staccato::Tracker` のオブジェクトを返す形で手抜きしています :sweat_drops: 。

## GA における client id と user id


ざっくり説明すると前者はcookieベースでブラウザ/デバイス単位で追跡するための識別子で、
後者はサービス側の一意のID（ユーザレコードのID等）を渡してブラウザやデバイス・セッションを超えてユーザを追跡するための識別子です。
詳しくは[こちら](https://developers.google.com/analytics/devguides/collection/analyticsjs/cookies-user-id?hl=ja)の公式docを。

これを埋め込みscript側と共通の値にしたいためStaccatoを使っています。
staccato-rails はコード追った所そのままでは user idが設定できない様でした。
client id も標準だと session で管理しているため staccato 独自の値が設定されてしまいますが、こちらは[簡単に変更できます](https://github.com/tpitale/staccato-rails#overriding-the-client_id)。


## controller で client id と user id, リクエスト元のIPを渡す

先程のドキュメントを見ると client id は `cookies['_ga']` から取り出せば良さそうです。

`hoges#create` 成功時に pageview を送信するexampleを書いてみます。

```ruby:app/controllers/hoges_controller.rb
...

def create
  ...
  
  if result.success?
    cookies['_ga'] ||= { value: Staccato.build_client_id, domain: '.example.com', expires: 2.years.from_now }
    tracker = GoogleAnalyticsTrackingGateway.new(client_id: cookies['_ga'], user_id: current_user.id, user_ip: request.remote_ip).tracker
    tracker.pageview(path: '/hoges/create', title: 'hogeの作成成功')

    # 通常の作成成功時の処理
    ...
  else
    # 作成失敗時の処理
    ...
  end
end
```

ここで気をつけないといけないのが parameter のkey名です。
Staccato はparameterを一度定義したkey名のものに絞り込み、その後 GAMP 用の key 名に変換します。
そのためpageview メソッドに直接GAMP側の `uid` パラメータを指定しても捨てられてしまうので、 `user_id`パラメータとして指定する必要があります。

[README に一覧](https://github.com/tpitale/staccato#global-options)はありますが、 [Googleのリファレンス](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters)と [こちらの key-value の組](https://github.com/tpitale/staccato/blob/7f724b97b6309be60604baa7bd3bda3ce215087d/lib/staccato/hit.rb#L21)を突き合わせて確認するのが確実です。

user_ip もリクエストから取り出して設定しないと別セッション扱いされてしまうので設定しておきます。

---

tracker の取得自体はcookie と current_user を見るだけですし外でも使いたいので、concerns に定義して `applications_controller` でincludeします。

```ruby:app/controllers/concerns/send_hit_to_google_analytics.rb

def send_pageview_to_ga(path:, title: nil)
  cookies['_ga'] ||= { value: Staccato.build_client_id, domains: '.example.com', expires: 2.years.from_now }

  tracker = GoogleAnalyticsTrackingGateway.new(client_id: cookies['_ga'], user_id: current_user.id, user_ip: request.remote_ip).tracker
  tracker.pageview(path: path, title: title)
end

```

```ruby:app/controllers/hoges_controller.rb
...

def create
  ...
  
  if result.success?
    send_pageview_to_ga(path: '/hoges/create', title: 'hogeの作成成功')

    # 通常の作成成功時の処理
    ...
  else
    # 作成失敗時の処理
    ...
  end
end
```
すっきりしましたね。
これで終了です。

実際に rails server を立ち上げて create に成功するとGAに送信されていると思います。 
![スクリーンショット 2017-12-11 6.17.04.png](https://qiita-image-store.s3.amazonaws.com/0/138695/2878e499-cc04-e95f-0f91-b50deac72cd1.png)


---


## 何故こんなことしてるの？

ユーザの一連の動きを目標に設定してコンバージョン率を見るためです。

view の無いページは通常のpageviewだけだと遷移後のページを指定して間接的に観測するしかありませんが、今回の様にしてGAにhitを送信することで直接記録することができます。

## 何でpageviewなの？

GoogleAnalyticsでは通常ユーザのクリックイベントをeventを使ってトラッキングします。
しかしpageview と eventを混じえた一連の動き(例. ページ:a: からページ:b:に遷移してイベント :one: 発生後 ページ:a:に遷移する) を追う事ができません。
**目標**は一連のページ遷移もしくは単発のイベント発生の有無のどちらか、**イベントフロー**はイベントの発生順序しか追えません

これを回避するための手法の一つとして、仮想的なURL等を使い全てをpageviewで揃えて目標に設定するというものがあります。
目標到達プロセスやゴールフローを使える様になるのでおすすめです。

## 何か影響ないの？

仮想的なページへ遷移した事になるので、前後のページの離脱率や滞在時間に影響は出ますので注意する必要はあります。
ただviewの無いactionに仕込むだけでしたら、今まで前のページに丸められていたものが分離するだけなので問題ないかなと思います。


# まとめ

Staccato を使ってサーバサイドからGAへpageviewのhitを送信し、viewが存在しないactionも直接的に観察できる様になりました。
必要であれば同様にしてDelayedJob等から送信する事もできますね。

トラッキングを防止する拡張等の影響をうけなくなるので、view 側のイベントで送っているものも含め全てサーバサイドから送る様にしたい気持ちはあるのですが、
今までクライアントサイドで送って貰っていたものが全てサーバサイドから送る事になるので少し考え中です。


---

[Misoca Advent Calendar 2017](https://qiita.com/advent-calendar/2017/misoca)、明日の担当は @mzp で少しスピリチュアルな感じの記事をお届けする予定です。