为 Logstash 插件贡献补丁

为 Logstash 插件贡献补丁

本节讨论成功为 Logstash 插件贡献补丁所需了解的信息。

每个插件都定义了自己的配置选项。这些选项在一定程度上控制着插件的行为。配置选项定义通常包括

  • 数据验证
  • 默认值
  • 任何必需的标志

插件是 Logstash 基类的子类。插件的基类定义了通用的配置和方法。

输入插件

输入插件从外部来源获取数据。输入插件始终与编解码器相关联。输入插件始终具有关联的编解码器插件。输入和编解码器插件协同工作以创建 Logstash 事件并将该事件添加到处理队列中。输入编解码器是 LogStash::Inputs::Base 类的子类。

输入 API

#register() -> nil

必需。此 API 为插件设置资源,通常是与外部源的连接。

#run(queue) -> nil

必需。此 API 获取或侦听源数据,通常循环直到停止。必须在循环内部处理错误。将任何创建的事件推送到方法参数中指定的队列对象。某些输入可能会接收批量数据以最大程度地减少外部调用的开销。

#stop() -> nil

可选。停止外部连接并清理。

编解码器插件

编解码器插件解码具有特定结构的输入数据,例如 JSON 输入数据。编解码器插件是 LogStash::Codecs::Base 的子类。

编解码器 API

#register() -> nil

与输入插件中同名 API 相同。

#decode(data){|event| block} -> nil

必须实现。用于根据方法参数中给定的原始数据创建事件。必须处理错误。调用者必须提供一个 Ruby 块。该块使用创建的事件进行调用。

#encode(event) -> nil

必需。用于根据给定的事件创建结构化数据对象。可以处理错误。此方法调用之前存储为 @on_event 的块,该块有两个参数:原始事件和数据对象。

过滤器插件

更改、修改或合并一个或多个事件的机制。过滤器插件是 LogStash::Filters::Base 类的子类。

过滤器 API

#register() -> nil

与输入插件中同名 API 相同。

#filter(event) -> nil

必需。可以处理错误。用于将突变函数应用于给定的事件。

输出插件

将事件发送到外部目的地的机制。此过程可能需要序列化。输出插件是 LogStash::Outputs::Base 类的子类。

输出 API

#register() -> nil

与输入插件中同名 API 相同。

#receive(event) -> nil

必需。必须处理错误。用于准备给定的事件以传输到外部目的地。某些输出可能会缓冲已准备好的事件以批量传输到目的地。

流程

识别错误或功能。在插件存储库中创建问题。创建补丁并提交拉取请求 (PR)。审查和可能重做后,PR 将被合并,插件将被发布。

《社区维护者指南》更详细地解释了接受、合并和发布补丁的过程。社区维护者指南还详细说明了贡献者和维护者应执行的角色。

测试方法

测试驱动开发

测试驱动开发 (TDD) 描述了一种使用测试来指导源代码演变的方法。出于我们的目的,我们只使用其中的一部分。在编写修复程序之前,我们创建测试,通过失败来说明错误。当我们编写了足够的代码以使测试通过并提交修复程序和测试作为补丁时,我们停止。没有必要在修复之前编写测试,但之后很容易编写一个通过的测试,该测试实际上可能无法验证错误是否真的已修复,尤其是在可以通过多个执行路径或不同的输入数据触发错误的情况下。

RSpec 框架

Logstash 使用 Rspec(一个 Ruby 测试框架)来定义和运行测试套件。以下是各种来源的摘要。

RSpec 示例。

 2 require "logstash/devutils/rspec/spec_helper"
 3 require "logstash/plugin"
 4
 5 describe "outputs/riemann" do
 6   describe "#register" do
 7     let(:output) do
 8       LogStash::Plugin.lookup("output", "riemann").new(configuration)
 9     end
10
11     context "when no protocol is specified" do
12       let(:configuration) { Hash.new }
13
14       it "the method completes without error" do
15         expect {output.register}.not_to raise_error
16       end
17     end
18
19     context "when a bad protocol is specified" do
20       let(:configuration) { {"protocol" => "fake"} }
21
22       it "the method fails with error" do
23         expect {output.register}.to raise_error
24       end
25     end
26
27     context "when the tcp protocol is specified" do
28       let(:configuration) { {"protocol" => "tcp"} }
29
30       it "the method completes without error" do
31         expect {output.register}.not_to raise_error
32       end
33     end
34   end
35
36   describe "#receive" do
37     let(:output) do
38       LogStash::Plugin.lookup("output", "riemann").new(configuration)
39     end
40
41     context "when operating normally" do
42       let(:configuration) { Hash.new }
43       let(:event) do
44         data = {"message"=>"hello", "@version"=>"1",
45                 "@timestamp"=>"2015-06-03T23:34:54.076Z",
46                 "host"=>"vagrant-ubuntu-trusty-64"}
47         LogStash::Event.new(data)
48       end
49
50       before(:example) do
51         output.register
52       end
53
54       it "should accept the event" do
55         expect { output.receive event }.not_to raise_error
56       end
57     end
58   end
59 end

Describe 块(示例 1 中的第 5、6 和 36 行)。

describe(string){block} -> nil
describe(Class){block} -> nil

使用 RSpec,我们始终描述插件方法的行为。describe 块按逻辑部分添加,可以接受现有的类名或字符串。第 5 行中使用的字符串是插件名称。第 6 行是 register 方法,第 36 行是 receive 方法。RSpec 的约定是在实例方法前加一个井号,在类方法前加一个点。

Context 块(第 11、19、27 和 41 行)。

context(string){block} -> nil

在 RSpec 中,context 块定义按变体对测试进行分组的部分。字符串应以 when 开头,然后详细说明变体。请参阅第 11 行。内容块中的测试应该只针对该变体。

Let 块(第 7、12、20、28、37、42 和 43 行)。

let(symbol){block} -> nil

在 RSpec 中,let 块定义用于测试块中的资源。这些资源会为每个测试块重新初始化。它们可以在测试块内部作为方法调用使用。在 describecontext 块中定义 let 块,这些块限定了 let 块和任何其他嵌套块。您可以在 let 块主体中使用稍后定义的其他 let 方法。请参阅第 7-9 行,这些行定义了输出资源并使用配置方法,该方法在第 12、20 和 28 行中以不同的变体定义。

Before 块(第 50 行)。

before(symbol){block} -> nil - symbol is one of :suite, :context, :example, but :all and :each are synonyms for :suite and :example respectively.

在 RSpec 中,before 块用于进一步设置在 let 块中初始化的任何资源。您不能在 before 块中定义 let 块。

您还可以定义 after 块,这些块通常用于清理 before 块执行的任何设置活动。

It 块(第 14、22、30 和 54 行)。

it(string){block} -> nil

在 RSpec 中,it 块设置验证被测代码行为的期望。字符串不应以 *it* 或 *should* 开头,但需要表达期望的结果。将来自封闭的 describe、contextit 块的文本放在一起时,应形成一个相当易读的句子,如第 5、6、11 和 14 行所示

outputs/riemann
#register when no protocol is specified the method completes without error

这样的可读代码使测试的目标易于理解。

Expect 方法(第 15、23、31、55 行)。

expect(object){block} -> nil

在 RSpec 中,expect 方法验证一个语句,该语句将实际结果与预期结果进行比较。 expect 方法通常与对 tonot_to 方法的调用配对使用。当预期错误或观察更改时,使用块形式。 tonot_to 方法需要一个 matcher 对象,该对象封装了预期值。 expect 方法的参数形式封装了实际值。将整个行放在一起时,会将实际值与预期值进行测试。

Matcher 方法(第 15、23、31、55 行)。

raise_error(error class|nil) -> matcher instance
be(object) -> matcher instance
eq(object) -> matcher instance
eql(object) -> matcher instance
  for more see http://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers

在 RSpec 中,matcher 是由等效方法调用 (be, eq) 生成的对象,它将用于评估预期值与实际值。

综合起来

此示例修复了 ZeroMQ 输出插件中的一个 问题。该问题不需要了解 ZeroMQ。

此示例中的活动具有以下先决条件

  • 对 Git 和 Github 的基本了解。请参阅 Github 入门训练营
  • 文本编辑器。
  • JRuby 运行时 环境chruby 工具管理 Ruby 版本。
  • JRuby 1.7.22 或更高版本。
  • 已安装 bundlerrake gem。
  • 已安装 ZeroMQ 安装
  1. 在 Github 中,为 ZeroMQ 输出插件存储库 创建分支。
  2. 在您的本地计算机上,克隆 分支到已知的文件夹,例如 logstash/
  3. 在文本编辑器中打开以下文件

    • logstash-output-zeromq/lib/logstash/outputs/zeromq.rb
    • logstash-output-zeromq/lib/logstash/util/zeromq.rb
    • logstash-output-zeromq/spec/outputs/zeromq_spec.rb
  4. 根据问题,服务器模式下的日志输出必须指示 bound。此外,测试文件不包含任何测试。

    util/zeromq.rb 的第 21 行读取 @logger.info("0mq: #{server? ? 'connected' : 'bound'}", :address => address)

  5. 在文本编辑器中,通过添加以下行,为文件 zeromq_spec.rb 要求 zeromq.rb

    require "logstash/outputs/zeromq"
    require "logstash/devutils/rspec/spec_helper"
  6. 所需的错误消息应读取

    LogStash::Outputs::ZeroMQ when in server mode a 'bound' info line is logged

    为了正确生成此消息,请添加一个 describe 块,其参数为完全限定的类名,一个 context 块和一个 it 块。

    describe LogStash::Outputs::ZeroMQ do
      context "when in server mode" do
        it "a 'bound' info line is logged" do
        end
      end
    end
  7. 要添加缺失的测试,请使用 ZeroMQ 输出的一个实例和一个替代日志记录器。此示例使用名为测试替身的 RSpec 功能作为替代日志记录器。

    zeromq_spec.rb 中添加以下行,位于 describe LogStash::Outputs::ZeroMQ do 之后以及 context "when in server mode" do 之前

      let(:output) { described_class.new("mode" => "server", "topology" => "pushpull" }
      let(:tracer) { double("logger") }
  8. 将主体添加到 it 块中。在 context "when in server mode" do 行之后添加以下五行

          allow(tracer).to receive(:debug)
          output.logger = logger
          expect(tracer).to receive(:info).with("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})
          output.register
          output.do_close

允许替身接收 debug 方法调用。

使输出使用测试替身。

在测试上设置一个期望,以接收 info 方法调用。

在输出上调用 register

在输出上调用 do_close,以便测试不会挂起。

在修改结束时,相关代码部分如下所示

require "logstash/outputs/zeromq"
require "logstash/devutils/rspec/spec_helper"

describe LogStash::Outputs::ZeroMQ do
  let(:output) { described_class.new("mode" => "server", "topology" => "pushpull") }
  let(:tracer) { double("logger") }

  context "when in server mode" do
    it "a ‘bound’ info line is logged" do
      allow(tracer).to receive(:debug)
      output.logger = tracer
      expect(tracer).to receive(:info).with("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})
      output.register
      output.do_close
    end
  end
end

要运行此测试

  1. 打开一个终端窗口
  2. 导航到克隆的插件文件夹
  3. 第一次运行测试时,运行命令 bundle install
  4. 运行命令 bundle exec rspec

假设所有先决条件都已正确安装,则测试将失败,并输出类似以下内容

Using Accessor#strict_set for specs
Run options: exclude {:redis=>true, :socket=>true, :performance=>true, :couchdb=>true, :elasticsearch=>true,
:elasticsearch_secure=>true, :export_cypher=>true, :integration=>true, :windows=>true}

LogStash::Outputs::ZeroMQ
  when in server mode
    a ‘bound’ info line is logged (FAILED - 1)

Failures:

  1) LogStash::Outputs::ZeroMQ when in server mode a ‘bound’ info line is logged
     Failure/Error: output.register
       Double "logger" received :info with unexpected arguments
         expected: ("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})
              got: ("0mq: connected", {:address=>"tcp://127.0.0.1:2120"})
     # ./lib/logstash/util/zeromq.rb:21:in `setup'
     # ./lib/logstash/outputs/zeromq.rb:92:in `register'
     # ./lib/logstash/outputs/zeromq.rb:91:in `register'
     # ./spec/outputs/zeromq_spec.rb:13:in `(root)'
     # /Users/guy/.gem/jruby/1.9.3/gems/rspec-wait-0.0.7/lib/rspec/wait.rb:46:in `(root)'

Finished in 0.133 seconds (files took 1.28 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/outputs/zeromq_spec.rb:10 # LogStash::Outputs::ZeroMQ when in server mode a ‘bound’ info line is logged

Randomized with seed 2568

要更正错误,请在文本编辑器中打开 util/zeromq.rb 文件,并在第 21 行交换 connectedbound 两个单词的位置。第 21 行现在显示为

@logger.info("0mq: #{server? ? 'bound' : 'connected'}", :address => address)

使用 bundle exec rspec 命令再次运行测试。

测试通过,并输出类似以下内容

Using Accessor#strict_set for specs
Run options: exclude {:redis=>true, :socket=>true, :performance=>true, :couchdb=>true, :elasticsearch=>true, :elasticsearch_secure=>true, :export_cypher=>true, :integration=>true, :windows=>true}

LogStash::Outputs::ZeroMQ
  when in server mode
    a ‘bound’ info line is logged

Finished in 0.114 seconds (files took 1.22 seconds to load)
1 example, 0 failures

Randomized with seed 45887

提交更改到 git 和 Github。

您可以从原始 Github 存储库的 拉取请求 部分查看您的拉取请求。插件维护者会审查您的工作,如有必要,会建议更改,并合并和发布新版本的插件。