logstash-output-scalyr 0.1.17.beta → 0.1.21.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: 96fd1b3adba21150a05f43d1e8d6510711197da866d3cd6ec8b57bae14e53391
4
- data.tar.gz: 221aac42711955c49c878c324e2570ebb55050cad13f2d4911b99c4b3f1eb5a1
3
+ metadata.gz: 7dd0297a3ddf2f2acfdad08c410822b8a4ee808800e3cfeffc5df4ec8a991348
4
+ data.tar.gz: 855b7e3a655656442cf3de76e15ad3d0f90ca460421d4a4f86df46056a4b05a9
5
5
  SHA512:
6
- metadata.gz: c8a678e7caf8778dbe9b44e8d8e48c809b016ac6b89424c4c29c29b63578fea61f236ae80a528dab7707c8ee534cfab0f20a252d688f44df53faccdd1ce907b0
7
- data.tar.gz: c263ae0ecffacb454f98f112d384c544cfd68fb0e0e6a0d7339a6b84add2c94b4bb99afd20a815b587f5722e0125d80fc25f9285574187c9f2a0ff2af7f219ec
6
+ metadata.gz: 8c3024576c03a088008c5b1acc54d02be67be701921392d91f5991ac4482f85f62ea3b07255e38a972735182feccb1520d558f5cb065538dabe35cc86e1ad719
7
+ data.tar.gz: c807baca5797bbf47cf69c8816a40300a515162617d4a714e719673572f1fe29fe3fa9ab73fd51a711af3953e212bd3dfe998e73c2c6de574a64ba6121446989
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Beta
2
2
 
3
+ ## 0.1.21.beta
4
+ - Fix issue with iterative flattening function when dealing with empty collections.
5
+
6
+ ## 0.1.20.beta
7
+ - Rewrite flattening function to no longer be recursive, to help avoid maxing out the stack.
8
+ - Added a configurable value `flattening_max_key_count` to create a limit on how large of a record we can flatten.
9
+ It limits the maximum amount of keys we can have in the final flattened record. Defaults to unlimited.
10
+
11
+ ## 0.1.19.beta
12
+ - Undo a change to nested value flattening functionality to keep existing formatting. This change can be re-enabled
13
+ by setting the `fix_deep_flattening_delimiters` configuration option to true.
14
+
15
+ ## 0.1.18.beta
16
+ - Add metrics for successfully sent and failed logstash events, and retries.
17
+ - Make array flattening optional during nested value flattening with the `flatten_nested_arrays` configuration option.
18
+
3
19
  ## 0.1.17.beta
4
20
  - Catch errors relating to Bignum conversions present in the ``json`` library and manually convert to string as
5
21
  a workaround.
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.17.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.21.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
@@ -68,7 +68,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
68
68
  # If true, nested values will be flattened (which changes keys to delimiter-separated concatenation of all
69
69
  # nested keys).
70
70
  config :flatten_nested_values, :validate => :boolean, :default => false
71
- config :flatten_nested_values_delimiter, :validate => :string, :default => "_"
71
+ config :flatten_nested_values_delimiter, :validate => :string, :default => "_"
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
72
75
 
73
76
  # If true, the 'tags' field will be flattened into key-values where each key is a tag and each value is set to
74
77
  # :flat_tag_value
@@ -241,6 +244,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
241
244
  # level metrics are handled by the HTTP Client class.
242
245
  @multi_receive_statistics = {
243
246
  :total_multi_receive_secs => 0,
247
+ :total_events_processed => 0,
248
+ :successful_events_processed => 0,
249
+ :failed_events_processed => 0,
250
+ :total_retry_count => 0,
244
251
  :total_java_class_cast_errors => 0
245
252
  }
246
253
  @plugin_metrics = get_new_metrics
@@ -345,6 +352,9 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
345
352
  sleep_interval = sleep_for(sleep_interval)
346
353
  exc_sleep += sleep_interval
347
354
  exc_retries += 1
355
+ @stats_lock.synchronize do
356
+ @multi_receive_statistics[:total_retry_count] += 1
357
+ end
348
358
  message = "Error uploading to Scalyr (will backoff-retry)"
349
359
  exc_data = {
350
360
  :error_class => e.e_class,
@@ -394,11 +404,19 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
394
404
  }
395
405
  exc_sleep += sleep_interval
396
406
  exc_retries += 1
407
+ @stats_lock.synchronize do
408
+ @multi_receive_statistics[:total_retry_count] += 1
409
+ end
397
410
  retry if @running and exc_retries < @max_retries
398
411
  log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
399
412
  next
400
413
  end
401
414
 
415
+ @stats_lock.synchronize do
416
+ @multi_receive_statistics[:total_events_processed] += multi_event_request[:logstash_events].length
417
+ @multi_receive_statistics[:successful_events_processed] += multi_event_request[:logstash_events].length
418
+ end
419
+
402
420
  if !exc_data.nil?
403
421
  message = "Retry successful after error."
404
422
  if exc_commonly_retried
@@ -437,6 +455,10 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
437
455
 
438
456
 
439
457
  def log_retry_failure(multi_event_request, exc_data, exc_retries, exc_sleep)
458
+ @stats_lock.synchronize do
459
+ @multi_receive_statistics[:total_events_processed] += multi_event_request[:logstash_events].length
460
+ @multi_receive_statistics[:failed_events_processed] += multi_event_request[:logstash_events].length
461
+ end
440
462
  message = "Failed to send #{multi_event_request[:logstash_events].length} events after #{exc_retries} tries."
441
463
  sample_events = Array.new
442
464
  multi_event_request[:logstash_events][0,5].each {|l_event|
@@ -612,7 +634,11 @@ class LogStash::Outputs::Scalyr < LogStash::Outputs::Base
612
634
  # flatten record
613
635
  if @flatten_nested_values
614
636
  start_time = Time.now.to_f
615
- record = Scalyr::Common::Util.flatten(record, delimiter=@flatten_nested_values_delimiter)
637
+ begin
638
+ 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)
639
+ rescue Scalyr::Common::Util::MaxKeyCountError => e
640
+ @logger.warn("Error while flattening record", :error_message => e.message, :sample_keys => e.sample_keys)
641
+ end
616
642
  end_time = Time.now.to_f
617
643
  flatten_nested_values_duration = end_time - start_time
618
644
  end
@@ -1,44 +1,95 @@
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='_')
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
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
50
+ end
17
51
 
18
- if obj.respond_to?(:has_key?)
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
19
59
 
20
- # input object is a hash
21
- obj.each do |key, value|
22
- if value.respond_to?(:each)
23
- flatten(value).each do |subkey, subvalue|
24
- result["#{key}#{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}"
25
73
  end
26
- else
27
- result["#{key}"] = value
28
74
  end
29
- end
75
+ result[result_key] = obj
30
76
 
31
- 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
32
83
 
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).each do |subkey, subvalue|
37
- result["#{index}#{delimiter}#{subkey}"] = subvalue
38
- end
39
- else
40
- result["#{index}"] = value
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
41
88
  end
89
+ if not key_list_width.empty?
90
+ key_list_width[-1] -= 1
91
+ end
92
+
42
93
  end
43
94
  end
44
95
 
@@ -1,2 +1,2 @@
1
1
  # encoding: utf-8
2
- PLUGIN_VERSION = "v0.1.17.beta"
2
+ PLUGIN_VERSION = "v0.1.21.beta"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-scalyr'
3
- s.version = '0.1.17.beta'
3
+ s.version = '0.1.21.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)"
@@ -288,6 +288,72 @@ describe LogStash::Outputs::Scalyr do
288
288
  end
289
289
  end
290
290
 
291
+ context "when configured to flatten values with custom delimiter and deep delimiter fix" do
292
+ config = {
293
+ 'api_write_token' => '1234',
294
+ 'flatten_tags' => true,
295
+ 'flat_tag_value' => 'true',
296
+ 'flat_tag_prefix' => 'tag_prefix_',
297
+ 'flatten_nested_values' => true, # this converts into string 'true'
298
+ 'flatten_nested_values_delimiter' => ".",
299
+ 'fix_deep_flattening_delimiters' => true,
300
+ }
301
+ plugin = LogStash::Outputs::Scalyr.new(config)
302
+ it "flattens nested values with a period" do
303
+ allow(plugin).to receive(:send_status).and_return(nil)
304
+ plugin.register
305
+ result = plugin.build_multi_event_request_array(sample_events)
306
+ body = JSON.parse(result[0][:body])
307
+ expect(body['events'].size).to eq(3)
308
+ expect(body['events'][2]['attrs']).to eq({
309
+ "nested.a" => 1,
310
+ "nested.b.0" => 3,
311
+ "nested.b.1" => 4,
312
+ "nested.b.2" => 5,
313
+ 'seq' => 3,
314
+ 'source_file' => 'my file 3',
315
+ 'source_host' => 'my host 3',
316
+ 'serverHost' => 'Logstash',
317
+ "tag_prefix_t1" => "true",
318
+ "tag_prefix_t2" => "true",
319
+ "tag_prefix_t3" => "true",
320
+ "parser" => "logstashParser",
321
+ })
322
+ end
323
+ end
324
+
325
+ context "when configured to flatten values with custom delimiter, no array flattening" do
326
+ config = {
327
+ 'api_write_token' => '1234',
328
+ 'flatten_tags' => true,
329
+ 'flat_tag_value' => 'true',
330
+ 'flat_tag_prefix' => 'tag_prefix_',
331
+ 'flatten_nested_values' => true, # this converts into string 'true'
332
+ 'flatten_nested_arrays' => false,
333
+ 'flatten_nested_values_delimiter' => ".",
334
+ }
335
+ plugin = LogStash::Outputs::Scalyr.new(config)
336
+ it "flattens nested values with a period" do
337
+ allow(plugin).to receive(:send_status).and_return(nil)
338
+ plugin.register
339
+ result = plugin.build_multi_event_request_array(sample_events)
340
+ body = JSON.parse(result[0][:body])
341
+ expect(body['events'].size).to eq(3)
342
+ expect(body['events'][2]['attrs']).to eq({
343
+ "nested.a" => 1,
344
+ "nested.b" => [3, 4, 5],
345
+ 'seq' => 3,
346
+ 'source_file' => 'my file 3',
347
+ 'source_host' => 'my host 3',
348
+ 'serverHost' => 'Logstash',
349
+ "tag_prefix_t1" => "true",
350
+ "tag_prefix_t2" => "true",
351
+ "tag_prefix_t3" => "true",
352
+ "parser" => "logstashParser",
353
+ })
354
+ end
355
+ end
356
+
291
357
  context "when configured to flatten values and tags" do
292
358
  config = {
293
359
  'api_write_token' => '1234',
@@ -343,6 +409,38 @@ describe LogStash::Outputs::Scalyr do
343
409
  end
344
410
  end
345
411
 
412
+ context "when configured to flatten with max keys configured to 3" do
413
+ config = {
414
+ 'api_write_token' => '1234',
415
+ 'flatten_nested_values' => true, # this converts into string 'true'
416
+ 'flattening_max_key_count' => 3,
417
+ }
418
+ plugin = LogStash::Outputs::Scalyr.new(config)
419
+ it "does not flatten" do
420
+ allow(plugin).to receive(:send_status).and_return(nil)
421
+ plugin.register
422
+ allow(plugin.instance_variable_get(:@logger)).to receive(:warn)
423
+ result = plugin.build_multi_event_request_array(sample_events)
424
+ body = JSON.parse(result[0][:body])
425
+ expect(body['events'].size).to eq(3)
426
+ expect(body['events'][2]['attrs']).to eq({
427
+ "nested" => {'a'=>1, 'b'=>[3,4,5]},
428
+ 'seq' => 3,
429
+ 'source_file' => 'my file 3',
430
+ 'source_host' => 'my host 3',
431
+ 'serverHost' => 'Logstash',
432
+ "tags" => ["t1", "t2", "t3"],
433
+ "parser" => "logstashParser",
434
+ })
435
+ expect(plugin.instance_variable_get(:@logger)).to have_received(:warn).with("Error while flattening record",
436
+ {
437
+ :error_message=>"Resulting flattened object will contain more keys than the configured flattening_max_key_count of 3",
438
+ :sample_keys=>["serverHost", "parser", "tags_2", "tags_1"]
439
+ }
440
+ ).exactly(3).times
441
+ end
442
+ end
443
+
346
444
  context "when receiving an event with Bignums" do
347
445
  config = {
348
446
  '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 = {
@@ -132,6 +238,198 @@ describe Scalyr::Common::Util do
132
238
  expect(Scalyr::Common::Util.flatten(din)).to eq(dout)
133
239
  end
134
240
 
241
+ it "flattens a single-level array, no array flattening" do
242
+ din = [1, 2, 3]
243
+ dout = [1, 2, 3]
244
+ expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
245
+ end
246
+
247
+ it "flattens a multi-level array, no array flattening" do
248
+ din = ['a', 'b', ['c', ['d', 'e', 'f'], 'g'], 'h', 'i']
249
+ dout = ['a', 'b', ['c', ['d', 'e', 'f'], 'g'], 'h', 'i']
250
+ expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
251
+ end
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
+
267
+ it "flattens a hash that contains an array, no array flattening" do
268
+ din = {
269
+ 'a' => 1,
270
+ 'c' => [100, 200, 300]
271
+ }
272
+ dout = {
273
+ 'a' => 1,
274
+ 'c' => [100, 200, 300]
275
+ }
276
+ expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
277
+ end
278
+
279
+ it "flattens a hash that contains an array that contains a hash, no array flattening" do
280
+ din = {
281
+ 'a' => 1,
282
+ 'c' => [
283
+ 100,
284
+ {'d' => 1000, 'e' => 2000},
285
+ 300
286
+ ]
287
+ }
288
+ dout = {
289
+ 'a' => 1,
290
+ 'c' => [
291
+ 100,
292
+ {'d' => 1000, 'e' => 2000},
293
+ 300
294
+ ]
295
+ }
296
+ expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
297
+ end
298
+
299
+ it "flattens a hash that contains an array that contains a hash that contains an array, no array flattening" do
300
+ din = {
301
+ 'a' => 1,
302
+ 'c' => [
303
+ 100,
304
+ {'d' => 1000, 'e' => 2000, 'f' => [4, 5, 6]},
305
+ 300
306
+ ]
307
+ }
308
+ dout = {
309
+ 'a' => 1,
310
+ 'c' => [
311
+ 100,
312
+ {'d' => 1000, 'e' => 2000, 'f' => [4, 5, 6]},
313
+ 300
314
+ ]
315
+ }
316
+ expect(Scalyr::Common::Util.flatten(din, "_", flatten_arrays=false)).to eq(dout)
317
+ end
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
+
135
433
  it "accepts custom delimiters" do
136
434
  din = {
137
435
  'a' => 1,
@@ -148,6 +446,42 @@ describe Scalyr::Common::Util do
148
446
  expect(Scalyr::Common::Util.flatten(din, ':')).to eq(dout)
149
447
  end
150
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
+
151
485
  it "stringifies non-string keys" do
152
486
  din = {
153
487
  'a' => 1,
@@ -183,4 +517,23 @@ describe Scalyr::Common::Util do
183
517
  it "raises exception if a non-dict is provided" do
184
518
  expect {Scalyr::Common::Util.flatten(1)}.to raise_error(TypeError)
185
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
186
539
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-scalyr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.17.beta
4
+ version: 0.1.21.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-07-06 00:00:00.000000000 Z
11
+ date: 2021-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement