debug.js (8162B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 27 Zotero.Debug = new function () { 28 var _console, _stackTrace, _store, _level, _lastTime, _output = []; 29 var _slowTime = false; 30 var _colorOutput = false; 31 var _consoleViewer = false; 32 var _consoleViewerQueue = []; 33 var _consoleViewerListener; 34 35 /** 36 * Initialize debug logging 37 * 38 * Debug logging can be set in several different ways: 39 * 40 * - via the debug.log pref in the client or connector 41 * - by enabling debug output logging from the Help menu 42 * - by passing -ZoteroDebug or -ZoteroDebugText on the command line 43 * 44 * In the client, debug.log and -ZoteroDebugText enable logging via the terminal, while -ZoteroDebug 45 * enables logging via an in-app HTML-based window. 46 * 47 * @param {Integer} [forceDebugLog = 0] - Force output even if pref disabled 48 * 2: window (-ZoteroDebug) 49 * 1: text console (-ZoteroDebugText) 50 * 0: disabled 51 */ 52 this.init = function (forceDebugLog = 0) { 53 _console = Zotero.Prefs.get('debug.log') || forceDebugLog == 1; 54 _consoleViewer = forceDebugLog == 2; 55 // When logging to the text console from the client on Mac/Linux, colorize output 56 if (_console && Zotero.isFx && !Zotero.isBookmarklet) { 57 _colorOutput = true; 58 59 // Time threshold in ms above which intervals should be colored red in terminal output 60 _slowTime = Zotero.Prefs.get('debug.log.slowTime'); 61 } 62 _store = Zotero.Prefs.get('debug.store'); 63 if (_store) { 64 Zotero.Prefs.set('debug.store', false); 65 } 66 _level = Zotero.Prefs.get('debug.level'); 67 _stackTrace = Zotero.Prefs.get('debug.stackTrace'); 68 69 this.storing = _store; 70 this.updateEnabled(); 71 72 if (Zotero.isStandalone) { 73 // Enable dump() from window (non-XPCOM) scopes when terminal or viewer logging is enabled. 74 // (These will always go to the terminal, even in viewer mode.) 75 Zotero.Prefs.set('browser.dom.window.dump.enabled', _console || _consoleViewer, true); 76 77 if (_consoleViewer) { 78 setTimeout(function () { 79 Zotero.openInViewer("chrome://zotero/content/debugViewer.html"); 80 }, 1000); 81 } 82 } 83 } 84 85 this.log = function (message, level, maxDepth, stack) { 86 if (!this.enabled) { 87 return; 88 } 89 90 if (typeof message != 'string') { 91 message = Zotero.Utilities.varDump(message, 0, maxDepth); 92 } 93 94 if (!level) { 95 level = 3; 96 } 97 98 // If level above debug.level value, don't display 99 if (level > _level) { 100 return; 101 } 102 103 var deltaStr = ''; 104 var deltaStrStore = ''; 105 var delta = 0; 106 var d = new Date(); 107 if (_lastTime) { 108 delta = d - _lastTime; 109 } 110 _lastTime = d; 111 var slowPrefix = ""; 112 var slowSuffix = ""; 113 if (_slowTime && delta > _slowTime) { 114 slowPrefix = "\x1b[31;40m"; 115 slowSuffix = "\x1b[0m"; 116 } 117 118 delta = ("" + delta).padStart(7, "0") 119 120 deltaStr = "(" + slowPrefix + "+" + delta + slowSuffix + ")"; 121 if (_store) { 122 deltaStrStore = "(+" + delta + ")"; 123 } 124 125 if (stack === true) { 126 // Display stack starting from where this was called 127 stack = Components.stack.caller; 128 } else if (stack >= 0) { 129 let i = stack; 130 stack = Components.stack.caller; 131 while(stack && i--) { 132 stack = stack.caller; 133 } 134 } else if (_stackTrace) { 135 // Stack trace enabled globally 136 stack = Components.stack.caller; 137 } else { 138 stack = undefined; 139 } 140 141 if (stack) { 142 message += '\n' + this.stackToString(stack); 143 } 144 145 if (_console || _consoleViewer) { 146 var output = '(' + level + ')' + deltaStr + ': ' + message; 147 if (Zotero.isFx && !Zotero.isBookmarklet) { 148 // Text console 149 if (_console) { 150 dump("zotero" + output + "\n\n"); 151 } 152 // Console window 153 if (_consoleViewer) { 154 // Remove ANSI color codes. We could replace this with HTML, but it's probably 155 // unnecessarily distracting/alarming to show the red in the viewer. Devs who care 156 // about times should just use a terminal. 157 if (slowPrefix) { 158 output = output.replace(slowPrefix, '').replace(slowSuffix, ''); 159 } 160 161 // If there's a listener, pass line immediately 162 if (_consoleViewerListener) { 163 _consoleViewerListener(output); 164 } 165 // Otherwise add to queue 166 else { 167 _consoleViewerQueue.push(output); 168 } 169 } 170 } else if(window.console) { 171 window.console.log(output); 172 } 173 } 174 if (_store) { 175 if (Math.random() < 1/1000) { 176 // Remove initial lines if over limit 177 var overage = this.count() - Zotero.Prefs.get('debug.store.limit'); 178 if (overage > 0) { 179 _output.splice(0, Math.abs(overage)); 180 } 181 } 182 _output.push('(' + level + ')' + deltaStrStore + ': ' + message); 183 } 184 } 185 186 187 this.get = Zotero.Promise.method(function(maxChars, maxLineLength) { 188 var output = _output; 189 var total = output.length; 190 191 if (total == 0) { 192 return ""; 193 } 194 195 if (maxLineLength) { 196 for (var i=0, len=output.length; i<len; i++) { 197 if (output[i].length > maxLineLength) { 198 output[i] = Zotero.Utilities.ellipsize(output[i], maxLineLength, false, true); 199 } 200 } 201 } 202 203 output = output.join('\n\n'); 204 205 if (maxChars) { 206 output = output.substr(maxChars * -1); 207 // Cut at two newlines 208 let matches = output.match(/^[\n]*\n\n/); 209 if (matches) { 210 output = output.substr(matches[0].length); 211 } 212 } 213 214 return Zotero.getSystemInfo().then(function(sysInfo) { 215 if (Zotero.isConnector) { 216 return Zotero.Errors.getErrors().then(function(errors) { 217 return errors.join('\n\n') + 218 "\n\n" + sysInfo + "\n\n" + 219 "=========================================================\n\n" + 220 output; 221 }); 222 } 223 else { 224 return Zotero.getErrors(true).join('\n\n') + 225 "\n\n" + sysInfo + "\n\n" + 226 "=========================================================\n\n" + 227 output; 228 } 229 }); 230 }); 231 232 233 this.getConsoleViewerOutput = function () { 234 var queue = _output.concat(_consoleViewerQueue); 235 _consoleViewerQueue = []; 236 return queue; 237 } 238 239 240 this.addConsoleViewerListener = function (listener) { 241 this.enabled = _consoleViewer = true; 242 _consoleViewerListener = listener; 243 }; 244 245 246 this.removeConsoleViewerListener = function () { 247 _consoleViewerListener = null; 248 // At least for now, stop logging once console viewer is closed 249 _consoleViewer = false; 250 this.updateEnabled(); 251 }; 252 253 254 this.setStore = function (enable) { 255 if (enable) { 256 this.clear(); 257 } 258 _store = enable; 259 this.updateEnabled(); 260 this.storing = _store; 261 } 262 263 264 this.updateEnabled = function () { 265 this.enabled = _console || _consoleViewer || _store; 266 }; 267 268 269 this.count = function () { 270 return _output.length; 271 } 272 273 274 this.clear = function () { 275 _output = []; 276 } 277 278 /** 279 * Format a stack trace for output in the same way that Error.stack does 280 * @param {Components.stack} stack 281 * @param {Integer} [lines=5] Number of lines to format 282 */ 283 this.stackToString = function (stack, lines) { 284 if (!lines) lines = 5; 285 var str = ''; 286 while(stack && lines--) { 287 str += '\n ' + (stack.name || '') + '@' + stack.filename 288 + ':' + stack.lineNumber; 289 stack = stack.caller; 290 } 291 return this.filterStack(str).substr(1); 292 }; 293 294 295 /** 296 * Strip Bluebird lines from a stack trace 297 * 298 * @param {String} stack 299 */ 300 this.filterStack = function (stack) { 301 return stack.split(/\n/).filter(line => line.indexOf('zotero/bluebird') == -1).join('\n'); 302 } 303 }