logstash-input-beats 0.9.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1aed7cc18f6010a5e6e40d4dfa62c073dc3bd512
4
- data.tar.gz: ed5565a612454c4905fa48edc7c6482af02a5008
3
+ metadata.gz: df4aadb42d40ce79081da88eb7519460e82cd37f
4
+ data.tar.gz: 5239753151ef027acb05146920ba553c0101ce21
5
5
  SHA512:
6
- metadata.gz: fa7854d4255d44ca06916883f3cc24e78b0c778d6503d9d3b91c37d3b5e99e769474e12224ce5d226eaa4b56dca83083f1cd1d57a499557b3960b550a0d714e1
7
- data.tar.gz: c423e548a02defb63de98ea6f65a3e339a0f55423d19b08b92af71fb033ebe8da7e1c0fd3add8b31f84b6c1f73699a57893fda9b6205ab3177d47656ba599edb
6
+ metadata.gz: 350918e94dc2ba03d0b9c57abab07902fb8b7ca45354213932db23f0c7477b91f89813ba83d5dcbce1c8f01406976608b50b0a286a1c573ec0e6a28b55a8c45f
7
+ data.tar.gz: 6c3f2ad8a8fec46786012b8df87f9c89e4500b5d675fb21bd9cf917f87bb53e44ef6bf9b95694dfb236622beb57adfd4b354317a280e049e7c65d72f4bbab5cb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ # 2.0.0
2
+ - Add support for stream identity, the ID will be generated from beat.id+resource_id or beat.name + beat.source if not present #22 #13
3
+ The identity allow the multiline codec to correctly merge string from multiples files.
1
4
  # 0.9.6
2
5
  - Fix an issue with rogue events created by buffered codecs #19
3
6
  # 0.9.5
@@ -2,9 +2,9 @@
2
2
  require "logstash/inputs/base"
3
3
  require "logstash/namespace"
4
4
  require "logstash/timestamp"
5
- require "logstash/compatibility_layer_api_v1"
6
5
  require "lumberjack/beats"
7
6
  require "lumberjack/beats/server"
7
+ require "logstash/codecs/identity_map_codec"
8
8
 
9
9
  # use Logstash provided json decoder
10
10
  Lumberjack::Beats::json = LogStash::Json
@@ -14,8 +14,6 @@ Lumberjack::Beats::json = LogStash::Json
14
14
  # https://github.com/elastic/filebeat[filebeat]
15
15
  #
16
16
  class LogStash::Inputs::Beats < LogStash::Inputs::Base
17
- include LogStash::CompatibilityLayerApiV1
18
-
19
17
  config_name "beats"
20
18
 
21
19
  default :codec, "plain"
@@ -50,7 +48,7 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
50
48
  # TODO(sissel): Add CA to authenticate clients with.
51
49
  BUFFERED_QUEUE_SIZE = 1
52
50
  RECONNECT_BACKOFF_SLEEP = 0.5
53
-
51
+
54
52
  def register
55
53
  require "concurrent"
56
54
  require "logstash/circuit_breaker"
@@ -69,7 +67,7 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
69
67
  :ssl_key_passphrase => @ssl_key_passphrase)
70
68
 
71
69
  # Create a reusable threadpool, we do not limit the number of connections
72
- # to the input, the circuit breaker with the timeout should take care
70
+ # to the input, the circuit breaker with the timeout should take care
73
71
  # of `blocked` threads and prevent logstash to go oom.
74
72
  @threadpool = Concurrent::CachedThreadPool.new(:idletime => 15)
75
73
 
@@ -80,6 +78,9 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
80
78
  @circuit_breaker = LogStash::CircuitBreaker.new("Beats input",
81
79
  :exceptions => [LogStash::SizedQueueTimeout::TimeoutError])
82
80
 
81
+ # wrap the configured codec to support identity stream
82
+ # from the producers
83
+ @codec = LogStash::Codecs::IdentityMapCodec.new(@codec)
83
84
  end # def register
84
85
 
85
86
  def ssl_configured?
@@ -99,7 +100,7 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
99
100
  connection = @lumberjack.accept # call that creates a new connection
100
101
  next if connection.nil? # if the connection is nil the connection was close.
101
102
 
102
- invoke(connection, @codec.clone) do |event|
103
+ invoke(connection) do |event|
103
104
  if stop?
104
105
  connection.close
105
106
  break
@@ -126,17 +127,17 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
126
127
  end
127
128
 
128
129
  public
129
- def create_event(codec, map)
130
+ def create_event(map, identity_stream)
130
131
  # Filebeats uses the `message` key and LSF `line`
131
132
  target_field = target_field_for_codec ? map.delete(target_field_for_codec) : nil
132
133
 
133
134
  if target_field.nil?
134
- event = LogStash::Event.new(map)
135
+ event = LogStash::Event.new(map)
135
136
  decorate(event)
136
137
  return event
137
138
  else
138
139
  # All codecs expects to work on string
139
- @codec.decode(target_field.to_s) do |decoded|
140
+ @codec.decode(target_field.to_s, identity_stream) do |decoded|
140
141
  ts = coerce_ts(map.delete("@timestamp"))
141
142
  decoded["@timestamp"] = ts unless ts.nil?
142
143
  map.each { |k, v| decoded[k] = v }
@@ -161,13 +162,13 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
161
162
  end
162
163
 
163
164
  private
164
- def invoke(connection, codec, &block)
165
+ def invoke(connection, &block)
165
166
  @threadpool.post do
166
167
  begin
167
168
  # If any errors occur in from the events the connection should be closed in the
168
169
  # library ensure block and the exception will be handled here
169
- connection.run do |map|
170
- event = create_event(codec, map)
170
+ connection.run do |map, identity_stream|
171
+ event = create_event(map, identity_stream)
171
172
  block.call(event) unless event.nil?
172
173
  end
173
174
 
@@ -150,17 +150,17 @@ module Lumberjack module Beats
150
150
  end # def transition
151
151
 
152
152
  # Feed data to this parser.
153
- #
153
+ #
154
154
  # Currently, it will return the raw payload of websocket messages.
155
155
  # Otherwise, it returns nil if no complete message has yet been consumed.
156
156
  #
157
- # @param [String] the string data to feed into the parser.
157
+ # @param [String] the string data to feed into the parser.
158
158
  # @return [String, nil] the websocket message payload, if any, nil otherwise.
159
159
  def feed(data, &block)
160
160
  @buffer << data
161
161
  #p :need => @need
162
162
  while have?(@need)
163
- send(@state, &block)
163
+ send(@state, &block)
164
164
  #case @state
165
165
  #when :header; header(&block)
166
166
  #when :window_size; window_size(&block)
@@ -310,6 +310,10 @@ module Lumberjack module Beats
310
310
  @ack_handler = nil
311
311
  end
312
312
 
313
+ def peer
314
+ "#{@fd.peeraddr[3]}:#{@fd.peeraddr[1]}"
315
+ end
316
+
313
317
  def run(&block)
314
318
  while !server.closed?
315
319
  read_socket(&block)
@@ -375,17 +379,34 @@ module Lumberjack module Beats
375
379
  end
376
380
 
377
381
  def data(map, &block)
378
- block.call(map) if block_given?
382
+ block.call(map, identity_stream(map)) if block_given?
379
383
  end
380
384
 
381
385
  def reset_next_ack(window_size)
382
- klass = (@version == Parser::PROTOCOL_VERSION_1) ? AckingProtocolV1 : AckingProtocolV2
386
+ klass = version_1? ? AckingProtocolV1 : AckingProtocolV2
383
387
  @ack_handler = klass.new(window_size)
384
388
  end
385
389
 
386
390
  def send_ack(sequence)
387
391
  @fd.syswrite(@ack_handler.ack_frame(sequence))
388
392
  end
393
+
394
+ def version_1?
395
+ @version == Parser::PROTOCOL_VERSION_1
396
+ end
397
+
398
+ def identity_stream(map)
399
+ id = map.fetch("beat", {})["id"]
400
+
401
+ if id && map["resource_id"]
402
+ identity_values = [id, map["resource_id"]]
403
+ else
404
+ identity_values = [map.fetch("beat", {})["name"],
405
+ map["source"]]
406
+ end
407
+
408
+ identity_values.compact.join("-")
409
+ end
389
410
  end # class Connection
390
411
 
391
412
  class AckingProtocolV1
@@ -395,7 +416,7 @@ module Lumberjack module Beats
395
416
  end
396
417
 
397
418
  def ack?(sequence)
398
- # The first encoded event will contain the sequence number
419
+ # The first encoded event will contain the sequence number
399
420
  # this is needed to know when we should ack.
400
421
  @next_ack = compute_next_ack(sequence) if @next_ack.nil?
401
422
  sequence == @next_ack
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "logstash-input-beats"
3
- s.version = "0.9.6"
3
+ s.version = "2.0.0"
4
4
  s.licenses = ["Apache License (2.0)"]
5
5
  s.summary = "Receive events using the lumberjack protocol."
6
6
  s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
@@ -19,10 +19,11 @@ Gem::Specification.new do |s|
19
19
  s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
20
20
 
21
21
  # Gem dependencies
22
- s.add_runtime_dependency "logstash-core", ">= 1.5.4", "< 3.0.0"
22
+ s.add_runtime_dependency "logstash-core", ">= 2.0.0", "< 3.0.0"
23
23
 
24
24
  s.add_runtime_dependency "logstash-codec-plain"
25
25
  s.add_runtime_dependency "concurrent-ruby", "0.9.1"
26
+ s.add_runtime_dependency "logstash-codec-multiline", "~> 2.0.3"
26
27
 
27
28
  s.add_development_dependency "flores", "~>0.0.6"
28
29
  s.add_development_dependency "rspec"
@@ -30,6 +31,5 @@ Gem::Specification.new do |s|
30
31
  s.add_development_dependency "pry"
31
32
  s.add_development_dependency "rspec-wait"
32
33
  s.add_development_dependency "logstash-devutils", "~> 0.0.18"
33
- s.add_development_dependency "logstash-codec-multiline"
34
34
  end
35
35
 
@@ -2,7 +2,6 @@
2
2
  require_relative "../spec_helper"
3
3
  require "stud/temporary"
4
4
  require "logstash/inputs/beats"
5
- require "logstash/compatibility_layer_api_v1"
6
5
  require "logstash/codecs/plain"
7
6
  require "logstash/codecs/multiline"
8
7
  require "logstash/event"
@@ -79,31 +78,19 @@ describe LogStash::Inputs::Beats do
79
78
  "type" => "example", "codec" => codec }
80
79
  end
81
80
 
82
-
83
- context "#codecs" do
84
- let(:lines) { {"line" => "one\ntwo\n two.2\nthree\n", "tags" => ["syslog"]} }
85
-
86
- before do
87
- allow(connection).to receive(:run).and_yield(lines)
88
- beats.register
89
- expect_any_instance_of(Lumberjack::Beats::Server).to receive(:accept).and_return(connection)
90
- end
91
-
92
- it "clone the codec per connection" do
93
- expect(beats.codec).to receive(:clone).once
94
- expect(beats).to receive(:invoke) { break }
95
- beats.run(queue)
96
- end
81
+ before do
82
+ beats.register
97
83
  end
98
84
 
99
85
  context "#create_event" do
100
86
  let(:config) { super.merge({ "add_field" => { "foo" => "bar", "[@metadata][hidden]" => "secret"}, "tags" => ["bonjour"]}) }
101
87
  let(:event_map) { { "hello" => "world" } }
102
88
  let(:codec) { LogStash::Codecs::Plain.new }
89
+ let(:identity_stream) { "custom-type-input_type-source" }
103
90
 
104
91
  context "without a `target_field` defined" do
105
92
  it "decorates the event" do
106
- event = beats.create_event(codec, event_map)
93
+ event = beats.create_event(event_map, identity_stream)
107
94
  expect(event["foo"]).to eq("bar")
108
95
  expect(event["[@metadata][hidden]"]).to eq("secret")
109
96
  expect(event["tags"]).to include("bonjour")
@@ -114,7 +101,7 @@ describe LogStash::Inputs::Beats do
114
101
  let(:event_map) { super.merge({"message" => "with a field"}) }
115
102
 
116
103
  it "decorates the event" do
117
- event = beats.create_event(beats.codec, event_map)
104
+ event = beats.create_event(event_map, identity_stream)
118
105
  expect(event["foo"]).to eq("bar")
119
106
  expect(event["[@metadata][hidden]"]).to eq("secret")
120
107
  expect(event["tags"]).to include("bonjour")
@@ -126,16 +113,14 @@ describe LogStash::Inputs::Beats do
126
113
  let(:event_map) { {"message" => "hello?", "tags" => ["syslog"]} }
127
114
 
128
115
  it "retuns nil" do
129
- event = beats.create_event(beats.codec, event_map)
116
+ event = beats.create_event(event_map, identity_stream)
130
117
  expect(event).to be_nil
131
118
  end
132
119
  end
133
120
  end
134
121
  end
135
122
 
136
- unless LogStash::CompatibilityLayerApiV1.is_v1?
137
- context "when interrupting the plugin" do
138
- it_behaves_like "an interruptible input plugin"
139
- end
123
+ context "when interrupting the plugin" do
124
+ it_behaves_like "an interruptible input plugin"
140
125
  end
141
126
  end
@@ -33,7 +33,7 @@ describe "A client" do
33
33
  tcp_server.accept do |socket|
34
34
  con = Lumberjack::Beats::Connection.new(socket, tcp_server)
35
35
  begin
36
- con.run { |data| queue << data }
36
+ con.run { |data, identity_stream| queue << [data, identity_stream] }
37
37
  rescue
38
38
  # Close connection on failure. For example SSL client will make
39
39
  # parser for TCP based server trip.
@@ -44,7 +44,7 @@ describe "A client" do
44
44
  end
45
45
 
46
46
  @ssl_server = Thread.new do
47
- ssl_server.run { |data| queue << data }
47
+ ssl_server.run { |data, identity_stream| queue << [data, identity_stream] }
48
48
  end
49
49
  end
50
50
 
@@ -55,6 +55,7 @@ describe "A client" do
55
55
  end
56
56
  sleep(0.5) # give time to the server to read the events
57
57
  expect(queue.size).to eq(random_number_of_events)
58
+ expect(queue.collect(&:last).uniq.size).to eq(1)
58
59
  end
59
60
 
60
61
  it "support sending multiple elements in one payload" do
@@ -62,7 +63,7 @@ describe "A client" do
62
63
  sleep(0.5)
63
64
 
64
65
  expect(queue.size).to eq(batch_size)
65
- expect(queue).to match_array(batch_payload)
66
+ expect(queue.collect(&:last).uniq.size).to eq(1)
66
67
  end
67
68
  end
68
69
 
@@ -106,7 +107,7 @@ describe "A client" do
106
107
  expect(client.write(batch_payload)).to eq(batch_size / 2)
107
108
  sleep(0.5)
108
109
  expect(queue.size).to eq(batch_size)
109
- expect(queue).to match_array(batch_payload)
110
+ expect(queue.collect(&:first)).to match_array(batch_payload)
110
111
  end
111
112
  end
112
113
  end
@@ -152,7 +153,7 @@ describe "A client" do
152
153
  context "using ssl encrypted connection" do
153
154
  context "with a valid certificate" do
154
155
  it "successfully connect to the server" do
155
- expect {
156
+ expect {
156
157
  Lumberjack::Beats::Client.new(:port => port,
157
158
  :host => host,
158
159
  :addresses => host,
@@ -161,7 +162,7 @@ describe "A client" do
161
162
  end
162
163
 
163
164
  it "should fail connecting to plain tcp server" do
164
- expect {
165
+ expect {
165
166
  Lumberjack::Beats::Client.new(:port => tcp_port,
166
167
  :host => host,
167
168
  :addresses => host,
@@ -4,6 +4,8 @@ require "spec_helper"
4
4
  require "flores/random"
5
5
 
6
6
  describe "Connnection" do
7
+ let(:ip) { "192.168.1.2" }
8
+ let(:port) { 4444 }
7
9
  let(:server) { double("server", :closed? => false) }
8
10
  let(:socket) { double("socket", :closed? => false) }
9
11
  let(:connection) { Lumberjack::Beats::Connection.new(socket, server) }
@@ -11,6 +13,55 @@ describe "Connnection" do
11
13
  let(:start_sequence) { Flores::Random.integer(0..2000) }
12
14
  let(:random_number_of_events) { Flores::Random.integer(2..200) }
13
15
 
16
+ subject { Lumberjack::Beats::Connection.new(socket, server) }
17
+
18
+ before do
19
+ allow(socket).to receive(:peeraddr).and_return(["AF_INET", port, "test.elastic.co", ip])
20
+ end
21
+
22
+ context "#peer" do
23
+ let(:socket) { double("socket", :closed? => false) }
24
+
25
+ it "return the ip and the port" do
26
+ expect(subject.peer).to eq("#{ip}:#{port}")
27
+ end
28
+ end
29
+
30
+ context "#identity_stream" do
31
+ let(:map) { { "message" => "Hello world" } }
32
+
33
+ context "with a map containing the beats.id and the file_id" do
34
+ let(:map) { super.merge({
35
+ "beat" => { "name" => "testing-host", "id" => "abc1234" },
36
+ "type" => "log",
37
+ "resource_id" => "123",
38
+ "input_type" => "propector",
39
+ "source" => "/var/log/message" }) }
40
+
41
+ it "generate a identity stream from an event start" do
42
+ expect(subject.identity_stream(map)).to eq("#{map["beat"]["id"]}-#{map["resource_id"]}")
43
+ end
44
+ end
45
+
46
+ context "with a map containing all the information" do
47
+ let(:map) { super.merge({
48
+ "beat" => { "name" => "testing-host" },
49
+ "type" => "log",
50
+ "input_type" => "propector",
51
+ "source" => "/var/log/message" }) }
52
+
53
+ it "generate a identity stream from an event start" do
54
+ expect(subject.identity_stream(map)).to eq("#{map["beat"]["name"]}-#{map["source"]}")
55
+ end
56
+ end
57
+
58
+ context "with a map containing no information" do
59
+ it "use the ip and the port" do
60
+ expect(subject.identity_stream(map)).to eq("")
61
+ end
62
+ end
63
+ end
64
+
14
65
  context "when the server is running" do
15
66
  before do
16
67
  allow(socket).to receive(:sysread).with(Lumberjack::Beats::Connection::READ_SIZE).and_return("")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-beats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-06 00:00:00.000000000 Z
11
+ date: 2015-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logstash-core
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: 1.5.4
19
+ version: 2.0.0
20
20
  - - <
21
21
  - !ruby/object:Gem::Version
22
22
  version: 3.0.0
@@ -24,7 +24,7 @@ dependencies:
24
24
  requirements:
25
25
  - - '>='
26
26
  - !ruby/object:Gem::Version
27
- version: 1.5.4
27
+ version: 2.0.0
28
28
  - - <
29
29
  - !ruby/object:Gem::Version
30
30
  version: 3.0.0
@@ -58,6 +58,20 @@ dependencies:
58
58
  version: 0.9.1
59
59
  prerelease: false
60
60
  type: :runtime
61
+ - !ruby/object:Gem::Dependency
62
+ name: logstash-codec-multiline
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: 2.0.3
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ~>
71
+ - !ruby/object:Gem::Version
72
+ version: 2.0.3
73
+ prerelease: false
74
+ type: :runtime
61
75
  - !ruby/object:Gem::Dependency
62
76
  name: flores
63
77
  version_requirements: !ruby/object:Gem::Requirement
@@ -142,20 +156,6 @@ dependencies:
142
156
  version: 0.0.18
143
157
  prerelease: false
144
158
  type: :development
145
- - !ruby/object:Gem::Dependency
146
- name: logstash-codec-multiline
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - '>='
150
- - !ruby/object:Gem::Version
151
- version: '0'
152
- requirement: !ruby/object:Gem::Requirement
153
- requirements:
154
- - - '>='
155
- - !ruby/object:Gem::Version
156
- version: '0'
157
- prerelease: false
158
- type: :development
159
159
  description: This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program
160
160
  email: info@elastic.co
161
161
  executables: []
@@ -163,14 +163,13 @@ extensions: []
163
163
  extra_rdoc_files: []
164
164
  files:
165
165
  - lib/logstash/circuit_breaker.rb
166
- - lib/logstash/compatibility_layer_api_v1.rb
167
166
  - lib/logstash/sized_queue_timeout.rb
168
167
  - lib/logstash/inputs/beats.rb
169
168
  - lib/lumberjack/beats.rb
170
169
  - lib/lumberjack/beats/client.rb
171
170
  - lib/lumberjack/beats/server.rb
172
- - spec/integration_spec.rb
173
171
  - spec/spec_helper.rb
172
+ - spec/integration_spec.rb
174
173
  - spec/inputs/beats_spec.rb
175
174
  - spec/logstash/circuit_breaker_spec.rb
176
175
  - spec/logstash/size_queue_timeout_spec.rb
@@ -215,8 +214,8 @@ signing_key:
215
214
  specification_version: 4
216
215
  summary: Receive events using the lumberjack protocol.
217
216
  test_files:
218
- - spec/integration_spec.rb
219
217
  - spec/spec_helper.rb
218
+ - spec/integration_spec.rb
220
219
  - spec/inputs/beats_spec.rb
221
220
  - spec/logstash/circuit_breaker_spec.rb
222
221
  - spec/logstash/size_queue_timeout_spec.rb
@@ -1,28 +0,0 @@
1
- # encoding: utf-8
2
- require "logstash/version"
3
- require "gems"
4
-
5
- # This module allow this plugin to work with the v1 API.
6
- module LogStash::CompatibilityLayerApiV1
7
- LOGSTASH_CORE_VERSION = Gem::Version.new(LOGSTASH_VERSION)
8
- V2_VERSION = Gem::Version.new("2.0.0.beta2")
9
-
10
- def self.included(base)
11
- base.send(:include, InstanceMethods) if self.is_v1?
12
- end
13
-
14
- def self.is_v1?
15
- LOGSTASH_CORE_VERSION < V2_VERSION
16
- end
17
-
18
- # This allow this plugin to work both in V1 and v2 of logstash-core
19
- module InstanceMethods
20
- def stop?
21
- false
22
- end
23
-
24
- def teardown
25
- stop
26
- end
27
- end
28
- end