mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			284 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			8.5 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.
 | 
						|
 | 
						|
import $ from "jquery";
 | 
						|
 | 
						|
import * as blueslip_stacktrace from "./blueslip_stacktrace";
 | 
						|
import {page_params} from "./page_params";
 | 
						|
import * as ui_report from "./ui_report";
 | 
						|
 | 
						|
if (Error.stackTraceLimit !== undefined) {
 | 
						|
    Error.stackTraceLimit = 100000;
 | 
						|
}
 | 
						|
 | 
						|
function pad(num, width) {
 | 
						|
    return num.toString().padStart(width, "0");
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
        }
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
class Logger {
 | 
						|
    _memory_log = [];
 | 
						|
 | 
						|
    get_log() {
 | 
						|
        return this._memory_log;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
for (const name of ["debug", "log", "info", "warn", "error"]) {
 | 
						|
    Logger.prototype[name] = make_logger_func(name);
 | 
						|
}
 | 
						|
 | 
						|
const logger = new Logger();
 | 
						|
 | 
						|
export function get_log() {
 | 
						|
    return logger.get_log();
 | 
						|
}
 | 
						|
 | 
						|
const reported_errors = new Set();
 | 
						|
const last_report_attempt = new Map();
 | 
						|
 | 
						|
function report_error(
 | 
						|
    msg,
 | 
						|
    stack = "No stacktrace available",
 | 
						|
    {show_ui_msg = false, more_info} = {},
 | 
						|
) {
 | 
						|
    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: show_ui_msg,
 | 
						|
            more_info: JSON.stringify(more_info),
 | 
						|
            href: window.location.href,
 | 
						|
            user_agent: window.navigator.userAgent,
 | 
						|
            log: logger.get_log().join("\n"),
 | 
						|
        },
 | 
						|
        timeout: 3 * 1000,
 | 
						|
        success() {
 | 
						|
            reported_errors.add(key);
 | 
						|
            if (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.client_error(
 | 
						|
                    "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"),
 | 
						|
                );
 | 
						|
            }
 | 
						|
        },
 | 
						|
        error() {
 | 
						|
            if (show_ui_msg && ui_report !== undefined) {
 | 
						|
                ui_report.client_error(
 | 
						|
                    "Oops.  It seems something has gone wrong. Please try reloading the page.",
 | 
						|
                    $("#home-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;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class BlueslipError extends Error {
 | 
						|
    name = "BlueslipError";
 | 
						|
 | 
						|
    constructor(msg, more_info) {
 | 
						|
        super(msg);
 | 
						|
        if (more_info !== undefined) {
 | 
						|
            this.more_info = more_info;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
export function exception_msg(ex) {
 | 
						|
    let message = ex.message;
 | 
						|
    if (ex.fileName !== undefined) {
 | 
						|
        message += " at " + ex.fileName;
 | 
						|
        if (ex.lineNumber !== undefined) {
 | 
						|
            message += ":" + ex.lineNumber;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return message;
 | 
						|
}
 | 
						|
 | 
						|
$(window).on("error", (event) => {
 | 
						|
    const ex = event.originalEvent.error;
 | 
						|
    if (!ex || ex instanceof BlueslipError) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    const message = 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;
 | 
						|
}
 | 
						|
 | 
						|
export function debug(msg, more_info) {
 | 
						|
    const args = build_arg_list(msg, more_info);
 | 
						|
    logger.debug(...args);
 | 
						|
}
 | 
						|
 | 
						|
export function log(msg, more_info) {
 | 
						|
    const args = build_arg_list(msg, more_info);
 | 
						|
    logger.log(...args);
 | 
						|
}
 | 
						|
 | 
						|
export function info(msg, more_info) {
 | 
						|
    const args = build_arg_list(msg, more_info);
 | 
						|
    logger.info(...args);
 | 
						|
}
 | 
						|
 | 
						|
export function warn(msg, more_info) {
 | 
						|
    const args = build_arg_list(msg, more_info);
 | 
						|
    logger.warn(...args);
 | 
						|
    if (page_params.debug_mode) {
 | 
						|
        console.trace();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
export function error(msg, more_info, stack = new Error("dummy").stack) {
 | 
						|
    const args = build_arg_list(msg, more_info);
 | 
						|
    logger.error(...args);
 | 
						|
    report_error(msg, stack, {more_info});
 | 
						|
 | 
						|
    if (page_params.debug_mode) {
 | 
						|
        throw new BlueslipError(msg, more_info);
 | 
						|
    }
 | 
						|
 | 
						|
    // This function returns to its caller in production!  To raise a
 | 
						|
    // fatal error even in production, use throw new Error(…) instead.
 | 
						|
}
 | 
						|
 | 
						|
export const timings = new Map();
 | 
						|
 | 
						|
export function measure_time(label, f) {
 | 
						|
    const t1 = performance.now();
 | 
						|
    const ret = f();
 | 
						|
    const t2 = performance.now();
 | 
						|
    const elapsed = t2 - t1;
 | 
						|
    timings.set(label, elapsed);
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
// 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.
 | 
						|
export function preview_node(node) {
 | 
						|
    if (node instanceof $) {
 | 
						|
        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;
 | 
						|
}
 |