logstash-input-beats 2.0.3 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|