Introduction
When an exception occurs in the code which is running on the server, either from log file (if handled and logged) or from event log (unhandled exception in case of Windows), we would be able to get error details for investigation. It's fine on the server side but capturing JavaScript errors occurring at client browser is not that easy. At times, we get complaints from users saying a web page or a functionality in it is not working properly. As a web developer, we find it hard to find out the issue.
I personally ran into such a situation recently and thought how easy it would be if we were able to track the JavaScript errors occurring at users' browser.
Will go step by step to implement a solution for this.
1. Create a HTTP Service
To send error details from browser to server, a simple http service is required. Just create a Web API project in VS and copy paste the below piece of code inside the Web API controller.
static readonly string logFile=WebConfigurationManager.AppSettings["LogFilePath"];
static readonly object _syncObject = new object();
public void trackLog()
{
try
{
string exData = Request.Headers.GetValues("Data").FirstOrDefault();
Log(exData);
}
catch (Exception e) { }
}
private static void Log(string msg)
{
if (!System.IO.File.Exists(logFile))
System.IO.File.Create(logFile).Dispose();
lock (_syncObject)
{
System.IO.File.AppendAllText(logFile, msg + " " +
DateTime.Now + Environment.NewLine + Environment.NewLine);
}
}
- Create a key 'LogFilePath' inside
appSetting
in .config file and assign it with absolute file path.
Example: D:\somefolder\log.txt - Since we are performing I/O operation by writing data to single file, synchronization needs to be applied. Especially, in our scenario, multiple http requests may be invoked which in turn will call our controller method
trackLog()
by different process. - In this example, Custom HTTP Header (Data) is used to carry the error data. POST can also be used based on our convenience. Please note that HTTP
GET
request using query string will expose the data that are being passed in the URL which I don't want. Prefer to be a silent task. - Extract data from header and write the same to the disk or any other source, say SQL.
Once all is done, Web API address should be something like below
protocol://<your_site_name>/api/<your_controller_name>/trackLog
2. Detect JS Exceptions and Fire a Request to Server
It's pretty simple, Just subscribe to window.onerror
event on the web page. So, whenever an exception occurs, assigned function will be invoked with error details which in turn will fire AJAX call.
var browserDetails=getBrowser();
var errList=['SyntaxError','TypeError','RangeError','URIError'];
window.onerror = function(error) {
logErr(error);
};
function logErr(error){
if(errList.indexOf(error.name)>=0){
$.ajax({
url: 'api/<controller_name>/trackLog',
async: true,
type: "GET",
beforeSend: function(xhr){xhr.setRequestHeader
('Data', browserDetails + ' : ' + error);},
success: function() { console.log('error logged : '+error); }
});
}
}
function getBrowser(){
var ua=navigator.userAgent,tem,M=ua.match
(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if(/trident/i.test(M[1])){
tem=/\brv[ :]+(\d+)/g.exec(ua) || [];
return 'IE'+' '+(tem[1]||'');
}
if(M[1]==='Chrome'){
tem=ua.match(/\bOPR\/(\d+)/)
if(tem!=null)
return 'Opera'+tem[1];
}
M=M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
if((tem=ua.match(/version\/(\d+)/i))!=null) {M.splice(1,1,tem[1]);}
return M[0]+' '+ M[1];
}
Pack the above code into a js file and include reference in master page if you require logging for the entire website or in a specific page based on your requirement. By placing it in a separate js file, we can switch logging on/off adding/removing this js file reference.
- In addition to the exception details, browser name & version can also be sent as illustrated above. Make sure that this code will be executed only once by placing it inside
PageLoad
or document.ready
, so that browser details will be get only once and not everytime when exception occurs. - JS Exceptions are classified into various kinds. We can include only those exceptions which we target to log. I have excluded Reference error as I don't want to track it.
- As and when an exception occurs,
window.onerror
will invoke the assigned function which will invoke logErr()
ultimately. - A note here. I have placed AJAX call in a separate JS function, making not tightly coupled with
onerror()
. So that, in case if we want to log our own messages to the server, logErr()
can be invoked programmatically.
Example: logErr('data is null');
Captured JS Exceptions in Log File