aurelia-logging
Version:
A minimal but effective logging mechanism with support for log levels and pluggable log appenders.
342 lines (303 loc) • 7.68 kB
JavaScript
/**
* Specifies the available logging levels.
*/
interface LogLevel {
/**
* No logging.
*/
none: number,
/**
* Log only error messages.
*/
error: number,
/**
* Log warnings messages or above.
*/
warn: number,
/**
* Log informational messages or above.
*/
info: number,
/**
* Log all messages.
*/
debug: number,
/**
* Additional log levels defined at runtime.
*/
[level: string]: number
}
/**
* Specifies the available logging levels.
*/
export const logLevel: LogLevel = {
none: 0,
error: 10,
warn: 20,
info: 30,
debug: 40
};
let loggers = {};
let appenders = [];
let globalDefaultLevel = logLevel.none;
const standardLevels = ['none', 'error', 'warn', 'info', 'debug'];
function isStandardLevel(level: string) {
return standardLevels.filter(l => l === level).length > 0;
}
function appendArgs() {
return [this, ...arguments];
}
function logFactory(level) {
const threshold = logLevel[level];
return function() {
// In this function, this === logger
if (this.level < threshold) {
return;
}
// We don't want to disable optimizations (such as inlining) in this function
// so we do the arguments manipulation in another function.
// Note that Function#apply is very special for V8.
const args = appendArgs.apply(this, arguments);
let i = appenders.length;
while (i--) {
appenders[i][level](...args);
}
};
}
function logFactoryCustom(level) {
//This function is the same as logFactory() except that it checks that the method
//is defined on the appender.
const threshold = logLevel[level];
return function() {
// In this function, this === logger
if (this.level < threshold) {
return;
}
// We don't want to disable optimizations (such as inlining) in this function
// so we do the arguments manipulation in another function.
// Note that Function#apply is very special for V8.
const args = appendArgs.apply(this, arguments);
let i = appenders.length;
while (i--) {
const appender = appenders[i];
if (appender[level] !== undefined) {
appender[level](...args);
}
}
};
}
function connectLoggers() {
let proto = Logger.prototype;
for (let level in logLevel) {
if (isStandardLevel(level)) {
if (level !== 'none') {
proto[level] = logFactory(level);
}
} else {
proto[level] = logFactoryCustom(level);
}
}
}
function disconnectLoggers() {
let proto = Logger.prototype;
for (let level in logLevel) {
if (level !== 'none') {
proto[level] = function() { };
}
}
}
/**
* Gets the instance of a logger associated with a particular id (or creates one if it doesn't already exist).
*
* @param id The id of the logger you wish to get an instance of.
* @return The instance of the logger, or creates a new logger if none exists for that id.
*/
export function getLogger(id: string): Logger {
return loggers[id] || new Logger(id);
}
/**
* Implemented by classes which wish to append log data to a target data store.
*/
interface Appender {
/**
* Appends a debug log.
*
* @param logger The source logger.
* @param rest The data to log.
*/
debug(logger: Logger, ...rest: any[]): void;
/**
* Appends an info log.
*
* @param logger The source logger.
* @param rest The data to log.
*/
info(logger: Logger, ...rest: any[]): void;
/**
* Appends a warning log.
*
* @param logger The source logger.
* @param rest The data to log.
*/
warn(logger: Logger, ...rest: any[]): void;
/**
* Appends an error log.
*
* @param logger The source logger.
* @param rest The data to log.
*/
error(logger: Logger, ...rest: any[]): void;
}
/**
* Adds an appender capable of processing logs and channeling them to an output.
*
* @param appender An appender instance to begin processing logs with.
*/
export function addAppender(appender: Appender): void {
if (appenders.push(appender) === 1) {
connectLoggers();
}
}
/**
* Removes an appender.
* @param appender An appender that has been added previously.
*/
export function removeAppender(appender: Appender): void {
appenders = appenders.filter(a => a !== appender);
}
/**
* Gets an array of all appenders.
*/
export function getAppenders() {
return [...appenders];
}
/**
* Removes all appenders.
*/
export function clearAppenders(): void {
appenders = [];
disconnectLoggers();
}
/**
* Adds a custom log level that will be added as an additional method to Logger.
* Logger will call the corresponding method on any appenders that support it.
*
* @param name The name for the new log level.
* @param value The numeric severity value for the level (higher is more severe).
*/
export function addCustomLevel(name: string, value: number): void {
if (logLevel[name] !== undefined) {
throw Error(`Log level "${name}" already exists.`);
}
if (isNaN(value)) {
throw Error('Value must be a number.');
}
logLevel[name] = value;
if (appenders.length > 0) {
//Reinitialize the Logger prototype with the new method.
connectLoggers();
} else {
//Add the custom level as a noop by default.
Logger.prototype[name] = function() { };
}
}
/**
* Removes a custom log level.
* @param name The name of a custom log level that has been added previously.
*/
export function removeCustomLevel(name: string): void {
if (logLevel[name] === undefined) {
return;
}
if (isStandardLevel(name)) {
throw Error(`Built-in log level "${name}" cannot be removed.`);
}
delete logLevel[name];
delete Logger.prototype[name];
}
/**
* Sets the level of logging for ALL the application loggers.
*
* @param level Matches a value of logLevel specifying the level of logging.
*/
export function setLevel(level: number): void {
globalDefaultLevel = level;
for (let key in loggers) {
loggers[key].setLevel(level);
}
}
/**
* Gets the level of logging of ALL the application loggers.
*
* @return The logLevel value used in all loggers.
*/
export function getLevel(): number {
return globalDefaultLevel;
}
/**
* A logger logs messages to a set of appenders, depending on the log level that is set.
*/
export class Logger {
/**
* The id that the logger was created with.
*/
id: string;
/**
* The logging severity level for this logger
*/
level: number;
/**
* You cannot instantiate the logger directly - you must use the getLogger method instead.
*/
constructor(id: string) {
let cached = loggers[id];
if (cached) {
return cached;
}
loggers[id] = this;
this.id = id;
this.level = globalDefaultLevel;
}
/**
* Logs a debug message.
*
* @param message The message to log.
* @param rest The data to log.
*/
debug(message: string, ...rest: any[]): void {}
/**
* Logs info.
*
* @param message The message to log.
* @param rest The data to log.
*/
info(message: string, ...rest: any[]): void {}
/**
* Logs a warning.
*
* @param message The message to log.
* @param rest The data to log.
*/
warn(message: string, ...rest: any[]): void {}
/**
* Logs an error.
*
* @param message The message to log.
* @param rest The data to log.
*/
error(message: string, ...rest: any[]): void {}
/**
* Sets the level of logging for this logger instance
*
* @param level Matches a value of logLevel specifying the level of logging.
*/
setLevel(level: number): void {
this.level = level;
}
/**
* Returns if the logger is in debug mode or not.
*/
isDebugEnabled(): boolean {
return this.level === logLevel.debug;
}
}