Commit c5695a2e authored by Marcel Rieger's avatar Marcel Rieger
Browse files

Update jquery logger plugin.

parent e034beca
/*!
* jQuery Logging Plugin v0.3.2
* https://github.com/riga/jquery.logger
*
* Copyright 2015, Marcel Rieger
* Dual licensed under the MIT or GPL Version 3 licenses.
* http://www.opensource.org/licenses/mit-license
* http://www.opensource.org/licenses/GPL-3.0
*
*/
(function($) {
// additional logging function for use in chains
$.fn.log = function(msg) {
if (window.console && window.console.log) {
if (msg) {
console.log("%o: %s", this, msg);
} else {
console.log(this);
/**
* Store the global logger.
*/
var globalLogger;
/**
* Default options.
*/
var options = {
// name of the global namespace
global: "global",
// delimitter that seperates namespaces
delimitter: ".",
// use appropriate console logging methods instead of the standard log method,
// the mapping is defined in `consoleMethods`
consoleMethods: true,
// use timestamps in logs
timestamps: true,
// experimental
// show file name and line number of the origin
origin: true
};
/**
* Our levels.
*/
var levels = {
"all" : 0,
"debug" : 10,
"info" : 20,
"warning": 30,
"error" : 40,
"fatal" : 50
};
// the max string length of all levels
var maxLevelLength = Math.max.apply(Math, Object.keys(levels).map(function(level) {
return level.length;
}));
/**
* Level -> console method mapping in case `useConsoleLevels` is true.
*/
var consoleMethods = {
"all" : "log",
"debug" : "debug",
"info" : "info",
"warning": "warn",
"error" : "error",
"fatal" : "error"
};
/**
* Helper function that prefixes a namespace with the global namespace.
*
* @param {string} namespace - The namespace to prefix.
* @returns {string}
*/
var prefixNamespace = function(namespace) {
var prefix = options.global + options.delimitter;
if (namespace === undefined || namespace == options.global) {
return options.global;
} else if (namespace.indexOf(prefix) != 0) {
return prefix + namespace;
} else {
return namespace;
}
};
/**
* Helper function that returns a specific logger for a given namespace.
*
* @param {string} namespace - Namespace of the logger
*/
var getLogger = function(namespace) {
if (namespace === undefined) {
return null;
}
// the global logger needs to be setup
if (!globalLogger) {
return null;
}
// prefix the namespace
namespace = prefixNamespace(namespace);
// split the namespace by our delimitter
var parts = namespace.split(options.delimitter);
// remove the global namespace
parts.shift();
// recursive lookup in each logger's subloggers
var logger = globalLogger;
while (parts.length) {
logger = logger.children()[parts.shift()];
// error case
if (!logger) {
break;
}
}
return self;
return logger;
};
// storage for the global logger
var _logger;
// the Logger plugin
$.Logger = function(namespace) {
/**
* Helper function that returns the name of a logger given its namespace.
*
* @param {string} namespace - The namespace of the logger.
* @returns {string}
*/
var getLoggerName = function(namespace) {
if (namespace === undefined) {
return null;
}
// some config
var global = "global",
delimitter = ".",
useConsoleLevels = true,
lineNumbers = true;
// our log levels
var levels = {
"all": 0,
"debug": 2,
"info": 4,
"warning": 6,
"error": 8,
"fatal": 10
};
// prefix the namespace
namespace = prefixNamespace(namespace);
// find the maximum level name length for pretty printing
var maxLevelLength = 0;
$.each(levels, function(name) {
maxLevelLength = Math.max(maxLevelLength, name.length);
// the last part is the name
return namespace.split(options.delimitter).pop();
};
/**
* Helper function that creates a local timestamp with ms precision.
*
* @returns {string}
*/
var timestamp = function() {
var d = new Date();
var s = "";
s += d.getFullYear();
s += "-";
s += ("0" + d.getMonth()).slice(-2);
s += "-";
s += ("0" + d.getDate()).slice(-2);
s += " ";
s += ("0" + d.getHours()).slice(-2);
s += ":";
s += ("0" + d.getMinutes()).slice(-2);
s += ":";
s += ("0" + d.getSeconds()).slice(-2);
s += ".";
s += ("0" + d.getMilliseconds()).slice(-3);
return s;
};
/**
* Helper function that returns the file name and the line number of the code line that
* invoked a log.
* Experimental. Tested on Chrome 41.0.2272.118, FireFox 37.0.1 and Safari 8.0.5.
*
* @returns {string|null}
*/
var stackRE = new RegExp(window.location.protocol + "//" + window.location.host);
var originRE = new RegExp("^.*" + window.location.protocol + "//" + window.location.host
+ ".+/([^/]+:\\d+:\\d+).*$");
var getOrigin = function() {
// create the stack
var stack = String((new Error()).stack).split("\n").map(function(msg) {
return msg.trim();
});
var fillString = function(s, l) {
while (s.length < l) {
s += " ";
}
return s;
};
// level -> consoleLevel methods
var consoleMethods = {
"all": "log",
"debug": "debug",
"info": "info",
"warning": "warn",
"error": "error",
"fatal": "error"
};
// we're only interested in the 5th line that contains a url
// (the number 5 depends on the invocation structure)
var line = stack.filter(function(line, i) {
return stackRE.test(line);
})[4];
// namespace default
namespace = namespace || global;
namespace = !namespace.indexOf(global) ? namespace : global + delimitter + namespace;
if (!line) {
return null;
}
// dynamic getter
var getLogger = function(_namespace) {
if (!_logger) {
return null;
}
// parse the line based on the format ".*protocol//host/path/file:line:local:*"
var match = line.match(originRE);
var parts = _namespace.split(delimitter);
// remove the 'global' part
parts.shift();
var logger = _logger;
while (parts.length) {
logger = logger.subLoggers()[parts.shift()];
if (!logger) {
break;
}
}
return logger;
};
if (!match) {
return null;
}
return match[1];
};
// dynamic setter
var getLoggerName = function(_namespace) {
var parts = _namespace.split(delimitter);
if(!parts.length) {
return null;
} else {
return parts.pop();
}
};
// try to get an existing logger
/**
* Logger definition.
*
* @param {string|object} [namespace=globalNamespace] - A string that defines the namespace of the
* new logger. In that case, the global namespace will be prefixed and the created logger is
* returned. When an object is passed, it is used to extend the default options and the main
* logger object is returned. Make sure to extend the options _before_ instantiating the first
* logger.
* @returns {logger|Logger}
*/
$.Logger = function(namespace) {
// create logger or extend default options
if ($.isPlainObject(namespace)) {
$.extend(options, namespace);
return $.Logger;
}
// prefix the global namespace
namespace = prefixNamespace(namespace);
// is there already a logger with that namespace?
var self = getLogger(namespace);
// create a new one?
if (!self) {
if (self) {
return self;
}
var _enabled = false,
_level = "info",
_supLogger = null,
_subLoggers = {},
name = getLoggerName(namespace),
// define a new logger
self = {
// current state
_enabled: true,
_canLog = function(level) {
if (!level) {
return enabled() && window.console;
} else {
var validLevel = levels[level] >= levels[_level];
return _enabled && window.console && validLevel;
}
// current level
_level: "all",
// the namespace
_namespace: namespace,
// the name
_name: getLoggerName(namespace),
// parent logger
_parent: null,
// child loggers
_children: {},
// name getter
name: function() {
return self._name;
},
supLogger = function() {
return _supLogger;
// namespace getter
namespace: function() {
return self._namespace;
},
subLoggers = function() {
return _subLoggers;
// parent getter
parent: function() {
return self._parent;
},
setSupLogger = function(logger) {
_supLogger = logger;
return self;
// children getter
children: function() {
return self._children;
},
addSubLogger = function(logger) {
_subLoggers[logger.name] = logger;
return self;
// specific child getter
child: function(name) {
return self._children[name] || null;
},
// enabled getter
enabled: function() {
return self._enabled;
},
enable = function() {
_enabled = true;
// enable all sub loggers
$.each(subLoggers(), function(i, sublogger) {
sublogger.enable();
// enabled setters
enable: function() {
self._enabled = true;
// enable all child loggers as well
$.each(self._children, function(_, child) {
child.enable();
});
return self;
},
disable = function() {
_enabled = false;
// disable all sub loggers
$.each(subLoggers(), function(i, sublogger) {
sublogger.disable();
disable: function() {
self._enabled = false;
// disable all child loggers as well
$.each(self._children, function(_, child) {
child.disable();
});
return self;
},
enabled = function() {
return _enabled;
},
level = function(__level) {
if (__level == null) {
return _level;
// level getter/setter
level: function(level) {
if (level === undefined) {
// getter
return self._level;
} else {
if (levels[__level] || !levels[__level]) {
_level = __level;
// change the level of all subLoggers
$.each(subLoggers(), function(i, sublogger) {
if (sublogger.level) {
sublogger.level(__level);
}
});
// setter
level = level.toLowerCase();
if (level in levels) {
self._level = level;
}
}
return self;
},
_log = function(level, arguments) {
var args = Array.prototype.slice.call(arguments);
if ( _canLog(level)) {
if (args[0]) {
args[0] = _prefix(level) + String(args[0]);
}
var method = "log";
if (useConsoleLevels) {
method = consoleMethods[level] || method;
method = window.console[method] ? method : "log";
}
if (lineNumbers) {
try {
var stack = String((new Error).stack).split("\n").map(function(msg) {
return msg.trim();
});
if (stack[0].toLowerCase() == "error") {
stack.shift();
}
var m = stack[2].match(/^.*(http\:\/\/(.+))$/);
if (m) {
var src = m[1].replace(/^\(/, "").replace(/\)$/, "");
src = src.split("/").pop();
src = src.replace(/\?[^\:]+/, "");
args.push("(" + src + ")");
}
} catch (err) {}
}
window.console[method].apply(window.console, args);
// creates the prefix for all logs (timestamp, level and namespace)
_prefix: function(level) {
var prefix = "";
// timestamp
if (options.timestamps) {
prefix += "[" + timestamp() + "] ";
}
return self;
// level
prefix += level.toUpperCase() + " - ";
// namespace w/o global namespace
var namespace = self.namespace();
if (self != globalLogger) {
namespace = namespace.substr(options.global.length + options.delimitter.length);
}
prefix += namespace + " ";
return prefix + "-";
},
_prefix = function(level) {
var prefix = "[" + _time() + "]";
// cut the 'global' string
var logNamespace;
if (namespace.indexOf(global) === 0 && namespace != global) {
logNamespace = namespace.replace(new RegExp(global + "\\."), "");
// creates the postfix for all logs (origin)
_postfix: function() {
var postfix = [];
// origin
if (options.origin) {
var origin = getOrigin();
if (origin != null) {
postfix.push(origin);
}
}
if (level) {
prefix += " " + fillString(level, maxLevelLength).toUpperCase() + " -";
return postfix.length ? "(" + postfix.join(", ") + ")" : "";
},
// checks whether a log is shown or not
_canLog: function(level) {
if (!window.console || level === undefined || !self.enabled()) {
return false;
}
if (logNamespace) {
prefix += " " + logNamespace + " -";
level = level.toLowerCase();
if (!(level in levels)) {
return false;
}
return prefix + " ";
},
log = function() {
_log(null, arguments);
return self;
},
debug = function() {
_log("debug", arguments);
return self;
},
info = function() {
_log("info", arguments);
return self;
return levels[level] >= levels[self.level()];
},
warn = function() {
_log("warning", arguments);
// actual log method, should not be invoked directly as some features would fail,
// e.g. line numbers
_log: function(level, arguments) {
if (!self._canLog(level)) {
return self;
}
var args = Array.prototype.slice.call(arguments);
// add prefix and postfix
var prefix = self._prefix(level);
var postfix = self._postfix();
if (args.length == 0) {
args.push(prefix);
} else {
args[0] = prefix + " " + args[0];
}