Skip to main content

Logger

Ditsmod uses the Logger class as an interface as well as a DI token. By default, ConsoleLogger is used for logging. There are 8 logging levels in total (borrowed from log4j):

  • all- All events should be logged.
  • trace - A fine-grained debug message, typically capturing the flow through the application.
  • debug - A general debugging event.
  • info - An event for informational purposes.
  • warn - An event that might possible lead to an error.
  • error - An error in the application, possibly recoverable.
  • fatal - A fatal event that will prevent the application from continuing.
  • off - No events will be logged. Intended for testing, it is not recommended to use it in product mode.

In this documentation, when we talk about "logging levels", we mean "log level of detail". The highest level of detail is all, the lowest level of detail is off.

Sometimes in this documentation, or in the Ditsmod system messages, you may come across two types indicating the logging level:

  • InputLogLevel - this type indicates the log level intended for a specific message. For example, the following entry uses the log level - info:
    logger.log('info', 'some message');
  • OutputLogLevel - this type indicates the limit level of logs above which messages are ignored. For example, the following entry sets the logging level to debug:
    logger.setLevel('debug');

If InputLogLevel is equal to or lower than OutputLogLevel, the message is writed by the logger, otherwise it is ignored. For example, the following combination will write a message:

logger.setLevel('debug');
logger.log('info', 'some message');

And in the following - it will be ignored:

logger.setLevel('warn');
logger.log('info', 'some message');

Substitution the system logger

If you want the system logs written by Ditsmod to be written by your own logger, it must implement the Logger interface. It can then be passed to DI at the application level:

import { Logger, rootModule } from '@ditsmod/core';
import { MyLogger } from './my-loggegr.js';

@rootModule({
// ...
providersPerApp: [
{ token: Logger, useClass: MyLogger }
],
})
export class AppModule {}

But, most likely, you will want to use some ready-made, well-known logger. And there is a good chance that its interface is different from that of Logger. But, as a rule, this is also not a problem, because before transferring the logger instance to DI, it can be patched so that it implements the necessary interface. For this, a provider with the useFactory property is used.

Let's write the code for this provider first. At the moment (2023-09-02), one of the most popular Node.js loggers is winston. For patching, we wrote a class method before which we added the methodFactory decorator:

import { Logger, LoggerConfig, OutputLogLevel, methodFactory } from '@ditsmod/core';
import { createLogger, addColors, format, transports } from 'winston';

export class PatchLogger {
@methodFactory()
patchLogger(config: LoggerConfig) {
const logger = createLogger();

const transport = new transports.Console({
format: format.combine(format.colorize(), format.simple()),
});

const customLevels = {
levels: {
off: 0,
fatal: 1,
error: 2,
warn: 3,
info: 4,
debug: 5,
trace: 6,
all: 7,
},
colors: {
fatal: 'red',
error: 'brown',
warn: 'yellow',
info: 'blue',
debug: 'green',
trace: 'grey',
all: 'grey',
},
};

logger.configure({
levels: customLevels.levels,
level: config.level,
transports: [transport],
});

// Logger must have `setLevel` method.
(logger as unknown as Logger).setLevel = (value: OutputLogLevel) => {
logger.level = value;
};

// Logger must have `getLevel` method.
(logger as unknown as Logger).getLevel = () => {
return logger.level as OutputLogLevel;
};

addColors(customLevels.colors);

return logger;
}
}

As you can see, in addition to the usual settings for winston, the highlighted lines add two methods to his instance - setLevel and getLevel - which it does not have, but which are necessary for Ditsmod to interact with it properly.

And now this class can be passed to DI at the application level:

import { Logger, rootModule } from '@ditsmod/core';
import { PatchLogger } from './patch-logger.js';

@rootModule({
// ...
providersPerApp: [
{ token: Logger, useFactory: [PatchLogger, PatchLogger.prototype.patchLogger] }
],
})
export class AppModule {}

You can view finished examples with loggers in the Ditsmod repository.

Using the logger in production mode

To change the logging level in production mode, you do not need to change the compiled code. You can create a custom controller, guard it, and then call the appropriate route to change the logging level that you specify in the URL:

import { AnyObj, controller, inject, Logger, LogLevel, QUERY_PARAMS, Res, route } from '@ditsmod/core';

import { requirePermissions } from '../auth/guards-utils.js';
import { Permission } from '../auth/types.js';

@controller()
export class SomeController {
@route('GET', 'set-loglevel', [requirePermissions(Permission.canSetLogLevel)])
setLogLevel(@inject(QUERY_PARAMS) queryParams: AnyObj, logger: Logger, res: Res) {
const level = queryParams.logLevel as LogLevel;
try {
logger.setLevel(level);
res.send('Setting logLevel successful!');
} catch (error: any) {
res.send(`Setting logLevel is failed: ${error.message}`);
}
}
}

As you can see, the route path /set-loglevel is created here, with protection through a guard that checks the permissions for such an action. This uses requirePermissions(), which you can read about in Helpers for guards with parameters.