从扩展中记录日志

如果在扩展运行过程中发生问题,您可能希望将该信息传达给用户。仅当问题非常严重以至于不应进行任何进一步处理时,扩展才应抛出错误(即引发异常)。否则,我们建议扩展在适当的严重性级别将问题报告为日志消息。您可以使用 Asciidoctor 的日志记录器记录消息。

当 Asciidoctor 处理文档时,它会创建一个作用域限定于该文档的 Logger 实例。您可以在扩展中访问该日志记录器来记录消息。本页将介绍如何操作。

混入 Logger

要访问 Asciidoctor Logger 的实例,您必须将 Asciidoctor::Logging 模块混入您的扩展中。这样做,它将提供对静态 logger 方法的访问,该方法将从 LoggerManager 中检索实例。

也可以使用文档对象上的 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

如上所述,您可以通过传递给扩展的 process 方法的文档对象上的 logger 方法来访问日志记录器。例如

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

      # ...
    end
  end
end

为了记录消息,将消息字符串传递给日志记录器实例上的一个严重性方法(例如 errorwarninfo 等),该实例由 logger 方法返回。下面是如何在 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.

在树处理器扩展中,如果您启用了 sourcemap,您可以从任何块的 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)) do
        message_with_context 'Found paragraph.', source_location: paragraph.source_location
      end
    end
    nil
  end
end

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

请参阅 Reader#cursor 以获取光标方法的列表。