为 Logstash 插件贡献补丁
本节讨论了您需要了解的信息,以便成功地为 Logstash 插件贡献补丁。
每个插件都定义了自己的配置选项。这些选项在一定程度上控制着插件的行为。配置选项定义通常包括:
- 数据验证
- 默认值
- 任何必需的标志
插件是 Logstash 基类的子类。插件的基类定义了通用的配置和方法。
输入插件从外部源摄取数据。输入插件总是与编解码器(codec)相关联。输入插件始终有一个关联的编解码插件。输入插件和编解码插件协同工作,创建 Logstash 事件并将该事件添加到处理队列中。输入编解码器是 LogStash::Inputs::Base 类的子类。
#register() -> nil- 必需。此 API 为插件设置资源,通常是到外部源的连接。
#run(queue) -> nil- 必需。此 API 获取或监听源数据,通常循环直到停止。必须在循环内处理错误。将任何创建的事件推送到方法参数中指定的队列对象。一些输入可能会接收批量数据,以最大限度地减少外部调用的开销。
#stop() -> nil- 可选。停止外部连接并进行清理。
编解码插件对具有特定结构的输入数据(例如 JSON 输入数据)进行解码。编解码插件是 LogStash::Codecs::Base 类的子类。
#register() -> nil- 与输入插件同名 API 相同。
#decode(data){|event| block} -> nil- 必须实现。用于从方法参数中给出的原始数据创建事件(Event)。必须处理错误。调用者必须提供一个 Ruby 代码块。该代码块会使用创建的事件进行调用。
#encode(event) -> nil- 必需。用于从给定的事件创建结构化数据对象。可以处理错误。此方法调用之前存储为 @on_event 的代码块,并带有两个参数:原始事件和数据对象。
一种用于更改、变异或合并一个或多个事件的机制。过滤插件是 LogStash::Filters::Base 类的子类。
#register() -> nil- 与输入插件同名 API 相同。
#filter(event) -> nil- 必需。可以处理错误。用于对给定的事件应用变异函数。
一种将事件发送到外部目的地的机制。此过程可能需要序列化。输出插件是 LogStash::Outputs::Base 类的子类。
#register() -> nil- 与输入插件同名 API 相同。
#receive(event) -> nil- 必需。必须处理错误。用于准备将给定事件传输到外部目的地。一些输出可能会缓冲准备好的事件,以批量传输到目的地。
确定了一个 Bug 或功能需求。在插件仓库中创建一个议题(issue)。创建一个补丁并提交合并请求(PR)。经过审核和可能的返工后,PR 会被合并,插件也会被发布。
《社区维护者指南》更详细地解释了补丁被接受、合并和发布的过程。《社区维护者指南》还详细说明了贡献者和维护者需要履行的角色职责。
测试驱动开发 (TDD) 描述了一种使用测试来指导源代码演进的方法。就我们的目的而言,我们仅使用了其中的一部分。在编写修复程序之前,我们先创建测试,这些测试通过失败来演示 Bug。当我们编写了足够的代码使测试通过后,我们就会停止,并将修复程序和测试作为补丁提交。不必非要在修复之前编写测试,但在修复之后编写一个通过的测试非常容易,而该测试可能实际上并未验证故障是否真正得到修复,特别是在可以通过多种执行路径或不同的输入数据触发故障的情况下。
Logstash 使用 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(string){block} -> nil
describe(Class){block} -> nil
使用 RSpec,我们始终在描述插件方法的行为。describe 代码块以逻辑部分添加,并且可以接受现有的类名或字符串。第 5 行使用的字符串是插件名称。第 6 行是 register 方法,第 36 行是 receive 方法。RSpec 的一个惯例是给实例方法加上井号(#)前缀,给类方法加上点(.)前缀。
context(string){block} -> nil
在 RSpec 中,context 代码块定义了按变体对测试进行分组的部分。字符串应以 when 一词开头,然后详细说明变体。参见第 11 行。内容代码块中的测试应该仅针对该变体。
let(symbol){block} -> nil
在 RSpec 中,let 代码块定义了供测试代码块使用的资源。这些资源会在每个测试代码块中重新初始化。它们在测试代码块内作为方法调用可用。在 describe 和 context 代码块中定义 let 代码块,这会限定 let 代码块和任何其他嵌套代码块的作用域。您可以在 let 代码块主体内使用稍后定义的其他 let 方法。参见第 7-9 行,它们定义了输出资源并使用了配置方法,这些配置方法在第 12、20 和 28 行中定义了不同的变体。
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(string){block} -> nil
在 RSpec 中,it 代码块设置用于验证测试代码行为的预期。字符串不应以 it 或 should 开头,但需要表达预期的结果。当将封闭的 describe、context 和 it 代码块中的文本放在一起时,应该形成一个相当可读的句子,如第 5、6、11 和 14 行所示。
outputs/riemann
#register when no protocol is specified the method completes without error
像这样可读的代码使得测试目标易于理解。
expect(object){block} -> nil
在 RSpec 中,expect 方法验证一个比较实际结果与预期结果的语句。expect 方法通常与对 to 或 not_to 方法的调用配对使用。在预期错误或观察更改时,使用代码块形式。to 或 not_to 方法需要一个封装预期值的 matcher(匹配器)对象。expect 方法的参数形式封装了实际值。将整行放在一起,它会根据预期值测试实际值。
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 或更高版本。
- 安装了
bundler和rakegems。 - 安装了 ZeroMQ。
在 Github 上,Fork ZeroMQ 输出插件仓库。
在您的本地机器上,克隆该 Fork 到一个已知文件夹中,例如
logstash/。在文本编辑器中打开以下文件:
logstash-output-zeromq/lib/logstash/outputs/zeromq.rblogstash-output-zeromq/lib/logstash/util/zeromq.rblogstash-output-zeromq/spec/outputs/zeromq_spec.rb
根据该议题,服务器模式下的日志输出必须显示
bound。此外,测试文件中不包含任何测试。注意util/zeromq.rb的第 21 行内容为@logger.info("0mq: #{server? ? 'connected' : 'bound'}", :address => address)在文本编辑器中,通过添加以下行,为
zeromq_spec.rb文件引入(require)zeromq.rb:require "logstash/outputs/zeromq" require "logstash/devutils/rspec/spec_helper"预期的错误消息应该是:
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为了添加缺失的测试,请使用 ZeroMQ 输出的一个实例和一个替代日志记录器。此示例使用一种称为测试替身 (test doubles) 的 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") }将主体添加到
it代码块中。在context "when in server mode" do这一行之后添加以下五行:allow(tracer).to receive(:debug)<1> output.logger = logger<2> expect(tracer).to receive(:info).with("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})<3> output.register<4> output.do_close<5>允许测试替身接收
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
要运行此测试:
- 打开一个终端窗口
- 导航到克隆的插件文件夹
- 第一次运行测试时,请运行命令
bundle install - 运行命令
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
Randomized with seed 2568
- LogStash<>OutputsZeroMQ when in server mode a ‘bound’ info line is logged
要纠正此错误,请在文本编辑器中打开 util/zeromq.rb 文件,并交换第 21 行中单词 connected 和 bound 的位置。第 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
将更改提交 (Commit) 到 git 和 Github。
您的合并请求 (Pull Request) 可以从原始 Github 仓库的 Pull Requests 部分看到。插件维护者会审核您的工作,并在必要时提出修改建议,然后合并并发布插件的新版本。