2020-02-06 03:21:18 +01:00
|
|
|
/* eslint-disable no-console */
|
|
|
|
|
2017-11-16 19:51:44 +01:00
|
|
|
// System documented in https://zulip.readthedocs.io/en/latest/subsystems/logging.html
|
2017-09-24 18:43:30 +02:00
|
|
|
|
2013-03-13 00:15:14 +01:00
|
|
|
// This must be included before the first call to $(document).ready
|
|
|
|
// in order to be able to report exceptions that occur during their
|
|
|
|
// execution.
|
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import $ from "jquery";
|
2021-03-11 05:43:45 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip_stacktrace from "./blueslip_stacktrace";
|
|
|
|
import * as ui_report from "./ui_report";
|
2019-06-20 11:59:55 +02:00
|
|
|
|
2013-08-15 18:54:17 +02:00
|
|
|
if (Error.stackTraceLimit !== undefined) {
|
|
|
|
Error.stackTraceLimit = 100000;
|
|
|
|
}
|
|
|
|
|
2020-07-23 01:10:33 +02:00
|
|
|
function pad(num, width) {
|
|
|
|
return num.toString().padStart(width, "0");
|
2013-10-22 21:11:13 +02:00
|
|
|
}
|
|
|
|
|
2020-07-23 01:10:33 +02:00
|
|
|
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();
|
2013-11-14 19:54:51 +01:00
|
|
|
}
|
2013-10-22 21:11:13 +02:00
|
|
|
|
2020-07-23 01:10:33 +02:00
|
|
|
if (console[name] !== undefined) {
|
|
|
|
return console[name](...args);
|
|
|
|
}
|
2013-10-22 21:11:13 +02:00
|
|
|
};
|
2020-07-23 01:10:33 +02:00
|
|
|
}
|
2013-10-22 21:11:13 +02:00
|
|
|
|
2020-07-23 01:10:33 +02:00
|
|
|
class Logger {
|
|
|
|
_memory_log = [];
|
|
|
|
|
|
|
|
get_log() {
|
|
|
|
return this._memory_log;
|
2013-10-22 21:11:13 +02:00
|
|
|
}
|
2020-07-23 01:10:33 +02:00
|
|
|
}
|
2013-10-22 21:11:13 +02:00
|
|
|
|
2020-07-23 01:10:33 +02:00
|
|
|
for (const name of ["debug", "log", "info", "warn", "error"]) {
|
|
|
|
Logger.prototype[name] = make_logger_func(name);
|
|
|
|
}
|
2013-10-22 21:11:13 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const logger = new Logger();
|
2013-10-22 21:11:13 +02:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function get_log() {
|
2013-10-22 21:11:13 +02:00
|
|
|
return logger.get_log();
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2013-10-22 21:11:13 +02:00
|
|
|
|
2020-02-12 03:15:19 +01:00
|
|
|
const reported_errors = new Set();
|
|
|
|
const last_report_attempt = new Map();
|
2018-05-22 01:50:25 +02:00
|
|
|
|
2013-03-12 21:19:49 +01:00
|
|
|
function report_error(msg, stack, opts) {
|
2020-07-16 22:40:18 +02:00
|
|
|
opts = {show_ui_msg: false, ...opts};
|
2013-03-12 21:19:49 +01:00
|
|
|
|
|
|
|
if (stack === undefined) {
|
2020-07-15 01:29:15 +02:00
|
|
|
stack = "No stacktrace available";
|
2013-03-11 20:54:27 +01:00
|
|
|
}
|
|
|
|
|
2017-09-15 22:33:43 +02:00
|
|
|
if (page_params.debug_mode) {
|
|
|
|
// In development, we display blueslip errors in the web UI,
|
|
|
|
// to make them hard to miss.
|
2019-06-20 11:59:55 +02:00
|
|
|
blueslip_stacktrace.display_stacktrace(msg, stack);
|
2017-09-15 22:33:43 +02:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const key = ":" + msg + stack;
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
reported_errors.has(key) ||
|
|
|
|
(last_report_attempt.has(key) &&
|
2013-03-27 18:12:26 +01:00
|
|
|
// Only try to report a given error once every 5 minutes
|
2020-07-15 00:34:28 +02:00
|
|
|
Date.now() - last_report_attempt.get(key) <= 60 * 5 * 1000)
|
|
|
|
) {
|
2013-03-11 20:58:55 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-12 03:15:19 +01:00
|
|
|
last_report_attempt.set(key, Date.now());
|
2013-03-27 18:12:26 +01:00
|
|
|
|
2013-03-13 00:15:14 +01:00
|
|
|
// 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.
|
2020-02-14 00:26:34 +01:00
|
|
|
//
|
|
|
|
// Important: We don't use channel.js here so that exceptions
|
|
|
|
// always make it to the server even if reload_state.is_in_progress.
|
2013-03-11 20:54:27 +01:00
|
|
|
$.ajax({
|
2020-07-15 01:29:15 +02:00
|
|
|
type: "POST",
|
|
|
|
url: "/json/report/error",
|
|
|
|
dataType: "json",
|
2019-10-28 23:39:19 +01:00
|
|
|
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,
|
2020-07-20 22:18:43 +02:00
|
|
|
success() {
|
2020-02-12 03:15:19 +01:00
|
|
|
reported_errors.add(key);
|
2018-08-03 21:56:42 +02:00
|
|
|
if (opts.show_ui_msg && ui_report !== undefined) {
|
2013-03-12 22:37:13 +01:00
|
|
|
// There are a few races here (and below in the error
|
|
|
|
// callback):
|
2018-08-03 21:56:42 +02:00
|
|
|
// 1) The ui_report module or something it requires might
|
2013-03-12 22:37:13 +01:00
|
|
|
// not have been compiled or initialized yet.
|
|
|
|
// 2) The DOM might not be ready yet and so fetching
|
|
|
|
// the #home-error div might fail.
|
|
|
|
|
2013-05-16 22:47:08 +02:00
|
|
|
// 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
|
2017-10-05 16:01:50 +02:00
|
|
|
// (2) by using $(document).ready because the
|
2013-03-12 22:37:13 +01:00
|
|
|
// 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.
|
2021-01-27 08:16:28 +01:00
|
|
|
ui_report.client_error(
|
2020-07-15 00:34:28 +02:00
|
|
|
"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"),
|
|
|
|
);
|
2013-03-11 23:34:14 +01:00
|
|
|
}
|
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
error() {
|
2018-08-03 21:56:42 +02:00
|
|
|
if (opts.show_ui_msg && ui_report !== undefined) {
|
2021-01-27 08:16:28 +01:00
|
|
|
ui_report.client_error(
|
2020-10-07 13:17:55 +02:00
|
|
|
"Oops. It seems something has gone wrong. Please try reloading the page.",
|
2020-07-15 00:34:28 +02:00
|
|
|
$("#home-error"),
|
|
|
|
);
|
2013-03-11 23:34:14 +01:00
|
|
|
}
|
2019-10-28 23:39:19 +01:00
|
|
|
},
|
2013-03-11 20:54:27 +01:00
|
|
|
});
|
2013-07-05 23:07:46 +02:00
|
|
|
|
2016-07-19 06:44:48 +02:00
|
|
|
if (page_params.save_stacktraces) {
|
2013-11-12 18:20:32 +01:00
|
|
|
// 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.)
|
2013-07-05 23:07:46 +02:00
|
|
|
window.last_stacktrace = stack;
|
|
|
|
}
|
2013-03-11 20:54:27 +01:00
|
|
|
}
|
|
|
|
|
2020-07-23 00:54:43 +02:00
|
|
|
class BlueslipError extends Error {
|
|
|
|
name = "BlueslipError";
|
2013-04-22 22:04:51 +02:00
|
|
|
|
2020-07-23 00:54:43 +02:00
|
|
|
constructor(msg, more_info) {
|
|
|
|
super(msg);
|
|
|
|
if (more_info !== undefined) {
|
|
|
|
this.more_info = more_info;
|
|
|
|
}
|
2013-04-04 21:29:14 +02:00
|
|
|
}
|
2013-03-12 22:24:45 +01:00
|
|
|
}
|
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function exception_msg(ex) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let message = ex.message;
|
2020-05-27 00:50:02 +02:00
|
|
|
if (ex.fileName !== undefined) {
|
2014-02-10 22:48:49 +01:00
|
|
|
message += " at " + ex.fileName;
|
2020-05-27 00:50:02 +02:00
|
|
|
if (ex.lineNumber !== undefined) {
|
2014-02-10 22:48:49 +01:00
|
|
|
message += ":" + ex.lineNumber;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return message;
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2014-02-10 22:48:49 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
$(window).on("error", (event) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const ex = event.originalEvent.error;
|
2019-07-10 02:15:07 +02:00
|
|
|
if (!ex || ex instanceof BlueslipError) {
|
2019-07-09 04:09:53 +02:00
|
|
|
return;
|
2013-03-12 22:37:13 +01:00
|
|
|
}
|
2021-03-16 23:38:59 +01:00
|
|
|
const message = exception_msg(ex);
|
2019-07-09 04:09:53 +02:00
|
|
|
report_error(message, ex.stack);
|
|
|
|
});
|
2013-03-12 22:37:13 +01:00
|
|
|
|
2013-10-22 19:59:13 +02:00
|
|
|
function build_arg_list(msg, more_info) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = [msg];
|
2013-04-04 21:29:14 +02:00
|
|
|
if (more_info !== undefined) {
|
2013-10-22 19:59:13 +02:00
|
|
|
args.push("\nAdditional information: ", more_info);
|
2013-04-04 21:29:14 +02:00
|
|
|
}
|
2013-10-22 19:59:13 +02:00
|
|
|
return args;
|
|
|
|
}
|
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function debug(msg, more_info) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = build_arg_list(msg, more_info);
|
2020-02-12 01:35:16 +01:00
|
|
|
logger.debug(...args);
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2013-10-30 15:16:36 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function log(msg, more_info) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = build_arg_list(msg, more_info);
|
2020-02-12 01:35:16 +01:00
|
|
|
logger.log(...args);
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2013-03-11 17:25:46 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function info(msg, more_info) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = build_arg_list(msg, more_info);
|
2020-02-12 01:35:16 +01:00
|
|
|
logger.info(...args);
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2013-03-11 17:25:46 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function warn(msg, more_info) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = build_arg_list(msg, more_info);
|
2020-02-12 01:35:16 +01:00
|
|
|
logger.warn(...args);
|
2013-03-25 23:26:14 +01:00
|
|
|
if (page_params.debug_mode) {
|
2013-03-11 17:25:46 +01:00
|
|
|
console.trace();
|
|
|
|
}
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2013-03-11 17:25:46 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function error(msg, more_info, stack) {
|
2017-09-15 22:33:43 +02:00
|
|
|
if (stack === undefined) {
|
2020-12-22 11:23:33 +01:00
|
|
|
stack = new Error("dummy").stack;
|
2017-09-15 22:33:43 +02:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = build_arg_list(msg, more_info);
|
2020-02-12 01:35:16 +01:00
|
|
|
logger.error(...args);
|
2020-07-20 22:18:43 +02:00
|
|
|
report_error(msg, stack, {more_info});
|
2017-09-15 22:33:43 +02:00
|
|
|
|
2013-03-25 23:26:14 +01:00
|
|
|
if (page_params.debug_mode) {
|
2013-04-04 21:29:14 +02:00
|
|
|
throw new BlueslipError(msg, more_info);
|
2013-03-11 17:25:46 +01:00
|
|
|
}
|
|
|
|
|
2020-09-24 07:56:29 +02:00
|
|
|
// This function returns to its caller in production! To raise a
|
|
|
|
// fatal error even in production, use throw new Error(…) instead.
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2013-03-11 17:25:46 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export const timings = new Map();
|
2020-01-15 15:05:44 +01:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
export function measure_time(label, f) {
|
2020-01-15 15:05:44 +01:00
|
|
|
const t1 = performance.now();
|
2021-02-04 00:30:50 +01:00
|
|
|
const ret = f();
|
2021-01-31 13:57:52 +01:00
|
|
|
const t2 = performance.now();
|
|
|
|
const elapsed = t2 - t1;
|
2021-03-16 23:38:59 +01:00
|
|
|
timings.set(label, elapsed);
|
2021-02-04 00:30:50 +01:00
|
|
|
return ret;
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|
2020-01-15 15:05:44 +01:00
|
|
|
|
2017-06-03 19:32:41 +02:00
|
|
|
// 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.
|
2021-03-16 23:38:59 +01:00
|
|
|
export function preview_node(node) {
|
2021-03-11 05:43:45 +01:00
|
|
|
if (node instanceof $) {
|
2017-06-03 19:32:41 +02:00
|
|
|
node = node[0];
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const tag = node.tagName.toLowerCase();
|
|
|
|
const className = node.className.length ? node.className : false;
|
|
|
|
const id = node.id.length ? node.id : false;
|
2017-06-03 19:32:41 +02:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
const node_preview =
|
|
|
|
"<" +
|
|
|
|
tag +
|
|
|
|
(id ? " id='" + id + "'" : "") +
|
|
|
|
(className ? " class='" + className + "'" : "") +
|
|
|
|
"></" +
|
|
|
|
tag +
|
|
|
|
">";
|
2017-06-03 19:32:41 +02:00
|
|
|
|
2019-10-28 23:39:19 +01:00
|
|
|
return node_preview;
|
2021-03-16 23:38:59 +01:00
|
|
|
}
|