Add HTML-based console viewer for easier real-time debug output
Since 1) debug output logging via the prefs isn't necessarily possible for startup errors in Standalone, 2) real-time output is prohibitively slow and has a miniscule scrollback buffer on Windows unless you use a Cygwin or Git terminal, and 3) copying/pasting/emailing was annoying anyway, make -ZoteroDebug open a popup window that shows errors and debug output and allows submitting straight to the server with a Debug ID. This should replace the existing debug output viewer as well, but that's less of a priority. -ZoteroDebugText or the debug.log pref can still be used to dump to the terminal.
This commit is contained in:
parent
9c0befceeb
commit
f44264cd4d
139
chrome/content/zotero/debugViewer.html
Normal file
139
chrome/content/zotero/debugViewer.html
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Debug Output</title>
|
||||||
|
<script src="include.js"></script>
|
||||||
|
<script src="debugViewer.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
background: lightgrey;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
height: 18px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > * {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
width: 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-id {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-result {
|
||||||
|
line-height: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-result-copy-id {
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-error {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
margin-top: 38px;
|
||||||
|
padding: 10px 9px;
|
||||||
|
font-family: Monaco, Consolas, Inconsolata, monospace;
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#errors {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px lightgray solid;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
CSS tooltip, adapted from http://stackoverflow.com/a/25836471
|
||||||
|
*/
|
||||||
|
[data-tooltip] {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
[data-tooltip]:before {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
top: 100%;
|
||||||
|
margin-top: 6px;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
[data-tooltip]:after {
|
||||||
|
content: '';
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-color: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -6px;
|
||||||
|
|
||||||
|
top: 100%;
|
||||||
|
border-width: 0 6px 6px;
|
||||||
|
border-bottom-color: #000;
|
||||||
|
}
|
||||||
|
/* Show the tooltip when hovering */
|
||||||
|
[data-tooltip]:hover:before,
|
||||||
|
[data-tooltip]:hover:after {
|
||||||
|
display: block;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<button id="submit-button" onclick="submit(this)" disabled>Submit…</button>
|
||||||
|
<button id="clear-button" onclick="clearOutput(this)" disabled>Clear</button>
|
||||||
|
<progress id="submit-progress" hidden></progress>
|
||||||
|
<p id="submit-result" hidden>
|
||||||
|
Submitted with Debug ID <span id="debug-id"></span>
|
||||||
|
<span id="submit-result-copy-id" onclick="copyIDToClipboard(this)">📋</span>
|
||||||
|
</p>
|
||||||
|
<p id="submit-error" hidden></p>
|
||||||
|
</header>
|
||||||
|
<div id="content">
|
||||||
|
<div id="errors"></div>
|
||||||
|
<div id="output"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
189
chrome/content/zotero/debugViewer.js
Normal file
189
chrome/content/zotero/debugViewer.js
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var interval = 1000;
|
||||||
|
var intervalID;
|
||||||
|
var stopping = false;
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
updateErrors().then(function () {
|
||||||
|
if (stopping) return;
|
||||||
|
|
||||||
|
addInitialOutput();
|
||||||
|
Zotero.Debug.addConsoleViewerListener(addLine)
|
||||||
|
intervalID = setInterval(() => updateErrors(), interval);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
stopping = true;
|
||||||
|
if (intervalID) {
|
||||||
|
clearInterval(intervalID);
|
||||||
|
intervalID = null;
|
||||||
|
}
|
||||||
|
Zotero.Debug.removeConsoleViewerListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateErrors() {
|
||||||
|
return Zotero.getSystemInfo()
|
||||||
|
.then(function (sysInfo) {
|
||||||
|
if (stopping) return;
|
||||||
|
|
||||||
|
var errors = Zotero.getErrors(true);
|
||||||
|
var errorStr = errors.length ? errors.join('\n\n') + '\n\n' : '';
|
||||||
|
|
||||||
|
var scroll = atPageBottom();
|
||||||
|
|
||||||
|
document.getElementById('errors').textContent = errorStr + sysInfo;
|
||||||
|
|
||||||
|
// TODO: This doesn't seem to work for some reason -- when errors are logged, it doesn't stay
|
||||||
|
// at the bottom
|
||||||
|
if (scroll) {
|
||||||
|
scrollToPageBottom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addInitialOutput() {
|
||||||
|
Zotero.Debug.getConsoleViewerOutput().forEach(function (line) {
|
||||||
|
addLine(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLine(line) {
|
||||||
|
var scroll = atPageBottom()
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.textContent = line;
|
||||||
|
var output = document.getElementById('output');
|
||||||
|
output.appendChild(p);
|
||||||
|
|
||||||
|
// If scrolled to the bottom of the page, stay there
|
||||||
|
if (scroll) {
|
||||||
|
scrollToPageBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('submit-button').removeAttribute('disabled');
|
||||||
|
document.getElementById('clear-button').removeAttribute('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
function atPageBottom() {
|
||||||
|
return (window.innerHeight + window.scrollY) >= document.body.offsetHeight - 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToPageBottom() {
|
||||||
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit(button) {
|
||||||
|
button.setAttribute('disabled', '');
|
||||||
|
clearSubmitStatus();
|
||||||
|
|
||||||
|
Components.utils.import("resource://zotero/config.js");
|
||||||
|
var url = ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1";
|
||||||
|
var output = document.getElementById('errors').textContent
|
||||||
|
+ "\n\n" + "=========================================================\n\n"
|
||||||
|
+ Array.from(document.getElementById('output').childNodes).map(p => p.textContent).join("\n\n");
|
||||||
|
var pm = document.getElementById('submit-progress');
|
||||||
|
pm.removeAttribute('hidden');
|
||||||
|
|
||||||
|
Zotero.HTTP.request(
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
compressBody: true,
|
||||||
|
body: output,
|
||||||
|
logBodyLength: 30,
|
||||||
|
timeout: 30000,
|
||||||
|
// Update progress meter
|
||||||
|
requestObserver: function (req) {
|
||||||
|
req.channel.notificationCallbacks = {
|
||||||
|
onProgress: function (request, context, progress, progressMax) {
|
||||||
|
if (!pm.value || progress > pm.value) {
|
||||||
|
pm.value = progress;
|
||||||
|
}
|
||||||
|
if (!pm.max || progressMax > pm.max) {
|
||||||
|
pm.max = progressMax;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// nsIInterfaceRequestor
|
||||||
|
getInterface: function (iid) {
|
||||||
|
try {
|
||||||
|
return this.QueryInterface(iid);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw Components.results.NS_NOINTERFACE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
QueryInterface: function(iid) {
|
||||||
|
if (iid.equals(Components.interfaces.nsISupports) ||
|
||||||
|
iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
|
||||||
|
iid.equals(Components.interfaces.nsIProgressEventSink)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
throw Components.results.NS_NOINTERFACE;
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(function (xmlhttp) {
|
||||||
|
var reported = xmlhttp.responseXML.getElementsByTagName('reported');
|
||||||
|
if (reported.length != 1) {
|
||||||
|
showSubmitError(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSubmitResult(reported[0].getAttribute('reportID'));
|
||||||
|
})
|
||||||
|
.catch(function (e) {
|
||||||
|
showSubmitError(e);
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.finally(function () {
|
||||||
|
pm.setAttribute('hidden', '');
|
||||||
|
button.removeAttribute('disabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSubmitResult(id) {
|
||||||
|
var elem = document.getElementById('submit-result');
|
||||||
|
elem.removeAttribute('hidden');
|
||||||
|
document.getElementById('debug-id').textContent = "D" + id;
|
||||||
|
var copyID = document.getElementById('submit-result-copy-id');
|
||||||
|
copyID.style.visibility = 'visible';
|
||||||
|
copyID.setAttribute('data-tooltip', 'Copy ID to Clipboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyIDToClipboard(elem) {
|
||||||
|
var id = document.getElementById('debug-id').textContent;
|
||||||
|
Components.classes["@mozilla.org/widget/clipboardhelper;1"]
|
||||||
|
.getService(Components.interfaces.nsIClipboardHelper)
|
||||||
|
.copyString(id);
|
||||||
|
elem.setAttribute('data-tooltip', 'Copied');
|
||||||
|
setTimeout(() => elem.style.visibility = 'hidden', 750);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSubmitError(e) {
|
||||||
|
var elem = document.getElementById('submit-error');
|
||||||
|
elem.removeAttribute('hidden');
|
||||||
|
elem.textContent = "Error submitting output";
|
||||||
|
Components.utils.reportError(e);
|
||||||
|
Zotero.debug(e, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSubmitStatus() {
|
||||||
|
document.getElementById('submit-result').setAttribute('hidden', '');
|
||||||
|
document.getElementById('submit-error').setAttribute('hidden', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearOutput(button) {
|
||||||
|
button.setAttribute('disabled', '');
|
||||||
|
document.getElementById('output').textContent = '';
|
||||||
|
clearSubmitStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', start);
|
||||||
|
window.addEventListener("unload", stop);
|
|
@ -25,19 +25,38 @@
|
||||||
|
|
||||||
|
|
||||||
Zotero.Debug = new function () {
|
Zotero.Debug = new function () {
|
||||||
var _console, _consolePref, _stackTrace, _store, _level, _lastTime, _output = [];
|
var _console, _stackTrace, _store, _level, _lastTime, _output = [];
|
||||||
var _slowTime = false;
|
var _slowTime = false;
|
||||||
var _colorOutput = false;
|
var _colorOutput = false;
|
||||||
|
var _consoleViewer = false;
|
||||||
|
var _consoleViewerQueue = [];
|
||||||
|
var _consoleViewerListener;
|
||||||
|
|
||||||
this.init = function (forceDebugLog) {
|
/**
|
||||||
_consolePref = Zotero.Prefs.get('debug.log');
|
* Initialize debug logging
|
||||||
_console = _consolePref || forceDebugLog;
|
*
|
||||||
if (_console && Zotero.isFx && !Zotero.isBookmarklet && (!Zotero.isWin || _consolePref)) {
|
* Debug logging can be set in several different ways:
|
||||||
|
*
|
||||||
|
* - via the debug.log pref in the client or connector
|
||||||
|
* - by enabling debug output logging in the Advanced prefs in the client
|
||||||
|
* - by passing -ZoteroDebug or -ZoteroDebugText on the command line
|
||||||
|
*
|
||||||
|
* In the client, debug.log and -ZoteroDebugText enable logging via the terminal, while -ZoteroDebug
|
||||||
|
* enables logging via an in-app HTML-based window.
|
||||||
|
*
|
||||||
|
* @param {Integer} [forceDebugLog = 0] - Force output even if pref disabled
|
||||||
|
* 2: window (-ZoteroDebug)
|
||||||
|
* 1: text console (-ZoteroDebugText)
|
||||||
|
* 0: disabled
|
||||||
|
*/
|
||||||
|
this.init = function (forceDebugLog = 0) {
|
||||||
|
_console = Zotero.Prefs.get('debug.log') || forceDebugLog == 1;
|
||||||
|
_consoleViewer = forceDebugLog == 2;
|
||||||
|
// When logging to the text console from the client on Mac/Linux, colorize output
|
||||||
|
if (_console && Zotero.isFx && !Zotero.isBookmarklet) {
|
||||||
_colorOutput = true;
|
_colorOutput = true;
|
||||||
}
|
|
||||||
if (_colorOutput) {
|
// Time threshold in ms above which intervals should be colored red in terminal output
|
||||||
// Time threshold in milliseconds above which intervals
|
|
||||||
// should be colored red in terminal output
|
|
||||||
_slowTime = Zotero.Prefs.get('debug.log.slowTime');
|
_slowTime = Zotero.Prefs.get('debug.log.slowTime');
|
||||||
}
|
}
|
||||||
_store = Zotero.Prefs.get('debug.store');
|
_store = Zotero.Prefs.get('debug.store');
|
||||||
|
@ -48,15 +67,23 @@ Zotero.Debug = new function () {
|
||||||
_stackTrace = Zotero.Prefs.get('debug.stackTrace');
|
_stackTrace = Zotero.Prefs.get('debug.stackTrace');
|
||||||
|
|
||||||
this.storing = _store;
|
this.storing = _store;
|
||||||
this.enabled = _console || _store;
|
this.updateEnabled();
|
||||||
|
|
||||||
if (Zotero.isStandalone) {
|
if (Zotero.isStandalone) {
|
||||||
Zotero.Prefs.set('browser.dom.window.dump.enabled', _console, true);
|
// Enable dump() from window (non-XPCOM) scopes when terminal or viewer logging is enabled.
|
||||||
|
// (These will always go to the terminal, even in viewer mode.)
|
||||||
|
Zotero.Prefs.set('browser.dom.window.dump.enabled', _console || _consoleViewer, true);
|
||||||
|
|
||||||
|
if (_consoleViewer) {
|
||||||
|
setTimeout(function () {
|
||||||
|
Zotero.openInViewer("chrome://zotero/content/debugViewer.html");
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log = function (message, level, stack) {
|
this.log = function (message, level, stack) {
|
||||||
if (!_console && !_store) {
|
if (!this.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,21 +145,23 @@ Zotero.Debug = new function () {
|
||||||
message += '\n' + Zotero.Debug.stackToString(stack);
|
message += '\n' + Zotero.Debug.stackToString(stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_console) {
|
if (_console || _consoleViewer) {
|
||||||
var output = 'zotero(' + level + ')' + deltaStr + ': ' + message;
|
var output = '(' + level + ')' + deltaStr + ': ' + message;
|
||||||
if(Zotero.isFx && !Zotero.isBookmarklet) {
|
if (Zotero.isFx && !Zotero.isBookmarklet) {
|
||||||
// On Windows, where the text console (-console) is inexplicably glacial,
|
// Text console
|
||||||
// log to the Browser Console instead if only the -ZoteroDebug flag is used.
|
if (_console) {
|
||||||
// Developers can use the debug.log/debug.time prefs and the Cygwin text console.
|
dump("zotero" + output + "\n\n");
|
||||||
//
|
|
||||||
// TODO: Get rid of the filename and line number
|
|
||||||
if (!_consolePref && Zotero.isWin && !Zotero.isStandalone) {
|
|
||||||
var console = Components.utils.import("resource://gre/modules/Console.jsm", {}).console;
|
|
||||||
console.log(output);
|
|
||||||
}
|
}
|
||||||
// Otherwise dump to the text console
|
// Console window
|
||||||
else {
|
if (_consoleViewer) {
|
||||||
dump(output + "\n\n");
|
// If there's a listener, pass line immediately
|
||||||
|
if (_consoleViewerListener) {
|
||||||
|
_consoleViewerListener(output);
|
||||||
|
}
|
||||||
|
// Otherwise add to queue
|
||||||
|
else {
|
||||||
|
_consoleViewerQueue.push(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if(window.console) {
|
} else if(window.console) {
|
||||||
window.console.log(output);
|
window.console.log(output);
|
||||||
|
@ -193,6 +222,26 @@ Zotero.Debug = new function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.getConsoleViewerOutput = function () {
|
||||||
|
var queue = _consoleViewerQueue;
|
||||||
|
_consoleViewerQueue = [];
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.addConsoleViewerListener = function (listener) {
|
||||||
|
_consoleViewerListener = listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.removeConsoleViewerListener = function () {
|
||||||
|
_consoleViewerListener = null;
|
||||||
|
// At least for now, stop logging once console viewer is closed
|
||||||
|
_consoleViewer = false;
|
||||||
|
this.updateEnabled();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
this.setStore = function (enable) {
|
this.setStore = function (enable) {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
this.clear();
|
this.clear();
|
||||||
|
@ -200,10 +249,14 @@ Zotero.Debug = new function () {
|
||||||
_store = enable;
|
_store = enable;
|
||||||
|
|
||||||
this.storing = _store;
|
this.storing = _store;
|
||||||
this.enabled = _console || _store;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.updateEnabled = function () {
|
||||||
|
this.enabled = _console || _consoleViewer || _store;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
this.count = function () {
|
this.count = function () {
|
||||||
return _output.length;
|
return _output.length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -485,9 +485,13 @@ function ZoteroCommandLineHandler() {}
|
||||||
ZoteroCommandLineHandler.prototype = {
|
ZoteroCommandLineHandler.prototype = {
|
||||||
/* nsICommandLineHandler */
|
/* nsICommandLineHandler */
|
||||||
handle : function(cmdLine) {
|
handle : function(cmdLine) {
|
||||||
// Force debug output
|
// Force debug output to window
|
||||||
if (cmdLine.handleFlag("ZoteroDebug", false)) {
|
if (cmdLine.handleFlag("ZoteroDebug", false)) {
|
||||||
zInitOptions.forceDebugLog = true;
|
zInitOptions.forceDebugLog = 2;
|
||||||
|
}
|
||||||
|
// Force debug output to text console
|
||||||
|
else if (cmdLine.handleFlag("ZoteroDebugText", false)) {
|
||||||
|
zInitOptions.forceDebugLog = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handler to open Zotero pane at startup in Zotero for Firefox
|
// handler to open Zotero pane at startup in Zotero for Firefox
|
||||||
|
|
Loading…
Reference in New Issue
Block a user