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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +1 -1
- data/lib/logstash/outputs/scalyr.rb +128 -25
- data/lib/scalyr/common/client.rb +8 -10
- data/lib/scalyr/common/util.rb +7 -0
- data/lib/scalyr/constants.rb +1 -1
- data/logstash-output-scalyr.gemspec +1 -1
- data/spec/logstash/outputs/scalyr_integration_spec.rb +79 -11
- data/spec/logstash/outputs/scalyr_spec.rb +26 -2
- data/vendor/bundle/jruby/2.5.0/bin/htmldiff +1 -1
- data/vendor/bundle/jruby/2.5.0/bin/ldiff +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16e88c3869fdb18e4132726af302e37c8bac2c10038ca3a078f85cebe67c4940
|
4
|
+
data.tar.gz: a42194413ec2969e31bbe44e72df7cf71d09911b61adae9db16b6041a97bc1b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
329
|
-
|
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
|
-
|
342
|
-
|
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
|
-
|
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
|
-
{
|
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
|
-
|
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
|
data/lib/scalyr/common/client.rb
CHANGED
@@ -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
|
|
data/lib/scalyr/common/util.rb
CHANGED
data/lib/scalyr/constants.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
PLUGIN_VERSION = "v0.1.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
+
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
|