如果在扩展运行时发生问题,你可能想要将这些信息通知给用户。如果问题非常严重以致于不应该进行任何进一步的处理,扩展应该抛出一个错误(即,抛出一个异常)。否则,我们建议扩展以一个合适的严重级别作为日志消息来报告问题。你可以使用Asciidoctor的日志记录器来记录消息。

当Asciidoctor处理一个文档时,它会创建一个针对该文档的https://ruby-doc.org/3.2.2/stdlibs/logger/Logger.html[Logger^]实例。你可以在你的扩展中访问这个记录器来记录消息。这个页面描述了如何做到这一点。

混入日志记录器

要访问Asciidoctor的Logger实例,你必须将`Asciidoctor::Logging`模块包含到你的扩展中。这样做将提供对静态的`logger`方法的访问权,从而从`LoggerManager`中检索实例。

Tip
记录器也可以通过文档对象上的 logger 方法来访问。如果你正在编写一个有权访问文档对象的扩展,这可能是一种更简单的方法。

如果你正在编写一个基于类的扩展,你可以使用`include`关键字来包含`Logging`模块。

class CustomBlock < Asciidoctor::Extensions::BlockProcessor
  include Asciidoctor::Logging

# ...
end

Asciidoctor::Extensions.register do
  block CustomBlock, :custom
end

如果您仅使用DSL来编写扩展,处理器类将被动态创建。因此,您需要通过引用该类上的`include`方法来混合`Logging`模块。

Asciidoctor::Extensions.register do
  block :custom do
    singleton_class.include Asciidoctor::Logging

# ...
  end
end
Tip

如上所述,您可以通过传递给扩展进程方法的文档对象上的`logger`方法访问记录器。例如:

Asciidoctor::Extensions.register do
  block :custom do
    process do |doc, reader, attrs|
      puts doc.logger

# ...
    end
  end
end

为了记录一条消息,请将消息字符串传递给由`logger`方法返回的logger实例上的一个严重性方法(例如,errorwarn、`info`等)。以下是在process方法中记录警告消息的示例。

def process doc, reader, attrs
  logger.warn 'We are logging!'

# ...
end

这是Asciidoctor将打印到日志的内容:

asciidoctor: WARN: We are logging!

请参考文档 Logger 以了解可用的方法。

这是一个完整的例子,展示了这些添加内容在上下文中的应用位置:

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

请记住,只有当用户启用其严重级别时,消息才会显示。默认情况下,将显示错误和警告消息。

除非扩展程序正在抛出错误过程中,否则你应该避免使用`fatal`方法。

为消息添加上下文

如果你向log方法传递一个字符串,Asciidoctor将只打印严重性级别和消息内容。这意味着用户不会知道这个消息是指哪部分AsciiDoc源代码,也不会知道消息来自于一个扩展。因此,你可能会想提供更多的细节。让我们开始,通过在消息中包含扩展的名称。

添加扩展名

首先,在记录消息时,你应该在消息前加上扩展名的前缀。

logger.warn '(custom-block-extension) Something is out of sorts.'

那将按如下方式打印消息:

asciidoctor: WARN: (custom-block-extension) Something is out of sorts.

或者,你可以将扩展名移动到紧跟程序名之后(例如,asciidoctor)。

logger.log ::Logger::WARN, 'hi', %(#{logger.progname} (custom-block-extension))

那将按如下方式打印消息:

asciidoctor (custom-block-extension): WARN: Something is out of sorts.

另一种获得相同输出的方法是使用接受消息为块的形式。

logger.warn(%(#{logger.progname} (custom-block-extension))) { 'Something is out of sorts.' }

区块形式在其他方面也很有用。如果消息的计算成本很高,那么区块形式将推迟消息的评估,直到(因此如果)它被记录下来。

现在读者知道这个消息来自一个扩展程序,但却不知道扩展程序正在处理的AsciiDoc源文件的位置。让我们来看看如何做到这一点。

添加源位置

除了`logger`方法外,Asciidoctor的`Logging`模块还添加了一个名为`message_with_context`的帮助器,该帮助器生成了一个消息对象,将来源信息与字符串消息绑定在一起。

对于块和块宏扩展,您可能想要引用找到块的位置,您可以使用 parent.document.reader.cursor_at_mark 方法调用来检索。对于树处理器扩展,您可以从块的 source_location 方法检索源位置。对于大多数其他扩展,您可能想要引用当前光标,可以从 parent.document.reader.cursor 或者针对只提供对文档对象访问的扩展的 (doc.reader) 访问。

这是一个用包含源位置的对象替换消息的例子:

logger.warn %(#{logger.progname} (custom-block-extension)) do
  message_with_context 'Something is out of sorts.', source_location: parent.document.reader.cursor_at_mark
end

这是 Asciidoctor 打印内容的一个例子:

asciidoctor (custom-block-extension): WARNING: test.adoc: line 4: Something is out of sorts.

在树处理器扩展中,如果启用了源映射,您可以从任何块中获取源位置。如果没有启用,代码将通过在不包含源位置的情况下记录消息来优雅地降级处理。

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)) do
        message_with_context 'Found paragraph.', source_location: paragraph.source_location
      end
    end
    nil
  end
end

Asciidoctor中的源位置跟踪并不完美,因此您可能需要检索光标并稍微调整它,以使其与消息中您想要引用的行对齐。

参考{url-api-gems}/asciidoctor/{release-version}/Asciidoctor/Reader#cursor-instance_method[Reader#cursor^]以获取光标方法的列表。