Files
zoneminder/web/js/logger.js
Isaac Connor 1c878be614 refs #4981 log a stack trace from the error-level JS loggers
Now that zmError/zmFatal/zmPanic no longer shadow the native Error
constructor, build a real Error from the message and report its stack
via logReport. This records where the log call originated instead of
just the bare message. An Error passed in directly is used as-is.
The console output and alert() behaviour are unchanged.
2026-07-11 11:48:08 -07:00

249 lines
8.5 KiB
JavaScript

//
// ZoneMinder logger javascript file, $Date: 2011-05-27 22:24:17 +0100 (Fri, 27 May 2011) $, $Revision: 3374 $
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
$j.ajaxSetup({timeout: AJAX_TIMEOUT});
const reportLogs = (typeof(ZM_LOG_INJECT) !== 'undefined') && ((ZM_LOG_INJECT !== '0') || canEdit["System"]);
if (!window.console) {
window.console =
{
init: function() {},
log: function() {},
debug: function() {},
info: function() {},
warn: function() {},
error: function() {}
};
} else if (!console.debug) {
// IE8 has console but doesn't have console.debug so lets alias it.
console.debug = console.log;
}
// Browser extensions (ad blockers, password managers, etc.) inject scripts
// into every page; their errors and CSP violations are not ZoneMinder's and
// would otherwise spam the log. Ignore anything sourced from an extension,
// unless ZM_LOG_BROWSER_EXTENSIONS is enabled (useful to detect a plugin
// touching the ZoneMinder tab).
// ZM_LOG_BROWSER_EXTENSIONS is emitted as the string '0'/'1' (boolean config),
// matching the ZM_LOG_INJECT convention above.
const logExtensionSources =
(typeof ZM_LOG_BROWSER_EXTENSIONS !== 'undefined') && (ZM_LOG_BROWSER_EXTENSIONS !== '0');
function isExtensionSource(url) {
return (typeof url === 'string') &&
/^(moz-extension|chrome-extension|safari-web-extension|safari-extension|webkit-masked-url)(:|$)/.test(url);
}
window.onerror = function(message, url, line, col, error) {
if (!logExtensionSources && isExtensionSource(url)) return;
if (!message || message === 'Script error.') {
message = 'Script error (no details available, possibly cross-origin)';
}
if (error && error.stack) {
message += '\n' + error.stack;
}
if (col) line = line + ':' + col;
logReport("ERR", message, url, line);
};
window.addEventListener("securitypolicyviolation", function logCSP(evt) {
if (!logExtensionSources && (isExtensionSource(evt.sourceFile) || isExtensionSource(evt.blockedURI))) return;
const level = evt.disposition == "enforce" ? "ERR" : "DBG";
let message = evt.blockedURI + " violated CSP " + evt.violatedDirective;
if ( evt.sample ) message += " (Sample: " + evt.sample + ")";
logReport(level, message, evt.sourceFile, evt.lineNumber);
});
function logReport( level, message, file, line ) {
if (!reportLogs) return;
if (message === undefined || message === null) {
message = 'Unknown error';
}
/* eslint-disable no-caller */
try {
if (arguments.callee && arguments.callee.caller && arguments.callee.caller.caller) {
const callerName = arguments.callee.caller.caller.name;
if (callerName) {
message += ' - '+callerName+'()';
}
}
} catch {
// arguments.callee unavailable in strict mode
}
const data = {
view: 'request',
request: 'log',
task: 'create',
level: level,
message: encodeURIComponent(message),
browser: browserInfo()
};
if (file) {
data.file = file;
} else if (location.search) {
//location.search is the querystring part, so ?blah=blah but there is almost never any value to this
data.file = location.search;
}
if (line) data.line = line;
$j.post(thisUrl, data, null, 'json');
}
// These logging helpers are named with a "zm" prefix so they do not collide
// with the native JS Error constructor or with functions of the same name in
// third-party libraries (hls.js, jquery, moment, bootstrap, ...). A bare
// global Error() in particular used to shadow window.Error, breaking every
// `new Error()` in bundled libraries. See issue #4981.
// Build a real Error so we can log a stack trace with the message. Now that we
// no longer shadow the native constructor this captures where the call
// originated, which the bare-message logging could not.
function stackFor(message) {
const err = (message instanceof Error) ? message : new Error(message);
return err.stack ? err.stack : String(message);
}
function zmPanic(message) {
console.error(message);
logReport("PNC", stackFor(message));
alert("PANIC: "+message);
}
function zmFatal(message) {
console.error(message);
logReport("FAT", stackFor(message));
alert("FATAL: "+message);
}
function zmError(message) {
console.error(message);
logReport("ERR", stackFor(message));
}
function zmWarning(message) {
console.warn(message);
logReport("WAR", message);
}
function zmInfo(message) {
console.info(message);
logReport("INF", message);
}
function zmDebug(message) {
console.debug(message);
//logReport("DBG", message);
}
function zmDump(value, label) {
if (label) console.debug(label+" => ");
console.debug(value);
}
/*
JQuery has deprecated its browser object. This function implements our own
browser object. It strikes a compromise between importing a full browser
detection js library and using the js navigator object in its umodified form.
This function derived from the following sample on stackoverflow:
https://stackoverflow.com/a/11219680
*/
function browserInfo() {
var browser = {};
var nAgt = navigator.userAgent;
var browserName = navigator.appName;
var fullVersion = ''+parseFloat(navigator.appVersion);
var majorVersion = parseInt(navigator.appVersion, 10);
var nameOffset;
var verOffset;
var ix;
// In Opera, the true version is after "Opera" or after "Version"
if ((verOffset=nAgt.indexOf("Opera")) != -1) {
browserName = "Opera";
fullVersion = nAgt.substring(verOffset+6);
if ((verOffset=nAgt.indexOf("Version")) != -1) {
fullVersion = nAgt.substring(verOffset+8);
}
// In MSIE, the true version is after "MSIE" in userAgent
} else if ((verOffset=nAgt.indexOf("MSIE")) != -1) {
browserName = "Microsoft Internet Explorer";
fullVersion = nAgt.substring(verOffset+5);
// In Chrome, the true version is after "Chrome"
} else if ((verOffset=nAgt.indexOf("Chrome")) != -1) {
browserName = "Chrome";
fullVersion = nAgt.substring(verOffset+7);
// In Safari, the true version is after "Safari" or after "Version"
} else if ((verOffset=nAgt.indexOf("Safari")) != -1) {
browserName = "Safari";
fullVersion = nAgt.substring(verOffset+7);
if ((verOffset=nAgt.indexOf("Version")) != -1) {
fullVersion = nAgt.substring(verOffset+8);
}
// In Firefox, the true version is after "Firefox"
} else if ((verOffset=nAgt.indexOf("Firefox")) != -1) {
browserName = "Firefox";
fullVersion = nAgt.substring(verOffset+8);
// In most other browsers, "name/version" is at the end of userAgent
} else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < (verOffset=nAgt.lastIndexOf('/')) ) {
browserName = nAgt.substring(nameOffset, verOffset);
fullVersion = nAgt.substring(verOffset+1);
if (browserName.toLowerCase()==browserName.toUpperCase()) {
browserName = navigator.appName;
}
}
// trim the fullVersion string at semicolon/space if present
if ((ix=fullVersion.indexOf(";")) != -1) {
fullVersion=fullVersion.substring(0, ix);
}
if ((ix=fullVersion.indexOf(" ")) != -1) {
fullVersion=fullVersion.substring(0, ix);
}
var majorVersion = parseInt(''+fullVersion, 10);
if (isNaN(majorVersion)) {
fullVersion = ''+parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion, 10);
}
// OSName variable is set as follows:
// "Windows" for all versions of Windows
// "MacOS" for all versions of Macintosh OS
// "Linux" for all versions of Linux
// "UNIX" for all other UNIX flavors
// "Unknown OS" indicates failure to detect the OS
var OSName="Unknown OS";
if (navigator.appVersion.indexOf("Win") != -1) OSName="Windows";
if (navigator.appVersion.indexOf("Mac") != -1) OSName="MacOS";
if (navigator.appVersion.indexOf("X11") != -1) OSName="UNIX";
if (navigator.appVersion.indexOf("Linux") != -1) OSName="Linux";
browser.name = browserName;
browser.version = fullVersion;
browser.platform = OSName;
return browser;
}