logstash-output-scalyr 0.1.17.beta → 0.1.21.beta

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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