logstash-output-scalyr 0.1.11.beta → 0.1.16.beta

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6df8debd88e7675c00715a8b4fa2888e14cea861436c0049c1cbb99b96f2d0c
4
- data.tar.gz: f07b38d925f732664390d391da3e84b3930644b77197b871043cabbf28bfd218
3
+ metadata.gz: 16e88c3869fdb18e4132726af302e37c8bac2c10038ca3a078f85cebe67c4940
4
+ data.tar.gz: a42194413ec2969e31bbe44e72df7cf71d09911b61adae9db16b6041a97bc1b4
5
5
  SHA512:
6
- metadata.gz: 542cacbdb0ecb8d9fbabfd955b3c88dcd826af141ded1924eeb9863b8a075fcb8c323448bf0dbf4ac06254309309cdc8a60b1899e5aaa9a51f167243ff1fb818
7
- data.tar.gz: f19aeb46e95f7207e11dccd1b89aa6927e9bec88276cc1a5393ac144139b559c40ac2102da0a22624ac532c27dc753fca7b83f860b6b4763b9f28f67e4662555
6
+ metadata.gz: 66d4261da66deaa8f5227b0ecfcdfce51f170c561902397c6020d3baa8694bd7c92291b0faa84178e4dda180c71892ab4a30ba425dc1133f5f7b349c1b7e44fa
7
+ data.tar.gz: 6689e3c9fd5fa2a571ca8076a4ae24b5eb1bf5da9216030b8c759a0baff17f4577a478f3c3efced4ddb22ecf273ec675f8831733aded928fd2c92c7ce91d1983
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Beta
2
2
 
3
+ ## 0.1.16.beta
4
+ - Fix race condition in ``register()`` method.
5
+
6
+ ## 0.1.15.beta
7
+ - Only call ``send_status`` method at the end of ``multi_receive()`` if there is at least one
8
+ record in the batch when ``report_status_for_empty_batches`` config option is set to ``false``.
9
+ - Update ``register()`` method to use a separate short-lived client session for sending initial
10
+ client status.
11
+
12
+ ## 0.1.14.beta
13
+ - Add configurable max retries for requests when running into errors.
14
+ - Add ability to send messages to the dead letter queue if we exhaust all retries and if it is configured.
15
+ - Log truncated error body for all errors to help with debugging.
16
+
17
+ ## 0.1.13
18
+ - Fix synchronization of status message sending code to avoid duplicate logs.
19
+
20
+ ## 0.1.12
21
+ - Add logging of successful request retries after an error for additional clarity.
22
+ - Add debug level logging of request body on error.
23
+
3
24
  ## 0.1.11.beta
4
25
  - Fixes to retry mechanisms.
5
26
  - More thorough catching of events, preferring to retry requests rather than crashing the plugin.
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.11.beta.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
@@ -105,6 +109,11 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
105
109
  # minutes.
106
110
  config :status_report_interval, :validate => :number, :default => 300
107
111
 
112
+ # True to also call send_status when multi_receive() is called with no events.
113
+ # In some situations (e.g. when logstash is configured with multiple scalyr
114
+ # plugins conditionally where most are idle) you may want to set this to false
115
+ config :report_status_for_empty_batches, :validate => :boolean, :default => true
116
+
108
117
  # Set to true to also log status messages with various metrics to stdout in addition to sending
109
118
  # this data to Scalyr
110
119
  config :log_status_messages_to_stdout, :validate => :boolean, :default => false
@@ -247,7 +256,25 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
247
256
  @logger.info(sprintf("Started Scalyr output plugin (%s)." % [PLUGIN_VERSION]), :class => self.class.name)
248
257
 
249
258
  # Finally, send a status line to Scalyr
250
- send_status
259
+ # We use a special separate short lived client session for sending the initial client status.
260
+ # This is done to avoid the overhead in case single logstash instance has many scalyr output
261
+ # plugins configured with conditionals and majority of them are inactive (aka receive no data).
262
+ # This way we don't need to keep idle long running connection open.
263
+ initial_send_status_client_session = Scalyr::Common::Client::ClientSession.new(
264
+ @logger, @add_events_uri,
265
+ @compression_type, @compression_level, @ssl_verify_peer, @ssl_ca_bundle_path, @append_builtin_cert,
266
+ @record_stats_for_status, @flush_quantile_estimates_on_status_send,
267
+ @http_connect_timeout, @http_socket_timeout, @http_request_timeout, @http_pool_max, @http_pool_max_per_route
268
+ )
269
+ send_status(initial_send_status_client_session)
270
+ initial_send_status_client_session.close
271
+
272
+ # We also "prime" the main HTTP client here, one which is used for sending subsequent requests.
273
+ # Here priming just means setting up the client parameters without opening any connections.
274
+ # Since client writes certs to a temporary file there could be a race in case we don't do that
275
+ # here since multi_receive() is multi threaded. An alternative would be to put a look around
276
+ # client init method (aka client_config())
277
+ @client_session.client
251
278
 
252
279
  end # def register
253
280
 
@@ -292,6 +319,16 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
292
319
 
293
320
  while !multi_event_request_array.to_a.empty?
294
321
  multi_event_request = multi_event_request_array.pop
322
+ # Variables to hold information about exceptions we run into, and our handling of retries for this request. We
323
+ # track this to log it when the retries succeed so we can be sure logs are going through.
324
+ # General exception info we log in the error
325
+ exc_data = nil
326
+ # Whether the exception is commonly retried or not, for determining log level
327
+ exc_commonly_retried = false
328
+ # Count of retries attempted for this request
329
+ exc_retries = 0
330
+ # Total time spent sleeping while retrying this request due to backoff
331
+ exc_sleep = 0
295
332
  begin
296
333
  # For some reason a retry on the multi_receive may result in the request array containing `nil` elements, we
297
334
  # ignore these.
@@ -305,8 +342,11 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
305
342
 
306
343
  rescue Scalyr::Common::Client::ServerError, Scalyr::Common::Client::ClientError => e
307
344
  sleep_interval = sleep_for(sleep_interval)
345
+ exc_sleep += sleep_interval
346
+ exc_retries += 1
308
347
  message = "Error uploading to Scalyr (will backoff-retry)"
309
348
  exc_data = {
349
+ :error_class => e.e_class,
310
350
  :url => e.url.to_s,
311
351
  :message => e.message,
312
352
  :batch_num => batch_num,
@@ -316,17 +356,24 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
316
356
  :will_retry_in_seconds => sleep_interval,
317
357
  }
318
358
  exc_data[:code] = e.code if e.code
319
- exc_data[:body] = e.body if @logger.debug? and e.body
359
+ if @logger.debug? and e.body
360
+ exc_data[:body] = e.body
361
+ elsif e.body
362
+ exc_data[:body] = Scalyr::Common::Util.truncate(e.body, 512)
363
+ end
320
364
  exc_data[:payload] = "\tSample payload: #{request[:body][0,1024]}..." if @logger.debug?
321
365
  if e.is_commonly_retried?
322
366
  # well-known retriable errors should be debug
323
367
  @logger.debug(message, exc_data)
368
+ exc_commonly_retried = true
324
369
  else
325
370
  # all other failed uploads should be errors
326
371
  @logger.error(message, exc_data)
372
+ exc_commonly_retried = false
327
373
  end
328
- sleep_interval *= 2
329
- retry if @running
374
+ retry if @running and exc_retries < @max_retries
375
+ log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
376
+ next
330
377
 
331
378
  rescue => e
332
379
  # Any unexpected errors should be fully logged
@@ -338,8 +385,26 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
338
385
  )
339
386
  @logger.debug("Failed multi_event_request", :multi_event_request => multi_event_request)
340
387
  sleep_interval = sleep_for(sleep_interval)
341
- sleep_interval *= 2
342
- retry if @running
388
+ exc_data = {
389
+ :error_message => e.message,
390
+ :error_class => e.class.name,
391
+ :backtrace => e.backtrace,
392
+ :multi_event_request => multi_event_request
393
+ }
394
+ exc_sleep += sleep_interval
395
+ exc_retries += 1
396
+ retry if @running and exc_retries < @max_retries
397
+ log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
398
+ next
399
+ end
400
+
401
+ if !exc_data.nil?
402
+ message = "Retry successful after error."
403
+ if exc_commonly_retried
404
+ @logger.debug(message, :error_data => exc_data, :retries => exc_retries, :sleep_time => exc_sleep)
405
+ else
406
+ @logger.info(message, :error_data => exc_data, :retries => exc_retries, :sleep_time => exc_sleep)
407
+ end
343
408
  end
344
409
  end
345
410
 
@@ -352,7 +417,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
352
417
  end
353
418
  end
354
419
 
355
- send_status
420
+ if @report_status_for_empty_batches or records_count > 0
421
+ send_status
422
+ end
423
+
356
424
  return result
357
425
 
358
426
  rescue => e
@@ -367,6 +435,23 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
367
435
  end # def multi_receive
368
436
 
369
437
 
438
+ def log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
439
+ message = "Failed to send #{multi_event_request[:logstash_events].length} events after #{exc_retries} tries."
440
+ sample_events = Array.new
441
+ multi_event_request[:logstash_events][0,5].each {|l_event|
442
+ sample_events << Scalyr::Common::Util.truncate(l_event.to_hash.to_json, 256)
443
+ }
444
+ @logger.error(message, :error_data => exc_data, :sample_events => sample_events, :retries => exc_retries, :sleep_time => exc_sleep)
445
+ if @dlq_writer
446
+ multi_event_request[:logstash_events].each {|l_event|
447
+ @dlq_writer.write(l_event, "#{exc_data[:message]}")
448
+ }
449
+ else
450
+ @logger.warn("Deal letter queue not configured, dropping #{multi_event_request[:logstash_events].length} events after #{exc_retries} tries.", :sample_events => sample_events)
451
+ end
452
+ end
453
+
454
+
370
455
  # Builds an array of multi-event requests from LogStash events
371
456
  # Each array element is a request that groups multiple events (to be posted to Scalyr's addEvents endpoint)
372
457
  #
@@ -395,6 +480,8 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
395
480
  current_threads = Hash.new
396
481
  # Create a Scalyr event object for each record in the chunk
397
482
  scalyr_events = Array.new
483
+ # Track the logstash events in each chunk to send them to the dlq in case of an error
484
+ l_events = Array.new
398
485
 
399
486
  thread_ids = Hash.new
400
487
  next_id = 1 #incrementing thread id for the session
@@ -586,9 +673,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
586
673
  # make sure we always have at least one event
587
674
  if scalyr_events.size == 0
588
675
  scalyr_events << scalyr_event
676
+ l_events << l_event
589
677
  append_event = false
590
678
  end
591
- multi_event_request = self.create_multi_event_request(scalyr_events, current_threads, logs)
679
+ multi_event_request = self.create_multi_event_request(scalyr_events, l_events, current_threads, logs)
592
680
  multi_event_request_array << multi_event_request
593
681
 
594
682
  total_bytes = 0
@@ -596,19 +684,21 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
596
684
  logs = Hash.new
597
685
  logs_ids = Hash.new
598
686
  scalyr_events = Array.new
687
+ l_events = Array.new
599
688
  end
600
689
 
601
690
  # if we haven't consumed the current event already
602
691
  # add it to the end of our array and keep track of the json bytesize
603
692
  if append_event
604
693
  scalyr_events << scalyr_event
694
+ l_events << l_event
605
695
  total_bytes += add_bytes
606
696
  end
607
697
 
608
698
  }
609
699
 
610
700
  # create a final request with any left over events
611
- multi_event_request = self.create_multi_event_request(scalyr_events, current_threads, logs)
701
+ multi_event_request = self.create_multi_event_request(scalyr_events, l_events, current_threads, logs)
612
702
  multi_event_request_array << multi_event_request
613
703
  multi_event_request_array
614
704
  end
@@ -626,7 +716,7 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
626
716
  # A request comprises multiple Scalyr Events. This function creates a request hash for
627
717
  # final upload to Scalyr (from an array of events, and an optional hash of current threads)
628
718
  # Note: The request body field will be json-encoded.
629
- def create_multi_event_request(scalyr_events, current_threads, current_logs)
719
+ def create_multi_event_request(scalyr_events, logstash_events, current_threads, current_logs)
630
720
 
631
721
  body = {
632
722
  :session => @session_id + Thread.current.object_id.to_s,
@@ -662,7 +752,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
662
752
  serialized_body = body.to_json
663
753
  end_time = Time.now.to_f
664
754
  serialization_duration = end_time - start_time
665
- { :body => serialized_body, :record_count => scalyr_events.size, :serialization_duration => serialization_duration }
755
+ {
756
+ :body => serialized_body, :record_count => scalyr_events.size, :serialization_duration => serialization_duration,
757
+ :logstash_events => logstash_events
758
+ }
666
759
 
667
760
  end # def create_multi_event_request
668
761
 
@@ -711,7 +804,8 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
711
804
  # Finally, note that there could be multiple instances of this plugin (one per worker), in which case each worker
712
805
  # thread sends their own status updates. This is intentional so that we know how much data each worker thread is
713
806
  # uploading to Scalyr over time.
714
- def send_status
807
+ def send_status(client_session = nil)
808
+ client_session = @client_session if client_session.nil?
715
809
 
716
810
  status_event = {
717
811
  :ts => (Time.now.to_f * (10**9)).round,
@@ -730,7 +824,7 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
730
824
  # echee TODO: get instance stats from session and create a status log line
731
825
  msg = 'plugin_status: '
732
826
  cnt = 0
733
- @client_session.get_stats.each do |k, v|
827
+ client_session.get_stats.each do |k, v|
734
828
  val = v.instance_of?(Float) ? sprintf("%.4f", v) : v
735
829
  val = val.nil? ? 0 : val
736
830
  msg << ' ' if cnt > 0
@@ -748,19 +842,28 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
748
842
  status_event[:attrs]['serverHost'] = @node_hostname
749
843
  status_event[:attrs]['parser'] = @status_parser
750
844
  end
845
+ multi_event_request = create_multi_event_request([status_event], nil, nil, nil)
846
+ begin
847
+ client_session.post_add_events(multi_event_request[:body], true, 0)
848
+ rescue => e
849
+ if e.body
850
+ @logger.warn(
851
+ "Unexpected error occurred while uploading status to Scalyr",
852
+ :error_message => e.message,
853
+ :error_class => e.class.name,
854
+ :body => Scalyr::Common::Util.truncate(e.body, 512)
855
+ )
856
+ else
857
+ @logger.warn(
858
+ "Unexpected error occurred while uploading status to Scalyr",
859
+ :error_message => e.message,
860
+ :error_class => e.class.name
861
+ )
862
+ end
863
+ return
864
+ end
865
+ @last_status_transmit_time = Time.now()
751
866
  end
752
- multi_event_request = create_multi_event_request([status_event], nil, nil)
753
- begin
754
- @client_session.post_add_events(multi_event_request[:body], true, 0)
755
- rescue => e
756
- @logger.warn(
757
- "Unexpected error occurred while uploading status to Scalyr",
758
- :error_message => e.message,
759
- :error_class => e.class.name
760
- )
761
- return
762
- end
763
- @last_status_transmit_time = Time.now()
764
867
 
765
868
  if @log_status_messages_to_stdout
766
869
  @logger.info msg
@@ -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
@@ -270,6 +267,7 @@ class ClientSession
270
267
 
271
268
 
272
269
  def close
270
+ @client.close if @client
273
271
  end # def close
274
272
 
275
273
 
@@ -45,5 +45,12 @@ def self.flatten(obj, delimiter='_')
45
45
  return result
46
46
  end
47
47
 
48
+ def self.truncate(content, max)
49
+ if content.length > max
50
+ return "#{content[0...(max-3)]}..."
51
+ end
52
+ return content
53
+ end
54
+
48
55
  end; end; end;
49
56
 
@@ -1,2 +1,2 @@
1
1
  # encoding: utf-8
2
- PLUGIN_VERSION = "v0.1.11.beta"
2
+ PLUGIN_VERSION = "v0.1.16.beta"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-scalyr'
3
- s.version = '0.1.11.beta'
3
+ s.version = '0.1.16.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",
@@ -152,7 +173,8 @@ describe LogStash::Outputs::Scalyr do
152
173
  :record_count=>3,
153
174
  :total_batches=>1,
154
175
  :url=>"https://agent.scalyr.com/addEvents",
155
- :will_retry_in_seconds=>2
176
+ :will_retry_in_seconds=>2,
177
+ :body=>"stubbed response"
156
178
  }
157
179
  )
158
180
  end
@@ -171,6 +193,7 @@ describe LogStash::Outputs::Scalyr do
171
193
  plugin.multi_receive(sample_events)
172
194
  expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
173
195
  {
196
+ :error_class=>"Scalyr::Common::Client::ServerError",
174
197
  :batch_num=>1,
175
198
  :code=>500,
176
199
  :message=>"Invalid JSON response from server",
@@ -178,11 +201,56 @@ describe LogStash::Outputs::Scalyr do
178
201
  :record_count=>3,
179
202
  :total_batches=>1,
180
203
  :url=>"https://agent.scalyr.com/addEvents",
181
- :will_retry_in_seconds=>2
204
+ :will_retry_in_seconds=>2,
205
+ :body=>"stubbed response"
182
206
  }
183
207
  )
184
208
  end
185
209
  end
210
+
211
+ context "when receiving a long non-json response" do
212
+ it "don't throw an error but do log one to error" do
213
+ stub_request(:post, "https://agent.scalyr.com/addEvents").
214
+ to_return(status: 500, body: "0123456789" * 52, headers: {})
215
+
216
+ plugin = LogStash::Outputs::Scalyr.new({'api_write_token' => '1234', 'ssl_ca_bundle_path' => '/fakepath/nocerts', 'append_builtin_cert' => false})
217
+ plugin.register
218
+ plugin.instance_variable_set(:@running, false)
219
+
220
+ allow(plugin.instance_variable_get(:@logger)).to receive(:error)
221
+ plugin.multi_receive(sample_events)
222
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:error).with("Error uploading to Scalyr (will backoff-retry)",
223
+ {
224
+ :error_class=>"Scalyr::Common::Client::ServerError",
225
+ :batch_num=>1,
226
+ :code=>500,
227
+ :message=>"Invalid JSON response from server",
228
+ :payload_size=>781,
229
+ :record_count=>3,
230
+ :total_batches=>1,
231
+ :url=>"https://agent.scalyr.com/addEvents",
232
+ :will_retry_in_seconds=>2,
233
+ :body=>("0123456789" * 50) + "012345678..."
234
+ }
235
+ )
236
+ end
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
186
254
  end
187
255
 
188
256
  end
@@ -69,7 +69,7 @@ describe LogStash::Outputs::Scalyr do
69
69
  end
70
70
 
71
71
  it "it doesnt include flatten metrics if flattening is disabled" do
72
- plugin1 = LogStash::Outputs::Scalyr.new({
72
+ plugin1 = LogStash::Outputs::Scalyr.new({
73
73
  'api_write_token' => '1234',
74
74
  'serverhost_field' => 'source_host',
75
75
  'log_constants' => ['tags'],
@@ -122,7 +122,7 @@ describe LogStash::Outputs::Scalyr do
122
122
  expect(status_event[:attrs]["message"]).to eq("plugin_status: total_requests_sent=20 total_requests_failed=10 total_request_bytes_sent=100 total_compressed_request_bytes_sent=50 total_response_bytes_received=100 total_request_latency_secs=100 total_serialization_duration_secs=100.5000 total_compression_duration_secs=10.2000 compression_type=deflate compression_level=9 total_multi_receive_secs=0 multi_receive_duration_p50=10 multi_receive_duration_p90=18 multi_receive_duration_p99=19 multi_receive_event_count_p50=0 multi_receive_event_count_p90=0 multi_receive_event_count_p99=0 event_attributes_count_p50=0 event_attributes_count_p90=0 event_attributes_count_p99=0 batches_per_multi_receive_p50=0 batches_per_multi_receive_p90=0 batches_per_multi_receive_p99=0 flatten_values_duration_secs_p50=0 flatten_values_duration_secs_p90=0 flatten_values_duration_secs_p99=0")
123
123
  end
124
124
 
125
- it "send_stats is called when events list is empty, but otherwise noop" do
125
+ it "send_stats is called when events list is empty, but otherwise is noop" do
126
126
  quantile_estimator = Quantile::Estimator.new
127
127
  plugin.instance_variable_set(:@plugin_metrics, {
128
128
  :multi_receive_duration_secs => Quantile::Estimator.new,
@@ -137,6 +137,30 @@ describe LogStash::Outputs::Scalyr do
137
137
  plugin.multi_receive([])
138
138
  end
139
139
 
140
+ it "send_stats is not called when events list is empty and report_status_for_empty_batches is false" do
141
+ plugin2 = LogStash::Outputs::Scalyr.new({
142
+ 'api_write_token' => '1234',
143
+ 'serverhost_field' => 'source_host',
144
+ 'log_constants' => ['tags'],
145
+ 'flatten_nested_values' => false,
146
+ 'report_status_for_empty_batches' => false,
147
+ })
148
+
149
+ mock_client_session = MockClientSession.new
150
+ quantile_estimator = Quantile::Estimator.new
151
+ plugin2.instance_variable_set(:@plugin_metrics, {
152
+ :multi_receive_duration_secs => Quantile::Estimator.new,
153
+ :multi_receive_event_count => Quantile::Estimator.new,
154
+ :event_attributes_count => Quantile::Estimator.new,
155
+ :flatten_values_duration_secs => Quantile::Estimator.new
156
+ })
157
+ plugin2.instance_variable_set(:@client_session, mock_client_session)
158
+ expect(plugin2).not_to receive(:send_status)
159
+ expect(quantile_estimator).not_to receive(:observe)
160
+ expect(mock_client_session).not_to receive(:post_add_events)
161
+ plugin2.multi_receive([])
162
+ end
163
+
140
164
  # Kind of a weak test but I don't see a decent way to write a stronger one without a live client session
141
165
  it "send_status only sends posts with is_status = true" do
142
166
  # 1. Initial send
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  'exec' "jruby" '-x' "$0" "$@"
3
- #!/Users/yans/.rvm/rubies/jruby-9.2.9.0/bin/jruby
3
+ #!/Users/tomaz/.rvm/rubies/jruby-9.2.9.0/bin/jruby
4
4
  #
5
5
  # This file was generated by RubyGems.
6
6
  #
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  'exec' "jruby" '-x' "$0" "$@"
3
- #!/Users/yans/.rvm/rubies/jruby-9.2.9.0/bin/jruby
3
+ #!/Users/tomaz/.rvm/rubies/jruby-9.2.9.0/bin/jruby
4
4
  #
5
5
  # This file was generated by RubyGems.
6
6
  #
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.11.beta
4
+ version: 0.1.16.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-11 00:00:00.000000000 Z
11
+ date: 2021-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement