logstash-output-sumologic 1.2.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,19 +1,19 @@
1
1
  # encoding: utf-8
2
- require "net/https"
3
- require "socket"
4
- require "thread"
5
- require "uri"
6
- require "logstash/outputs/sumologic/common"
7
- require "logstash/outputs/sumologic/compressor"
8
- require "logstash/outputs/sumologic/header_builder"
9
- require "logstash/outputs/sumologic/statistics"
10
- require "logstash/outputs/sumologic/message_queue"
11
2
 
12
3
  module LogStash; module Outputs; class SumoLogic;
13
4
  class Sender
14
5
 
6
+ require "net/https"
7
+ require "socket"
8
+ require "thread"
9
+ require "uri"
10
+ require "logstash/outputs/sumologic/common"
11
+ require "logstash/outputs/sumologic/compressor"
12
+ require "logstash/outputs/sumologic/header_builder"
13
+ require "logstash/outputs/sumologic/statistics"
14
+ require "logstash/outputs/sumologic/message_queue"
15
15
  include LogStash::Outputs::SumoLogic::Common
16
- STOP_TAG = "PLUGIN STOPPED"
16
+
17
17
 
18
18
  def initialize(client, queue, stats, config)
19
19
  @client = client
@@ -28,9 +28,6 @@ module LogStash; module Outputs; class SumoLogic;
28
28
  @tokens = SizedQueue.new(@sender_max)
29
29
  @sender_max.times { |t| @tokens << t }
30
30
 
31
- @header_builder = LogStash::Outputs::SumoLogic::HeaderBuilder.new(config)
32
- @headers = @header_builder.build()
33
- @stats_headers = @header_builder.build_stats()
34
31
  @compressor = LogStash::Outputs::SumoLogic::Compressor.new(config)
35
32
 
36
33
  end # def initialize
@@ -42,11 +39,11 @@ module LogStash; module Outputs; class SumoLogic;
42
39
  @stopping.make_false()
43
40
  @sender_t = Thread.new {
44
41
  while @stopping.false?
45
- content = @queue.deq()
46
- send_request(content)
42
+ batch = @queue.deq()
43
+ send_request(batch)
47
44
  end # while
48
- @queue.drain().map { |content|
49
- send_request(content)
45
+ @queue.drain().map { |batch|
46
+ send_request(batch)
50
47
  }
51
48
  log_info("waiting while senders finishing...")
52
49
  while @tokens.size < @sender_max
@@ -58,7 +55,7 @@ module LogStash; module Outputs; class SumoLogic;
58
55
  def stop()
59
56
  log_info("shutting down sender...")
60
57
  @stopping.make_true()
61
- @queue.enq(STOP_TAG)
58
+ @queue.enq(Batch.new(Hash.new, STOP_TAG))
62
59
  @sender_t.join
63
60
  log_info("sender is fully shutted down")
64
61
  end # def stop
@@ -93,7 +90,9 @@ module LogStash; module Outputs; class SumoLogic;
93
90
 
94
91
  private
95
92
 
96
- def send_request(content)
93
+ def send_request(batch)
94
+ content = batch.payload
95
+ headers = batch.headers
97
96
  if content == STOP_TAG
98
97
  log_info("STOP_TAG is received.")
99
98
  return
@@ -103,10 +102,8 @@ module LogStash; module Outputs; class SumoLogic;
103
102
 
104
103
  if @stats_enabled && content.start_with?(STATS_TAG)
105
104
  body = @compressor.compress(content[STATS_TAG.length..-1])
106
- headers = @stats_headers
107
105
  else
108
106
  body = @compressor.compress(content)
109
- headers = @headers
110
107
  end
111
108
 
112
109
  log_dbg("sending request",
@@ -128,8 +125,8 @@ module LogStash; module Outputs; class SumoLogic;
128
125
  :code => response.code,
129
126
  :headers => headers,
130
127
  :contet => content[0..20])
131
- if response.code == 429 || response.code == 503 || response.code == 504
132
- requeue_message(content)
128
+ if response.code == 429 || response.code == 502 || response.code == 503 || response.code == 504
129
+ requeue_message(batch)
133
130
  end
134
131
  else
135
132
  log_dbg("request accepted",
@@ -145,24 +142,29 @@ module LogStash; module Outputs; class SumoLogic;
145
142
  :message => exception.message,
146
143
  :class => exception.class.name,
147
144
  :backtrace => exception.backtrace)
148
- requeue_message(content)
145
+ requeue_message(batch)
149
146
  end
150
147
 
151
148
  @stats.record_request(content.bytesize, body.bytesize)
152
149
  request.call
153
150
  end # def send_request
154
151
 
155
- def requeue_message(content)
156
- if @stopping.false? && @sleep_before_requeue >= 0
152
+ def requeue_message(batch)
153
+ content = batch.payload
154
+ if @stats_enabled && content.start_with?(STATS_TAG)
155
+ log_warn("do not requeue stats payload",
156
+ :content => content)
157
+ elsif @stopping.false? && @sleep_before_requeue >= 0
157
158
  log_info("requeue message",
158
159
  :after => @sleep_before_requeue,
159
160
  :queue_size => @queue.size,
160
161
  :content_size => content.size,
161
- :content => content[0..20])
162
+ :content => content[0..20],
163
+ :headers => batch.headers)
162
164
  Stud.stoppable_sleep(@sleep_before_requeue) { @stopping.true? }
163
- @queue.enq(content)
165
+ @queue.enq(batch)
164
166
  end
165
167
  end # def reque_message
166
168
 
167
169
  end
168
- end; end; end
170
+ end; end; end
@@ -1,9 +1,9 @@
1
1
  # encoding: utf-8
2
- require "logstash/outputs/sumologic/common"
3
2
 
4
3
  module LogStash; module Outputs; class SumoLogic;
5
4
  class Statistics
6
5
 
6
+ require "logstash/outputs/sumologic/common"
7
7
  include LogStash::Outputs::SumoLogic::Common
8
8
 
9
9
  attr_reader :initialize_time
@@ -11,14 +11,10 @@ module LogStash; module Outputs; class SumoLogic;
11
11
  attr_reader :total_input_bytes
12
12
  attr_reader :total_metrics_datapoints
13
13
  attr_reader :total_log_lines
14
- attr_reader :current_pile_items
15
- attr_reader :current_pile_bytes
16
14
  attr_reader :total_enque_times
17
15
  attr_reader :total_enque_bytes
18
16
  attr_reader :total_deque_times
19
17
  attr_reader :total_deque_bytes
20
- attr_reader :current_queue_items
21
- attr_reader :current_queue_bytes
22
18
  attr_reader :total_output_requests
23
19
  attr_reader :total_output_bytes
24
20
  attr_reader :total_output_bytes_compressed
@@ -32,14 +28,10 @@ module LogStash; module Outputs; class SumoLogic;
32
28
  @total_input_bytes = Concurrent::AtomicFixnum.new
33
29
  @total_metrics_datapoints = Concurrent::AtomicFixnum.new
34
30
  @total_log_lines = Concurrent::AtomicFixnum.new
35
- @current_pile_items = Concurrent::AtomicFixnum.new
36
- @current_pile_bytes = Concurrent::AtomicFixnum.new
37
31
  @total_enque_times = Concurrent::AtomicFixnum.new
38
32
  @total_enque_bytes = Concurrent::AtomicFixnum.new
39
33
  @total_deque_times = Concurrent::AtomicFixnum.new
40
34
  @total_deque_bytes = Concurrent::AtomicFixnum.new
41
- @current_queue_items = Concurrent::AtomicFixnum.new
42
- @current_queue_bytes = Concurrent::AtomicFixnum.new
43
35
  @total_output_requests = Concurrent::AtomicFixnum.new
44
36
  @total_output_bytes = Concurrent::AtomicFixnum.new
45
37
  @total_output_bytes_compressed = Concurrent::AtomicFixnum.new
@@ -53,16 +45,9 @@ module LogStash; module Outputs; class SumoLogic;
53
45
  @total_response.get(key) ? @total_response.get(key).value : 0
54
46
  end
55
47
 
56
- def record_multi_input(events, bytesize)
57
- @total_input_events.update { |v| v + events }
58
- @total_input_bytes.update { |v| v + bytesize }
59
- end # def record_multi_input
60
-
61
- def record_input(entry)
48
+ def record_input(size)
62
49
  @total_input_events.increment()
63
- @total_input_bytes.update { |v| v + entry.bytesize }
64
- @current_pile_items.increment()
65
- @current_pile_bytes.update { |v| v + entry.bytesize }
50
+ @total_input_bytes.update { |v| v + size }
66
51
  end # def record_input
67
52
 
68
53
  def record_log_process()
@@ -73,23 +58,14 @@ module LogStash; module Outputs; class SumoLogic;
73
58
  @total_metrics_datapoints.update { |v| v + dps }
74
59
  end # def record_metrics_process
75
60
 
76
- def record_clear_pile()
77
- @current_pile_items.value= 0
78
- @current_pile_bytes.value= 0
79
- end # def record_pile_clear
80
-
81
- def record_enque(payload)
61
+ def record_enque(size)
82
62
  @total_enque_times.increment()
83
- @total_enque_bytes.update { |v| v + payload.bytesize }
84
- @current_queue_items.increment()
85
- @current_queue_bytes.update { |v| v + payload.bytesize }
63
+ @total_enque_bytes.update { |v| v + size }
86
64
  end # def record_enque
87
65
 
88
- def record_deque(payload)
66
+ def record_deque(size)
89
67
  @total_deque_times.increment()
90
- @total_deque_bytes.update { |v| v + payload.bytesize }
91
- @current_queue_items.decrement()
92
- @current_queue_bytes.update { |v| v - payload.bytesize }
68
+ @total_deque_bytes.update { |v| v + size }
93
69
  end # def record_deque
94
70
 
95
71
  def record_request(size, size_compressed)
@@ -14,6 +14,7 @@ require "logstash/plugin_mixins/http_client"
14
14
  class LogStash::Outputs::SumoLogic < LogStash::Outputs::Base
15
15
  declare_threadsafe!
16
16
 
17
+ require "logstash/outputs/sumologic/batch"
17
18
  require "logstash/outputs/sumologic/common"
18
19
  require "logstash/outputs/sumologic/compressor"
19
20
  require "logstash/outputs/sumologic/header_builder"
@@ -101,22 +102,26 @@ class LogStash::Outputs::SumoLogic < LogStash::Outputs::Base
101
102
  # For carbon2 metrics format only, define the meta tags (which will NOT be used to identify the metrics)
102
103
  config :meta_tags, :validate => :hash, :default => {}
103
104
 
104
- # For messages fail to send or get 429/503/504 response, try to resend after (x) seconds; don't resend if (x) < 0
105
+ # For messages fail to send or get 429/502/503/504 response, try to resend after (x) seconds; don't resend if (x) < 0
105
106
  config :sleep_before_requeue, :validate => :number, :default => 30
106
107
 
108
+ config :stats_category, :validate => :string, :default => CATEGORY_HEADER_DEFAULT_STATS
109
+
107
110
  # Sending throughput data as metrics
108
111
  config :stats_enabled, :validate => :boolean, :default => false
109
112
 
110
113
  # Sending throughput data points every (x) seconds
111
114
  config :stats_interval, :validate => :number, :default => 60
112
115
 
116
+ # Disable cookies by default (used in HTTP mixin)
117
+ config :cookies, :validate => :boolean, :default => false
118
+
113
119
  attr_reader :stats
114
120
 
115
121
  def register
116
122
  set_logger(@logger)
117
- @stats = Statistics.new()
123
+ @stats = Statistics.new
118
124
  @queue = MessageQueue.new(@stats, config)
119
- @builder = PayloadBuilder.new(@stats, config)
120
125
  @piler = Piler.new(@queue, @stats, config)
121
126
  @monitor = Monitor.new(@queue, @stats, config)
122
127
  @sender = Sender.new(client, @queue, @stats, config)
@@ -130,24 +135,12 @@ class LogStash::Outputs::SumoLogic < LogStash::Outputs::Base
130
135
  end # def register
131
136
 
132
137
  def multi_receive(events)
133
- # events.map { |e| receive(e) }
134
- begin
135
- content = Array(events).map { |event| @builder.build(event) }.join($/)
136
- @queue.enq(content)
137
- @stats.record_multi_input(events.size, content.bytesize)
138
- rescue Exception => exception
139
- log_err("error when processing events",
140
- :events => events,
141
- :message => exception.message,
142
- :class => exception.class.name,
143
- :backtrace => exception.backtrace)
144
- end
138
+ Array(events).map { |event| receive(event) }
145
139
  end # def multi_receive
146
140
 
147
141
  def receive(event)
148
142
  begin
149
- content = @builder.build(event)
150
- @piler.input(content)
143
+ @piler.input(event)
151
144
  rescue Exception => exception
152
145
  log_err("error when processing event",
153
146
  :event => event,
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-sumologic'
3
- s.version = '1.2.2'
3
+ s.version = '1.4.0'
4
4
  s.licenses = ['Apache-2.0']
5
5
  s.summary = 'Deliever the log to Sumo Logic cloud service.'
6
6
  s.description = 'This gem is a Logstash output plugin to deliver the log or metrics to Sumo Logic cloud service. Go to https://github.com/SumoLogic/logstash-output-sumologic for getting help, reporting issues, etc.'
@@ -20,8 +20,8 @@ Gem::Specification.new do |s|
20
20
  # Gem dependencies
21
21
  s.add_runtime_dependency 'manticore', '>= 0.5.4', '< 1.0.0'
22
22
  s.add_runtime_dependency 'logstash-core-plugin-api', '>= 1.60', '<= 2.99'
23
- s.add_runtime_dependency 'logstash-codec-plain'
24
- s.add_runtime_dependency 'logstash-mixin-http_client'
23
+ s.add_runtime_dependency 'logstash-mixin-http_client', '>= 6', '< 8'
25
24
 
25
+ s.add_development_dependency 'logstash-codec-plain'
26
26
  s.add_development_dependency 'logstash-devutils'
27
27
  end
@@ -5,12 +5,14 @@ require "logstash/outputs/sumologic"
5
5
  describe LogStash::Outputs::SumoLogic::HeaderBuilder do
6
6
 
7
7
  result = {}
8
+ event = LogStash::Event.new("foo" => "bar", "message" => "Hello world")
8
9
 
9
10
  before :each do
10
- result = builder.build()
11
+ result = builder.build(event)
11
12
  end
12
13
 
13
14
  context "should build headers by default" do
15
+
14
16
  let(:builder) { LogStash::Outputs::SumoLogic::HeaderBuilder.new("url" => "http://localhost/1234") }
15
17
 
16
18
  specify {
@@ -41,6 +43,21 @@ describe LogStash::Outputs::SumoLogic::HeaderBuilder do
41
43
 
42
44
  end # context
43
45
 
46
+ context "should override source_category with template" do
47
+
48
+ let(:builder) {
49
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new(
50
+ "url" => "http://localhost/1234",
51
+ "source_category" => "my source category %{foo}")
52
+ }
53
+
54
+ specify {
55
+ expect(result.count).to eq(5)
56
+ expect(result["X-Sumo-Category"]).to eq("my source category bar")
57
+ }
58
+
59
+ end # context
60
+
44
61
  context "should override source_name" do
45
62
 
46
63
  let(:builder) {
@@ -56,6 +73,21 @@ describe LogStash::Outputs::SumoLogic::HeaderBuilder do
56
73
 
57
74
  end # context
58
75
 
76
+ context "should override source_name with template" do
77
+
78
+ let(:builder) {
79
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new(
80
+ "url" => "http://localhost/1234",
81
+ "source_name" => "my source name %{foo}")
82
+ }
83
+
84
+ specify {
85
+ expect(result.count).to eq(5)
86
+ expect(result["X-Sumo-Name"]).to eq("my source name bar")
87
+ }
88
+
89
+ end # context
90
+
59
91
  context "should override source_host" do
60
92
 
61
93
  let(:builder) {
@@ -71,6 +103,21 @@ describe LogStash::Outputs::SumoLogic::HeaderBuilder do
71
103
 
72
104
  end # context
73
105
 
106
+ context "should override source_host with template" do
107
+
108
+ let(:builder) {
109
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new(
110
+ "url" => "http://localhost/1234",
111
+ "source_host" => "my source host %{foo}")
112
+ }
113
+
114
+ specify {
115
+ expect(result.count).to eq(5)
116
+ expect(result["X-Sumo-Host"]).to eq("my source host bar")
117
+ }
118
+
119
+ end # context
120
+
74
121
  context "should hornor extra_headers" do
75
122
 
76
123
  let(:builder) {
@@ -194,4 +241,90 @@ describe LogStash::Outputs::SumoLogic::HeaderBuilder do
194
241
 
195
242
  end # context
196
243
 
244
+ context "should build headers for stats" do
245
+ let(:builder) {
246
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new("url" => "http://localhost/1234")
247
+ }
248
+
249
+ specify {
250
+ stats_result = builder.build_stats()
251
+ expected = {
252
+ "X-Sumo-Client" => "logstash-output-sumologic",
253
+ "X-Sumo-Name" => "logstash-output-sumologic",
254
+ "X-Sumo-Host" => Socket.gethostname,
255
+ "X-Sumo-Category" => "Logstash.stats",
256
+ "Content-Type" => "application/vnd.sumologic.carbon2"
257
+ }
258
+ expect(stats_result).to eq(expected)
259
+ }
260
+ end
261
+
262
+ context "should build headers for stats with overridden source category" do
263
+ let(:builder) {
264
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new("url" => "http://localhost/1234", "stats_category" => "custom")
265
+ }
266
+
267
+ specify {
268
+ stats_result = builder.build_stats()
269
+ expect(stats_result["X-Sumo-Category"]).to eq("custom")
270
+ }
271
+ end
272
+
273
+ context "should build headers for stats with compression" do
274
+ let(:builder) {
275
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new("url" => "http://localhost/1234", "compress" => true, "compress_encoding" => "gzip")
276
+ }
277
+
278
+ specify {
279
+ stats_result = builder.build_stats()
280
+ expect(stats_result["Content-Encoding"]).to eq("gzip")
281
+ }
282
+ end
283
+
284
+ context "should build headers for stats with extra_headers" do
285
+ let(:builder) {
286
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new(
287
+ "url" => "http://localhost/1234",
288
+ "extra_headers" => {
289
+ "foo" => "bar"
290
+ })
291
+ }
292
+
293
+ specify {
294
+ stats_result = builder.build_stats()
295
+ expect(stats_result.count).to eq(6)
296
+ expect(stats_result["foo"]).to eq("bar")
297
+ }
298
+ end
299
+
300
+ context "should build headers for stats with extra_headers but never overwrite pre-defined headers" do
301
+
302
+ let(:builder) {
303
+ LogStash::Outputs::SumoLogic::HeaderBuilder.new(
304
+ "url" => "http://localhost/1234",
305
+ "extra_headers" => {
306
+ "foo" => "bar",
307
+ "X-Sumo-Client" => "a",
308
+ "X-Sumo-Name" => "b",
309
+ "X-Sumo-Host" => "c",
310
+ "X-Sumo-Category" => "d",
311
+ "Content-Type" => "e"
312
+ })
313
+ }
314
+
315
+ specify {
316
+ stats_result = builder.build_stats()
317
+ expected = {
318
+ "foo" => "bar",
319
+ "X-Sumo-Client" => "logstash-output-sumologic",
320
+ "X-Sumo-Name" => "logstash-output-sumologic",
321
+ "X-Sumo-Host" => Socket.gethostname,
322
+ "X-Sumo-Category" => "Logstash.stats",
323
+ "Content-Type" => "application/vnd.sumologic.carbon2"
324
+ }
325
+ expect(stats_result).to eq(expected)
326
+ }
327
+
328
+ end # context
329
+
197
330
  end # describe
@@ -1,46 +1,48 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/devutils/rspec/spec_helper"
3
3
  require "logstash/outputs/sumologic"
4
+ include LogStash::Outputs
5
+
6
+ describe SumoLogic::MessageQueue do
4
7
 
5
- describe LogStash::Outputs::SumoLogic::MessageQueue do
6
-
7
8
  context "working in pile mode if interval > 0 && pile_max > 0" do
8
9
 
9
- let(:stats) { LogStash::Outputs::SumoLogic::Statistics.new() }
10
+ let(:queue) { SumoLogic::MessageQueue.new(stats, "queue_max" => 10) }
11
+ let(:stats) { SumoLogic::Statistics.new }
10
12
 
11
13
  it "enq() correctly" do
12
- queue = LogStash::Outputs::SumoLogic::MessageQueue.new(stats, "queue_max" => 10)
13
14
  10.times { |i|
14
- queue.enq("test - #{i}")
15
+ queue.enq(SumoLogic::Batch.new(Hash.new, "test - #{i}"))
15
16
  expect(queue.size()).to eq(i + 1)
16
17
  expect(stats.total_enque_times.value).to eq(i + 1)
17
18
  }
19
+ expect(queue.bytesize()).to eq(100)
18
20
  end
19
21
 
20
22
  it "deq() correctly" do
21
- queue = LogStash::Outputs::SumoLogic::MessageQueue.new(stats, "queue_max" => 10)
22
23
  10.times { |i|
23
- queue.enq("test - #{i}")
24
+ queue.enq(SumoLogic::Batch.new(Hash.new, "test - #{i}"))
24
25
  }
25
26
  10.times { |i|
26
27
  expect(queue.size()).to eq(10 - i)
27
28
  result = queue.deq()
28
- expect(result).to eq("test - #{i}")
29
+ expect(result.payload).to eq("test - #{i}")
29
30
  expect(stats.total_deque_times.value).to eq(i + 1)
30
31
  }
32
+ expect(queue.bytesize()).to eq(0)
31
33
  end
32
34
 
33
35
  it "drain() correctly" do
34
- queue = LogStash::Outputs::SumoLogic::MessageQueue.new(stats, "queue_max" => 10)
35
36
  10.times { |i|
36
- queue.enq("test - #{i}")
37
+ queue.enq(SumoLogic::Batch.new(Hash.new, "test - #{i}"))
37
38
  }
38
39
  result = queue.drain()
39
40
  expect(queue.size()).to eq(0)
40
41
  expect(stats.total_deque_times.value).to eq(10)
41
42
  expect(result.size).to eq(10)
43
+ expect(queue.bytesize()).to eq(0)
42
44
  10.times { |i|
43
- expect(result[i]).to eq("test - #{i}")
45
+ expect(result[i].payload).to eq("test - #{i}")
44
46
  }
45
47
  end
46
48