-
-
Notifications
You must be signed in to change notification settings - Fork 160
Loglevel 2.0 #119
Copy link
Copy link
Open
Description
Loglevel is good. It's time to make it fresh. More clearly. More modern.
- Remove support for legacy browsers.
- Remove support for persistance levels.
- Remove noConflict()
- Сustomizable levels
- Flat flow of plugins (only one extra line in the stacktrace for any number of plugins)
log.getLogger('child')=>log('child')- getter/setter log.level
- maybe something else...
Implementation loglevel.js
const noop = () => {};
const loggers = {};
const configs = {};
const chain = [];
let levels = ['trace', 'debug', 'info', 'warn', 'error', 'silent'];
let levelsInfo = levels.slice();
class Plugin {
constructor(name) {
this.name = name;
}
// eslint-disable-next-line class-methods-use-this
factory() {
return () => {};
}
};
// Build the best logging method possible for this env
// Wherever possible we want to bind, not wrap, to preserve stack traces
function defaultFactory(methodValue) {
/* eslint-disable no-console */
if (typeof console === 'undefined') return noop;
const methodName = console[levels[methodValue]] ? levels[methodValue] : 'log';
if (console[methodName]) return console[methodName].bind(console);
return noop;
/* eslint-enable no-console */
}
function pluginsFactory(methodValue, logger) {
const methods = [];
chain.forEach((plugin) => {
const rootConfig = configs[plugin.name][''];
const loggerConfig = configs[plugin.name][logger.title];
if (rootConfig || loggerConfig) {
methods.push(
plugin.factory(
methodValue,
logger,
Object.assign({}, plugin.defaults, rootConfig, loggerConfig),
),
);
}
});
const native = defaultFactory(methodValue);
return (...args) => {
for (let i = 0; i < methods.length; i++) {
methods[i](args);
}
native(...args);
};
}
let factory = defaultFactory;
function rebuildMethods(logger) {
for (let i = 0; i < levels.length - 1; i++) {
logger[levels[i]] = i < logger.level ? noop : factory(i, logger);
}
}
function removeMethods(logger) {
for (let i = 0; i < levels.length - 1; i++) {
delete logger[levels[i]];
}
}
function Logger(logName, logLevel) {
const logger = this || {};
const defineProperty = Object.defineProperty;
defineProperty(logger, 'title', {
get() {
return logName;
},
});
defineProperty(logger, 'level', {
get() {
return logLevel;
},
set(lvl) {
let newLevel = lvl;
if (typeof newLevel === 'string') {
newLevel = levels.indexOf(newLevel.toLowerCase());
}
if (typeof newLevel === 'number' && newLevel >= 0 && newLevel < levels.length) {
logLevel = newLevel;
rebuildMethods(logger);
} else {
throw new Error(`Invalid level: ${lvl}`);
}
},
});
defineProperty(logger, 'levels', {
get() {
return levelsInfo;
},
});
logger.use = function (plugin, config) {
// if (
// typeof plugin === 'object' &&
// typeof plugin.name === 'string' &&
// typeof plugin.factory === 'function'
// ) {
if (plugin instanceof Plugin) {
const pluginName = plugin.name;
if (!configs[pluginName]) {
// lazy plugging
configs[pluginName] = {};
chain.push(plugin);
factory = pluginsFactory;
}
plugin = pluginName;
}
if (typeof plugin !== 'string' || !configs[plugin]) {
throw new Error(`Invalid plugin: ${plugin}`);
}
configs[plugin][logName] = config || {};
rebuildMethods(logger);
};
logger.level = logLevel;
loggers[logName] = logger;
return logger;
}
function log(name, level) {
name = name || '';
if (typeof name !== 'string') {
throw new TypeError(`Invalid name: ${name}`);
}
return loggers[name] || new Logger(name, level || log.level);
}
Logger.call(log, '', 3);
log.Plugin = Plugin;
log.config = (newLevels, newLevel) => {
Object.keys(loggers).forEach(logger => removeMethods(loggers[logger]));
levels = newLevels;
levelsInfo = levels.slice();
Object.keys(loggers).forEach((logger) => {
loggers[logger].level = newLevel;
});
};
export default log;Example
const log = require('loglevel');
class Prefixer extends log.Plugin {
constructor() {
super('prefixer');
this.defaults = {
text: 'default prefix',
};
this.prevs = {};
}
factory(method, logger, config) {
return (args) => {
const timestamp = Date.now();
const prev = this.prevs[logger.title];
const delta = prev ? timestamp - prev : 0;
this.prevs[logger.title] = timestamp;
args.unshift(
`[+${delta}ms] ${logger.levels[method].toUpperCase()} (${logger.title}) "${config.text}":`,
);
};
}
}
log.level = 'trace';
(function StackTraceTest() {
log.trace();
}());
const child = log('child');
child.info('child');
const prefixer = new Prefixer();
child.use(prefixer, { text: 'custom prefix:' });
log.info('root');
child.info('child');
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += Math.log(0.1);
}
log.use('prefixer');
log.info('root');
child.info(sum);
log.config(['verbose', 'trace', 'critical', 'silent'], 'trace');
log.critical('critical');
child.verbose('verbose1');
child.level = 'verbose';
child.verbose('verbose2');
(function StackTraceTest() {
log.trace();
child.trace();
}());Output
C:\Users\u36\Dropbox\kutuluk\logler>node ./examples/example
Trace
at StackTraceTest (C:\Users\u36\Dropbox\kutuluk\logler\examples\example.js:29:7)
...
child
root
[+0ms] INFO (child) "custom prefix:": child
[+0ms] INFO () "default prefix": root
[+27ms] INFO (child) "custom prefix:": -2302585.0930085
[+1ms] CRITICAL () "default prefix": critical
[+1ms] VERBOSE (child) "custom prefix:": verbose2
Trace: [+0ms] TRACE () "default prefix":
at Function.trace (C:\Users\u36\Dropbox\kutuluk\logler\lib\logger.js:105:14)
at StackTraceTest (C:\Users\u36\Dropbox\kutuluk\logler\examples\example.js:64:7)
...
Trace: [+2ms] TRACE (child) "custom prefix:":
at Logger.trace (C:\Users\u36\Dropbox\kutuluk\logler\lib\logger.js:105:14)
at StackTraceTest (C:\Users\u36\Dropbox\kutuluk\logler\examples\example.js:65:9)
...
If this fits into the development concept of loglevel, I will make a pull request
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels