logstash-input-beats 2.0.3 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/logstash/inputs/beats.rb +115 -101
- data/lib/logstash/{circuit_breaker.rb → inputs/beats_support/circuit_breaker.rb} +16 -10
- data/lib/logstash/inputs/beats_support/codec_callback_listener.rb +26 -0
- data/lib/logstash/inputs/beats_support/connection_handler.rb +79 -0
- data/lib/logstash/inputs/beats_support/decoded_event_transform.rb +34 -0
- data/lib/logstash/inputs/beats_support/event_transform_common.rb +40 -0
- data/lib/logstash/inputs/beats_support/raw_event_transform.rb +18 -0
- data/lib/logstash/inputs/beats_support/synchronous_queue_with_offer.rb +36 -0
- data/lib/lumberjack/beats/server.rb +58 -11
- data/logstash-input-beats.gemspec +4 -3
- data/spec/inputs/beats_spec.rb +35 -126
- data/spec/{logstash → inputs/beats_support}/circuit_breaker_spec.rb +11 -10
- data/spec/inputs/beats_support/codec_callback_listener_spec.rb +52 -0
- data/spec/inputs/beats_support/connection_handler_spec.rb +93 -0
- data/spec/inputs/beats_support/decoded_event_transform_spec.rb +67 -0
- data/spec/inputs/beats_support/event_transform_common_spec.rb +11 -0
- data/spec/inputs/beats_support/raw_event_transform_spec.rb +26 -0
- data/spec/integration_spec.rb +22 -12
- data/spec/lumberjack/beats/server_spec.rb +3 -3
- data/spec/support/logstash_test.rb +25 -0
- data/spec/support/shared_examples.rb +56 -0
- metadata +74 -45
- data/lib/logstash/sized_queue_timeout.rb +0 -64
- data/spec/logstash/size_queue_timeout_spec.rb +0 -100
@@ -1,9 +1,9 @@
|
|
1
|
-
require_relative "
|
2
|
-
require "logstash/circuit_breaker"
|
1
|
+
require_relative "../../spec_helper"
|
2
|
+
require "logstash/inputs/beats_support/circuit_breaker"
|
3
3
|
|
4
4
|
class DummyErrorTest < StandardError; end
|
5
5
|
|
6
|
-
describe LogStash::CircuitBreaker do
|
6
|
+
describe LogStash::Inputs::BeatsSupport::CircuitBreaker do
|
7
7
|
let(:error_threshold) { 1 }
|
8
8
|
let(:options) do
|
9
9
|
{
|
@@ -12,7 +12,7 @@ describe LogStash::CircuitBreaker do
|
|
12
12
|
}
|
13
13
|
end
|
14
14
|
|
15
|
-
subject {
|
15
|
+
subject { described_class.new("testing", options) }
|
16
16
|
|
17
17
|
context "when the breaker is closed" do
|
18
18
|
it "closed by default" do
|
@@ -24,7 +24,7 @@ describe LogStash::CircuitBreaker do
|
|
24
24
|
subject.execute do
|
25
25
|
raise DummyErrorTest
|
26
26
|
end
|
27
|
-
}.to raise_error(LogStash::CircuitBreaker::HalfOpenBreaker)
|
27
|
+
}.to raise_error(LogStash::Inputs::BeatsSupport::CircuitBreaker::HalfOpenBreaker)
|
28
28
|
end
|
29
29
|
|
30
30
|
it "open if we pass the errors threadshold" do
|
@@ -32,18 +32,19 @@ describe LogStash::CircuitBreaker do
|
|
32
32
|
subject.execute do
|
33
33
|
raise DummyErrorTest
|
34
34
|
end
|
35
|
-
}.to raise_error(LogStash::CircuitBreaker::HalfOpenBreaker)
|
35
|
+
}.to raise_error(LogStash::Inputs::BeatsSupport::CircuitBreaker::HalfOpenBreaker)
|
36
36
|
|
37
37
|
expect {
|
38
38
|
subject.execute do
|
39
39
|
raise DummyErrorTest
|
40
40
|
end
|
41
|
-
}.to raise_error(LogStash::CircuitBreaker::OpenBreaker)
|
41
|
+
}.to raise_error(LogStash::Inputs::BeatsSupport::CircuitBreaker::OpenBreaker)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
context "When the breaker is open" do
|
46
|
-
let(:
|
46
|
+
let(:retry_time) { 2 }
|
47
|
+
let(:options) { super.merge(:time_before_retry => retry_time) }
|
47
48
|
|
48
49
|
before do
|
49
50
|
# trip the breaker
|
@@ -62,7 +63,7 @@ describe LogStash::CircuitBreaker do
|
|
62
63
|
end
|
63
64
|
|
64
65
|
it "resets the breaker after the time before retry" do
|
65
|
-
|
66
|
+
sleep(retry_time + 1)
|
66
67
|
expect(subject.closed?).to eq(true)
|
67
68
|
end
|
68
69
|
|
@@ -73,7 +74,7 @@ describe LogStash::CircuitBreaker do
|
|
73
74
|
subject.execute do
|
74
75
|
runned = true
|
75
76
|
end
|
76
|
-
rescue LogStash::CircuitBreaker::OpenBreaker
|
77
|
+
rescue LogStash::Inputs::BeatsSupport::CircuitBreaker::OpenBreaker
|
77
78
|
end
|
78
79
|
|
79
80
|
expect(runned).to eq(false)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/inputs/beats"
|
3
|
+
require "logstash/event"
|
4
|
+
require "spec_helper"
|
5
|
+
require "thread"
|
6
|
+
|
7
|
+
Thread.abort_on_exception = true
|
8
|
+
describe LogStash::Inputs::BeatsSupport::CodecCallbackListener do
|
9
|
+
let(:data) { "Hello world" }
|
10
|
+
let(:map) do
|
11
|
+
{
|
12
|
+
"beat" => { "hostname" => "newhost" }
|
13
|
+
}
|
14
|
+
end
|
15
|
+
let(:path) { "/var/log/message" }
|
16
|
+
let(:transformer) { double("codec_transformer") }
|
17
|
+
let(:queue_timeout) { 1 }
|
18
|
+
let(:queue) { LogStash::Inputs::BeatsSupport::SynchronousQueueWithOffer.new(queue_timeout) }
|
19
|
+
let(:event) { LogStash::Event.new }
|
20
|
+
|
21
|
+
before do
|
22
|
+
allow(transformer).to receive(:transform).with(event, map).and_return(event)
|
23
|
+
end
|
24
|
+
|
25
|
+
subject { described_class.new(data, map, path, transformer, queue) }
|
26
|
+
|
27
|
+
it "expose the data" do
|
28
|
+
expect(subject.data).to eq(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "expose the path" do
|
32
|
+
expect(subject.path).to eq(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when the queue is not blocked for too long" do
|
36
|
+
let(:queue_timeout) { 5 }
|
37
|
+
|
38
|
+
it "doesnt raise an exception" do
|
39
|
+
Thread.new do
|
40
|
+
loop { queue.take }
|
41
|
+
end
|
42
|
+
sleep(0.1)
|
43
|
+
expect { subject.process_event(event) }.not_to raise_error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when the queue is blocked for too long" do
|
48
|
+
it "raises an exception" do
|
49
|
+
expect { subject.process_event(event) }.to raise_error(LogStash::Inputs::Beats::InsertingToQueueTakeTooLong)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/inputs/beats"
|
3
|
+
require_relative "../../support/logstash_test"
|
4
|
+
require "spec_helper"
|
5
|
+
|
6
|
+
describe LogStash::Inputs::BeatsSupport::ConnectionHandler do
|
7
|
+
let(:config) do
|
8
|
+
{
|
9
|
+
"port" => 0,
|
10
|
+
"type" => "example",
|
11
|
+
"tags" => "beats"
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:logger) { DummyLogger.new }
|
16
|
+
let(:input) do
|
17
|
+
LogStash::Inputs::Beats.new(config).tap do |i|
|
18
|
+
i.register
|
19
|
+
end
|
20
|
+
end
|
21
|
+
let(:connection) { double("connection") }
|
22
|
+
let(:queue) { DummyNeverBlockedQueue.new }
|
23
|
+
|
24
|
+
subject { described_class.new(connection, input, queue) }
|
25
|
+
|
26
|
+
context "#accept" do
|
27
|
+
let(:connection) { DummyConnection.new(events) }
|
28
|
+
let(:events) {
|
29
|
+
[
|
30
|
+
{ :map => { "id" => 1 }, :identity_stream => "/var/log/message" },
|
31
|
+
{ :map => { "id" => 2 }, :identity_stream => "/var/log/message_2" }
|
32
|
+
]
|
33
|
+
}
|
34
|
+
it "should delegate work to `#process` for each items" do
|
35
|
+
events.each do |element|
|
36
|
+
expect(subject).to receive(:process).with(element[:map], element[:identity_stream]).and_return(true)
|
37
|
+
end
|
38
|
+
|
39
|
+
subject.accept
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#process" do
|
44
|
+
let(:identity_stream) { "/var/log/message" }
|
45
|
+
|
46
|
+
context "without a `target_field_for_codec`" do
|
47
|
+
let(:map) { { "hello" => "world" } }
|
48
|
+
|
49
|
+
context "queue is not blocked" do
|
50
|
+
it "insert the event into the queue" do
|
51
|
+
subject.process(map, identity_stream)
|
52
|
+
event = queue.take
|
53
|
+
|
54
|
+
expect(event["hello"]).to eq(map["hello"])
|
55
|
+
expect(event["tags"]).to include("beats_input_raw_event")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "queue is blocked" do
|
60
|
+
let(:queue_timeout) { 1 }
|
61
|
+
let(:queue) { LogStash::Inputs::BeatsSupport::SynchronousQueueWithOffer.new(queue_timeout) }
|
62
|
+
|
63
|
+
it "raise an exception" do
|
64
|
+
expect { subject.process(map, identity_stream) }.to raise_error(LogStash::Inputs::Beats::InsertingToQueueTakeTooLong)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with a codec" do
|
70
|
+
let(:message) { "Hello world" }
|
71
|
+
let(:map) { { "message" => message } }
|
72
|
+
|
73
|
+
context "queue is not blocked" do
|
74
|
+
it "insert the event into the queue" do
|
75
|
+
subject.process(map, identity_stream)
|
76
|
+
event = queue.take
|
77
|
+
|
78
|
+
expect(event["message"]).to eq(message)
|
79
|
+
expect(event["tags"]).to include("beats_input_codec_plain_applied")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "queue is blocked" do
|
84
|
+
let(:queue_timeout) { 1 }
|
85
|
+
let(:queue) { LogStash::Inputs::BeatsSupport::SynchronousQueueWithOffer.new(queue_timeout) }
|
86
|
+
|
87
|
+
it "raise an exception" do
|
88
|
+
expect { subject.process(map, identity_stream) }.to raise_error(LogStash::Inputs::Beats::InsertingToQueueTakeTooLong)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/event"
|
3
|
+
require "logstash/timestamp"
|
4
|
+
require "logstash/inputs/beats"
|
5
|
+
require_relative "../../support/shared_examples"
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
describe LogStash::Inputs::BeatsSupport::DecodedEventTransform do
|
9
|
+
let(:config) do
|
10
|
+
{
|
11
|
+
"port" => 0,
|
12
|
+
"type" => "example",
|
13
|
+
"tags" => "beats"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:input) do
|
18
|
+
LogStash::Inputs::Beats.new(config).tap do |i|
|
19
|
+
i.register
|
20
|
+
end
|
21
|
+
end
|
22
|
+
let(:event) { LogStash::Event.new }
|
23
|
+
let(:map) do
|
24
|
+
{
|
25
|
+
"@metadata" => { "hello" => "world"},
|
26
|
+
"super" => "mario"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
subject { described_class.new(input).transform(event, map) }
|
31
|
+
|
32
|
+
include_examples "Common Event Transformation"
|
33
|
+
|
34
|
+
it "tags the event" do
|
35
|
+
expect(subject["tags"]).to include("beats_input_codec_plain_applied")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "merges the other data from the map to the event" do
|
39
|
+
expect(subject["super"]).to eq(map["super"])
|
40
|
+
expect(subject["@metadata"]).to include(map["@metadata"])
|
41
|
+
end
|
42
|
+
|
43
|
+
context "map contains a timestamp" do
|
44
|
+
context "when its valid" do
|
45
|
+
let(:timestamp) { Time.now }
|
46
|
+
let(:map) { super.merge({"@timestamp" => timestamp }) }
|
47
|
+
|
48
|
+
it "uses as the event timestamp" do
|
49
|
+
expect(subject["@timestamp"]).to eq(LogStash::Timestamp.coerce(timestamp))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when its not valid" do
|
54
|
+
let(:map) { super.merge({"@timestamp" => "invalid" }) }
|
55
|
+
|
56
|
+
it "fallback the current time" do
|
57
|
+
expect(subject["@timestamp"]).to be_kind_of(LogStash::Timestamp)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when the map doesn't provide a timestamp" do
|
63
|
+
it "fallback the current time" do
|
64
|
+
expect(subject["@timestamp"]).to be_kind_of(LogStash::Timestamp)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/event"
|
3
|
+
require "logstash/inputs/beats"
|
4
|
+
require_relative "../../support/shared_examples"
|
5
|
+
require "spec_helper"
|
6
|
+
|
7
|
+
describe LogStash::Inputs::BeatsSupport::EventTransformCommon do
|
8
|
+
subject { described_class.new(input).transform(event) }
|
9
|
+
|
10
|
+
include_examples "Common Event Transformation"
|
11
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/event"
|
3
|
+
require "logstash/inputs/beats"
|
4
|
+
require_relative "../../support/shared_examples"
|
5
|
+
require "spec_helper"
|
6
|
+
|
7
|
+
describe LogStash::Inputs::BeatsSupport::RawEventTransform do
|
8
|
+
let(:config) do
|
9
|
+
{
|
10
|
+
"port" => 0,
|
11
|
+
"type" => "example",
|
12
|
+
"tags" => "beats"
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:input) { LogStash::Inputs::Beats.new(config) }
|
17
|
+
let(:event) { LogStash::Event.new }
|
18
|
+
|
19
|
+
subject { described_class.new(input).transform(event) }
|
20
|
+
|
21
|
+
include_examples "Common Event Transformation"
|
22
|
+
|
23
|
+
it "tags the event" do
|
24
|
+
expect(subject["tags"]).to include("beats_input_raw_event")
|
25
|
+
end
|
26
|
+
end
|
data/spec/integration_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe "A client" do
|
|
17
17
|
let(:host) { "127.0.0.1" }
|
18
18
|
let(:queue) { [] }
|
19
19
|
|
20
|
-
before do
|
20
|
+
before :each do
|
21
21
|
expect(File).to receive(:read).at_least(1).with(certificate_file_crt) { certificate.first.to_s }
|
22
22
|
expect(File).to receive(:read).at_least(1).with(certificate_file_key) { certificate.last.to_s }
|
23
23
|
|
@@ -29,23 +29,33 @@ describe "A client" do
|
|
29
29
|
:ssl_key => certificate_file_key)
|
30
30
|
|
31
31
|
@tcp_server = Thread.new do
|
32
|
+
tcp_server.run { |data, identity_stream| queue << [data, identity_stream] }
|
32
33
|
while true
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
tcp_server.accept do |socket|
|
35
|
+
next if socket.nil?
|
36
|
+
|
37
|
+
begin
|
38
|
+
con = Lumberjack::Beats::Connection.new(socket, tcp_server)
|
39
|
+
con.run { |data, identity_stream| queue << [data, identity_stream] }
|
40
|
+
rescue
|
41
|
+
# Close connection on failure. For example SSL client will make
|
42
|
+
# parser for TCP based server trip.
|
43
|
+
# Connection is closed by Server connection object
|
44
|
+
end
|
45
|
+
end
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
46
49
|
@ssl_server = Thread.new do
|
47
50
|
ssl_server.run { |data, identity_stream| queue << [data, identity_stream] }
|
48
51
|
end
|
52
|
+
|
53
|
+
sleep(0.1) while @ssl_server.status != "run" && @tcp_server != "run"
|
54
|
+
end
|
55
|
+
|
56
|
+
after :each do
|
57
|
+
@tcp_server.kill
|
58
|
+
@ssl_server.kill
|
49
59
|
end
|
50
60
|
|
51
61
|
shared_examples "send payload" do
|
@@ -132,7 +142,7 @@ describe "A client" do
|
|
132
142
|
:addresses => host,
|
133
143
|
:ssl => false)
|
134
144
|
client.write({ "line" => "foobar" })
|
135
|
-
}.to raise_error
|
145
|
+
}.to raise_error
|
136
146
|
end
|
137
147
|
|
138
148
|
context "When transmitting a payload" do
|
@@ -21,3 +21,28 @@ module LogStashTest
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
class DummyNeverBlockedQueue < Array
|
26
|
+
def offer(element, timeout = nil)
|
27
|
+
push(element)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :take, :shift
|
31
|
+
end
|
32
|
+
|
33
|
+
class DummyConnection
|
34
|
+
def initialize(events)
|
35
|
+
@events = events
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
@events.each do |element|
|
40
|
+
yield element[:map], element[:identity_stream]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def peer
|
45
|
+
"localhost:5555"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
shared_examples "Common Event Transformation" do
|
3
|
+
let(:tag) { "140-rpm-beats" }
|
4
|
+
let(:config) do
|
5
|
+
{
|
6
|
+
"port" => 0,
|
7
|
+
"type" => "example",
|
8
|
+
"tags" => tag
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:input) do
|
13
|
+
LogStash::Inputs::Beats.new(config).tap do |i|
|
14
|
+
i.register
|
15
|
+
end
|
16
|
+
end
|
17
|
+
let(:event) { LogStash::Event.new(event_map) }
|
18
|
+
let(:event_map) do
|
19
|
+
{
|
20
|
+
"message" => "Hello world",
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
it "adds configured tags to the event" do
|
25
|
+
expect(subject["tags"]).to include(tag)
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when the `beast.hotname` doesnt exist on the event" do
|
29
|
+
let(:already_exist) { "already_exist" }
|
30
|
+
let(:event_map) { super.merge({ "host" => already_exist }) }
|
31
|
+
|
32
|
+
it "doesnt change the value" do
|
33
|
+
expect(subject["host"]).to eq(already_exist)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the `beat.hostname` exist in the event" do
|
38
|
+
let(:producer_host) { "newhost01" }
|
39
|
+
let(:event_map) { super.merge({ "beat" => { "hostname" => producer_host }}) }
|
40
|
+
|
41
|
+
context "when `host` key doesn't exist on the event" do
|
42
|
+
it "copy the `beat.hostname` to `host` or backward compatibility" do
|
43
|
+
expect(subject["host"]).to eq(producer_host)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when `host` key exists on the event" do
|
48
|
+
let(:already_exist) { "already_exist" }
|
49
|
+
let(:event_map) { super.merge({ "host" => already_exist }) }
|
50
|
+
|
51
|
+
it "doesn't override it" do
|
52
|
+
expect(subject["host"]).to eq(already_exist)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|