Log from an Extension
If a problem occurs while an extension is running, you probably want to communicate that information to the user. The extension should only throw an error (i.e., raise an exception) if the problem is so severe that no additional processing should be done. Otherwise, we recommend that extensions report the problem as a log message at an appropriate severity level. You can use Asciidoctor’s logger to log messages.
When Asciidoctor processes a document, it creates a Logger instance scoped to that document. You can access that logger in your extension to log messages. This page describes how to do that.
Mix in the Logger
To access the instance of Asciidoctor’s Logger, you need to include the Asciidoctor::Logging
module into your extension.
In doing so, it will provide access to the static logger
method, which will retrieve the instance from the LoggerManager
.
If you’re writing a class-based extension, you can include the Logging
module using the include
keyword.
class CustomBlock < Asciidoctor::Extensions::BlockProcessor
include Asciidoctor::Logging
# ...
end
Asciidoctor::Extensions.register do
block CustomBlock, :custom
end
If you’re writing an extension purely using the DSL, the processor class is created dynamically.
Therefore, you need to mix in the Logging
module by referencing the include
method on that class instead.
Asciidoctor::Extensions.register do
block :custom do
singleton_class.include Asciidoctor::Logging
# ...
end
end
In order to log a message, pass the message string to one of the severity methods (e.g., error
, warn
, info
, etc) on the logger instance returned by the logger
method.
Here’s an example of how to log a warning message in the process method.
def process doc, reader, attr
logger.warn 'We are logging!'
# ...
end
Here’s what Asciidoctor will print to the log:
asciidoctor: WARN: We are logging!
Refer to the documentation for Logger to learn about the available methods.
Here’s a complete example to show where these additions are used in context:
Asciidoctor::Extensions.register do
block :custom do
singleton_class.include Asciidoctor::Logging
on_context :paragraph
process do |parent, reader, attrs|
logger.warn 'We are logging!'
create_paragraph parent, reader.lines, attrs
end
end
end
Keep in mind that the message will only be displayed if its severity level is enabled by the user. By default, error and warn(ing) messages will be displayed.
You should refrain from using the fatal
method unless the extension is in the process of throwing an error.
Add context to the message
If you pass a string to the log method, Asciidoctor will only print the severity level and the message. That means the user won’t know what AsciiDoc source the message refers to nor that the message is coming from an extension. Therefore, you’ll probably want to provide more detail. Let’s begin by including the extension name in the message.
Add the extension name
First, when logging a message, you should prefix that message with the name of the extension.
logger.warn '(custom-block-extension) Something is out of sorts.'
That will print the message as follows:
asciidoctor: WARN: (custom-block-extension) Something is out of sorts.
Alternately, you can move your extension name so that it immediately follows the program name (i.e., asciidoctor
).
logger.log ::Logger::WARN, 'hi', %(#{logger.progname} (custom-block-extension))
That will print the message as follows:
asciidoctor (custom-block-extension): WARN: Something is out of sorts.
Another way to get the same output is to use the form that accepts the message as a block.
logger.warn(%(#{logger.progname} (custom-block-extension))) { 'Something is out of sorts.' }
The block form is useful in other ways. If the computation of the message is expensive, the block form defers evaluation of the message until (and thus if) it is logged.
Now the reader knows that the message is coming from an extension, but not the location of the AsciiDoc source the extension is processing. Let’s look at how to do that.
Add the source location
In addition to the logger
method, Asciidoctor’s Logging
module also adds a helper named message_with_context
that generates a message object that bundles source information with the string message.
For block and block macro extensions, you probably want to reference the location where the block is found, which you can retrieve using the parent.document.reader.cursor_at_mark
method call.
For a tree processor extension, you can retrieve the source location from the source_location
method on the block.
For most other extensions, you likely want to reference the current cursor, which is accessible from parent.document.reader.cursor
or (doc.reader
for extensions that only provide access to the document object).
Here’s an example that replaces the message with an object that includes the source location:
logger.warn(%(#{logger.progname} (custom-block-extension))) {
message_with_context 'Something is out of sorts.', source_location: parent.document.reader.cursor_at_mark
}
Here’s an example of what Asciidoctor will print:
asciidoctor (custom-block-extension): WARNING: test.adoc: line 4: Something is out of sorts.
In a tree processor extension, you can get the source location from any block if the sourcemap is enabled. If it’s not enabled, the code will degrade gracefully by logging the message without the source location.
Asciidoctor::Extensions.register do
tree_processor do |doc|
singleton_class.include Asciidoctor::Logging
doc.find_by context: :paragraph do |paragraph|
logger.warn(%(#{logger.progname} (custom-tree-processor))) {
message_with_context 'Found paragraph.', source_location: paragraph.source_location
}
end
nil
end
end
Source location tracking in Asciidoctor is not perfect, so you may need to retrieve the cursor and adjust it slightly to align it with the line you want to reference in the message.
Refer to Reader#cursor for a list of cursor methods.