Logging

All warning and error messages in Asciidoctor.js are routed through a logger. Messages related to the input document also include context about the source location (file name, file directory, file path, line number), which can be useful for integrations and tooling.

When using the API, you can feed your own logger to the LoggerManager to capture, route, or observe the messages.

Logger

Default logger

The default logger will output all warning and error messages to stderr (the standard error output stream).

Depending on your JavaScript environment, stderr will be resolved to:

In-memory logger

In addition to the default logger, Asciidoctor.js provides a built-in in-memory logger named MemoryLogger. This logger won’t output messages to stderr but instead they will be stored in-memory.

First we need to instantiate this logger using the create function:

const memoryLogger = asciidoctor.MemoryLogger.create()

Once the logger is instantiated, we need to tell the Asciidoctor processor that we want to use it:

asciidoctor.LoggerManager.setLogger(memoryLogger)

The above code will effectively replace the default logger with the in-memory logger.

Error and warning messages

The in-memory logger stores every warning and error messages generated by the Asciidoctor.js processor. Once the processing is done, you can retrieve all these messages using the getMessages function:

const loggerManager = asciidoctor.LoggerManager
const memoryLogger = asciidoctor.MemoryLogger.create()
loggerManager.setLogger(memoryLogger)

asciidoctor.convert('input')

memoryLogger.getMessages() // returns an array of Message

For every message, you can get the following information:

const message = memoryLogger.getMessages()[0]
console.log(message.getSeverity()) (1)
console.log(message.getText()) (2)
const sourceLocation = message.getSourceLocation() (3)
if (sourceLocation) {
  console.log(sourceLocation.getLineNumber()) (4)
  console.log(sourceLocation.getFile()) (5)
  console.log(sourceLocation.getDirectory()) (6)
  console.log(sourceLocation.getPath()) (7)
}
1 returns the severity (ERROR or WARNING)
2 returns the error or warning text message
3 returns the context about the source location (can be undefined)
4 returns the source line number associated to the message
5 returns the file name associated to the message (or undefined when converting from a String)
6 returns the absolute path to the source file parent directory, or the execution path when converting from a String
7 returns the path associated to the message (or <stdin> when converting from a String)

Logger instances

The Asciidoctor.js processor can only have one logger configured. If you want to restore the default logger after replacing it, you should save a reference:

const loggerManager = asciidoctor.LoggerManager
const defaultLogger = loggerManager.getLogger() (1)
try {
  const memoryLogger = asciidoctor.MemoryLogger.create()
  loggerManager.setLogger(memoryLogger) (2)
  // convert a document then do something with the in-memory logger
} finally {
  loggerManager.setLogger(defaultLogger) (3)
}
1 save the default logger
2 replace the default logger with the in-memory logger
3 restore the default logger

Custom logger

In this section, we will explain how to replace the default logger by the popular logging library Winston. To instantiate a new logger, we can use the newLogger function on the LoggerManager:

const winston = require('winston') (1)
const winstonLogger = asciidoctor.LoggerManager.newLogger('WinstonLogger', {
  postConstruct: function () {
    this.logger = winston.createLogger({
      level: 'warning',
      format: winston.format.json(),
      transports: [
        new winston.transports.File({ filename: 'error.log', level: 'error' }),
        new winston.transports.File({ filename: 'combined.log' })
      ]
    })
  },
  add: function (severity, _, message) {
    const level = asciidoctor.LoggerSeverity.get(severity).toLowerCase() (2)
    logger.log({
      level: level,
      message: message.getText()
    }) (3)
  }
})
1 Import the winston library (winston package must be installed: npm install winston)
2 Convert the severity (number) to a logger level ('error', 'warn'…​)
3 Send messages to the Winston logger

Logger formatter

Default logger formatter

The default formatter will output messages using the following human readable format:

asciidoctor: ${severity}: ${message}
The message can include context about the source location.

Here’s an example using the default formatter:

asciidoctor: ERROR: <stdin>: line 8: invalid part, must have at least one section (e.g., chapter, appendix, etc.)

Custom Logger formatter

In this section, we will demonstrate how to replace the default formatter to output messages as JSON. To do that, we are going to use the newFormatter function to instantiate a new formatter and the setFormatter function to replace the default formatter on a logger:

const loggerManager = asciidoctor.LoggerManager
const defaultLogger = loggerManager.getLogger()
const jsonFormatter = asciidoctor.LoggerManager.newFormatter('JsonFormatter', {
  call: function (severity, time, programName, message) {
    const text = message['text']
    const sourceLocation = message['source_location']
    return JSON.stringify({
      programName: programName,
      message: text,
      sourceLocation: {
        lineNumber: sourceLocation.getLineNumber(),
        path: sourceLocation.getPath()
      },
      severity: severity
    }) + '\n'
  }
})
defaultLogger.setFormatter(jsonFormatter)

Here’s the result:

{"programName":"asciidoctor","message":"invalid part, must have at least one section (e.g., chapter, appendix, etc.)","sourceLocation":{"lineNumber":8,"path":"<stdin>"},"severity":"ERROR"}