@karmaniverous/loggable

This library is deprecated in favor of @karmaniverous/controlled-proxy, which takes a much more generic and SOLID approach to the problem. See this article for an analysis of the issues with the mixin approach.

Loggable Mixin

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.