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:
Dan Stillman 2017-01-14 17:03:22 -05:00
parent 9c0befceeb
commit f44264cd4d
4 changed files with 414 additions and 29 deletions

View 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)">&#128203;</span>
</p>
<p id="submit-error" hidden></p>
</header>
<div id="content">
<div id="errors"></div>
<div id="output"></div>
</div>
</body>
</html>

View 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);

View file

@ -25,19 +25,38 @@
Zotero.Debug = new function () {
var _console, _consolePref, _stackTrace, _store, _level, _lastTime, _output = [];
var _console, _stackTrace, _store, _level, _lastTime, _output = [];
var _slowTime = false;
var _colorOutput = false;
var _consoleViewer = false;
var _consoleViewerQueue = [];
var _consoleViewerListener;
this.init = function (forceDebugLog) {
_consolePref = Zotero.Prefs.get('debug.log');
_console = _consolePref || forceDebugLog;
if (_console && Zotero.isFx && !Zotero.isBookmarklet && (!Zotero.isWin || _consolePref)) {
/**
* Initialize debug logging
*
* 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;
}
if (_colorOutput) {
// Time threshold in milliseconds above which intervals
// should be colored red in terminal output
// Time threshold in ms above which intervals should be colored red in terminal output
_slowTime = Zotero.Prefs.get('debug.log.slowTime');
}
_store = Zotero.Prefs.get('debug.store');
@ -48,15 +67,23 @@ Zotero.Debug = new function () {
_stackTrace = Zotero.Prefs.get('debug.stackTrace');
this.storing = _store;
this.enabled = _console || _store;
this.updateEnabled();
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) {
if (!_console && !_store) {
if (!this.enabled) {
return;
}
@ -118,21 +145,23 @@ Zotero.Debug = new function () {
message += '\n' + Zotero.Debug.stackToString(stack);
}
if (_console) {
var output = 'zotero(' + level + ')' + deltaStr + ': ' + message;
if(Zotero.isFx && !Zotero.isBookmarklet) {
// On Windows, where the text console (-console) is inexplicably glacial,
// log to the Browser Console instead if only the -ZoteroDebug flag is used.
// Developers can use the debug.log/debug.time prefs and the Cygwin text console.
//
// 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);
if (_console || _consoleViewer) {
var output = '(' + level + ')' + deltaStr + ': ' + message;
if (Zotero.isFx && !Zotero.isBookmarklet) {
// Text console
if (_console) {
dump("zotero" + output + "\n\n");
}
// Otherwise dump to the text console
else {
dump(output + "\n\n");
// Console window
if (_consoleViewer) {
// If there's a listener, pass line immediately
if (_consoleViewerListener) {
_consoleViewerListener(output);
}
// Otherwise add to queue
else {
_consoleViewerQueue.push(output);
}
}
} else if(window.console) {
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) {
if (enable) {
this.clear();
@ -200,10 +249,14 @@ Zotero.Debug = new function () {
_store = enable;
this.storing = _store;
this.enabled = _console || _store;
}
this.updateEnabled = function () {
this.enabled = _console || _consoleViewer || _store;
};
this.count = function () {
return _output.length;
}

View file

@ -485,9 +485,13 @@ function ZoteroCommandLineHandler() {}
ZoteroCommandLineHandler.prototype = {
/* nsICommandLineHandler */
handle : function(cmdLine) {
// Force debug output
// Force debug output to window
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