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
4 changed files with 414 additions and 29 deletions
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 () {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue