本节讨论成功向 Logstash 插件贡献补丁所需了解的信息。
每个插件都定义了自己的配置选项。这些选项在某种程度上控制插件的行为。配置选项定义通常包括
- 数据验证
- 默认值
- 任何必需的标志
插件是 Logstash 基类的子类。插件的基类定义了通用配置和方法。
输入插件从外部源摄取数据。输入插件始终与编解码器关联。输入插件始终有一个关联的编解码器插件。输入和编解码器插件协同工作以创建 Logstash 事件,并将该事件添加到处理队列中。输入编解码器是 LogStash::Inputs::Base
类的子类。
编解码器插件解码具有特定结构的输入数据,例如 JSON 输入数据。编解码器插件是 LogStash::Codecs::Base
的子类。
一种更改、变异或合并一个或多个事件的机制。过滤器插件是 LogStash::Filters::Base
类的子类。
一种将事件发送到外部目的地的机制。此过程可能需要序列化。输出插件是 LogStash::Outputs::Base
类的子类。
识别出错误或功能。在插件存储库中创建一个问题。创建一个补丁并提交拉取请求 (PR)。经过审查和可能的返工后,PR 被合并并发布该插件。
社区维护者指南更详细地解释了如何使补丁被接受、合并和发布的过程。社区维护者指南还详细说明了贡献者和维护者应履行的角色。
测试驱动开发 (TDD) 描述了一种使用测试来指导源代码演化的方法。出于我们的目的,我们仅使用其中的一部分。在编写修复程序之前,我们创建通过失败来演示错误的测试。当我们编写了足够的代码以使测试通过并提交修复和测试作为补丁时,我们会停止。没有必要在修复之前编写测试,但是之后很容易编写一个可以通过的测试,该测试可能实际上没有验证故障是否真的已修复,特别是如果可以通过多个执行路径或不同的输入数据触发故障。
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
描述块(示例 1 中的第 5、6 和 36 行)。
describe(string){block} -> nil describe(Class){block} -> nil
使用 RSpec,我们始终描述插件方法行为。描述块添加在逻辑部分中,并且可以接受现有的类名或字符串。第 5 行中使用的字符串是插件名称。第 6 行是 register 方法,第 36 行是 receive 方法。这是 RSpec 的惯例,即实例方法以一个哈希为前缀,类方法以一个点为前缀。
上下文块(第 11、19、27 和 41 行)。
context(string){block} -> nil
在 RSpec 中,上下文块定义按变体对测试进行分组的部分。该字符串应以单词 when
开头,然后详细说明变体。请参见第 11 行。内容块中的测试应仅适用于该变体。
Let 块(第 7、12、20、28、37、42 和 43 行)。
let(symbol){block} -> nil
在 RSpec 中,let
块定义测试块中使用的资源。这些资源会为每个测试块重新初始化。它们在测试块内可用作方法调用。let
块在 describe
和 context
块中定义,这些块限定 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、context
和 it
块中的文本放在一起时,应形成一个相当可读的句子,如第 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
方法通常与对 to
或 not_to
方法的调用配对。在期望错误或观察更改时,请使用块形式。to
或 not_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 中,匹配器是由等效方法调用(be,eq)生成的对象,该对象将用于评估预期值与实际值。
此示例修复了 ZeroMQ 输出插件中的一个问题。该问题不需要 ZeroMQ 的知识。
此示例中的活动具有以下先决条件
- Git 和 Github 的最少知识。请参阅 Github 训练营。
- 文本编辑器。
- JRuby 运行时 环境。
chruby
工具管理 Ruby 版本。 - JRuby 1.7.22 或更高版本。
- 已安装
bundler
和rake
gem。 - 已安装 ZeroMQ。
- 在 Github 中,派生 ZeroMQ 输出插件存储库。
- 在本地计算机上,将派生克隆到已知文件夹,例如
logstash/
。 -
在文本编辑器中打开以下文件
-
logstash-output-zeromq/lib/logstash/outputs/zeromq.rb
-
logstash-output-zeromq/lib/logstash/util/zeromq.rb
-
logstash-output-zeromq/spec/outputs/zeromq_spec.rb
-
-
根据问题,服务器模式下的日志输出必须指示
bound
。此外,测试文件不包含任何测试。util/zeromq.rb
的第 21 行读取@logger.info("0mq: #{server? ? 'connected' : 'bound'}", :address => address)
-
在文本编辑器中,通过添加以下行,需要为文件
zeromq_spec.rb
提供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
块、一个上下文块和一个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 功能作为替代记录器。
在
describe LogStash::Outputs::ZeroMQ do
之后和context "when in server mode" do
之前,将以下行添加到zeromq_spec.rb
中let(:output) { described_class.new("mode" => "server", "topology" => "pushpull" } let(:tracer) { double("logger") }
-
将主体添加到
it
块。在行context "when in server mode" do
之后添加以下五行
允许 double 接收 |
|
使输出使用测试 double。 |
|
设置测试的预期,以接收 |
|
在输出上调用 |
|
在输出上调用 |
在修改结束时,相关代码部分读取
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 # LogStash::Outputs::ZeroMQ when in server mode a ‘bound’ info line is logged Randomized with seed 2568
要更正错误,请在文本编辑器中打开 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
提交 更改到 git 和 Github。
您的拉取请求可以在原始 Github 仓库的 拉取请求 部分看到。插件维护者会审查您的工作,并在必要时提出修改建议,然后合并并发布新版本的插件。