1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
14 # The Original Code is Google Safe Browsing.
16 # The Initial Developer of the Original Code is Google Inc.
17 # Portions created by the Initial Developer are Copyright (C) 2006
18 # the Initial Developer. All Rights Reserved.
21 # Fritz Schneider <fritz@google.com> (original author)
22 # Annie Sullivan <sullivan@google.com>
23 # Aaron Boodman <aa@google.com>
25 # Alternatively, the contents of this file may be used under the terms of
26 # either the GNU General Public License Version 2 or later (the "GPL"), or
27 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 # in which case the provisions of the GPL or the LGPL are applicable instead
29 # of those above. If you wish to allow use of your version of this file only
30 # under the terms of either the GPL or the LGPL, and not to allow others to
31 # use your version of this file under the terms of the MPL, indicate your
32 # decision by deleting the provisions above and replace them with the notice
33 # and other provisions required by the GPL or the LGPL. If you do not delete
34 # the provisions above, a recipient may use your version of this file under
35 # the terms of any one of the MPL, the GPL or the LGPL.
37 # ***** END LICENSE BLOCK *****
41 // Generic logging/debugging functionality that:
43 // (*) when disabled compiles to no-ops at worst (for calls to the service)
44 // and to nothing at best (calls to G_Debug() and similar are compiled
45 // away when you use a jscompiler that strips dead code)
47 // (*) has dynamically configurable/creatable debugging "zones" enabling
50 // (*) hides its plumbing so that all calls in different zones are uniform,
51 // so you can drop files using this library into other apps that use it
52 // without any configuration
54 // (*) can be controlled programmatically or via preferences. The
55 // preferences that control the service and its zones are under
56 // the preference branch "safebrowsing-debug-service."
58 // (*) outputs function call traces when the "loggifier" zone is enabled
60 // (*) can write output to logfiles so that you can get a call trace
61 // from someone who is having a problem
65 // var G_GDEBUG = true // Enable this module
66 // var G_debugService = new G_DebugService(); // in global context
68 // // You can use it with arbitrary primitive first arguement
69 // G_Debug("myzone", "Yo yo yo"); // outputs: [myzone] Yo yo yo\n
71 // // But it's nice to use it with an object; it will probe for the zone name
73 // this.debugZone = "someobj";
75 // Obj.prototype.foo = function() {
76 // G_Debug(this, "foo called");
78 // (new Obj).foo(); // outputs: [someobj] foo called\n
80 // G_debugService.loggifier.loggify(Obj.prototype); // enable call tracing
82 // // En/disable specific zones programmatically (you can also use preferences)
83 // G_debugService.enableZone("somezone");
84 // G_debugService.disableZone("someotherzone");
85 // G_debugService.enableAllZones();
87 // // We also have asserts and errors:
88 // G_Error(this, "Some error occurred"); // will throw
89 // G_Assert(this, (x > 3), "x not greater than three!"); // will throw
91 // See classes below for more methods.
93 // TODO add code to set prefs when not found to the default value of a tristate
94 // TODO add error level support
95 // TODO add ability to turn off console output
97 // -------> TO START DEBUGGING: set G_GDEBUG to true
99 // These are the functions code will typically call. Everything is
100 // wrapped in if's so we can compile it away when G_GDEBUG is false.
103 if (typeof G_GDEBUG == "undefined") {
104 throw new Error("G_GDEBUG constant must be set before loading debug.js");
109 * Write out a debugging message.
111 * @param who The thingy to convert into a zone name corresponding to the
112 * zone to which this message belongs
113 * @param msg Message to output
115 function G_Debug(who, msg) {
117 G_GetDebugZone(who).debug(msg);
124 function G_DebugL(who, msg) {
126 var zone = G_GetDebugZone(who);
128 if (zone.zoneIsEnabled()) {
130 "\n************************************************************\n");
135 "************************************************************\n\n");
141 * Write out a call tracing message
143 * @param who The thingy to convert into a zone name corresponding to the
144 * zone to which this message belongs
145 * @param msg Message to output
147 function G_TraceCall(who, msg) {
149 if (G_debugService.callTracingEnabled()) {
150 G_debugService.dump(msg + "\n");
156 * Write out an error (and throw)
158 * @param who The thingy to convert into a zone name corresponding to the
159 * zone to which this message belongs
160 * @param msg Message to output
162 function G_Error(who, msg) {
164 G_GetDebugZone(who).error(msg);
169 * Assert something as true and signal an error if it's not
171 * @param who The thingy to convert into a zone name corresponding to the
172 * zone to which this message belongs
173 * @param condition Boolean condition to test
174 * @param msg Message to output
176 function G_Assert(who, condition, msg) {
178 G_GetDebugZone(who).assert(condition, msg);
183 * Helper function that takes input and returns the DebugZone
184 * corresponding to it.
186 * @param who Arbitrary input that will be converted into a zone name. Most
187 * likely an object that has .debugZone property, or a string.
188 * @returns The DebugZone object corresponding to the input
190 function G_GetDebugZone(who) {
194 if (who && who.debugZone) {
195 zone = who.debugZone;
196 } else if (typeof who == "string") {
200 return G_debugService.getZone(zone);
204 // Classes that implement the functionality.
207 * A debug "zone" is a string derived from arbitrary types (but
208 * typically derived from another string or an object). All debugging
209 * messages using a particular zone can be enabled or disabled
210 * independent of other zones. This enables you to turn on/off logging
211 * of particular objects or modules. This object implements a single
212 * zone and the methods required to use it.
215 * @param service Reference to the DebugService object we use for
217 * @param prefix String indicating the unique prefix we should use
218 * when creating preferences to control this zone
219 * @param zone String indicating the name of the zone
221 function G_DebugZone(service, prefix, zone) {
223 this.debugService_ = service;
224 this.prefix_ = prefix;
226 this.zoneEnabledPrefName_ = prefix + ".zone." + this.zone_;
227 this.settings_ = new G_DebugSettings();
232 * @returns Boolean indicating if this zone is enabled
234 G_DebugZone.prototype.zoneIsEnabled = function() {
236 var explicit = this.settings_.getSetting(this.zoneEnabledPrefName_, null);
238 if (explicit !== null) {
241 return this.debugService_.allZonesEnabled();
247 * Enable this logging zone
249 G_DebugZone.prototype.enableZone = function() {
251 this.settings_.setDefault(this.zoneEnabledPrefName_, true);
256 * Disable this logging zone
258 G_DebugZone.prototype.disableZone = function() {
260 this.settings_.setDefault(this.zoneEnabledPrefName_, false);
265 * Write a debugging message to this zone
267 * @param msg String of message to write
269 G_DebugZone.prototype.debug = function(msg) {
271 if (this.zoneIsEnabled()) {
272 this.debugService_.dump("[" + this.zone_ + "] " + msg + "\n");
278 * Write an error to this zone and throw
280 * @param msg String of error to write
282 G_DebugZone.prototype.error = function(msg) {
284 this.debugService_.dump("[" + this.zone_ + "] " + msg + "\n");
285 throw new Error(msg);
291 * Assert something as true and error if it is not
293 * @param condition Boolean condition to test
294 * @param msg String of message to write if is false
296 G_DebugZone.prototype.assert = function(condition, msg) {
298 if (condition !== true) {
299 G_Error(this.zone_, "ASSERT FAILED: " + msg);
306 * The debug service handles auto-registration of zones, namespacing
307 * the zones preferences, and various global settings such as whether
308 * all zones are enabled.
311 * @param opt_prefix Optional string indicating the unique prefix we should
312 * use when creating preferences
314 function G_DebugService(opt_prefix) {
316 this.prefix_ = opt_prefix ? opt_prefix : "safebrowsing-debug-service";
317 this.consoleEnabledPrefName_ = this.prefix_ + ".alsologtoconsole";
318 this.allZonesEnabledPrefName_ = this.prefix_ + ".enableallzones";
319 this.callTracingEnabledPrefName_ = this.prefix_ + ".trace-function-calls";
320 this.logFileEnabledPrefName_ = this.prefix_ + ".logfileenabled";
321 this.logFileErrorLevelPrefName_ = this.prefix_ + ".logfile-errorlevel";
324 this.loggifier = new G_Loggifier();
325 this.settings_ = new G_DebugSettings();
329 // Error levels for reporting console messages to the log.
330 G_DebugService.ERROR_LEVEL_INFO = "INFO";
331 G_DebugService.ERROR_LEVEL_WARNING = "WARNING";
332 G_DebugService.ERROR_LEVEL_EXCEPTION = "EXCEPTION";
336 * @returns Boolean indicating if we should send messages to the jsconsole
338 G_DebugService.prototype.alsoDumpToConsole = function() {
340 return this.settings_.getSetting(this.consoleEnabledPrefName_, false);
345 * @returns whether to log output to a file as well as the console.
347 G_DebugService.prototype.logFileIsEnabled = function() {
349 return this.settings_.getSetting(this.logFileEnabledPrefName_, false);
354 * Turns on file logging. dump() output will also go to the file specified by
357 G_DebugService.prototype.enableLogFile = function() {
359 this.settings_.setDefault(this.logFileEnabledPrefName_, true);
364 * Turns off file logging
366 G_DebugService.prototype.disableLogFile = function() {
368 this.settings_.setDefault(this.logFileEnabledPrefName_, false);
373 * @returns an nsIFile instance pointing to the current log file location
375 G_DebugService.prototype.getLogFile = function() {
377 return this.logFile_;
382 * Sets a new log file location
384 G_DebugService.prototype.setLogFile = function(file) {
386 this.logFile_ = file;
391 * Enables sending messages to the jsconsole
393 G_DebugService.prototype.enableDumpToConsole = function() {
395 this.settings_.setDefault(this.consoleEnabledPrefName_, true);
400 * Disables sending messages to the jsconsole
402 G_DebugService.prototype.disableDumpToConsole = function() {
404 this.settings_.setDefault(this.consoleEnabledPrefName_, false);
409 * @param zone Name of the zone to get
410 * @returns The DebugZone object corresopnding to input. If not such
411 * zone exists, a new one is created and returned
413 G_DebugService.prototype.getZone = function(zone) {
415 if (!this.zones_[zone])
416 this.zones_[zone] = new G_DebugZone(this, this.prefix_, zone);
418 return this.zones_[zone];
423 * @param zone Zone to enable debugging for
425 G_DebugService.prototype.enableZone = function(zone) {
427 var toEnable = this.getZone(zone);
428 toEnable.enableZone();
433 * @param zone Zone to disable debugging for
435 G_DebugService.prototype.disableZone = function(zone) {
437 var toDisable = this.getZone(zone);
438 toDisable.disableZone();
443 * @returns Boolean indicating whether debugging is enabled for all zones
445 G_DebugService.prototype.allZonesEnabled = function() {
447 return this.settings_.getSetting(this.allZonesEnabledPrefName_, false);
452 * Enables all debugging zones
454 G_DebugService.prototype.enableAllZones = function() {
456 this.settings_.setDefault(this.allZonesEnabledPrefName_, true);
461 * Disables all debugging zones
463 G_DebugService.prototype.disableAllZones = function() {
465 this.settings_.setDefault(this.allZonesEnabledPrefName_, false);
470 * @returns Boolean indicating whether call tracing is enabled
472 G_DebugService.prototype.callTracingEnabled = function() {
474 return this.settings_.getSetting(this.callTracingEnabledPrefName_, false);
479 * Enables call tracing
481 G_DebugService.prototype.enableCallTracing = function() {
483 this.settings_.setDefault(this.callTracingEnabledPrefName_, true);
488 * Disables call tracing
490 G_DebugService.prototype.disableCallTracing = function() {
492 this.settings_.setDefault(this.callTracingEnabledPrefName_, false);
497 * Gets the minimum error that will be reported to the log.
499 G_DebugService.prototype.getLogFileErrorLevel = function() {
501 var level = this.settings_.getSetting(this.logFileErrorLevelPrefName_,
502 G_DebugService.ERROR_LEVEL_EXCEPTION);
504 return level.toUpperCase();
509 * Sets the minimum error level that will be reported to the log.
511 G_DebugService.prototype.setLogFileErrorLevel = function(level) {
513 // normalize case just to make it slightly easier to not screw up.
514 level = level.toUpperCase();
516 if (level != G_DebugService.ERROR_LEVEL_INFO &&
517 level != G_DebugService.ERROR_LEVEL_WARNING &&
518 level != G_DebugService.ERROR_LEVEL_EXCEPTION) {
519 throw new Error("Invalid error level specified: {" + level + "}");
522 this.settings_.setDefault(this.logFileErrorLevelPrefName_, level);
527 * Internal dump() method
529 * @param msg String of message to dump
531 G_DebugService.prototype.dump = function(msg) {
535 if (this.alsoDumpToConsole()) {
537 var console = Components.classes['@mozilla.org/consoleservice;1']
538 .getService(Components.interfaces.nsIConsoleService);
539 console.logStringMessage(msg);
541 dump("G_DebugZone ERROR: COULD NOT DUMP TO CONSOLE\n");
545 this.maybeDumpToFile(msg);
550 * Writes the specified message to the log file, if file logging is enabled.
552 G_DebugService.prototype.maybeDumpToFile = function(msg) {
553 if (this.logFileIsEnabled() && this.logFile_) {
555 /* try to get the correct line end character for this platform */
556 if (!this._LINE_END_CHAR)
557 this._LINE_END_CHAR =
558 Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
559 .OS == "WINNT" ? "\r\n" : "\n";
560 if (this._LINE_END_CHAR != "\n")
561 msg = msg.replace(/\n/g, this._LINE_END_CHAR);
564 var stream = Cc["@mozilla.org/network/file-output-stream;1"]
565 .createInstance(Ci.nsIFileOutputStream);
566 stream.init(this.logFile_,
567 0x02 | 0x08 | 0x10 /* PR_WRONLY | PR_CREATE_FILE | PR_APPEND */
568 -1 /* default perms */, 0 /* no special behavior */);
569 stream.write(msg, msg.length);
577 * Implements nsIConsoleListener.observe(). Gets called when an error message
578 * gets reported to the console and sends it to the log file as well.
580 G_DebugService.prototype.observe = function(consoleMessage) {
582 var errorLevel = this.getLogFileErrorLevel();
584 // consoleMessage can be either nsIScriptError or nsIConsoleMessage. The
585 // latter does not have things like line number, etc. So we special case
587 if (!(consoleMessage instanceof Ci.nsIScriptError)) {
588 // Only report these messages if the error level is INFO.
589 if (errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
590 this.maybeDumpToFile(G_DebugService.ERROR_LEVEL_INFO + ": " +
591 consoleMessage.message + "\n");
597 // We make a local copy of these fields because writing to it doesn't seem
599 var flags = consoleMessage.flags;
600 var sourceName = consoleMessage.sourceName;
601 var lineNumber = consoleMessage.lineNumber;
603 // Sometimes, a scripterror instance won't have any flags set. We
604 // default to exception.
606 flags = Ci.nsIScriptError.exceptionFlag;
609 // Default the filename and line number if they aren't set.
611 sourceName = "<unknown>";
615 lineNumber = "<unknown>";
618 // Report the error in the log file.
619 if (flags & Ci.nsIScriptError.warningFlag) {
620 // Only report warnings if the error level is warning or better.
621 if (errorLevel == G_DebugService.ERROR_LEVEL_WARNING ||
622 errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
623 this.reportScriptError_(consoleMessage.message,
626 G_DebugService.ERROR_LEVEL_WARNING);
628 } else if (flags & Ci.nsIScriptError.exceptionFlag) {
629 // Always report exceptions.
630 this.reportScriptError_(consoleMessage.message,
633 G_DebugService.ERROR_LEVEL_EXCEPTION);
639 * Private helper to report an nsIScriptError instance to the log/console.
641 G_DebugService.prototype.reportScriptError_ = function(message, sourceName,
643 var message = "\n------------------------------------------------------------\n" +
644 label + ": " + message +
645 "\nlocation: " + sourceName + ", " + "line: " + lineNumber +
646 "\n------------------------------------------------------------\n\n";
649 this.maybeDumpToFile(message);
655 * A class that instruments methods so they output a call trace,
656 * including the values of their actual parameters and return value.
657 * This code is mostly stolen from Aaron Boodman's original
658 * implementation in clobber utils.
660 * Note that this class uses the "loggifier" debug zone, so you'll see
661 * a complete call trace when that zone is enabled.
665 function G_Loggifier() {
667 // Careful not to loggify ourselves!
673 * Marks an object as having been loggified. Loggification is not
676 * @param obj Object to be marked
678 G_Loggifier.prototype.mark_ = function(obj) {
680 obj.__loggified_ = true;
685 * @param obj Object to be examined
686 * @returns Boolean indicating if the object has been loggified
688 G_Loggifier.prototype.isLoggified = function(obj) {
690 return !!obj.__loggified_;
695 * Attempt to extract the class name from the constructor definition.
696 * Assumes the object was created using new.
698 * @param constructor String containing the definition of a constructor,
699 * for example what you'd get by examining obj.constructor
700 * @returns Name of the constructor/object if it could be found, else "???"
702 G_Loggifier.prototype.getFunctionName_ = function(constructor) {
704 return constructor.name || "???";
709 * Wraps all the methods in an object so that call traces are
710 * automatically outputted.
712 * @param obj Object to loggify. SHOULD BE THE PROTOTYPE OF A USER-DEFINED
713 * object. You can get into trouble if you attempt to
714 * loggify something that isn't, for example the Window.
716 * Any additional parameters are considered method names which should not be
720 * G_debugService.loggifier.loggify(MyClass.prototype,
721 * "firstMethodNotToLog",
722 * "secondMethodNotToLog",
725 G_Loggifier.prototype.loggify = function(obj) {
727 if (!G_debugService.callTracingEnabled()) {
731 if (typeof window != "undefined" && obj == window ||
732 this.isLoggified(obj)) // Don't go berserk!
735 var zone = G_GetDebugZone(obj);
736 if (!zone || !zone.zoneIsEnabled()) {
742 // Helper function returns an instrumented version of
743 // objName.meth, with "this" bound properly. (BTW, because we're
744 // in a conditional here, functions will only be defined as
745 // they're encountered during execution, so declare this helper
748 function wrap(meth, objName, methName) {
751 // First output the call along with actual parameters
752 var args = new Array(arguments.length);
754 for (var i = 0; i < args.length; i++) {
755 args[i] = arguments[i];
756 argsString += (i == 0 ? "" : ", ");
758 if (typeof args[i] == "function") {
759 argsString += "[function]";
761 argsString += args[i];
765 G_TraceCall(this, "> " + objName + "." + methName + "(" +
768 // Then run the function, capturing the return value and throws
770 var retVal = meth.apply(this, arguments);
771 var reportedRetVal = retVal;
773 if (typeof reportedRetVal == "undefined")
774 reportedRetVal = "void";
775 else if (reportedRetVal === "")
776 reportedRetVal = "\"\" (empty string)";
778 if (e && !e.__logged) {
779 G_TraceCall(this, "Error: " + e.message + ". " +
780 e.fileName + ": " + e.lineNumber);
784 // Sometimes we can't add the __logged flag because it's an
790 throw e; // Re-throw!
793 // And spit it out already
796 "< " + objName + "." + methName + ": " + reportedRetVal);
802 var ignoreLookup = {};
804 if (arguments.length > 1) {
805 for (var i = 1; i < arguments.length; i++) {
806 ignoreLookup[arguments[i]] = true;
810 // Wrap each method of obj
812 // Work around bug in Firefox. In ffox typeof RegExp is "function",
813 // so make sure this really is a function. Bug as of FFox 1.5b2.
814 if (typeof obj[p] == "function" && obj[p].call && !ignoreLookup[p]) {
815 var objName = this.getFunctionName_(obj.constructor);
816 obj[p] = wrap(obj[p], objName, p);
824 * Simple abstraction around debug settings. The thing with debug settings is
825 * that we want to be able to specify a default in the application's startup,
826 * but have that default be overridable by the user via their prefs.
828 * To generalize this, we package up a dictionary of defaults with the
829 * preferences tree. If a setting isn't in the preferences tree, then we grab it
832 function G_DebugSettings() {
834 this.prefs_ = new G_Preferences();
838 * Returns the value of a settings, optionally defaulting to a given value if it
839 * doesn't exist. If no default is specified, the default is |undefined|.
841 G_DebugSettings.prototype.getSetting = function(name, opt_default) {
842 var override = this.prefs_.getPref(name, null);
844 if (override !== null) {
846 } else if (typeof this.defaults_[name] != "undefined") {
847 return this.defaults_[name];
854 * Sets the default value for a setting. If the user doesn't override it with a
855 * preference, this is the value which will be returned by getSetting().
857 G_DebugSettings.prototype.setDefault = function(name, val) {
858 this.defaults_[name] = val;
861 var G_debugService = new G_DebugService(); // Instantiate us!
864 G_debugService.enableAllZones();
869 // Stubs for the debugging aids scattered through this component.
870 // They will be expanded if you compile yourself a debug build.
872 function G_Debug(who, msg) { }
873 function G_Assert(who, condition, msg) { }
874 function G_Error(who, msg) { }
875 var G_debugService = { __noSuchMethod__: function() { } };