mirror of
https://github.com/zulip/zulip.git
synced 2025-11-05 14:35:27 +00:00
This generalizes existing code for the presence code path that is generically useful for avoiding useless work that will be discarded. We make an exception for the one type of request that needs to happen while reloading, namely the one to clean up our event queue.
295 lines
9.3 KiB
JavaScript
295 lines
9.3 KiB
JavaScript
/* eslint-disable no-console */
|
|
|
|
// System documented in https://zulip.readthedocs.io/en/latest/subsystems/logging.html
|
|
|
|
// This must be included before the first call to $(document).ready
|
|
// in order to be able to report exceptions that occur during their
|
|
// execution.
|
|
|
|
const blueslip_stacktrace = require("./blueslip_stacktrace");
|
|
|
|
if (Error.stackTraceLimit !== undefined) {
|
|
Error.stackTraceLimit = 100000;
|
|
}
|
|
|
|
function Logger() {
|
|
this._memory_log = [];
|
|
}
|
|
|
|
Logger.prototype = (function () {
|
|
function pad(num, width) {
|
|
let ret = num.toString();
|
|
while (ret.length < width) {
|
|
ret = "0" + ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function make_logger_func(name) {
|
|
return function Logger_func(...args) {
|
|
const now = new Date();
|
|
const date_str =
|
|
now.getUTCFullYear() + '-' +
|
|
pad(now.getUTCMonth() + 1, 2) + '-' +
|
|
pad(now.getUTCDate(), 2) + ' ' +
|
|
pad(now.getUTCHours(), 2) + ':' +
|
|
pad(now.getUTCMinutes(), 2) + ':' +
|
|
pad(now.getUTCSeconds(), 2) + '.' +
|
|
pad(now.getUTCMilliseconds(), 3) + ' UTC';
|
|
|
|
const str_args = args.map(x => typeof x === "object" ? JSON.stringify(x) : x);
|
|
|
|
const log_entry = date_str + " " + name.toUpperCase() +
|
|
': ' + str_args.join("");
|
|
this._memory_log.push(log_entry);
|
|
|
|
// Don't let the log grow without bound
|
|
if (this._memory_log.length > 1000) {
|
|
this._memory_log.shift();
|
|
}
|
|
|
|
if (console[name] !== undefined) {
|
|
return console[name](...args);
|
|
}
|
|
return;
|
|
};
|
|
}
|
|
|
|
const proto = {
|
|
get_log: function Logger_get_log() {
|
|
return this._memory_log;
|
|
},
|
|
};
|
|
|
|
const methods = ['debug', 'log', 'info', 'warn', 'error'];
|
|
let i;
|
|
for (i = 0; i < methods.length; i += 1) {
|
|
proto[methods[i]] = make_logger_func(methods[i]);
|
|
}
|
|
|
|
return proto;
|
|
}());
|
|
|
|
const logger = new Logger();
|
|
|
|
exports.get_log = function blueslip_get_log() {
|
|
return logger.get_log();
|
|
};
|
|
|
|
const reported_errors = new Set();
|
|
const last_report_attempt = new Map();
|
|
|
|
function report_error(msg, stack, opts) {
|
|
opts = _.extend({show_ui_msg: false}, opts);
|
|
|
|
if (stack === undefined) {
|
|
stack = 'No stacktrace available';
|
|
}
|
|
|
|
if (page_params.debug_mode) {
|
|
// In development, we display blueslip errors in the web UI,
|
|
// to make them hard to miss.
|
|
blueslip_stacktrace.display_stacktrace(msg, stack);
|
|
}
|
|
|
|
const key = ':' + msg + stack;
|
|
if (reported_errors.has(key)
|
|
|| last_report_attempt.has(key)
|
|
// Only try to report a given error once every 5 minutes
|
|
&& Date.now() - last_report_attempt.get(key) <= 60 * 5 * 1000) {
|
|
return;
|
|
}
|
|
|
|
last_report_attempt.set(key, Date.now());
|
|
|
|
// TODO: If an exception gets thrown before we setup ajax calls
|
|
// to include the CSRF token, our ajax call will fail. The
|
|
// elegant thing to do in that case is to either wait until that
|
|
// setup is done or do it ourselves and then retry.
|
|
//
|
|
// Important: We don't use channel.js here so that exceptions
|
|
// always make it to the server even if reload_state.is_in_progress.
|
|
$.ajax({
|
|
type: 'POST',
|
|
url: '/json/report/error',
|
|
dataType: 'json',
|
|
data: {
|
|
message: msg,
|
|
stacktrace: stack,
|
|
ui_message: opts.show_ui_msg,
|
|
more_info: JSON.stringify(opts.more_info),
|
|
href: window.location.href,
|
|
user_agent: window.navigator.userAgent,
|
|
log: logger.get_log().join("\n"),
|
|
},
|
|
timeout: 3 * 1000,
|
|
success: function () {
|
|
reported_errors.add(key);
|
|
if (opts.show_ui_msg && ui_report !== undefined) {
|
|
// There are a few races here (and below in the error
|
|
// callback):
|
|
// 1) The ui_report module or something it requires might
|
|
// not have been compiled or initialized yet.
|
|
// 2) The DOM might not be ready yet and so fetching
|
|
// the #home-error div might fail.
|
|
|
|
// For (1) we just don't show the message if the ui
|
|
// hasn't been loaded yet. The user will probably
|
|
// get another error once it does. We can't solve
|
|
// (2) by using $(document).ready because the
|
|
// callback never gets called (I think what's going
|
|
// on here is if the exception was raised by a
|
|
// function that was called as a result of the DOM
|
|
// becoming ready then the internal state of jQuery
|
|
// gets messed up and our callback never gets
|
|
// invoked). In any case, it will pretty clear that
|
|
// something is wrong with the page and the user will
|
|
// probably try to reload anyway.
|
|
ui_report.message("Oops. It seems something has gone wrong. " +
|
|
"The error has been reported to the fine " +
|
|
"folks at Zulip, but, in the mean time, " +
|
|
"please try reloading the page.",
|
|
$("#home-error"), "alert-error");
|
|
}
|
|
},
|
|
error: function () {
|
|
if (opts.show_ui_msg && ui_report !== undefined) {
|
|
ui_report.message("Oops. It seems something has gone wrong. " +
|
|
"Please try reloading the page.",
|
|
$("#home-error"), "alert-error");
|
|
}
|
|
},
|
|
});
|
|
|
|
if (page_params.save_stacktraces) {
|
|
// Save the stacktrace so it can be examined even in
|
|
// development servers. (N.B. This assumes you have set DEBUG
|
|
// = False on your development server, or else this code path
|
|
// won't execute to begin with -- useful for testing
|
|
// (un)minification.)
|
|
window.last_stacktrace = stack;
|
|
}
|
|
}
|
|
|
|
function BlueslipError(msg, more_info) {
|
|
// One can't subclass Error normally so we have to play games
|
|
// with setting __proto__
|
|
const self = new Error(msg);
|
|
self.name = "BlueslipError";
|
|
|
|
// Indirect access to __proto__ keeps jslint quiet
|
|
const proto = '__proto__';
|
|
self[proto] = BlueslipError.prototype;
|
|
|
|
if (more_info !== undefined) {
|
|
self.more_info = more_info;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
BlueslipError.prototype = Object.create(Error.prototype);
|
|
|
|
exports.exception_msg = function blueslip_exception_msg(ex) {
|
|
let message = ex.message;
|
|
if (ex.hasOwnProperty('fileName')) {
|
|
message += " at " + ex.fileName;
|
|
if (ex.hasOwnProperty('lineNumber')) {
|
|
message += ":" + ex.lineNumber;
|
|
}
|
|
}
|
|
return message;
|
|
};
|
|
|
|
$(window).on('error', function (event) {
|
|
const ex = event.originalEvent.error;
|
|
if (!ex || ex instanceof BlueslipError) {
|
|
return;
|
|
}
|
|
const message = exports.exception_msg(ex);
|
|
report_error(message, ex.stack);
|
|
});
|
|
|
|
function build_arg_list(msg, more_info) {
|
|
const args = [msg];
|
|
if (more_info !== undefined) {
|
|
args.push("\nAdditional information: ", more_info);
|
|
}
|
|
return args;
|
|
}
|
|
|
|
exports.debug = function blueslip_debug(msg, more_info) {
|
|
const args = build_arg_list(msg, more_info);
|
|
logger.debug(...args);
|
|
};
|
|
|
|
exports.log = function blueslip_log(msg, more_info) {
|
|
const args = build_arg_list(msg, more_info);
|
|
logger.log(...args);
|
|
};
|
|
|
|
exports.info = function blueslip_info(msg, more_info) {
|
|
const args = build_arg_list(msg, more_info);
|
|
logger.info(...args);
|
|
};
|
|
|
|
exports.warn = function blueslip_warn(msg, more_info) {
|
|
const args = build_arg_list(msg, more_info);
|
|
logger.warn(...args);
|
|
if (page_params.debug_mode) {
|
|
console.trace();
|
|
}
|
|
};
|
|
|
|
exports.error = function blueslip_error(msg, more_info, stack) {
|
|
if (stack === undefined) {
|
|
stack = Error().stack;
|
|
}
|
|
const args = build_arg_list(msg, more_info);
|
|
logger.error(...args);
|
|
report_error(msg, stack, {more_info: more_info});
|
|
|
|
if (page_params.debug_mode) {
|
|
throw new BlueslipError(msg, more_info);
|
|
}
|
|
};
|
|
|
|
exports.fatal = function blueslip_fatal(msg, more_info) {
|
|
report_error(msg, Error().stack, {more_info: more_info});
|
|
throw new BlueslipError(msg, more_info);
|
|
};
|
|
|
|
exports.timings = new Map();
|
|
|
|
exports.start_timing = function (label) {
|
|
const t1 = performance.now();
|
|
|
|
return function () {
|
|
const t2 = performance.now();
|
|
const elapsed = t2 - t1;
|
|
exports.timings.set(label, elapsed);
|
|
};
|
|
};
|
|
|
|
// Produces an easy-to-read preview on an HTML element. Currently
|
|
// only used for including in error report emails; be sure to discuss
|
|
// with other developers before using it in a user-facing context
|
|
// because it is not XSS-safe.
|
|
exports.preview_node = function (node) {
|
|
if (node.constructor === jQuery) {
|
|
node = node[0];
|
|
}
|
|
|
|
const tag = node.tagName.toLowerCase();
|
|
const className = node.className.length ? node.className : false;
|
|
const id = node.id.length ? node.id : false;
|
|
|
|
const node_preview = "<" + tag +
|
|
(id ? " id='" + id + "'" : "") +
|
|
(className ? " class='" + className + "'" : "") +
|
|
"></" + tag + ">";
|
|
|
|
return node_preview;
|
|
};
|
|
|
|
window.blueslip = exports;
|