logstash-output-scalyr 0.1.18.beta → 0.1.22.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +2 -2
  4. data/README.md +7 -1
  5. data/lib/logstash/outputs/scalyr.rb +22 -3
  6. data/lib/scalyr/common/util.rb +73 -27
  7. data/lib/scalyr/constants.rb +1 -1
  8. data/logstash-output-scalyr.gemspec +1 -1
  9. data/spec/logstash/outputs/scalyr_integration_spec.rb +8 -2
  10. data/spec/logstash/outputs/scalyr_spec.rb +74 -2
  11. data/spec/scalyr/common/util_spec.rb +289 -0
  12. data/vendor/bundle/jruby/2.5.0/bin/htmldiff +1 -1
  13. data/vendor/bundle/jruby/2.5.0/bin/ldiff +1 -1
  14. data/vendor/bundle/jruby/2.5.0/cache/manticore-0.7.1-java.gem +0 -0
  15. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/APACHE-LICENSE-2.0.txt +0 -0
  16. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/CHANGELOG.md +12 -3
  17. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/Gemfile +2 -1
  18. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/LICENSE.txt +0 -0
  19. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/README.md +17 -4
  20. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/Rakefile +0 -0
  21. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/ext/manticore/org/manticore/HttpDeleteWithEntity.java +0 -0
  22. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/ext/manticore/org/manticore/HttpGetWithEntity.java +0 -0
  23. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/ext/manticore/org/manticore/Manticore.java +0 -0
  24. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/gem-public_cert.pem +0 -0
  25. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/commons-codec/commons-codec/1.10/commons-codec-1.10.jar +0 -0
  26. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
  27. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/faraday/adapter/manticore.rb +1 -6
  28. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/client/proxies.rb +0 -0
  29. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/client.rb +24 -16
  30. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/cookie.rb +0 -0
  31. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/facade.rb +0 -0
  32. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/java_extensions.rb +0 -0
  33. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/response.rb +12 -12
  34. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/stubbed_response.rb +0 -0
  35. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore/version.rb +1 -1
  36. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore.rb +26 -2
  37. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/manticore_jars.rb +0 -0
  38. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/org/apache/httpcomponents/httpclient/4.5.2/httpclient-4.5.2.jar +0 -0
  39. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/org/apache/httpcomponents/httpcore/4.4.4/httpcore-4.4.4.jar +0 -0
  40. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/org/apache/httpcomponents/httpmime/4.5.2/httpmime-4.5.2.jar +0 -0
  41. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/lib/org/manticore/manticore-ext.jar +0 -0
  42. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/manticore.gemspec +4 -2
  43. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/manticore/client_proxy_spec.rb +0 -0
  44. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/manticore/client_spec.rb +15 -3
  45. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/manticore/cookie_spec.rb +0 -0
  46. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/manticore/facade_spec.rb +0 -0
  47. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/manticore/response_spec.rb +1 -1
  48. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/manticore/stubbed_response_spec.rb +0 -0
  49. data/vendor/bundle/jruby/2.5.0/gems/{manticore-0.6.4-java → manticore-0.7.1-java}/spec/spec_helper.rb +0 -0
  50. data/vendor/bundle/jruby/2.5.0/specifications/{manticore-0.6.4-java.gemspec → manticore-0.7.1-java.gemspec} +10 -9
  51. metadata +39 -39
  52. data/vendor/bundle/jruby/2.5.0/cache/manticore-0.6.4-java.gem +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9099fcf20201bf8e3905273393a38bb1addefb219e1874a45a439bbfd2dcddbd
4
- data.tar.gz: a031faa8657951b66a09c172f6be69b774a2192e661ef99370813abe62250cbb
3
+ metadata.gz: 8ea703e55bc28d2bb00445c2a3e2d822bfe978bbe889292caafa6265dff1de2b
4
+ data.tar.gz: 3dc35cf36a356e5651d69e1a7d4623f296e5818a4b1c0fc8965b1a55b94a20ab
5
5
  SHA512:
6
- metadata.gz: c25cad81473d0224fb5c75558e302f6383d69361d3263aeaf0fe619b8c2a3a6866bf0eaf811588b9235edb4e15e11a2d0b95ecb99e2a3045ee6c6e66998e1627
7
- data.tar.gz: fed7e02e32c70ce199dbd7ec5841711ff77c65d3a56ebe53eeada8c165d5b8fa603fc15b3c00e0e397682a50899dd141bfdcacdcbb97244e2b26205c92802238
6
+ metadata.gz: 9306c619ce51c8a9cf83cb545229d4c73ac496a133e07e7e55b434301e1efc2f668187c3aa1584997ab1252ce3c7510ac9c4d719ba363933160bf2f874d0a137
7
+ data.tar.gz: 621b8f9b4095f16e51b88b8aec2844d795de75cba980ba27e0510a04f57ee9e4b34eb42513455d7509acaa8af8e085febb92cd248401e642129e09d70987a476
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Beta
2
2
 
3
+ ## 0.1.22.beta
4
+ - Add new plugin metric for tracking the duration of ``build_multi_event_request_array`` method.
5
+ - Update internal dependencies (``manticore``) to latest stable version.
6
+
7
+ ## 0.1.21.beta
8
+ - Fix issue with iterative flattening function when dealing with empty collections.
9
+
10
+ ## 0.1.20.beta
11
+ - Rewrite flattening function to no longer be recursive, to help avoid maxing out the stack.
12
+ - Added a configurable value `flattening_max_key_count` to create a limit on how large of a record we can flatten.
13
+ It limits the maximum amount of keys we can have in the final flattened record. Defaults to unlimited.
14
+
15
+ ## 0.1.19.beta
16
+ - Undo a change to nested value flattening functionality to keep existing formatting. This change can be re-enabled
17
+ by setting the `fix_deep_flattening_delimiters` configuration option to true.
18
+
3
19
  ## 0.1.18.beta
4
20
  - Add metrics for successfully sent and failed logstash events, and retries.
5
21
  - Make array flattening optional during nested value flattening with the `flatten_nested_arrays` configuration option.
data/Gemfile CHANGED
@@ -16,5 +16,5 @@ end
16
16
 
17
17
  gem 'pry'
18
18
  gem 'pry-nav'
19
- gem 'quantile'
20
- gem 'manticore', platform: :jruby
19
+ gem 'quantile', '~> 0.2.1'
20
+ gem 'manticore', '~> 0.7.1', platform: :jruby
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.18.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.22.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
 
@@ -321,6 +321,12 @@ If you want to run just the unit tests, you can run the command displayed below.
321
321
  bundle exec rspec spec/logstash/outputs/scalyr_spec.rb spec/scalyr/common/util_spec.rb
322
322
  ```
323
323
 
324
+ Or to run a single test function defined on line XXX
325
+
326
+ ```bash
327
+ bundle exec rspec spec/scalyr/common/util_spec.rb:XXX
328
+ ```
329
+
324
330
  ## Instrumentation and metrics
325
331
 
326
332
  By default, plugin logs a special line with metrics to Scalyr every 5 minutes. This line contains
@@ -70,6 +70,8 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
70
70
  config :flatten_nested_values, :validate => :boolean, :default => false
71
71
  config :flatten_nested_values_delimiter, :validate => :string, :default => "_"
72
72
  config :flatten_nested_arrays, :validate => :boolean, :default => true
73
+ config :fix_deep_flattening_delimiters, :validate => :boolean, :default => false
74
+ config :flattening_max_key_count, :validate => :number, :default => -1
73
75
 
74
76
  # If true, the 'tags' field will be flattened into key-values where each key is a tag and each value is set to
75
77
  # :flat_tag_value
@@ -287,6 +289,7 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
287
289
  # Convenience method to create a fresh quantile estimator
288
290
  def get_new_metrics
289
291
  return {
292
+ :build_multi_duration_secs => Quantile::Estimator.new,
290
293
  :multi_receive_duration_secs => Quantile::Estimator.new,
291
294
  :multi_receive_event_count => Quantile::Estimator.new,
292
295
  :event_attributes_count => Quantile::Estimator.new,
@@ -311,17 +314,25 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
311
314
  return events if @noop_mode
312
315
 
313
316
  begin
317
+ records_count = events.to_a.length
318
+
319
+ # We also time the duration of the build_multi_event_request_array method
314
320
  start_time = Time.now.to_f
315
321
 
316
322
  multi_event_request_array = build_multi_event_request_array(events)
317
- # Loop over all array of multi-event requests, sending each multi-event to Scalyr
318
323
 
324
+ if records_count > 0
325
+ @stats_lock.synchronize do
326
+ @plugin_metrics[:build_multi_duration_secs].observe(Time.now.to_f - start_time)
327
+ end
328
+ end
329
+
330
+ # Loop over all array of multi-event requests, sending each multi-event to Scalyr
319
331
  sleep_interval = @retry_initial_interval
320
332
  batch_num = 1
321
333
  total_batches = multi_event_request_array.length unless multi_event_request_array.nil?
322
334
 
323
335
  result = []
324
- records_count = events.to_a.length
325
336
 
326
337
  while !multi_event_request_array.to_a.empty?
327
338
  multi_event_request = multi_event_request_array.pop
@@ -632,7 +643,11 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
632
643
  # flatten record
633
644
  if @flatten_nested_values
634
645
  start_time = Time.now.to_f
635
- record = Scalyr::Common::Util.flatten(record, delimiter=@flatten_nested_values_delimiter, flatten_arrays=@flatten_nested_arrays)
646
+ begin
647
+ record = Scalyr::Common::Util.flatten(record, delimiter=@flatten_nested_values_delimiter, flatten_arrays=@flatten_nested_arrays, fix_deep_flattening_delimiters=@fix_deep_flattening_delimiters, max_key_count=@flattening_max_key_count)
648
+ rescue Scalyr::Common::Util::MaxKeyCountError => e
649
+ @logger.warn("Error while flattening record", :error_message => e.message, :sample_keys => e.sample_keys)
650
+ end
636
651
  end_time = Time.now.to_f
637
652
  flatten_nested_values_duration = end_time - start_time
638
653
  end
@@ -810,6 +825,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
810
825
  @stats_lock.synchronize do
811
826
  current_stats = @multi_receive_statistics.clone
812
827
 
828
+ current_stats[:build_multi_duration_secs_p50] = @plugin_metrics[:build_multi_duration_secs].query(0.5)
829
+ current_stats[:build_multi_duration_secs_p90] = @plugin_metrics[:build_multi_duration_secs].query(0.9)
830
+ current_stats[:build_multi_duration_secs_p99] = @plugin_metrics[:build_multi_duration_secs].query(0.99)
831
+
813
832
  current_stats[:multi_receive_duration_p50] = @plugin_metrics[:multi_receive_duration_secs].query(0.5)
814
833
  current_stats[:multi_receive_duration_p90] = @plugin_metrics[:multi_receive_duration_secs].query(0.9)
815
834
  current_stats[:multi_receive_duration_p99] = @plugin_metrics[:multi_receive_duration_secs].query(0.99)
@@ -1,50 +1,96 @@
1
1
  module Scalyr; module Common; module Util;
2
2
 
3
+ class MaxKeyCountError < StandardError
4
+ attr_reader :message, :sample_keys
5
+
6
+ def initialize(message, sample_keys)
7
+ @message = message
8
+ @sample_keys = sample_keys
9
+ end
10
+ end
3
11
 
4
12
  # Flattens a hash or array, returning a hash where keys are a delimiter-separated string concatenation of all
5
13
  # nested keys. Returned keys are always strings. If a non-hash or array is provided, raises TypeError.
6
14
  # Please see rspec util_spec.rb for expected behavior.
7
- def self.flatten(obj, delimiter='_', flatten_arrays=true)
15
+ # Includes a known bug where defined delimiter will not be used for nesting levels past the first, this is kept
16
+ # because some queries and dashboards already rely on the broken functionality.
17
+ def self.flatten(hash_obj, delimiter='_', flatten_arrays=true, fix_deep_flattening_delimiters=false, max_key_count=-1)
8
18
 
9
19
  # base case is input object is not enumerable, in which case simply return it
10
- if !obj.respond_to?(:each)
20
+ if !hash_obj.respond_to?(:each)
11
21
  raise TypeError.new('Input must be a hash or array')
12
22
  end
23
+ # case where we pass in a valid array, but don't want to flatten arrays
24
+ if !hash_obj.respond_to?(:has_key?) and !flatten_arrays
25
+ return hash_obj
26
+ end
13
27
 
28
+ stack = []
29
+ stack << hash_obj
30
+ key_stack = []
31
+ key_stack << ""
32
+ key_list = []
33
+ key_list_width = []
14
34
  result = Hash.new
15
- # require 'pry'
16
- # binding.pry
17
-
18
- if obj.respond_to?(:has_key?)
19
-
20
- # input object is a hash
21
- obj.each do |key, value|
22
- if (flatten_arrays and value.respond_to?(:each)) or value.respond_to?(:has_key?)
23
- flatten(value, delimiter, flatten_arrays).each do |subkey, subvalue|
24
- result["#{key}#{delimiter}#{subkey}"] = subvalue
25
- end
26
- else
27
- result["#{key}"] = value
35
+ test_key = 0
36
+ #Debugging
37
+ #require 'pry'
38
+ #binding.pry
39
+
40
+ until stack.empty?
41
+ obj = stack.pop
42
+ key_list << key_stack.pop
43
+
44
+ # Case when object is a hash
45
+ if obj.respond_to?(:has_key?) and obj.keys.count > 0
46
+ key_list_width << obj.keys.count
47
+ obj.each do |key, value|
48
+ key_stack << key
49
+ stack << value
28
50
  end
29
- end
30
51
 
31
- elsif flatten_arrays
52
+ # Case when object is an array we intend to flatten
53
+ elsif flatten_arrays and obj.respond_to?(:each) and obj.count > 0
54
+ key_list_width << obj.count
55
+ obj.each_with_index do |value, index|
56
+ key_stack << index
57
+ stack << value
58
+ end
32
59
 
33
- # input object is an array or set
34
- obj.each_with_index do |value, index|
35
- if value.respond_to?(:each)
36
- flatten(value, delimiter, flatten_arrays).each do |subkey, subvalue|
37
- result["#{index}#{delimiter}#{subkey}"] = subvalue
60
+ else
61
+ result_key = ""
62
+ delim = delimiter
63
+ key_list.each_with_index do |key, index|
64
+ # We have a blank key at the start of the key list to avoid issues with calling pop, so we ignore delimiter
65
+ # for the first two keys
66
+ if index > 1
67
+ result_key += "#{delim}#{key}"
68
+ if not fix_deep_flattening_delimiters
69
+ delim = "_"
70
+ end
71
+ else
72
+ result_key += "#{key}"
38
73
  end
39
- else
40
- result["#{index}"] = value
41
74
  end
42
- end
75
+ result[result_key] = obj
43
76
 
44
- else
77
+ if max_key_count > -1 and result.keys.count > max_key_count
78
+ raise MaxKeyCountError.new(
79
+ "Resulting flattened object will contain more keys than the configured flattening_max_key_count of #{max_key_count}",
80
+ result.keys[0..6]
81
+ )
82
+ end
45
83
 
46
- result = obj
84
+ throw_away = key_list.pop
85
+ until key_list_width.empty? or key_list_width[-1] > 1
86
+ throw_away = key_list_width.pop
87
+ throw_away = key_list.pop
88
+ end
89
+ if not key_list_width.empty?
90
+ key_list_width[-1] -= 1
91
+ end
47
92
 
93
+ end
48
94
  end
49
95
 
50
96
  return result
@@ -1,2 +1,2 @@
1
1
  # encoding: utf-8
2
- PLUGIN_VERSION = "v0.1.18.beta"
2
+ PLUGIN_VERSION = "v0.1.22.beta"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-scalyr'
3
- s.version = '0.1.18.beta'
3
+ s.version = '0.1.22.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)"
@@ -7,6 +7,12 @@ require "json"
7
7
  require 'webmock/rspec'
8
8
  WebMock.allow_net_connect!
9
9
 
10
+ RSpec.configure do |rspec|
11
+ rspec.expect_with :rspec do |c|
12
+ c.max_formatted_output_length = nil
13
+ end
14
+ end
15
+
10
16
  describe LogStash::Outputs::Scalyr do
11
17
  let(:sample_events) {
12
18
  events = []
@@ -58,7 +64,7 @@ describe LogStash::Outputs::Scalyr do
58
64
  {
59
65
  :error_class=>"Manticore::UnknownException",
60
66
  :batch_num=>1,
61
- :message=>"Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
67
+ :message=>"java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
62
68
  :payload_size=>781,
63
69
  :record_count=>3,
64
70
  :total_batches=>1,
@@ -84,7 +90,7 @@ describe LogStash::Outputs::Scalyr do
84
90
  {
85
91
  :error_class=>"Manticore::UnknownException",
86
92
  :batch_num=>1,
87
- :message=>"Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
93
+ :message=>"java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty",
88
94
  :payload_size=>781,
89
95
  :record_count=>3,
90
96
  :total_batches=>1,
@@ -80,6 +80,7 @@ describe LogStash::Outputs::Scalyr do
80
80
  plugin1.instance_variable_set(:@client_session, mock_client_session)
81
81
  plugin1.instance_variable_set(:@session_id, "some_session_id")
82
82
  plugin1.instance_variable_set(:@plugin_metrics, {
83
+ :build_multi_duration_secs => Quantile::Estimator.new,
83
84
  :multi_receive_duration_secs => Quantile::Estimator.new,
84
85
  :multi_receive_event_count => Quantile::Estimator.new,
85
86
  :event_attributes_count => Quantile::Estimator.new,
@@ -87,10 +88,11 @@ describe LogStash::Outputs::Scalyr do
87
88
  :batches_per_multi_receive => Quantile::Estimator.new
88
89
  })
89
90
  plugin1.instance_variable_get(:@plugin_metrics)[:multi_receive_duration_secs].observe(1)
91
+ plugin1.instance_variable_get(:@plugin_metrics)[:build_multi_duration_secs].observe(1)
90
92
  plugin1.instance_variable_set(:@multi_receive_statistics, {:total_multi_receive_secs => 0})
91
93
 
92
94
  status_event = plugin1.send_status
93
- 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=1 multi_receive_duration_p90=1 multi_receive_duration_p99=1 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")
95
+ 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 build_multi_duration_secs_p50=1 build_multi_duration_secs_p90=1 build_multi_duration_secs_p99=1 multi_receive_duration_p50=1 multi_receive_duration_p90=1 multi_receive_duration_p99=1 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")
94
96
  end
95
97
 
96
98
  it "returns and sends correct status event on send_stats on initial and subsequent send" do
@@ -106,6 +108,7 @@ describe LogStash::Outputs::Scalyr do
106
108
  plugin.instance_variable_set(:@client_session, mock_client_session)
107
109
  # Setup one quantile calculation to make sure at least one of them calculates as expected
108
110
  plugin.instance_variable_set(:@plugin_metrics, {
111
+ :build_multi_duration_secs => Quantile::Estimator.new,
109
112
  :multi_receive_duration_secs => Quantile::Estimator.new,
110
113
  :multi_receive_event_count => Quantile::Estimator.new,
111
114
  :event_attributes_count => Quantile::Estimator.new,
@@ -119,12 +122,13 @@ describe LogStash::Outputs::Scalyr do
119
122
 
120
123
  plugin.instance_variable_set(:@multi_receive_statistics, {:total_multi_receive_secs => 0})
121
124
  status_event = plugin.send_status
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")
125
+ 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 build_multi_duration_secs_p50=0 build_multi_duration_secs_p90=0 build_multi_duration_secs_p99=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
126
  end
124
127
 
125
128
  it "send_stats is called when events list is empty, but otherwise is noop" do
126
129
  quantile_estimator = Quantile::Estimator.new
127
130
  plugin.instance_variable_set(:@plugin_metrics, {
131
+ :build_multi_duration_secs => Quantile::Estimator.new,
128
132
  :multi_receive_duration_secs => Quantile::Estimator.new,
129
133
  :multi_receive_event_count => Quantile::Estimator.new,
130
134
  :event_attributes_count => Quantile::Estimator.new,
@@ -149,6 +153,7 @@ describe LogStash::Outputs::Scalyr do
149
153
  mock_client_session = MockClientSession.new
150
154
  quantile_estimator = Quantile::Estimator.new
151
155
  plugin2.instance_variable_set(:@plugin_metrics, {
156
+ :build_multi_duration_secs => Quantile::Estimator.new,
152
157
  :multi_receive_duration_secs => Quantile::Estimator.new,
153
158
  :multi_receive_event_count => Quantile::Estimator.new,
154
159
  :event_attributes_count => Quantile::Estimator.new,
@@ -174,6 +179,7 @@ describe LogStash::Outputs::Scalyr do
174
179
  plugin.instance_variable_set(:@last_status_transmit_time, 100)
175
180
  plugin.instance_variable_set(:@client_session, mock_client_session)
176
181
  plugin.instance_variable_set(:@plugin_metrics, {
182
+ :build_multi_duration_secs => Quantile::Estimator.new,
177
183
  :multi_receive_duration_secs => Quantile::Estimator.new,
178
184
  :multi_receive_event_count => Quantile::Estimator.new,
179
185
  :event_attributes_count => Quantile::Estimator.new,
@@ -265,6 +271,40 @@ describe LogStash::Outputs::Scalyr do
265
271
  'flatten_nested_values_delimiter' => ".",
266
272
  }
267
273
  plugin = LogStash::Outputs::Scalyr.new(config)
274
+ it "flattens nested values with a period" do
275
+ allow(plugin).to receive(:send_status).and_return(nil)
276
+ plugin.register
277
+ result = plugin.build_multi_event_request_array(sample_events)
278
+ body = JSON.parse(result[0][:body])
279
+ expect(body['events'].size).to eq(3)
280
+ expect(body['events'][2]['attrs']).to eq({
281
+ "nested.a" => 1,
282
+ "nested.b_0" => 3,
283
+ "nested.b_1" => 4,
284
+ "nested.b_2" => 5,
285
+ 'seq' => 3,
286
+ 'source_file' => 'my file 3',
287
+ 'source_host' => 'my host 3',
288
+ 'serverHost' => 'Logstash',
289
+ "tag_prefix_t1" => "true",
290
+ "tag_prefix_t2" => "true",
291
+ "tag_prefix_t3" => "true",
292
+ "parser" => "logstashParser",
293
+ })
294
+ end
295
+ end
296
+
297
+ context "when configured to flatten values with custom delimiter and deep delimiter fix" do
298
+ config = {
299
+ 'api_write_token' => '1234',
300
+ 'flatten_tags' => true,
301
+ 'flat_tag_value' => 'true',
302
+ 'flat_tag_prefix' => 'tag_prefix_',
303
+ 'flatten_nested_values' => true, # this converts into string 'true'
304
+ 'flatten_nested_values_delimiter' => ".",
305
+ 'fix_deep_flattening_delimiters' => true,
306
+ }
307
+ plugin = LogStash::Outputs::Scalyr.new(config)
268
308
  it "flattens nested values with a period" do
269
309
  allow(plugin).to receive(:send_status).and_return(nil)
270
310
  plugin.register
@@ -375,6 +415,38 @@ describe LogStash::Outputs::Scalyr do
375
415
  end
376
416
  end
377
417
 
418
+ context "when configured to flatten with max keys configured to 3" do
419
+ config = {
420
+ 'api_write_token' => '1234',
421
+ 'flatten_nested_values' => true, # this converts into string 'true'
422
+ 'flattening_max_key_count' => 3,
423
+ }
424
+ plugin = LogStash::Outputs::Scalyr.new(config)
425
+ it "does not flatten" do
426
+ allow(plugin).to receive(:send_status).and_return(nil)
427
+ plugin.register
428
+ allow(plugin.instance_variable_get(:@logger)).to receive(:warn)
429
+ result = plugin.build_multi_event_request_array(sample_events)
430
+ body = JSON.parse(result[0][:body])
431
+ expect(body['events'].size).to eq(3)
432
+ expect(body['events'][2]['attrs']).to eq({
433
+ "nested" => {'a'=>1, 'b'=>[3,4,5]},
434
+ 'seq' => 3,
435
+ 'source_file' => 'my file 3',
436
+ 'source_host' => 'my host 3',
437
+ 'serverHost' => 'Logstash',
438
+ "tags" => ["t1", "t2", "t3"],
439
+ "parser" => "logstashParser",
440
+ })
441
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:warn).with("Error while flattening record",
442
+ {
443
+ :error_message=>"Resulting flattened object will contain more keys than the configured flattening_max_key_count of 3",
444
+ :sample_keys=>["serverHost", "parser", "tags_2", "tags_1"]
445
+ }
446
+ ).exactly(3).times
447
+ end
448
+ end
449
+
378
450
  context "when receiving an event with Bignums" do
379
451
  config = {
380
452
  'api_write_token' => '1234',
@@ -1,7 +1,113 @@
1
1
  # encoding: utf-8
2
2
  require "scalyr/common/util"
3
3
 
4
+ LARGE_OBJECT_IN = {
5
+ "level": "info",
6
+ "ts": "2020-08-11T02:26:17.078Z",
7
+ "caller": "api/foo:480",
8
+ "msg": "assign active task foobar",
9
+ "accountId": 12345,
10
+ "cycleId": 6789,
11
+ "uuid": "a405a4b58810e3aaa078f751bd32baa8b60aaad1",
12
+ "task": {
13
+ "Id": 1211111181111111400,
14
+ "TaskTypes": [
15
+ 4,
16
+ 11,
17
+ 10,
18
+ 12,
19
+ 17,
20
+ 14
21
+ ],
22
+ "Ips": [
23
+ "127.0.0.1",
24
+ "127.0.0.2",
25
+ "127.0.0.3",
26
+ "127.0.0.4",
27
+ "127.0.0.5",
28
+ ],
29
+ "FooProps": {
30
+ "10": {
31
+ "TcpPorts": [
32
+ 22,
33
+ 23,
34
+ 25,
35
+ 80,
36
+ 55,
37
+ 8000,
38
+ 8080,
39
+ ],
40
+ "UdpPorts": []
41
+ }
42
+ },
43
+ "Subnet": "127.0.0.0/24"
44
+ },
45
+ "relevance": 0,
46
+ "scannerIp": "10.0.0.2",
47
+ "gatewayIp": "10.0.0.1",
48
+ "gatewayMac": "fa:fa:fa:fa",
49
+ "wired": true,
50
+ "elapsed": 74.86664
51
+ }
4
52
 
53
+ LARGE_OBJECT_OUT = {
54
+ "accountId" => 12345,
55
+ "caller" => "api/foo:480",
56
+ "cycleId" => 6789,
57
+ "elapsed" => 74.86664,
58
+ "gatewayIp" => "10.0.0.1",
59
+ "gatewayMac" => "fa:fa:fa:fa",
60
+ "level" => "info",
61
+ "msg" => "assign active task foobar",
62
+ "relevance" => 0,
63
+ "scannerIp" => "10.0.0.2",
64
+ "task_FooProps_10_TcpPorts_0" => 22,
65
+ "task_FooProps_10_TcpPorts_1" => 23,
66
+ "task_FooProps_10_TcpPorts_2" => 25,
67
+ "task_FooProps_10_TcpPorts_3" => 80,
68
+ "task_FooProps_10_TcpPorts_4" => 55,
69
+ "task_FooProps_10_TcpPorts_5" => 8000,
70
+ "task_FooProps_10_TcpPorts_6" => 8080,
71
+ "task_FooProps_10_UdpPorts" => [],
72
+ "task_Id" => 1211111181111111400,
73
+ "task_Ips_0" => "127.0.0.1",
74
+ "task_Ips_1" => "127.0.0.2",
75
+ "task_Ips_2" => "127.0.0.3",
76
+ "task_Ips_3" => "127.0.0.4",
77
+ "task_Ips_4" => "127.0.0.5",
78
+ "task_Subnet" => "127.0.0.0/24",
79
+ "task_TaskTypes_0" => 4,
80
+ "task_TaskTypes_1" => 11,
81
+ "task_TaskTypes_2" => 10,
82
+ "task_TaskTypes_3" => 12,
83
+ "task_TaskTypes_4" => 17,
84
+ "task_TaskTypes_5" => 14,
85
+ "ts" => "2020-08-11T02:26:17.078Z",
86
+ "uuid" => "a405a4b58810e3aaa078f751bd32baa8b60aaad1",
87
+ "wired" => true,
88
+ }
89
+
90
+ LARGE_OBJECT_OUT_NO_FLATTEN_ARRAYS = {
91
+ "accountId" => 12345,
92
+ "caller" => "api/foo:480",
93
+ "cycleId" => 6789,
94
+ "elapsed" => 74.86664,
95
+ "gatewayIp" => "10.0.0.1",
96
+ "gatewayMac" => "fa:fa:fa:fa",
97
+ "level" => "info",
98
+ "msg" => "assign active task foobar",
99
+ "relevance" => 0,
100
+ "scannerIp" => "10.0.0.2",
101
+ "task_FooProps_10_TcpPorts" => [22, 23, 25, 80, 55, 8000, 8080],
102
+ "task_FooProps_10_UdpPorts" => [],
103
+ "task_Id" => 1211111181111111400,
104
+ "task_Ips" => ["127.0.0.1", "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5"],
105
+ "task_Subnet" => "127.0.0.0/24",
106
+ "task_TaskTypes" => [4, 11, 10, 12, 17, 14],
107
+ "ts" => "2020-08-11T02:26:17.078Z",
108
+ "uuid" => "a405a4b58810e3aaa078f751bd32baa8b60aaad1",
109
+ "wired" => true,
110
+ }
5
111
  describe Scalyr::Common::Util do
6
112
  it "does not flatten an already-flat dict" do
7
113
  din = {
@@ -144,6 +250,20 @@ describe Scalyr::Common::Util do
144
250
  expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
145
251
  end
146
252
 
253
+ it "flattens a hash that contains an array with hashes, no array flattening" do
254
+ din = {
255
+ 'a' => 1,
256
+ "b" => {"a": "a"},
257
+ 'c' => { "f" => [100, 200, {"g" => 1}] }
258
+ }
259
+ dout = {
260
+ 'a' => 1,
261
+ "b_a" => "a",
262
+ 'c_f' => [100, 200, {"g" => 1}]
263
+ }
264
+ expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
265
+ end
266
+
147
267
  it "flattens a hash that contains an array, no array flattening" do
148
268
  din = {
149
269
  'a' => 1,
@@ -196,6 +316,120 @@ describe Scalyr::Common::Util do
196
316
  expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
197
317
  end
198
318
 
319
+ it "flattens large hash correctly" do
320
+ expect(Scalyr::Common::Util.flatten(LARGE_OBJECT_IN, "_", flatten_arrays=true)).to eq(LARGE_OBJECT_OUT)
321
+ end
322
+
323
+ it "flattens large hash correctly not flatten arrays" do
324
+ expect(Scalyr::Common::Util.flatten(LARGE_OBJECT_IN, "_", flatten_arrays=false)).to eq(LARGE_OBJECT_OUT_NO_FLATTEN_ARRAYS)
325
+ end
326
+
327
+ it "flattens hash containing empty list correctly" do
328
+ obj_in = {
329
+ "abc" => 123,
330
+ "array" => [],
331
+ "hash" => {
332
+ "value" => "abc123",
333
+ "another_array" => []
334
+ }
335
+ }
336
+
337
+ obj_out = {
338
+ "abc" => 123,
339
+ "array" => [],
340
+ "hash_value" => "abc123",
341
+ "hash_another_array" => []
342
+ }
343
+ expect(Scalyr::Common::Util.flatten(obj_in, "_", flatten_arrays=true)).to eq(obj_out)
344
+ end
345
+
346
+ it "flattens hash containing empty list correctly not flatten arrays" do
347
+ obj_in = {
348
+ "abc" => 123,
349
+ "array" => [],
350
+ "hash" => {
351
+ "value" => "abc123",
352
+ "another_array" => []
353
+ }
354
+ }
355
+
356
+ obj_out = {
357
+ "abc" => 123,
358
+ "array" => [],
359
+ "hash_value" => "abc123",
360
+ "hash_another_array" => []
361
+ }
362
+ expect(Scalyr::Common::Util.flatten(obj_in, "_", flatten_arrays=false)).to eq(obj_out)
363
+ end
364
+
365
+ it "flattens hash containing empty hash correctly" do
366
+ obj_in = {
367
+ "abc" => 123,
368
+ "empty_hash" => {},
369
+ "hash" => {
370
+ "value" => "abc123",
371
+ "another_hash" => {}
372
+ }
373
+ }
374
+
375
+ obj_out = {
376
+ "abc" => 123,
377
+ "empty_hash" => {},
378
+ "hash_value" => "abc123",
379
+ "hash_another_hash" => {}
380
+ }
381
+ expect(Scalyr::Common::Util.flatten(obj_in, "_", flatten_arrays=true)).to eq(obj_out)
382
+ end
383
+
384
+ it "flattens hash containing nested bignum correctly" do
385
+ obj_in = {
386
+ "abc" => 123,
387
+ "hash" => {
388
+ "value" => 2000023030042002050202030320240
389
+ }
390
+ }
391
+
392
+ obj_out = {
393
+ "abc" => 123,
394
+ "hash_value" => 2000023030042002050202030320240
395
+ }
396
+ expect(Scalyr::Common::Util.flatten(obj_in, "_", flatten_arrays=true)).to eq(obj_out)
397
+ end
398
+
399
+ it "flattens hash containing nested boolean correctly" do
400
+ obj_in = {
401
+ "abc" => 123,
402
+ "hash" => {
403
+ "value" => true,
404
+ "otherValue" => false
405
+ }
406
+ }
407
+
408
+ obj_out = {
409
+ "abc" => 123,
410
+ "hash_value" => true,
411
+ "hash_otherValue" => false
412
+ }
413
+ expect(Scalyr::Common::Util.flatten(obj_in, "_", flatten_arrays=true)).to eq(obj_out)
414
+ end
415
+
416
+ it "flattens hash containing nested float correctly" do
417
+ obj_in = {
418
+ "abc" => 123,
419
+ "hash" => {
420
+ "value" => 321.12345,
421
+ "otherValue" => 0.0000003
422
+ }
423
+ }
424
+
425
+ obj_out = {
426
+ "abc" => 123,
427
+ "hash_value" => 321.12345,
428
+ "hash_otherValue" => 0.0000003
429
+ }
430
+ expect(Scalyr::Common::Util.flatten(obj_in, "_", flatten_arrays=true)).to eq(obj_out)
431
+ end
432
+
199
433
  it "accepts custom delimiters" do
200
434
  din = {
201
435
  'a' => 1,
@@ -212,6 +446,42 @@ describe Scalyr::Common::Util do
212
446
  expect(Scalyr::Common::Util.flatten(din, ':')).to eq(dout)
213
447
  end
214
448
 
449
+ it "accepts custom delimiters with greater depth" do
450
+ din = {
451
+ 'a' => 1,
452
+ 'b' => {
453
+ 'c' => {
454
+ 'e' => 100
455
+ },
456
+ 'd' => 200,
457
+ }
458
+ }
459
+ dout = {
460
+ 'a' => 1,
461
+ 'b:c_e' => 100,
462
+ 'b:d' => 200,
463
+ }
464
+ expect(Scalyr::Common::Util.flatten(din, ':')).to eq(dout)
465
+ end
466
+
467
+ it "accepts custom delimiters with greater depth and deep delimiters fix" do
468
+ din = {
469
+ 'a' => 1,
470
+ 'b' => {
471
+ 'c' => {
472
+ 'e' => 100
473
+ },
474
+ 'd' => 200,
475
+ }
476
+ }
477
+ dout = {
478
+ 'a' => 1,
479
+ 'b:c:e' => 100,
480
+ 'b:d' => 200,
481
+ }
482
+ expect(Scalyr::Common::Util.flatten(din, ':', true, true)).to eq(dout)
483
+ end
484
+
215
485
  it "stringifies non-string keys" do
216
486
  din = {
217
487
  'a' => 1,
@@ -247,4 +517,23 @@ describe Scalyr::Common::Util do
247
517
  it "raises exception if a non-dict is provided" do
248
518
  expect {Scalyr::Common::Util.flatten(1)}.to raise_error(TypeError)
249
519
  end
520
+
521
+ it "flattens a hash 5000 layers deep" do
522
+ din = {
523
+ 'a' => {},
524
+ }
525
+ hash = din
526
+ for i in 0...4999
527
+ hash = hash["a"]
528
+ hash["a"] = {}
529
+ if i == 4998
530
+ hash["a"] = "b"
531
+ end
532
+ end
533
+
534
+ dout = {
535
+ 'a' + "_a" * 4999 => "b",
536
+ }
537
+ expect(Scalyr::Common::Util.flatten(din, '_')).to eq(dout)
538
+ end
250
539
  end