マーケティングエンジニア必見、ログ取得にはsendBeaconを使え!

2023年9月8日

Javascript

eyecatch 15年以上前から、Webマーケティングサービスを独自に構築してきて、様々なツールを開発してきましたが、 Webマーケティングサービスで最も気を使う機能は、ログ取得まわりでしょう。 規模が大きくなると、データ容量も半端なく多く(大きく)なるので、それに相当するデータ蓄積方式や、データ抽出方法、集計方法などを検討する必要があります。 そんなデータ周りに関してはデータベース設計から、バッチ処理設計、効率的な論理集計方法など、色々考えなければいけない箇所もあり、 かなりのアルゴリズム要素が含まれていますが、エンジニアの腕の見せ所でもあります。 そんな中、一昔前は意外と難しかった、離脱時のログ取得に関するデータ取得を、JavascriptのEcmaScript6バージョンから搭載されたので、使ってみた所、これまでの苦労がアホみたいに思えてきたので、 知らない人と、未来の自分のために、内容を書き残しておこうと思います。

ログデータの保存方法

ログデータというのは、webサーバーに蓄積されるアクセスログを想像する人も多いかもしれませんが、任意ログと言われる、独自に取得をするログデータは、マーケティングシステムには欠かせません。 例えば、何かの販売を行っているサイトであれば、以下のようなログデータが考えられます。
・ユーザー情報のデータ ・購入データ ・どのページを見たかという回覧データ
こうしたログはアクセスログだけではその後の解析ログとしては使えないので、任意ログとして、サーバーに書き込んでおく必要があります。 他にも、Webサイトの内容に沿ったデータを独自に取得したい場合もあるので、この辺は設計思考によって自己責任で取得する事になります。

離脱ログの難しさ

こうした中で、これまで難しかったログが、離脱ログです。 別ページへリンク遷移する場合や、ブラウザの閉じるボタンを押した時に、その瞬間の情報を保持する必要があるのが、離脱ログです。 ページへのアクセス開始は、アクセスログの書き込まれた時間で認識できますが、何秒間ぐらいページを見ていたかというのは、終了時間を取得して差し引くことによって計算ができますが、終了時間が無いとこうした計算はできません。 そのため離脱ログという情報を取得するのですが、サーバーにログ出力をするデータを送信しても、それよりも早くページ遷移してしまうと、ログデータはサーバーに書き込まれる前(パケットがサーバーに届く前)に消滅してしまいます。 これを防ぐために、サーバーに書き込まれたあとで実行するコールバック処理を埋め込んでおいて、確実にログ書き込みをするという方法を行っていましたが、ブラウザの閉じるボタンなどは、有無を言わさず実行されてしまうので、なかなか制度が良いとは言えませんでした。

ログデータのサーバー書き込み方法

Webサイトからログデータを任意にサーバーに書き込むのはいくつかの方法があります。

ビーコン方式

imgタグや、scriptタグを利用して、プログラムのURLにアクセスをすることでログを取得する方式です。 imgタグ方式 <img src='https://example.com/log.php?uid=foo&value=bar'> scriptタグ方式 <script id='beacon' src='https://example.com/log.js?uid=foo&value=bar'></script> これらの方法は、デメリットも多く、GETメソッドでしか送信ができないので、URLにログデータの値を含むので、個人情報などを送る場合には使うことができません。 また、imgタグの場合は、ちゃんと(透明などの)画像データを返すreturn処理が必要になるし、 scriptタグの場合は、javascriptでのrespponseを返す必要があります。 こうしたレスポンス処理を怠ると、ブラウザ側で、エラー表示されてしまって、みっともない結果になってしまいます。

リダイレクト方式

ログ取得をするサーバーに一度遷移してから、そこから自動でリダイレクトさせる方法があります。 Google検索した結果のリンクをクリックすると、このリダイレクト方式でページ遷移しているのがわかります。 自分のWebサイト内に設置されている、Aリンクタグの遷移として使うと効果的ですが、ログ保存のタイミングがアクセスログ程度と利用範囲が狭い事が欠点です。 <a href='https://example.com/log.php?uid=foo&value=bar'></a> そして、この方式も、Getメソッド方式のみでのデータ送信になるので、ビーコンと同じく情報セキュリティに気を使う必要があります。

Ajax

やはり最も有効と思うのが、このAjax方式です。 好きなタイミングで、データを暗号化して送信ができるため、もはやデータのやり取りは自由自在です。 const query = { mode : 'search', user : 'foo', value : 'var', } const xhr = new XMLHttpRequest() xhr.withCredentials = true; xhr.open('POST' , 'https://example.php/' , true) xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = (e => {console.log(e.target.response)}) const query_string = Object.entries(query).map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&'); xhr.send(query_string) 上記だと、非同期処理もしてくれるので、送った時点でページが閉じられてもサーバーにデータが送られます。 そして、コールバック処理もできるのでかなり便利に使えます。 このコードは、XHR方式ですが、理解している人は、Fetch方式で送信しても問題ありません。

ブラウザのBeacon APIを使おう

上記Ajax方式よりも確実にログデータを送信する方法がさらにあり、 BeaconAPIというブラウザ機能を使う方法です。

サンプルコード

log.php <?php $datas = json_encode($_POST, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); file_put_contents('data.log' , $datas.PHP_EOL , FILE_APPEND); beacon.html <h1>Beacon</h1> <button id='click'>click</button> <div>please page close.</div> <script> document.getElementById('click').onclick = function(){ beacon({ mode : 'click', user : 'aaa', value : 'bbb', }) } window.onunload = function(){ beacon({ mode : 'unload', user : 'foo', value : 'var', }) } function beacon(query){ const data = new FormData(); for(const key in query){ data.append(key, query[key]); } // const res = navigator.sendBeacon('log.php' , JSON.stringify(query)) const res = navigator.sendBeacon('log.php' , data) console.log(res) } </script> 上記プログラムでは、htmlにscriptタグを埋め込んでみました。 ボタンをクリックした場合と、ページ離脱の場合に、data.logというファイルが作り出されます。

解説

navigator.sendBeacon(ログデータ書き込みプログラム(url) , データ) 上記のデータで書き込みができるのですが、Ajaxと違って、無駄なプログラムの宣言がいらないのが魅力的です。 デメリットとしては、このAPIは、コールバックを処理することができません。 ただし、返り値として、正常にサーバーに送られた場合に、trueが返ってくるので、正常完了の確認はできます。 Ajaxの方が汎用性があると思われガチですが、このBeacon機能を使うと、パケット容量が少なく通信ができるので、より軽量なデータのやり取りが可能になります。 SDGsです。 こんな便利なBeaconAPI使わな損でしょう!

リファレンスサイト

https://developer.mozilla.org/ja/docs/Web/API/Navigator/sendBeacon

Can I use