logstash-output-scalyr 0.1.13 → 0.1.14.beta

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
  SHA256:
3
- metadata.gz: 89b457b34b9579d3e6c7b82c7aed5a5ae54026904deee677d31f0a0ef5ed3e22
4
- data.tar.gz: 945e0414df96259ba39747e1f79ee8ed6d3c265d0c91aae0c15c91b9578988b9
3
+ metadata.gz: 1af6e37035c37c270036487e9b939f40e8454d39ddea9983a737f409957e0747
4
+ data.tar.gz: 95438909311aa595d2d4e771424765f3705cdc649225ca81c053e3b9c129b183
5
5
  SHA512:
6
- metadata.gz: 411e2319e7fb06de8af26ea876776500327e8fb57002cef3f4a3b153294039b486fe574089f3acdd8761309335dd83b9e3f75dbbd23c5ad701d59702e7d7fe11
7
- data.tar.gz: d17765c84763f27858548ede5afd45cc0d2ff11ef1462b586afa195fa0186f2cf0318a86463354285acae7d598aa45319305dea7ccc94581aa614a82e56dbe85
6
+ metadata.gz: d2a9f542cfaaf8bb9e84ebf976694c548e153e1e170417309644b9206bf4df2ba8844447cbb2b0afd4ba8d03922688f6348a6fb77a6c8489a3f2a49bb873fed9
7
+ data.tar.gz: 514fab3aa32f39e1ebe6e4bf164a9b9f648b82e7aa861d9cbc2edd6d44d9c6b4d9d3ea9b7c281ae26bf719e4373bde6059f220033dc660821a369b6186180798
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Beta
2
2
 
3
+ ## 0.1.14.beta
4
+ - Add configurable max retries for requests when running into errors.
5
+ - Add ability to send messages to the dead letter queue if we exhaust all retries and if it is configured.
6
+ - Log truncated error body for all errors to help with debugging.
7
+
3
8
  ## 0.1.13
4
9
  - Fix synchronization of status message sending code to avoid duplicate logs.
5
10
 
data/README.md CHANGED
@@ -10,7 +10,7 @@ You can view documentation for this plugin [on the Scalyr website](https://app.s
10
10
  # Quick start
11
11
 
12
12
  1. Build the gem, run `gem build logstash-output-scalyr.gemspec`
13
- 2. Install the gem into a Logstash installation, run `/usr/share/logstash/bin/logstash-plugin install logstash-output-scalyr-0.1.13.gem` or follow the latest official instructions on working with plugins from Logstash.
13
+ 2. Install the gem into a Logstash installation, run `/usr/share/logstash/bin/logstash-plugin install logstash-output-scalyr-0.1.14.beta.gem` or follow the latest official instructions on working with plugins from Logstash.
14
14
  3. Configure the output plugin (e.g. add it to a pipeline .conf)
15
15
  4. Restart Logstash
16
16
 
@@ -78,6 +78,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
78
78
 
79
79
  # Initial interval in seconds between bulk retries. Doubled on each retry up to `retry_max_interval`
80
80
  config :retry_initial_interval, :validate => :number, :default => 1
81
+ # How many times to retry sending an event before giving up on it
82
+ config :max_retries, :validate => :number, :default => 5
83
+ # Whether or not to send messages that failed to send a max_retries amount of times to the DLQ or just drop them
84
+ config :send_to_dlq, :validate => :boolean, :default => true
81
85
 
82
86
  # Set max interval in seconds between bulk retries.
83
87
  config :retry_max_interval, :validate => :number, :default => 64
@@ -319,6 +323,7 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
319
323
  exc_retries += 1
320
324
  message = "Error uploading to Scalyr (will backoff-retry)"
321
325
  exc_data = {
326
+ :error_class => e.e_class,
322
327
  :url => e.url.to_s,
323
328
  :message => e.message,
324
329
  :batch_num => batch_num,
@@ -330,7 +335,7 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
330
335
  exc_data[:code] = e.code if e.code
331
336
  if @logger.debug? and e.body
332
337
  exc_data[:body] = e.body
333
- elsif e.message == "Invalid JSON response from server" and e.body
338
+ elsif e.body
334
339
  exc_data[:body] = Scalyr::Common::Util.truncate(e.body, 512)
335
340
  end
336
341
  exc_data[:payload] = "\tSample payload: #{request[:body][0,1024]}..." if @logger.debug?
@@ -343,7 +348,9 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
343
348
  @logger.error(message, exc_data)
344
349
  exc_commonly_retried = false
345
350
  end
346
- retry if @running
351
+ retry if @running and exc_retries < @max_retries
352
+ log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
353
+ next
347
354
 
348
355
  rescue => e
349
356
  # Any unexpected errors should be fully logged
@@ -363,7 +370,9 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
363
370
  }
364
371
  exc_sleep += sleep_interval
365
372
  exc_retries += 1
366
- retry if @running
373
+ retry if @running and exc_retries < @max_retries
374
+ log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
375
+ next
367
376
  end
368
377
 
369
378
  if !exc_data.nil?
@@ -400,6 +409,23 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
400
409
  end # def multi_receive
401
410
 
402
411
 
412
+ def log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
413
+ message = "Failed to send #{multi_event_request[:logstash_events].length} events after #{exc_retries} tries."
414
+ sample_events = Array.new
415
+ multi_event_request[:logstash_events][0,5].each {|l_event|
416
+ sample_events << Scalyr::Common::Util.truncate(l_event.to_hash.to_json, 256)
417
+ }
418
+ @logger.error(message, :error_data => exc_data, :sample_events => sample_events, :retries => exc_retries, :sleep_time => exc_sleep)
419
+ if @dlq_writer
420
+ multi_event_request[:logstash_events].each {|l_event|
421
+ @dlq_writer.write(l_event, "#{exc_data[:message]}")
422
+ }
423
+ else
424
+ @logger.warn("Deal letter queue not configured, dropping #{multi_event_request[:logstash_events].length} events after #{exc_retries} tries.", :sample_events => sample_events)
425
+ end
426
+ end
427
+
428
+
403
429
  # Builds an array of multi-event requests from LogStash events
404
430
  # Each array element is a request that groups multiple events (to be posted to Scalyr's addEvents endpoint)
405
431
  #
@@ -428,6 +454,8 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
428
454
  current_threads = Hash.new
429
455
  # Create a Scalyr event object for each record in the chunk
430
456
  scalyr_events = Array.new
457
+ # Track the logstash events in each chunk to send them to the dlq in case of an error
458
+ l_events = Array.new
431
459
 
432
460
  thread_ids = Hash.new
433
461
  next_id = 1 #incrementing thread id for the session
@@ -619,9 +647,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
619
647
  # make sure we always have at least one event
620
648
  if scalyr_events.size == 0
621
649
  scalyr_events << scalyr_event
650
+ l_events << l_event
622
651
  append_event = false
623
652
  end
624
- multi_event_request = self.create_multi_event_request(scalyr_events, current_threads, logs)
653
+ multi_event_request = self.create_multi_event_request(scalyr_events, l_events, current_threads, logs)
625
654
  multi_event_request_array << multi_event_request
626
655
 
627
656
  total_bytes = 0
@@ -629,19 +658,21 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
629
658
  logs = Hash.new
630
659
  logs_ids = Hash.new
631
660
  scalyr_events = Array.new
661
+ l_events = Array.new
632
662
  end
633
663
 
634
664
  # if we haven't consumed the current event already
635
665
  # add it to the end of our array and keep track of the json bytesize
636
666
  if append_event
637
667
  scalyr_events << scalyr_event
668
+ l_events << l_event
638
669
  total_bytes += add_bytes
639
670
  end
640
671
 
641
672
  }
642
673
 
643
674
  # create a final request with any left over events
644
- multi_event_request = self.create_multi_event_request(scalyr_events, current_threads, logs)
675
+ multi_event_request = self.create_multi_event_request(scalyr_events, l_events, current_threads, logs)
645
676
  multi_event_request_array << multi_event_request
646
677
  multi_event_request_array
647
678
  end
@@ -659,7 +690,7 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
659
690
  # A request comprises multiple Scalyr Events. This function creates a request hash for
660
691
  # final upload to Scalyr (from an array of events, and an optional hash of current threads)
661
692
  # Note: The request body field will be json-encoded.
662
- def create_multi_event_request(scalyr_events, current_threads, current_logs)
693
+ def create_multi_event_request(scalyr_events, logstash_events, current_threads, current_logs)
663
694
 
664
695
  body = {
665
696
  :session => @session_id + Thread.current.object_id.to_s,
@@ -695,7 +726,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
695
726
  serialized_body = body.to_json
696
727
  end_time = Time.now.to_f
697
728
  serialization_duration = end_time - start_time
698
- { :body => serialized_body, :record_count => scalyr_events.size, :serialization_duration => serialization_duration }
729
+ {
730
+ :body => serialized_body, :record_count => scalyr_events.size, :serialization_duration => serialization_duration,
731
+ :logstash_events => logstash_events
732
+ }
699
733
 
700
734
  end # def create_multi_event_request
701
735
 
@@ -781,15 +815,24 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
781
815
  status_event[:attrs]['serverHost'] = @node_hostname
782
816
  status_event[:attrs]['parser'] = @status_parser
783
817
  end
784
- multi_event_request = create_multi_event_request([status_event], nil, nil)
818
+ multi_event_request = create_multi_event_request([status_event], nil, nil, nil)
785
819
  begin
786
820
  @client_session.post_add_events(multi_event_request[:body], true, 0)
787
821
  rescue => e
788
- @logger.warn(
789
- "Unexpected error occurred while uploading status to Scalyr",
790
- :error_message => e.message,
791
- :error_class => e.class.name
792
- )
822
+ if e.body
823
+ @logger.warn(
824
+ "Unexpected error occurred while uploading status to Scalyr",
825
+ :error_message => e.message,
826
+ :error_class => e.class.name,
827
+ :body => Scalyr::Common::Util.truncate(e.body, 512)
828
+ )
829
+ else
830
+ @logger.warn(
831
+ "Unexpected error occurred while uploading status to Scalyr",
832
+ :error_message => e.message,
833
+ :error_class => e.class.name
834
+ )
835
+ end
793
836
  return
794
837
  end
795
838
  @last_status_transmit_time = Time.now()
@@ -7,13 +7,14 @@ module Scalyr; module Common; module Client
7
7
  #---------------------------------------------------------------------------------------------------------------------
8
8
  class ServerError < StandardError
9
9
 
10
- attr_reader :code, :url, :body
10
+ attr_reader :code, :url, :body, :e_class
11
11
 
12
- def initialize(msg=nil, code=nil, url=nil, body=nil)
12
+ def initialize(msg=nil, code=nil, url=nil, body=nil, e_class="Scalyr::Common::Client::ServerError")
13
13
  super(msg)
14
14
  @code = code.to_i
15
15
  @url = url
16
16
  @body = body
17
+ @e_class = e_class
17
18
  end
18
19
 
19
20
  def is_commonly_retried?
@@ -33,13 +34,14 @@ end
33
34
  #---------------------------------------------------------------------------------------------------------------------
34
35
  class ClientError < StandardError
35
36
 
36
- attr_reader :code, :url, :body
37
+ attr_reader :code, :url, :body, :e_class
37
38
 
38
- def initialize(msg=nil, url=nil)
39
+ def initialize(msg=nil, url=nil, e_class="Scalyr::Common::Client::ClientError")
39
40
  super(msg)
40
41
  @code = nil # currently no way to get this from Net::HTTP::Persistent::Error
41
42
  @url = url
42
43
  @body = nil
44
+ @e_class = e_class
43
45
  end
44
46
 
45
47
  def is_commonly_retried?
@@ -236,15 +238,10 @@ class ClientSession
236
238
  bytes_received = response.body.bytesize # echee: double check
237
239
  # echee TODO add more statistics
238
240
 
239
- # TODO: Manticore doesn't raise SSL errors as this but as "UnknownExceptions", need to dig in and see if there is a
240
- # way to detect that it is from SSL.
241
- rescue OpenSSL::SSL::SSLError => e
242
- raise e
243
-
244
241
  rescue Manticore::ManticoreException => e
245
242
  # The underlying persistent-connection library automatically retries when there are network-related errors.
246
243
  # Eventually, it will give up and raise this generic error, at which time, we convert it to a ClientError
247
- raise ClientError.new(e.message, @add_events_uri)
244
+ raise ClientError.new(e.message, @add_events_uri, e.class.name)
248
245
 
249
246
  ensure
250
247
  if @record_stats_for_status or !is_status
@@ -1,2 +1,2 @@
1
1
  # encoding: utf-8
2
- PLUGIN_VERSION = "v0.1.13"
2
+ PLUGIN_VERSION = "v0.1.14.beta"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-scalyr'
3
- s.version = '0.1.13'
3
+ s.version = '0.1.14.beta'
4
4
  s.licenses = ['Apache-2.0']
5
5
  s.summary = "Scalyr output plugin for Logstash"
6
6
  s.description = "Sends log data collected by Logstash to Scalyr (https://www.scalyr.com)"
@@ -28,8 +28,11 @@ describe LogStash::Outputs::Scalyr do
28
28
  plugin = LogStash::Outputs::Scalyr.new({'api_write_token' => '1234'})
29
29
  plugin.register
30
30
  plugin.instance_variable_set(:@running, false)
31
- expect(plugin.instance_variable_get(:@logger)).to receive(:error).with("Error uploading to Scalyr (will backoff-retry)",
31
+ allow(plugin.instance_variable_get(:@logger)).to receive(:error)
32
+ plugin.multi_receive(sample_events)
33
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
32
34
  {
35
+ :error_class=>"Scalyr::Common::Client::ServerError",
33
36
  :batch_num=>1,
34
37
  :code=>401,
35
38
  :message=>"error/client/badParam",
@@ -37,10 +40,10 @@ describe LogStash::Outputs::Scalyr do
37
40
  :record_count=>3,
38
41
  :total_batches=>1,
39
42
  :url=>"https://agent.scalyr.com/addEvents",
40
- :will_retry_in_seconds=>2
43
+ :will_retry_in_seconds=>2,
44
+ :body=>"{\n \"message\": \"Couldn't decode API token ...234.\",\n \"status\": \"error/client/badParam\"\n}"
41
45
  }
42
46
  )
43
- plugin.multi_receive(sample_events)
44
47
  end
45
48
  end
46
49
 
@@ -49,8 +52,11 @@ describe LogStash::Outputs::Scalyr do
49
52
  plugin = LogStash::Outputs::Scalyr.new({'api_write_token' => '1234', 'ssl_ca_bundle_path' => '/fakepath/nocerts', 'append_builtin_cert' => false})
50
53
  plugin.register
51
54
  plugin.instance_variable_set(:@running, false)
52
- expect(plugin.instance_variable_get(:@logger)).to receive(:error).with("Error uploading to Scalyr (will backoff-retry)",
55
+ allow(plugin.instance_variable_get(:@logger)).to receive(:error)
56
+ plugin.multi_receive(sample_events)
57
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
53
58
  {
59
+ :error_class=>"Manticore::UnknownException",
54
60
  :batch_num=>1,
55
61
  :message=>"Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
56
62
  :payload_size=>781,
@@ -60,7 +66,6 @@ describe LogStash::Outputs::Scalyr do
60
66
  :will_retry_in_seconds=>2
61
67
  }
62
68
  )
63
- plugin.multi_receive(sample_events)
64
69
  end
65
70
  end
66
71
 
@@ -73,8 +78,11 @@ describe LogStash::Outputs::Scalyr do
73
78
  plugin = LogStash::Outputs::Scalyr.new({'api_write_token' => '1234', 'append_builtin_cert' => false})
74
79
  plugin.register
75
80
  plugin.instance_variable_set(:@running, false)
76
- expect(plugin.instance_variable_get(:@logger)).to receive(:error).with("Error uploading to Scalyr (will backoff-retry)",
81
+ allow(plugin.instance_variable_get(:@logger)).to receive(:error)
82
+ plugin.multi_receive(sample_events)
83
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
77
84
  {
85
+ :error_class=>"Manticore::UnknownException",
78
86
  :batch_num=>1,
79
87
  :message=>"Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
80
88
  :payload_size=>781,
@@ -84,7 +92,6 @@ describe LogStash::Outputs::Scalyr do
84
92
  :will_retry_in_seconds=>2
85
93
  }
86
94
  )
87
- plugin.multi_receive(sample_events)
88
95
  end
89
96
  ensure
90
97
  `sudo mv /tmp/system_certs #{OpenSSL::X509::DEFAULT_CERT_DIR}`
@@ -110,8 +117,11 @@ describe LogStash::Outputs::Scalyr do
110
117
  plugin = LogStash::Outputs::Scalyr.new({'api_write_token' => '1234', 'scalyr_server' => 'https://invalid.mitm.should.fail.test.agent.scalyr.com:443'})
111
118
  plugin.register
112
119
  plugin.instance_variable_set(:@running, false)
113
- expect(plugin.instance_variable_get(:@logger)).to receive(:error).with("Error uploading to Scalyr (will backoff-retry)",
120
+ allow(plugin.instance_variable_get(:@logger)).to receive(:error)
121
+ plugin.multi_receive(sample_events)
122
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
114
123
  {
124
+ :error_class=>"Manticore::UnknownException",
115
125
  :batch_num=>1,
116
126
  :message=>"Host name 'invalid.mitm.should.fail.test.agent.scalyr.com' does not match the certificate subject provided by the peer (CN=*.scalyr.com)",
117
127
  :payload_size=>781,
@@ -121,7 +131,6 @@ describe LogStash::Outputs::Scalyr do
121
131
  :will_retry_in_seconds=>2
122
132
  }
123
133
  )
124
- plugin.multi_receive(sample_events)
125
134
  ensure
126
135
  # Clean up the hosts file
127
136
  `sudo truncate -s 0 /etc/hosts`
@@ -129,6 +138,17 @@ describe LogStash::Outputs::Scalyr do
129
138
  end
130
139
  end
131
140
  end
141
+
142
+ context "when an error occurs with retries at 5" do
143
+ it "exits after 5 retries and emits a log" do
144
+ plugin = LogStash::Outputs::Scalyr.new({'retry_initial_interval' => 0.1, 'api_write_token' => '1234', 'ssl_ca_bundle_path' => '/fakepath/nocerts', 'append_builtin_cert' => false})
145
+ plugin.register
146
+ allow(plugin.instance_variable_get(:@logger)).to receive(:error)
147
+ plugin.multi_receive(sample_events)
148
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Failed to send 3 events after 5 tries.", anything
149
+ )
150
+ end
151
+ end
132
152
  end
133
153
 
134
154
  describe "response_handling_tests" do
@@ -145,6 +165,7 @@ describe LogStash::Outputs::Scalyr do
145
165
  plugin.multi_receive(sample_events)
146
166
  expect(plugin.instance_variable_get(:@logger)).to have_received(:debug).with("Error uploading to Scalyr (will backoff-retry)",
147
167
  {
168
+ :error_class=>"Scalyr::Common::Client::ServerError",
148
169
  :batch_num=>1,
149
170
  :code=>503,
150
171
  :message=>"Invalid JSON response from server",
@@ -172,6 +193,7 @@ describe LogStash::Outputs::Scalyr do
172
193
  plugin.multi_receive(sample_events)
173
194
  expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
174
195
  {
196
+ :error_class=>"Scalyr::Common::Client::ServerError",
175
197
  :batch_num=>1,
176
198
  :code=>500,
177
199
  :message=>"Invalid JSON response from server",
@@ -199,6 +221,7 @@ describe LogStash::Outputs::Scalyr do
199
221
  plugin.multi_receive(sample_events)
200
222
  expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
201
223
  {
224
+ :error_class=>"Scalyr::Common::Client::ServerError",
202
225
  :batch_num=>1,
203
226
  :code=>500,
204
227
  :message=>"Invalid JSON response from server",
@@ -212,6 +235,22 @@ describe LogStash::Outputs::Scalyr do
212
235
  )
213
236
  end
214
237
  end
238
+
239
+ context 'when DLQ is enabled' do
240
+ let(:dlq_writer) { double('DLQ writer') }
241
+ it 'should send the event to the DLQ' do
242
+ stub_request(:post, "https://agent.scalyr.com/addEvents").
243
+ to_return(status: 500, body: "stubbed response", headers: {})
244
+
245
+ plugin = LogStash::Outputs::Scalyr.new({'api_write_token' => '1234', 'ssl_ca_bundle_path' => '/fakepath/nocerts', 'append_builtin_cert' => false})
246
+ plugin.register
247
+ plugin.instance_variable_set(:@running, false)
248
+ plugin.instance_variable_set('@dlq_writer', dlq_writer)
249
+
250
+ expect(dlq_writer).to receive(:write).exactly(3).times.with(anything, anything)
251
+ plugin.multi_receive(sample_events)
252
+ end
253
+ end
215
254
  end
216
255
 
217
256
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-scalyr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edward Chee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-19 00:00:00.000000000 Z
11
+ date: 2021-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -4052,9 +4052,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
4052
4052
  version: '0'
4053
4053
  required_rubygems_version: !ruby/object:Gem::Requirement
4054
4054
  requirements:
4055
- - - ">="
4055
+ - - ">"
4056
4056
  - !ruby/object:Gem::Version
4057
- version: '0'
4057
+ version: 1.3.1
4058
4058
  requirements: []
4059
4059
  rubyforge_project:
4060
4060
  rubygems_version: 2.7.10