better-js-logging/better-logging-base

View on GitHub
src/logging-enhancer.js

Summary

Maintainability
C
1 day
Test Coverage
/* global module, exports, window */

/*
    LoggingEnhancer can be used to enhance any logging function and can be tested without angular
*/
(function() {
    
    'use strict';

    var LoggingEnhancer = function(sprintf, moment) {
        var self = this;

        this.LEVEL = { TRACE: 4, DEBUG: 3, INFO: 2, WARN: 1, ERROR: 0, OFF: -1 };

        // returns a value for testing purposes only
        this.enhanceLogging = function(loggingFunc, level, context, config, datetimePattern, datetimeLocale, prefixPattern) {
            config.logLevels = config.logLevels || [];
            return function() {
                if (levelPassesThreshold(context, level, config)) {
                    var enhancedArguments = enhanceLogline(arguments, context, level, datetimePattern, datetimeLocale, prefixPattern);
                    loggingFunc.apply(null, enhancedArguments);
                    return enhancedArguments;
                }
                else {
                    return null; // no log produced
                }
            };

            function levelPassesThreshold(context, level, config) {
                return level > self.LEVEL.OFF && level <= getLogLevelThreshold(context, config);

                function getLogLevelThreshold(context, config) {
                    if (context) {
                        if (config.logLevels[context] !== undefined) {
                            return config.logLevels[context];
                        }
                        else if (context.indexOf('.') !== -1) {
                            return getLogLevelThreshold(context.substring(0, context.lastIndexOf('.')), config);
                        }
                    }
                    return config.logLevels['*'] !== undefined ? config.logLevels['*'] : self.LEVEL.TRACE;
                }
            }

            function enhanceLogline(args, context, level, datetimePattern, datetimeLocale, prefixPattern) {
                var prefix = generatePrefix(context, level, datetimePattern, datetimeLocale, prefixPattern);
                var processedArgs = maybeApplySprintf([].slice.call(args));
                return [prefix].concat([].slice.call(processedArgs));

                function maybeApplySprintf(args) {
                    var sprintfAvailable = typeof sprintf !== 'undefined';
                    var sprintfCandidate = sprintfAvailable && args.length >= 2 && typeof args[0] === 'string' && args[0].indexOf('%') !== -1;
                    if (sprintfCandidate) {
                        try {
                            // apply sprintf with the proper arguments
                            var placeholderCount = self.countSprintfHolders(args[0]);
                            if (placeholderCount > 0) {
                                args[0] = sprintf.apply(null, args);
                                args.splice(1, placeholderCount); // remove arguments consumed by sprintf
                            }
                        }
                        catch (e) {
                            // invalid arguments passed into sprintf, continue without applying
                            args.unshift(e);
                        }
                    }

                    return args;
                }
            }

            function generatePrefix(context, level, datetimePattern, datetimeLocale, prefixPattern) {
                var dateStr = '';
                if (typeof moment !== 'undefined') {
                    dateStr = moment().locale(datetimeLocale).format(datetimePattern);
                }
                else {
                    var d = new Date();
                    var timeStr = new Date().toTimeString().match(/^([0-9]{2}:[0-9]{2}:[0-9]{2})/)[0];
                    dateStr = d.getDate() + '-' + (d.getMonth() + 1) + '-' + d.getFullYear() + ' ' + timeStr;
                }
                
                for (var levelName in self.LEVEL) {
                    if (self.LEVEL[levelName] === level) { break; }
                }
                levelName = levelName.toLowerCase();

                if (typeof sprintf !== 'undefined') {
                    return sprintf(prefixPattern, dateStr, context, levelName);
                }
                else {
                    // use fixed layout: '%s::[%s]%s> '
                    return dateStr + '::' + context + '::' + levelName + '> ';
                }
            }
        };

        self.countSprintfHolders = function(pattern) {
            var hasNamedHolders = /\x25\([a-zA-Z0-9_]+\)[b-fijosuxX]/.test(pattern);
            if (hasNamedHolders) {
                return 1;
            }

            var placeholderCounter = 0;

            function f(index) {
                return function() {
                    // keep track of highest arg index, needed for single -but indexed- placeholders placeholder (ie. %6$s consumes the first 6 arguments)
                    placeholderCounter = Math.max(placeholderCounter, index);
                };
            }

            sprintf(pattern, f(1), f(2), f(3), f(4), f(5), f(6), f(7), f(8), f(9), f(10));
            return placeholderCounter;
        };
    };

    if (typeof module !== 'undefined') {
        module.exports.LoggingEnhancer = LoggingEnhancer;
    } else if (typeof exports !== 'undefined') {
        exports.LoggingEnhancer = LoggingEnhancer;
    } else if (typeof window !== 'undefined') {
        window.loggingEnhancer = new LoggingEnhancer(window.sprintf, window.moment);
    } else {
        throw new Error('unable to expose LoggingEnhancer: no module, exports object and no global window detected');
    }
    
})();