Skip to content

Logging Pipeline

The Logging Pipeline logs the execution of commands and queries handled through the Xeno.JS CQRS pipeline.

It records:

  • when a request starts;
  • when a request completes successfully;
  • when a request returns a failed Result;
  • when request execution throws an exception.

The pipeline uses the application logger, so you can configure where logs are written without changing your commands or handlers.

You need:

  • an AppBuilder instance;
  • a command or query handled through the Xeno.JS mediator;
  • the CQRS pipeline enabled with addPipeline().

You do not need to register the LoggingPipeline manually.

Enable the CQRS pipeline through AppBuilder:

import { AppBuilder } from 'xeno-js'
const app = new AppBuilder()
.addPipeline()
.build()

When addPipeline() is configured, Xeno.JS adds the logging pipeline together with the other common CQRS pipelines.

Both commands and queries use the logging pipeline.

Use addLogger() to configure the logger used by the application.

For example, to configure the minimum log level:

import { AppBuilder } from 'xeno-js'
import { LOG_LEVEL } from '@xeno-js/shared'
const app = new AppBuilder()
.addLogger((config) => {
config.level = LOG_LEVEL.INFO
})
.addPipeline()
.build()

The available configuration includes:

Option Description
level Minimum log level forwarded to the configured logging clients.
console Enables or disables console logging.
sentry.config Configures Sentry logging.
pino.config Configures Pino logging.
customLoggers Registers custom logging clients.

The default AppBuilder configuration uses console logging and the DEBUG level.

Xeno.JS filters messages below the configured minimum level.

For example:

import { AppBuilder } from 'xeno-js'
import { LOG_LEVEL } from '@xeno-js/shared'
const app = new AppBuilder()
.addLogger((config) => {
config.level = LOG_LEVEL.WARN
})
.addPipeline()
.build()

With LOG_LEVEL.WARN, informational messages from the Logging Pipeline are filtered out, while warnings and errors remain available to logging clients.

The Logging Pipeline itself uses:

  • info for request start;
  • info for successful completion;
  • error for failed Result values;
  • error for thrown exceptions.

Console logging is enabled by default.

You can configure it explicitly:

const app = new AppBuilder()
.addLogger((config) => {
config.console = true
})
.addPipeline()
.build()

To disable the console logger:

const app = new AppBuilder()
.addLogger((config) => {
config.console = false
})
.addPipeline()
.build()

Disabling the console logger does not disable the Logging Pipeline itself. The pipeline can still send logs to other configured logging clients.

Xeno.JS can use Pino as a logging client.

Configure it through pino.config:

const app = new AppBuilder()
.addLogger((config) => {
config.pino.config = {
destination: 'stdout',
prettyPrint: true,
env: 'development',
}
})
.addPipeline()
.build()

Set destination to file and provide a file path:

const app = new AppBuilder()
.addLogger((config) => {
config.pino.config = {
destination: 'file',
filePath: 'logs/app.log',
prettyPrint: false,
env: 'production',
}
})
.addPipeline()
.build()

Supported destinations are:

  • stdout;
  • file.

If filePath is omitted when using the file destination, Xeno.JS uses logs/app.log.

Pino logging also applies its own redaction for sensitive fields such as:

  • password;
  • token;
  • secret;
  • authorization;
  • headers.authorization.

Xeno.JS can also send warning and error logs to Sentry.

Configure the Sentry DSN:

const app = new AppBuilder()
.addLogger((config) => {
config.sentry.config = {
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
}
})
.addPipeline()
.build()

The Sentry DSN is required when Sentry logging is enabled.

If environment is not provided, Xeno.JS uses NODE_ENV and falls back to development.

Sentry logging is primarily used for warnings and errors. Informational and debug messages are not sent as Sentry events.

You can configure more than one logging destination.

For example, console and Pino can be enabled together:

const app = new AppBuilder()
.addLogger((config) => {
config.console = true
config.pino.config = {
destination: 'stdout',
prettyPrint: true,
env: 'development',
}
})
.addPipeline()
.build()

The application logger forwards each accepted message to the configured logging clients.

Use customLoggers when you need to integrate a logging destination that is not provided by Xeno.JS.

A custom logger must implement the ILoggerClient contract from @xeno-js/shared.

For example:

import type { ILoggerClient } from '@xeno-js/shared'
const customLogger = (): ILoggerClient => ({
track(level, message, context, error) {
console.log({
level,
message,
context,
error,
})
},
})

Register it with addLogger():

const app = new AppBuilder()
.addLogger((config) => {
config.customLoggers = [
() => customLogger(),
]
})
.addPipeline()
.build()

Custom logger factories receive the application service scope, so they can resolve application services when required.

For a request such as:

{
type: 'COMMAND',
intent: 'CREATE_USER'
}

the pipeline logs the start of the request:

Handling COMMAND CREATE_USER

When the request succeeds:

Successfully handled COMMAND CREATE_USER

When the handler returns a failed Result:

Failed to handle COMMAND CREATE_USER: User already exists

When request execution throws:

Exception while handling COMMAND CREATE_USER

The failed Result is returned unchanged.

Thrown exceptions are logged and then rethrown.

A command or query can complete without throwing but still return a failed Result.

For example:

const result = await mediator.send(command)
if (!result.isOk()) {
// The Logging Pipeline has already logged the failure.
}

The Logging Pipeline does not replace or transform the Result.

It logs the error and returns the same result to the caller.

If execution throws an exception:

try {
await mediator.send(command)
} catch (error) {
// The Logging Pipeline has already logged the exception.
}

The pipeline logs the exception and rethrows it.

This means the Logging Pipeline does not act as the application’s exception handler. Use the Exception Pipeline when you need to configure exception handling behavior.

A typical application can configure logging and the CQRS pipeline together:

import { AppBuilder } from 'xeno-js'
import { LOG_LEVEL } from '@xeno-js/shared'
const app = new AppBuilder()
.addLogger((config) => {
config.level = LOG_LEVEL.INFO
config.console = true
config.pino.config = {
destination: 'stdout',
prettyPrint: true,
env: 'development',
}
})
.addPipeline()
.build()

After the application is built, commands and queries executed through the configured mediator are logged automatically.

If commands or queries are not producing logs, check:

  1. addPipeline() is called;
  2. the command or query is executed through the Xeno.JS mediator;
  3. console logging has not been disabled with config.console = false;
  4. the configured minimum config.level does not filter the messages you expect;
  5. at least one logging client is configured.

For example, if you configure:

config.level = LOG_LEVEL.WARN

the normal info messages generated by the Logging Pipeline will not be forwarded.

Pino logs are not written to the expected destination

Section titled “Pino logs are not written to the expected destination”

Check the Pino configuration:

config.pino.config = {
destination: 'file',
filePath: 'logs/app.log',
}

For a file destination:

  • destination must be file;
  • filePath should point to the desired output file.

For standard output:

config.pino.config = {
destination: 'stdout',
}

Pino Logger Documentation

If Sentry logging is enabled, verify that the configuration contains a DSN:

config.sentry.config = {
dsn: process.env.SENTRY_DSN,
}

If the Sentry configuration is enabled without a DSN, application initialization fails with:

Sentry DSN is required when Sentry logging is enabled.

Sentry Node Logger Documentation




Xeno.JS is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section