Solving jQuery Ajax Errors Catch and Report

The main issue I’m facing with this script is trying to identify the specific function (or “caller”) that triggered an ajaxError event. In normal JavaScript errors, I can catch details like the line number, filename, and function stack, thanks to ErrorEvent. However, with ajaxError events, getting these details is much trickier.

Error Code:

codeconst error_log_url = '/log';
const errorPost = function (data) {
$.ajax({
url: error_log_url,
type: 'post',
data: data,
success: function (res) {
console.log(res)
}, error: function (res) {
console.log(res)
}
})
}
window.addEventListener('error', function (e) {

let params = {
message: e.message || "Exception Handler",
url: e.filename || "",
lineno: e.lineno || 0,
colno: e.colno || 0
}

errorPost(params)
}, true);

// wrap function for new error stack with adding event to certain element
window.wrap = function (func) {
// make sure you only wrap the function once
if (!func._wrapped) {
func._wrapped = function () {
try {
func.apply(this, arguments);
} catch (exception) {
throw exception
}
}
}
return func._wrapped;
}

// override add & remove event listeners with above wrap function
let addEvenListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
addEvenListener.call(this, event, wrap(callback), bubble);
}

let removeEventLister = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
removeEventLister.call(this, event, callback._wrapped || callback, bubble);
}

$(document).ajaxError(function( event, jqxhr, settings, thrownError ) {

// please look at here, how can I get the caller name that produced this error!
console.log(arguments.callee.caller)

if (settings.url != error_log_url)
errorPost({
message: event.type,
filename: event.currentTarget.location.origin + settings.url
})

});

Solution:

The primary issue in your code is that arguments.callee.caller is often unreliable, especially in strict mode, and it can frequently return null. To capture additional context, such as the function that initiated the AJAX call, a practical workaround is to use a wrapper function for AJAX calls that adds metadata like the caller function name or stack trace to each request. This allows you to track the origin of AJAX errors more effectively.

Here’s a modified version of your code that incorporates these principles:

  1. Wrapper Function for AJAX Calls: By creating a trackedAjax function, you can pass the caller function name as metadata, giving more context when an error occurs.
  2. Manual Caller Info and Stack Trace: Using Error().stack within the wrapper provides a basic stack trace, making debugging easier when AJAX errors are caught.

Correct Code:

codeconst error_log_url = '/log';

// Wrapper function for AJAX calls with caller information
function trackedAjax(options, callerName = "Anonymous") {
options.callerName = callerName; // Attach caller info to options for context
return $.ajax(options);
}

// Function to post error data
const errorPost = function (data) {
$.ajax({
url: error_log_url,
type: 'post',
data: data,
success: function (res) {
console.log(res);
}, error: function (res) {
console.log(res);
}
});
};

// Global error handler
window.addEventListener('error', function (e) {
let params = {
message: e.message || "Exception Handler",
url: e.filename || "",
lineno: e.lineno || 0,
colno: e.colno || 0
};
errorPost(params);
}, true);

// Wrap function to handle new errors and stack traces for events
window.wrap = function (func) {
if (!func._wrapped) {
func._wrapped = function () {
try {
func.apply(this, arguments);
} catch (exception) {
throw exception;
}
};
}
return func._wrapped;
};

// Override addEventListener and removeEventListener with wrap
let addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
addEventListener.call(this, event, wrap(callback), bubble);
};

let removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
removeEventListener.call(this, event, callback._wrapped || callback, bubble);
};

// Updated ajaxError handler
$(document).ajaxError(function (event, jqxhr, settings, thrownError) {
// Capture caller and stack information
let callerInfo = settings.callerName || "Unknown Caller";
let errorDetails = {
message: thrownError || event.type,
filename: settings.url,
caller: callerInfo,
stack: new Error().stack, // Stack trace for more context
timestamp: new Date().toISOString()
};

console.error("AJAX Error:", errorDetails); // Log error details

// Post error details excluding errors from error log itself
if (settings.url !== error_log_url) {
errorPost(errorDetails);
}
});

// Example usage of the trackedAjax wrapper with caller information
function fetchData() {
trackedAjax({
url: '/data',
method: 'GET'
}, 'fetchData'); // Pass caller function name
}

Explanation of Key Changes:

  1. trackedAjax Wrapper:
    • The trackedAjax function allows you to add metadata such as callerName to each AJAX request. When an error occurs, this metadata provides valuable context, allowing you to track where the request originated.
  2. Stack Trace with Error().stack:
    • Including new Error().stack captures a stack trace, which is logged in the ajaxError handler. While it doesn’t directly give the line number of the original AJAX call, it provides more context for debugging the issue.
  3. Usage Example:
    • fetchData calls trackedAjax with the function name as an argument. This helps track specific functions that make AJAX calls, allowing you to debug issues more precisely.

Final Thought

With these adjustments, you now have a more robust solution that logs detailed information, including the caller function and stack trace, whenever an AJAX error occurs. This setup provides a clearer context for error reporting and makes tracking AJAX issues significantly easier, even without relying on unreliable methods like arguments.callee.caller.

Related blog posts