unified_debug/unified_debug.js (168 lines of code) (raw):

/* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This is a common internal run-time debugging package for C and JavaScript. * In the spirit of Fred Fish's dbug package, which is widely used in the * MySQL server, this package manages "printf()-style" debugging of source * code. * * It allows a JavaScript user to control debugging output both from * JavaScript code and from compiled C code. The user can direct the debugging * output to a particular file, and can enable output from indiviual source code * files. * * SUMMARY: * * var unified_debug = require("unified_debug.js"); * * var udebug = unified_debug.getLogger("myFilename.js"); * * udebug.log(<message>) // write message (at DEBUG level) * udebug.log_urgent(<message>) // write message at URGENT level * udebug.log_notice(<message>) // write message at NOTICE level * udebug.log_info(<message>) // write message at INFO level * udebug.log_debug(<message>) // write message at DEBUG level * udebug.log_detail(<message>) // write message at DETAIL level * udebug.set_file_level(level) // override output level for this file * udebug.is_debug() // returns TRUE if debug level is enabled * * * unified_debug.on() // turn debugging on * unified_debug.off() // turn debugging off * unified_debug.level_urgent() // set output level to URGENT * unified_debug.level_notice() // set output level to NOTICE * unified_debug.level_info() // set output level to INFO * unified_debug.level_debug() // set output level to DEBUG * unified_debug.level_detail() // set output level to DETAIL * unified_debug.close() // close the destination stream * * // Set the destination stream for debugging messages: * unified_debug.set_destination(writeableStream) * * // Register a native code module, which must export setLogger(), * // setLevel(), and setFileLevel() functions to JavaScript: * unified_debug.register_client(module) * * // Set a per-filename output level: * unifed_debug.set_file_level(filename, level) * * // Register a receiver function which will be called with * // arguments (level, filename, message) whenever a message is logged. * unified_debug.register_receiver(receiverFunction); * */ /*jslint vars: true, white: true, node: true, nomen: true, unparam: true, plusplus: true */ /*global global_unified_debug: true */ "use strict"; var path = require("path"), util = require("util"), assert = require("assert"), UDEB_OFF = 0, UDEB_URGENT = 1, UDEB_NOTICE = 2, UDEB_INFO = 3, UDEB_DEBUG = 4, UDEB_DETAIL = 5, write_log_message, _global_; // This is the default logListener; it writes the message on destinationStream. write_log_message = function(level, file, message) { message += "\n"; _global_.destinationStream.write(message, 'ascii'); }; /* NPM can cause problems. It is possible that one module loads unified_debug from one path, but another module loads it from a different path. Setting the debug level in one copy would do nothing to enable messages from the other copy. We work around this by holding some state at global scope. */ if(global.global_unified_debug === undefined) { global.global_unified_debug = { on : 1, // initially on level : UDEB_NOTICE, // initial level destinationStream : process.stderr, // initial message destination nativeCodeClients : [], logListeners : [], fileLoggers : {}, presetPerFileLevel : {} }; // INITIALIZATION TIME: REGISTER write_log_message as a listener: global.global_unified_debug.logListeners.push(write_log_message); } _global_ = global.global_unified_debug; // Send a log message to all listeners. function handle_log_event(level, file, message) { var i; for(i = 0 ; i < _global_.logListeners.length ; i++) { _global_.logListeners[i](level, file, message); } } // Customize the destination stream exports.set_destination = function(writableStream) { _global_.destinationStream = writableStream; }; // close the destination stream exports.close = function() { if(_global_.destinationStream !== process.stderr && _global_.destinationStream !== process.stdout) { _global_.destinationStream.end(); } }; /* Register a log receiver */ exports.register_receiver = function(rFunc) { _global_.logListeners.push(rFunc); }; /* Set per-file debugging level */ exports.set_file_level = function(filename, level) { var i, client; if(_global_.fileLoggers[filename]) { _global_.fileLoggers[filename].set_file_level(level); } else { /* Maybe a file not yet registered */ _global_.presetPerFileLevel[filename] = level; /* Maybe a C++ file */ for(i = 0 ; i < _global_.nativeCodeClients.length ; i++) { client = _global_.nativeCodeClients[i]; client.setFileLevel(filename, level); } } }; /* Tell native code logging clients about the level */ function clientSetLevel(l) { var i, client; for(i = 0 ; i < _global_.nativeCodeClients.length ; i++) { client = _global_.nativeCodeClients[i]; client.setLevel(l); } } /* Turn debugging output on. */ exports.on = function() { clientSetLevel(_global_.level); if(! _global_.on) { _global_.on = 1; handle_log_event(_global_.level, "unified_debug.js", "unified debug enabled"); } }; /* Turn debugging output off. */ exports.off = function() { _global_.on = 0; clientSetLevel(UDEB_OFF); }; /* Set the logging level */ function udeb_set_level(lvl) { _global_.level = lvl; clientSetLevel(_global_.level); } exports.level_urgent = function() { udeb_set_level(UDEB_URGENT); }; exports.level_notice = function() { udeb_set_level(UDEB_NOTICE); }; exports.level_info = function() { udeb_set_level(UDEB_INFO); }; exports.level_debug = function() { udeb_set_level(UDEB_DEBUG); }; exports.level_detail = function() { udeb_set_level(UDEB_DETAIL); }; /* Register a C client so that it can send debugging output up to JavaScript */ exports.register_client = function(client) { var fileName; assert(typeof client.setLogger === 'function'); assert(typeof client.setLevel === 'function'); assert(typeof client.setFileLevel === 'function'); client.setLogger(handle_log_event); client.setLevel(_global_.level); _global_.nativeCodeClients.push(client); /* Register per-file logging levels */ for(fileName in _global_.presetPerFileLevel) { if(_global_.presetPerFileLevel.hasOwnProperty(fileName)) { client.setFileLevel(fileName, _global_.presetPerFileLevel[fileName]); } } }; function dispatch_log_message(level, filename, msg_array) { var message = util.format.apply(null, msg_array); if(level > UDEB_NOTICE) { message = filename + " " + message; } handle_log_event(level, filename, message); } function Logger() { this.file_level = 0; } Logger.prototype = { URGENT : UDEB_URGENT, NOTICE : UDEB_NOTICE, INFO : UDEB_INFO, DEBUG : UDEB_DEBUG, DETAIL : UDEB_DETAIL, set_file_level : function(x) { this.file_level = x; } }; /*********************************************************** * get a custom logger class for a source file * ***********************************************************/ exports.getLogger = function(filename) { if (_global_.fileLoggers[filename]) { throw new Error('The file name ' + filename + ' has already been registered.'); } // assert(! _global_.fileLoggers[filename]); // A file cannot be registered twice function makeLogFunction(level) { return function() { if((global_unified_debug.level >= level) || (this.file_level >= level)) { dispatch_log_message(level, filename, arguments); } }; } function makeIsFunction(level) { return function() { return (global_unified_debug.level >= level) || (this.file_level >= level); }; } var theLogger = new Logger(); if(_global_.presetPerFileLevel[filename]) { theLogger.file_level = _global_.presetPerFileLevel[filename]; delete _global_.presetPerFileLevel[filename]; } else { theLogger.file_level = UDEB_URGENT; } theLogger.log_urgent = makeLogFunction(1); theLogger.log_notice = makeLogFunction(2); theLogger.log_info = makeLogFunction(3); theLogger.log_debug = makeLogFunction(4); theLogger.log_detail = makeLogFunction(5); theLogger.log = theLogger.log_debug; theLogger.is_urgent = makeIsFunction(1); theLogger.is_notice = makeIsFunction(2); theLogger.is_info = makeIsFunction(3); theLogger.is_debug = makeIsFunction(4); theLogger.is_detail = makeIsFunction(5); _global_.fileLoggers[filename] = theLogger; return theLogger; };