@karmaniverous/loggable

Logging is an implementation decision that should be deferred as late as possible.

When you are writing a Typescript or Javascript class, the Loggable mixin permits the consumer of your class to:

  • Inject a preferred logging system into your class, so the logs generated by your class will be consistent with those generated by the rest of the applicaion.

  • Decide at runtime what level of logging your class should produce.

npm i @karmaniverous/loggable

By default, Loggable uses the console logger and disables debug logs.

import { Loggable } from '@karmaniverous/loggable';

// Defaults to console logger & disables debug logs.
class MyClass extends Loggable() {
myMethod() {
this.logger.debug('debug log');
this.logger.info('info log');
}
}

const myInstance = new MyClass();

// By default, disables debug logs.
myInstance.myMethod();
// info log

// Change disabled logger endpoints on the fly.
myInstance.loggableOptions.disabled = ['info'];
myInstance.myMethod();
// debug log

// Use the instance logger directly.
myInstance.logger.debug('debug log');
myInstance.logger.info('info log');
// debug log

// Set `enableAll` to `true` to ignore disabled endpoints.
myInstance.loggableOptions.enableAll = true;
myInstance.myMethod();
// debug log
// info log

You can inject a custom logger into a custom base class with custom options.

import { Loggable } from '@karmaniverous/loggable';
import winston from 'winston';

class MyBaseClass {
protected repeat(message: string, times: number) {
return Array.from({ length: times })
.map(() => message)
.join(' ');
}
}

// Custom base class, logger & options.
class MyClass extends Loggable(MyBaseClass, winston, {
disabled: ['debug', 'info'],
}) {
myMethod() {
this.logger.debug(this.repeat('debug log', 2));
this.logger.info(this.repeat('info log', 2));
this.logger.error(this.repeat('error log', 2));
}
}

const myInstance = new MyClass();

myInstance.myMethod();
// error log error log <-- winston logger!

You can create a generic class that also defers the choice of logger and options!

import { Loggable } from '@karmaniverous/loggable';
import winston from 'winston';

// This function returns an anonymous class that extends Loggable.
function MyGenericClass<Logger = Console>(
logger: Logger = console as Logger,
options: Partial<LoggableOptions> = {},
) {
// The anonymous class extends Loggable with the provided logger & options.
return class extends Loggable(undefined, logger, options) {
myMethod() {
this.logger.debug('debug log');
this.logger.info('info log');
this.logger.error('error log');
}
};
}

// Generate a winston-logged version of my class...
const MyWinstonLoggedClass = MyGenericClass(winston, {
disabled: ['debug', 'info'],
});

// ...and instantiate it.
const myInstance = new MyWinstonLoggedClass();

myInstance.myMethod();
// error log <-- winston logger!

One potential gotcha here: this.logger will support whatever methods the provided logger has.

If you provide a logger that doesn't have a debug method, for example, you will get a runtime error when you try to call this.logger.debug. So be sure to stick to commonly supported logger method names like debug, info, and error!


Built for you with ❤️ on Bali! Find more great tools & templates on my GitHub Profile.