Bob Ippolito - MochiKit-1.1

Documentation | Source
/***

MochiKit.Logging 1.1

See <http://mochikit.com/> for documentation, downloads, license, etc.

(c) 2005 Bob Ippolito.  All rights Reserved.

***/
if (typeof(dojo) != 'undefined') {
    dojo.provide('MochiKit.Logging');
    dojo.require('MochiKit.Base');
}

if (typeof(JSAN) != 'undefined') {
    JSAN.use("MochiKit.Base", []);
}

try {
    if (typeof(MochiKit.Base) == 'undefined') {
        throw "";
    }
} catch (e) {
    throw "MochiKit.Logging depends on MochiKit.Base!";
}

if (typeof(MochiKit.Logging) == 'undefined') {
    MochiKit.Logging = {};
}

MochiKit.Logging.NAME = "MochiKit.Logging";
MochiKit.Logging.VERSION = "1.1";
MochiKit.Logging.__repr__ = function () {
    return "[" + this.NAME + " " + this.VERSION + "]";
};

MochiKit.Logging.toString = function () {
    return this.__repr__();
};


MochiKit.Logging.EXPORT = [
    "LogLevel",
    "LogMessage",
    "Logger",
    "alertListener",
    "logger",
    "log",
    "logError",
    "logDebug",
    "logFatal",
    "logWarning"
];


MochiKit.Logging.EXPORT_OK = [
    "logLevelAtLeast",
    "isLogMessage",
    "compareLogMessage"
];


MochiKit.Logging.LogMessage = function (num, level, info) {
    this.num = num;
    this.level = level;
    this.info = info;
    this.timestamp = new Date();
};

MochiKit.Logging.LogMessage.prototype = {
    repr: function () {
        var m = MochiKit.Base;
        return 'LogMessage(' + 
            m.map(
                m.repr,
                [this.num, this.level, this.info]
            ).join(', ') + ')';
    },
    toString: MochiKit.Base.forward("repr")
};

MochiKit.Base.update(MochiKit.Logging, {
    logLevelAtLeast: function (minLevel) {
        /***

            Return a function that will match log messages whose level
            is at least minLevel

        ***/
        var self = MochiKit.Logging;
        if (typeof(minLevel) == 'string') {
            minLevel = self.LogLevel[minLevel];
        }
        return function (msg) {
            var msgLevel = msg.level;
            if (typeof(msgLevel) == 'string') {
                msgLevel = self.LogLevel[msgLevel];
            }
            return msgLevel >= minLevel;
        }
    },

    isLogMessage: function (/* ... */) {
        var LogMessage = MochiKit.Logging.LogMessage;
        for (var i = 0; i < arguments.length; i++) {
            if (!(arguments[i] instanceof LogMessage)) {
                return false;
            }
        }
        return true;
    },

    compareLogMessage: function (a, b) {
        return MochiKit.Base.compare([a.level, a.info], [b.level, b.info]);
    },

    alertListener: function (msg) {
        /***

        Ultra-obnoxious alert(...) listener

        ***/
        alert(
            "num: " + msg.num +
            "\nlevel: " +  msg.level +
            "\ninfo: " + msg.info.join(" ")
        );
    }

});

MochiKit.Logging.Logger = function (/* optional */maxSize) {
    /***

        A basic logger object that has a buffer of recent messages
        plus a listener dispatch mechanism for "real-time" logging
        of important messages

        maxSize is the maximum number of entries in the log.
        If maxSize >= 0, then the log will not buffer more than that
        many messages.

        There is a default logger available named "logger", and several
        of its methods are also global functions:

            logger.log      -> log
            logger.debug    -> logDebug
            logger.warning  -> logWarning
            logger.error    -> logError
            logger.fatal    -> logFatal
        
    ***/
    this.counter = 0;
    if (typeof(maxSize) == 'undefined' || maxSize == null) {
        maxSize = -1;
    }
    this.maxSize = maxSize;
    this._messages = [];
    this.listeners = {};
};

MochiKit.Logging.Logger.prototype = {
    clear: function () {
        /***

            Clear all messages from the message buffer.

        ***/
        this._messages.splice(0, this._messages.length);
    },

    dispatchListeners: function (msg) {
        /***

            Dispatch a log message to all listeners.

        ***/
        for (var k in this.listeners) {
            var pair = this.listeners[k];
            if (pair[0] && !pair[0](msg)) {
                continue;
            }
            pair[1](msg);
        }
    },

    addListener: function (ident, filter, listener) {
        /***

            Add a listener for log messages.
            
            ident is a unique identifier that may be used to remove the listener
            later on.
            
            filter can be one of the following:
                null:
                    listener(msg) will be called for every log message
                    received.

                string:
                    logLevelAtLeast(filter) will be used as the function
                    (see below).

                function:
                    filter(msg) will be called for every msg, if it returns
                    true then listener(msg) will be called.

            listener is a function that takes one argument, a log message.  A log
            message has three properties:

                num:
                    A counter that uniquely identifies a log message (per-logger)

                level:
                    A string or number representing the log level.  If string, you
                    may want to use LogLevel[level] for comparison.
                
                info:
                    A list of objects passed as arguments to the log function.

        ***/
                
                
        if (typeof(filter) == 'string') {
            filter = MochiKit.Logging.logLevelAtLeast(filter);
        }
        this.listeners[ident] = [filter, listener];
    },

    removeListener: function (ident) {
        /***

            Remove a listener using the ident given to addListener

        ***/
        delete this.listeners[ident];
    },

    baseLog: function (level, message/*, ...*/) {
        /***

            The base functionality behind all of the log functions.
            The first argument is the log level as a string or number,
            and all other arguments are used as the info list.

            This function is available partially applied as:

                Logger.debug    'DEBUG'
                Logger.log      'INFO'
                Logger.error    'ERROR'
                Logger.fatal    'FATAL'
                Logger.warning  'WARNING'

            For the default logger, these are also available as global functions,
            see the Logger constructor documentation for more info.

        ***/
                
        var msg = new MochiKit.Logging.LogMessage(
            this.counter,
            level,
            MochiKit.Base.extend(null, arguments, 1)
        );
        this._messages.push(msg);
        this.dispatchListeners(msg);
        this.counter += 1;
        while (this.maxSize >= 0 && this._messages.length > this.maxSize) {
            this._messges.shift();
        }
    },

    getMessages: function (howMany) {
        /***

            Return a list of up to howMany messages from the message buffer.

        ***/
        var firstMsg = 0;
        if (!(typeof(howMany) == 'undefined' || howMany == null)) {
            firstMsg = Math.max(0, this._messages.length - howMany);
        }
        return this._messages.slice(firstMsg);
    },

    getMessageText: function (howMany) {
        /***

            Get a string representing up to the last howMany messages in the
            message buffer.  The default is 30.

            The message looks like this:

                LAST {messages.length} MESSAGES:
                  [{msg.num}] {msg.level}: {m.info.join(' ')}
                  [{msg.num}] {msg.level}: {m.info.join(' ')}
                  ...

            If you want some other format, use Logger.getMessages and do it
            yourself.

        ***/
        if (typeof(howMany) == 'undefined' || howMany == null) {
            howMany = 30;
        }
        var messages = this.getMessages(howMany);
        if (messages.length) {
            var lst = map(function (m) {
                return '\n  [' + m.num + '] ' + m.level + ': ' + m.info.join(' '); 
            }, messages);
            lst.unshift('LAST ' + messages.length + ' MESSAGES:');
            return lst.join('');
        }
        return '';
    },

    debuggingBookmarklet: function (inline) {
        if (typeof(MochiKit.LoggingPane) == "undefined") {
            alert(this.getMessageText());
        } else {
            MochiKit.LoggingPane.createLoggingPane(inline || false);
        }
    }
};


MochiKit.Logging.__new__ = function () {
    this.LogLevel = {
        ERROR: 40,
        FATAL: 50,
        WARNING: 30,
        INFO: 20,
        DEBUG: 10
    };

    var m = MochiKit.Base;
    m.registerComparator("LogMessage",
        this.isLogMessage,
        this.compareLogMessage
    );

    var partial = m.partial;

    var Logger = this.Logger;
    var baseLog = Logger.prototype.baseLog;
    m.update(this.Logger.prototype, {
        debug: partial(baseLog, 'DEBUG'),
        log: partial(baseLog, 'INFO'),
        error: partial(baseLog, 'ERROR'),
        fatal: partial(baseLog, 'FATAL'),
        warning: partial(baseLog, 'WARNING')
    });

    // indirectly find logger so it can be replaced
    var self = this;
    var connectLog = function (name) {
        return function () {
            self.logger[name].apply(self.logger, arguments);
        }
    };

    this.log = connectLog('log');
    this.logError = connectLog('error');
    this.logDebug = connectLog('debug');
    this.logFatal = connectLog('fatal');
    this.logWarning = connectLog('warning');
    this.logger = new Logger();

    this.EXPORT_TAGS = {
        ":common": this.EXPORT,
        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
    };

    m.nameFunctions(this);

};

MochiKit.Logging.__new__();

MochiKit.Base._exportSymbols(this, MochiKit.Logging);