JSおよびASPでユーザーアクティビティを収集します

ユーザーアクションの自動記録機能(ブレッドクラムと呼ばれます)の機能をWinFormsWpfで記述した後、クライアントサーバーテクノロジに到達するときがきました。



画像

簡単なJavaScriptから始めましょう。 デスクトップアプリケーションとは異なり、ここではすべてが非常に単純です。イベントをサブスクライブし、必要なデータを書き留めます。一般的にはそれだけです。



標準のaddEventListenerを使用して、jsのイベントをサブスクライブします。 ページのすべての要素からイベント通知を受け取るために、ウィンドウオブジェクトにイベントハンドラーをハングさせます。



必要なすべてのイベントをサブスクライブするクラスを作成します。



class eventRecorder { constructor() { this._events = [ "DOMContentLoaded", "click", ..., "submit" ]; } startListening(eventCallback) { this._mainCallback = function (event) { this.collectBreadcrumb(event, eventCallback); }.bind(this); for (let i = 0; i < this._events.length; i++) { window.addEventListener( this._events[i], this._mainCallback, false ); } } stopListening() { if (this._mainCallback) { for (let i = 0; i < this._events.length; i++) { window.removeEventListener( this._events[i], this._mainCallback, false ); } } } }
      
      





今、私たちは購読するのが理にかなっている非常に価値のあるイベントを選ぶという刺激的な苦痛を待っています。 最初に、イベントの完全なリストを見つけます: Events 。 ああ、それらのいくつ...ログに不要な情報が散らからないようにするには、最も重要なイベントを選択する必要があります。





ただし、以前に行ったように、単にaddEventListenerにサブスクライブできないイベントがあります。 これらは、ajaxリクエストやコンソールロギングなどのイベントです。 ユーザーアクションの全体像を把握するには、Ajaxリクエストをログに記録することが重要です。さらに、多くの場合、サーバーとの対話時にクラッシュが発生します。 コンソールでは、サイト開発者自身とサードパーティライブラリの両方から、重要なデバッグ情報を(警告、エラー、または単にログの形式で)書き込むことができます。



これらの種類のイベントでは、標準のjs関数のラッパーを作成する必要があります。 それらでは、標準関数を独自の(createBreadcrumb)に置き換えます。アクション(この場合、パンくずリストに書き込む)と並行して、以前に保存した標準関数を呼び出します。 コンソールの外観は次のとおりです。



 export default class consoleEventRecorder { constructor() { this._events = [ "log", "error", "warn" ]; } startListening(eventCallback) { for (let i = 0; i < this._events.length; i++) { this.wrapObject(console, this._events[i], eventCallback); } } wrapObject(object, property, callback) { this._defaultCallback[property] = object[property]; let wrapperClass = this; object[property] = function () { let args = Array.prototype.slice.call(arguments, 0); wrapperClass.createBreadcrumb(args, property, callback); if (typeof wrapperClass._defaultCallback[property] === "function") { Function.prototype.apply.call(wrapperClass. _defaultCallback[property], console, args); } }; } }
      
      





Ajaxリクエストの場合、すべてがやや複雑です。標準のopen関数を再定義する必要があるという事実に加えて、リクエストステータスの変更に関するデータを受信するために、onload関数にコールバックを追加する必要もあります。



そして、これが私たちが得たものです:



 addXMLRequestListenerCallback(callback) { if (XMLHttpRequest.callbacks) { XMLHttpRequest.callbacks.push(callback); } else { XMLHttpRequest.callbacks = [callback]; this._defaultCallback = XMLHttpRequest.prototype.open; const wrapper = this; XMLHttpRequest.prototype.open = function () { const xhr = this; try { if ('onload' in xhr) { if (!xhr.onload) { xhr.onload = callback; } else { const oldFunction = xhr.onload; xhr.onload = function() { callback(Array.prototype.slice.call(arguments)); oldFunction.apply(this, arguments); } } } } catch (e) { this.onreadystatechange = callback; } wrapper._defaultCallback.apply(this, arguments); } } }
      
      





確かに、ラッパーには1つの重大な欠点があることに注意する価値があります。呼び出された関数は関数内を移動するため、呼び出されたファイルの名前が変更されます。



ES6のJavaScriptクライアントの完全なソースコードは、 GitHubで表示できます。 お客様のドキュメントはこちらです。



そして、ASP.NETでこの問題を解決するためにできることについて少し説明します。 サーバー側では、クラッシュに先行するすべての着信要求を追跡します。 ASP.NET(WebForms + MVC)の場合、次のIHttpModuleおよびHttpApplication.BeginRequestイベントを実装します



 using System.Web; public class AspExceptionHandler : IHttpModule { public void OnInit(HttpApplication context) { try { if(LogifyAlert.Instance.CollectBreadcrumbs) context.BeginRequest += this.OnBeginRequest; } catch { } } void OnBeginRequest(object sender, EventArgs e) { AspBreadcrumbsRecorder .Instance .AddBreadcrumb(sender as HttpApplication); } }
      
      





異なるユーザーからのリクエストを分離してフィルタリングするには、Cookieトラッカーを使用します。 リクエスト情報を保存するときに、必要なCookieが含まれているかどうかを確認します。 まだなら、その値を追加して保存し、検証することを忘れないでください:



 using System.Web; public class AspBreadcrumbsRecorder : BreadcrumbsRecorderBase{ internal void AddBreadcrumb(HttpApplication httpApplication) { ... HttpRequest request = httpApplication.Context.Request; HttpResponse response = httpApplication.Context.Response; Breadcrumb breadcrumb = new Breadcrumb(); breadcrumb.CustomData = new Dictionary<string, string>() { ... { "session", TryGetSessionId(request, response) } }; base.AddBreadcrumb(breadcrumb); } string CookieName = "BreadcrumbsCookie"; string TryGetSessionId(HttpRequest request, HttpResponse response) { string cookieValue = null; try { HttpCookie cookie = request.Cookies[CookieName]; if(cookie != null) { Guid validGuid = Guid.Empty; if(Guid.TryParse(cookie.Value, out validGuid)) cookieValue = cookie.Value; } else { cookieValue = Guid.NewGuid().ToString(); cookie = new HttpCookie(CookieName, cookieValue); cookie.HttpOnly = true; response.Cookies.Add(cookie); } } catch { } return cookieValue; } }
      
      





これにより、ユーザーがまだログインしていない場合やセッションが完全にオフなっている場合でも、たとえばSessionStateにログオンして、一意のセッションを分離することができます。







したがって、このアプローチは古き良きASP.NET(WebForms + MVC)と同様に機能し、

新しいASP.NET Coreでは、通常のセッションとは多少異なります。



ミドルウェア:



 using Microsoft.AspNetCore.Http; internal class LogifyAlertMiddleware { RequestDelegate next; public LogifyAlertMiddleware(RequestDelegate next) { this.next = next; ... } public async Task Invoke(HttpContext context) { try { if(LogifyAlert.Instance.CollectBreadcrumbs) NetCoreWebBreadcrumbsRecorder.Instance.AddBreadcrumb(context); await next(context); } ... } }
      
      





リクエストを保存しています:



 using Microsoft.AspNetCore.Http; public class NetCoreWebBreadcrumbsRecorder : BreadcrumbsRecorderBase { internal void AddBreadcrumb(HttpContext context) { if(context.Request != null && context.Request.Path != null && context.Response != null) { Breadcrumb breadcrumb = new Breadcrumb(); breadcrumb.CustomData = new Dictionary<string, string>() { ... { "session", TryGetSessionId(context) } }; base.AddBreadcrumb(breadcrumb); } } string CookieName = "BreadcrumbsCookie"; string TryGetSessionId(HttpContext context) { string cookieValue = null; try { string cookie = context.Request.Cookies[CookieName]; if(!string.IsNullOrEmpty(cookie)) { Guid validGuid = Guid.Empty; if(Guid.TryParse(cookie, out validGuid)) cookieValue = cookie; } if(string.IsNullOrEmpty(cookieValue)) { cookieValue = Guid.NewGuid().ToString(); context.Response.Cookies.Append(CookieName, cookieValue, new CookieOptions() { HttpOnly = true }); } } catch { } return cookieValue; } }
      
      





GitHub上のASP.NETクライアントの完全なソースコード: ASP.NETおよびASP.NET Core



All Articles