Ingestly の計測タグをウェブサイトに設置する

[Ingestly / リアルタイムウェブ解析](https://qiita.com/hsano/items/dcb4be5bd210b54390be) でIngestlyについて紹介、 [Ingestly の計測エンドポイントを構成する](https://qiita.com/hsano/items/7764583899e8112782fc)ではエンドポイントの構成を細かく順を追って説明しました。
いよいよ計測タグをウェブサイトに設置しますが、正直難しいことは何もないです。

# Ingestly Client JS を入手する

[Githubのリポジトリの Releases ページ](https://github.com/ingestly/ingestly-client-javascript/releases) から、 `ingestly.js` と `page_code.js` をそれぞれダウンロードする。

<img width="1038" alt="スクリーンショット 2019-08-04 13.52.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/e8ccd4e7-5c16-d93b-714f-7a93108d6aca.png">


基本的にビルド済みの最新版がここにある。（ES201xで書いたものをWebpackやBabelでトランスパイルしている）

- **ingestly.js** : SDK本体で、計測に必要な全てが含まれている
- **page_code.js** : 個々のページでの初期化、設定、計測について記述したサンプルファイル

# 必要な設定を行う

`page_code.js` を開いて、個々の設定値を埋めていくだけでできてしまう。

<img width="469" alt="スクリーンショット 2019-07-14 22.47.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/5d549b12-4205-933e-6f9b-6f7850aaa314.png">

## 最低限必須の設定

`config()` メソッドに渡す設定変数の内、`endpoint` の変更だけが必須。

```js
// 6〜8行目
Ingestly.config({
    endpoint: 'stat.example.com',
    apiKey: '2ee204330a7b2701a6bf413473fcc486',
```

- `endpoint` はビーコンの送信先を表す。エンドポイントを構成したときに[設定したドメイン](https://qiita.com/hsano/items/7764583899e8112782fc#ドメインを設定する)をここに指定する。
- `apiKey` は、エンドポイントを構成したときに特に何もしていなければ変える必要は無い。タグが転用されたりしてゴミデータが来た場合に計測を拒否するためのもので、[エンドポイントのVCL](https://github.com/ingestly/ingestly-endpoint/blob/ca5e44c79f1cacf01195989046843ee899383f94/ingestly.vcl#L6-L9)で受け付けるか否かは設定できる。

これだけ設定すれば最低限の計測はできる。

## サイトに実装する

ウェブサイトへの実装は他のアクセス解析ツールと同じで、JSを読み込む記述をするだけでOK。もちろん各種タグマネジメントツールからの配信でも動作する。
最もシンプルな実装は、各ページの `</body>` の直前の行で以下のようにjsファイルを読み込む。

```html
<script src="ingestly.js"></script>
<script src="page_code.js"></script>
```

- この2つは読み込み順序が前後すると動かないので、`async`にしないこと
- `body`内であれば、先頭でも最後でもどこでも動く
- [Ingestly Client JSのリポジトリにサンプル実装](https://github.com/ingestly/ingestly-client-javascript/blob/059d4927a967fbda2106b9e66fb526f4adde5cb8/example/index.html#L141-L142)がある

<img width="397" alt="スクリーンショット 2019-07-14 22.59.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/42003de7-a034-d05f-6223-e507994d6f2f.png">


## ページについての情報を渡す

ここまでの設定・実装で最低限の計測は開始される。この先は発展的な分析のための設定を説明する。

### SDK初期化時に一括設定する

`page_code.js` の中で、 `init()` メソッドを呼んでいる箇所があり、ここでページごとの情報を渡してSDKを初期化している。

- 変数名は、初期状態で記述されているものは全てこれまでの基本的な設定でBigQueryに記録される。
- `*Attr` で終わる変数は、JSON文字列としてBigQueryに記録されるので、任意の変数を追加すれば、BigQuery側のスキーマやFastly側のログフォーマットを改変することなく、即時計測が可能。ABテスト等アドホックな分析ニーズに対して追加の変数を渡したい場合に、スキーマ更新をしていると面倒だったりするので、そういうときにこれらの枠を活用する。
- 任意の変数を追加することも可能で、まずJS側で追加して送信し、追ってBigQuery側スキーマとFastly側ログフォーマットをそれぞれ更新すればOK。変数を増やすことも削ることも簡単にでき、BigQueryの制限である10,000カラムまで拡張できる。（ブラウザのURL長制限があるので無意味に増やしすぎないこと）

```js
Ingestly.init({
    pgUrl: window.location.href,
    pgRef: window.document.referrer,
    pgTitle: window.document.title,
    pgAttr: {},
    usId: '',
    usStatus: '',
    usAttr: {},
    cnId: '',
    cnHeadline: '',
    cnStatus: '',
    cnAttr: {},
    cpCode: Ingestly.getQueryVal('cid'),
    cpName: Ingestly.getQueryVal('utm_campaign'),
    cpSource: Ingestly.getQueryVal('utm_source'),
    cpMedium: Ingestly.getQueryVal('utm_medium'),
    cpTerm: Ingestly.getQueryVal('utm_term'),
    cpContent: Ingestly.getQueryVal('utm_content'),
    cpAttr: {}
});
```

|カテゴリ|変数|意味|
|:---|:---|:---|
|ページ|`pgUrl`|ページURL。通常は`location.href`から参照する|
|ページ|`pgRef`|リファラー。通常は`document.referrer`|
|ページ|`pgTitle`|ページタイトル。`title`タグから取得している|
|ユーザー|`usId`|ログイン機構がある場合、ログイン中ユーザーを識別するような情報（非個人情報）|
|ユーザー|`usStatus`|ユーザーの認証状態や権限等を想定。細かい情報は`usAttr`に入れてもOK|
|コンテンツ|`cnId`|記事のID。記事マスターのようなものと紐づける場合のキーになるもの|
|コンテンツ|`cnHeadline`|記事見出し。見出しは変わるものなので、表示された時点での情報を記録する|
|コンテンツ|`cnStatus`|記事の状態。ペイウォールがあったり、「続きを読む」等のUIで隠れているような場合と、全文が見える状態を区別できる情報を想定|

他に、`cp*` 系の変数はキャンペーン（広告）を識別する情報で、Google AnalyticsやAdobe Analyticsと同様のGETパラメータを取得するようにデフォルトで指定してある。必要があれば変更する。

### ページビューやイベントを計測するときだけ異なる値を渡す場合

- `page_code.js` の中で、以下の1行がページビューを計測するためのメソッドの呼び出しになっている。
- このメソッドには引数としてオブジェクトを1つ渡せるようになっており、`init()` で一括してした値を上書きできる。
- 上書き処理では差分のみが上書きされる、つまり既に定義されている変数は新しい情報になり、`init()`で定義していない変数なら追記される。
- ネストされたオブジェクトは上書きマージされるが、配列に対しては配列全体を上書きしてしまうので注意。

初期状態はこうなっていて…

```js
Ingestly.trackPage();
```

このように引数が渡せる。

```js
Ingestly.trackPage({
    pgTitle: 'new title',
    cnStatus: 'unlocked'
});
```


## 様々な自動計測

Ingestlyでは、「最速」「最軽量」を目指しているので、SDKは20KBを下回ることを目標にしており、v0.6.0の時点では15KBに満たない。しかし「最速」「最軽量」は技術的な側面だけでなく、実装や運用の工数、つまり最も高額な「人間の稼働時間」も最小化することを目指している。

- スクロール深度をはじめとする、ウェブ解析で必須の様々な計測をSDKに組み込み済み
- ブラウザに対する負荷がかかりにくい仕組みを選択
- sendBeaconを優先した非同期で軽量な通信方式、つまり `document.write` や `createElement` を使った場合に起こりがちなForced Reflowが発生せず、ノンブロッキングな仕組み
- 様々な計測関連の設定は `config()`メソッドに集約している

### 基本機能に関する設定

`config()`の引数の前半にある以下の4つの変数は、SDKの基本動作に関する設定を担う。

```js
Ingestly.config({
...
    eventName: 'ingestlyRecurringEvent',
    eventFrequency: 250,
    prefix: 'ingestly',
    targetWindow: 'self',
```

|変数|意味|
|:---|:---|
|`eventName`|スクロールや読了計測を行う処理を発火するために使う再帰的なイベントの名称。サイトで既に使っているイベント名と競合しなければOK|
|`eventFrequency`|上記の再帰イベントを発火させる頻度。デフォルトは250（ミリ秒）となっており、各種観測処理は秒間4回ない程度実行される|
|`prefix`|CookieやlocalStorageに値を書き込む場合のキー名のプリフィックス。通常はこのprefixの後ろに `-id` を付けたものが使われる|
|`targetWindow `|Ingestly Client JSがiframeの内側に実装されているような場合に、`parent` や `top` を指定することで、計測対象を上位のフレームに切り替えることができる。通常の直接実装であれば `self` のままでOK|


### 自動計測についてのオプション

`config()`の中の `options` 以下で様々な自動計測の設定が可能。

```js
Ingestly.config({
...
    options: {
        ...
    }
});
```

#### RUM = Real User Monitoring 計測

実際の訪問者のブラウザ上で、どれくらいの時間でページが描画されたか、パフォーマンスを計測する概念がRUM。
Googleの [クリティカル レンダリング パスの測定](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/measure-crp?hl=ja) や 日経の [RUMとA/Bテストを使ったパフォーマンスのモニタリング](https://hack.nikkei.com/blog/tech_book_fest04_performance_monitoring_rum/)を読むとなぜ必要か、何を計るかが理解しやすい。

```js
rum: {
    enable: true
},
```

と指定することで、RUM計測専用にビーコンを送ることができる。

- ここを `false` にしても、全てのビーコンにはRUMに関する情報がのるようになっていて、あくまで専用のビーコンを送るか否かを選択する。
- 専用のビーコンを送ることで1ページビューあたり1件のRUM用レコードが作成されるので集計が楽。（RUM用以外のビーコンからパフォーマンス情報を得るには、root_idでパーティションして最も最後に送られた1レコードを得る必要がある。
- BigQuery上では、 `timing_*` 系のカラムに情報が格納される。


#### 離脱計測

ページ上での滞在時間を計測するために、unload（またはそれに類する）イベントの時点で離脱計測用ビーコンを送る場合に利用する。
ブラウザによって対象イベントが異なるので、利用できる最も早い段階のイベントを採用する仕組みになっているが、iOSのSafariではどうしてもビーコンが飛ばない模様。

```js
unload: {
    enable: true
},
```

- `true`にすることで離脱の時点でビーコンが飛ぶようになる


#### クリック計測

Ingestlyのクリック計測は、data属性を用いて計測対象を明示的に指定する方式を採用している。一般的にクリック計測というと、クリックされた全ての要素を計測対象としていることが多いが、そうすると計測する必要の無いものまでビーコンが飛んでしまい、UX的にも微妙であるし、分析においても不用意にレコード数が増大、フィルター条件が複雑になってしまう。
この計測の仕組みは[日経のAtlas Tracking JS](https://github.com/Nikkei/atlas-tracking-js)と共通していて、デフォルトでは `data-trackable` 属性を計測対象のエレメントに付与し、値に分かりやすい要素名を入れる。
`data-trackable`属性はページのHTML構造に合わせてセマンティックに付与する、つまりページ上の構成要素に対して名前を振っていくことで、計測時に`data-trackable`の値がツリー構造になり、会話の中で「ヘッダーのログインフォームにあるサインアップボタン」のようにやりとりされる要素を、`header > login-form > sign-up` のような値として扱うことが可能になる。
ここはデザインの段階、HTMLを組み立てる段階でしっかりと属性を追加する必要がある。

```js
clicks: {
    enable: true,
    targetAttr: 'data-trackable'
},
```

- `enable`を`true`とすることで自動計測を有効化
- `targetAttr` には計測対象を検出するためのdata属性名を指定する

BigQuery上は `clock_*` 系の多数のカラムに関連する値が格納される。


#### スクロール深度計測

スクロール計測はページ全体に対して、可視領域の下端がどこまで行ったかを計測する。

<img width="1700" alt="スクリーンショット 2019-07-15 0.12.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/b7688daf-dbb8-7da9-53a1-a17d07a556a1.png">

ただし、深度だけで計測してしまうと、慣性スクロールや操作ミスで「行き過ぎ」た場合も計測されてしまう。
そこでIngestlyでは、閾値の秒数を指定することで、「一定の深度またはそれ以上の深度がx秒間維持されたら」計測する仕組みになっている。

**単位がパーセントの場合**
ページの内容が固定で高さが極端に変化しない場合、計測単位をパーセントにして計測する。
スクロール深度が`granularity` で指定した割合変化するごとに計測するので、この設定例では20%ごとに計測することになる。
`threshold`が計測するまでの時間の閾値を秒で表しており、この場合は例えば 40%に到達または40%以上の状態で2秒経過したら計測する。

```js
scroll: {
    enable: true,
    threshold: 2,
    granularity: 20,
    unit: 'percent'
},
```

**単位がピクセルの場合**
ページが無限スクロールであったり、Lazy Loadで後から内容が大きく変化する場合、割合では正確に計測できない（100%到達後にさらに下にコンテンツが増える）ため、ピクセル単位の計測をする。
`unit`を`pixel`に変えた上で、`granularity`は変化量をピクセルで指定する。この場合は500ピクセルスクロールし、時間閾値に達するごとに計測される。

```js
scroll: {
    enable: true,
    threshold: 2,
    granularity: 500,
    unit: 'pixel'
},
```

これらの設定で計測される情報は、BigQueryでは`scroll_*`系のカラムに格納される。

#### 読了計測

読了計測で一般的な手法は、コンテンツの末尾にある「要素が見えたら」読了と見なすという手法だが、それは100%最期まで到達した場合のみを意味しており、およその意味が伝わるまでとか、そもそも記事がどこまで読まれたのかを把握することはできず、ゼロか100かといった評価になってしまう。
Ingestlyでは、読了か否かはサイトやコンテンツに左右されるという前提で、スクロール深度のように複数のビーコンを送信した上で、読了か否かの基準は分析者が任意に決められるようにしている。

<img width="1693" alt="スクリーンショット 2019-07-15 0.43.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107873/cde6f9d3-2a02-adbe-856c-b721792095ef.png">

読了計測がスクロール計測と異なるのは、スクロールがページ全体の高さにフォーカスしているのに対し、読了では記事本文にフォーカスする点である。
記事ページにおいて、ヘッダーがどんなに見えても記事の内容は伝わらないし、記事を一瞬でスキップして記事下の関連記事等、記事ではない部分が見えていたからといって記事の内容が伝わっているわけではない。そこで、記事本文の要素の可視性のみを観測しよう、というのがIngestlyの読了計測である。


```js
read: {
    enable: true,
    threshold: 2,
    granularity: 20,
    targets: [].slice.call(window.document.getElementsByTagName('article'))
},
```

- `granularity`はパーセンテージで、読了はピクセル単位の計測に対応しない
- `targets`は観測対象のエレメントの配列で、1ページに複数の記事が含まれる場合にも複数要素を同時に観測できる
- この例ではページ内の全ての`article`タグが監視対象となる

読了に関する情報はBigQueryでは`read_*`系のカラムに格納される。
記事に紐付く様々な情報（著者や公開日等）はinit時に渡すこともできるが、読了計測で観測対象になる要素にdata属性で付与することでBigQueryに記録されるようになる。


#### メディア計測

メディア計測はHTML5の`video`タグと`audio`タグに対応する、メディア再生に関する計測機能。
具体的な計測内容は、再生開始・再生中x秒間隔・再生中断・再生終了を計測し、どこまで再生されたかを把握することができる。

```js
media: {
    enable: true,
    heartbeat: 5,
},
```

- `enable`を`true`で全ての`video`ならびに`audio`タグが計測対象になる
- `heartbeat`は再生中に何秒経過するごとに計測するかを指すもので、この例では5秒ごとにビーコンが送信される


# おわりに

ここまでの流れで、エンドポイントの構成と計測タグの実装が完了します。
あとは実際にBigQueryにデータが蓄積されたら、クエリーするだけというところまで到達しました。
クエリーを組み立てるには、計測結果のデータ構造やカラムの意味を知る必要があるので、次の記事ではRe:dashでの可視化しながらクエリーの例を紹介したいと思います。