fluent-plugin-sumologic_output 1.7.3 → 1.8.0

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: 52b0c77e900d61ba360421933440383208033b29b0f08b012250519189872f24
4
- data.tar.gz: ff86f952744e650e30ba94d1185f4d270a3d8d6e7bfee5f30a0810e043a9483f
3
+ metadata.gz: 280c08203277770f0dc9277d9df2f8249fba58283816e4fd5b511560fb533ac3
4
+ data.tar.gz: ad2b07fadad2698bf9169a0556a3c63422446267c292c27c71e0d5a8e7b5669a
5
5
  SHA512:
6
- metadata.gz: 74c43d262f35e2767d170a05dbec58911590c0851a724f6c4adb0047a26601383e4d8fa5e2671c845bbd89a65781fb0d35e4bc2f8d7ed6bd0f0e376d6c7ec958
7
- data.tar.gz: 73b8ea2a6dbf4593eca7d4a702eeea14f27728bd7aa28ddc15e968301a5a2eb21f76ae18f1be2dd759f42cb357c2ceda92aec49e136755d76c954ebded889cdd
6
+ metadata.gz: 32636372bdbb936f6d3c68a223b1cdd4b9e716522fd526df80b079f1712c77f658f16c3f60d5554f80d53354006e8d11e40a9fbe68c0b4fbd767fe053a9cc8bb
7
+ data.tar.gz: 76b52ddad11c35a1e2be4a6f818e6f2f60f10ad6a74147c43fb05f2689dbc4603c9429437ff6c7cdd9a1cdf398894096adb925f1dd922b117bf509ba12c8ffa4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. Tracking did not begin until version 1.10.
4
4
 
5
+ <a name="1.8.0"></a>
6
+ # [1.8.0] (2022-04-22)
7
+
8
+ - feat: add exponential backoff for sending data [#76](https://github.com/SumoLogic/fluentd-output-sumologic/pull/76)
9
+ - feat(max_request_size): add max_request_size to limit size of requests [#78](https://github.com/SumoLogic/fluentd-output-sumologic/pull/78)
10
+
11
+ <a name="1.7.5"></a>
12
+ # [1.7.5] (2022-04-11)
13
+
14
+ - refactor: add a debug log on sending [#75](https://github.com/SumoLogic/fluentd-output-sumologic/pull/75)
15
+
16
+ <a name="1.7.4"></a>
17
+ # [1.7.4] (2021-04-08)
18
+
19
+ - fix: handle receiver warning messages [#73](https://github.com/SumoLogic/fluentd-output-sumologic/pull/73)
20
+
21
+ <a name="1.7.3"></a>
22
+ # [1.7.3] (2021-10-19)
23
+ - Expose httpclient send_timeout [#66](https://github.com/SumoLogic/fluentd-output-sumologic/pull/68)
24
+ - Fix json parsing [#69](https://github.com/SumoLogic/fluentd-output-sumologic/pull/69)
25
+
26
+ <a name="1.7.2"></a>
27
+ # [1.7.2] (2020-11-23)
28
+ - Fix configuration for older fluentd versions [#63](https://github.com/SumoLogic/fluentd-output-sumologic/pull/63)
29
+
5
30
  <a name="1.7.1"></a>
6
31
  # [1.7.1] (2020-04-28)
7
32
  - Fix configuration for older fluentd versions [#63](https://github.com/SumoLogic/fluentd-output-sumologic/pull/63)
data/README.md CHANGED
@@ -38,6 +38,13 @@ Configuration options for fluent.conf are:
38
38
  * `compress_encoding` - Compression encoding format, either `gzip` or `deflate` (default `gzip`)
39
39
  * `custom_fields` - Comma-separated key=value list of fields to apply to every log. [more information](https://help.sumologic.com/Manage/Fields#http-source-fields)
40
40
  * `custom_dimensions` - Comma-separated key=value list of dimensions to apply to every metric. [more information](https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source/Upload-Metrics-to-an-HTTP-Source#supported-http-headers)
41
+ * `use_internal_retry` - Enable custom retry mechanism. As this is `false` by default due to backward compatibility,
42
+ we recommend to enable it and configure the following parameters (`retry_min_interval`, `retry_max_interval`, `retry_timeout`, `retry_max_times`)
43
+ * `retry_min_interval` - Minimum interval to wait between sending tries (default is `1s`)
44
+ * `retry_max_interval` - Maximum interval to wait between sending tries (default is `5m`)
45
+ * `retry_timeout` - Time after which the data is going to be dropped (default is `72h`) (`0s` means that there is no timeout)
46
+ * `retry_max_times` - Maximum number of retries (default is `0`) (`0` means that there is no max retry times, retries will happen forever)
47
+ * `max_request_size` - Maximum request size (before applying compression). Default is `0k` which means no limit
41
48
 
42
49
  __NOTE:__ <sup>*</sup> [Placeholders](https://docs.fluentd.org/v1.0/articles/buffer-section#placeholders) are supported
43
50
 
@@ -137,6 +144,14 @@ Example
137
144
  }
138
145
  ```
139
146
 
147
+ ## Retry Mechanism
148
+
149
+ `retry_min_interval`, `retry_max_interval`, `retry_timeout`, `retry_max_times` are not the [buffer retries parameters][buffer_retries].
150
+ Due to technical reason, this plugin implements it's own retrying back-off exponential mechanism.
151
+ It is disabled by default, but we recommend to enable it by setting `use_internal_retry` to `true`.
152
+
153
+ [buffer_retries]: https://docs.fluentd.org/configuration/buffer-section#retries-parameters
154
+
140
155
  ### TLS 1.2 Requirement
141
156
 
142
157
  Sumo Logic only accepts connections from clients using TLS version 1.2 or greater. To utilize the content of this repo, ensure that it's running in an execution environment that is configured to use TLS 1.2 or greater.
data/Vagrantfile CHANGED
@@ -12,7 +12,9 @@ Vagrant.configure('2') do |config|
12
12
  config.disksize.size = '50GB'
13
13
  config.vm.box_check_update = false
14
14
  config.vm.host_name = 'fluentd-output-sumologic'
15
- config.vm.network :private_network, ip: "192.168.78.45"
15
+ # Vbox 6.1.28+ restricts host-only adapters to 192.168.56.0/21
16
+ # See: https://www.virtualbox.org/manual/ch06.html#network_hostonly
17
+ config.vm.network :private_network, ip: "192.168.56.45"
16
18
 
17
19
  config.vm.provider 'virtualbox' do |vb|
18
20
  vb.gui = false
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |gem|
6
6
  gem.name = "fluent-plugin-sumologic_output"
7
- gem.version = "1.7.3"
7
+ gem.version = "1.8.0"
8
8
  gem.authors = ["Steven Adams", "Frank Reno"]
9
9
  gem.email = ["stevezau@gmail.com", "frank.reno@me.com"]
10
10
  gem.description = %q{Output plugin to SumoLogic HTTP Endpoint}
@@ -13,12 +13,13 @@ class SumologicConnection
13
13
  COMPRESS_DEFLATE = 'deflate'
14
14
  COMPRESS_GZIP = 'gzip'
15
15
 
16
- def initialize(endpoint, verify_ssl, connect_timeout, send_timeout, proxy_uri, disable_cookies, sumo_client, compress_enabled, compress_encoding)
16
+ def initialize(endpoint, verify_ssl, connect_timeout, send_timeout, proxy_uri, disable_cookies, sumo_client, compress_enabled, compress_encoding, logger)
17
17
  @endpoint = endpoint
18
18
  @sumo_client = sumo_client
19
19
  create_http_client(verify_ssl, connect_timeout, send_timeout, proxy_uri, disable_cookies)
20
20
  @compress = compress_enabled
21
21
  @compress_encoding = (compress_encoding ||= COMPRESS_GZIP).downcase
22
+ @logger = logger
22
23
 
23
24
  unless [COMPRESS_DEFLATE, COMPRESS_GZIP].include? @compress_encoding
24
25
  raise "Invalid compression encoding #{@compress_encoding} must be gzip or deflate"
@@ -30,6 +31,29 @@ class SumologicConnection
30
31
  unless response.ok?
31
32
  raise RuntimeError, "Failed to send data to HTTP Source. #{response.code} - #{response.body}"
32
33
  end
34
+
35
+ # response is 20x, check response content
36
+ return if response.content.length == 0
37
+
38
+ # if we get a non-empty response, check it
39
+ begin
40
+ response_map = JSON.load(response.content)
41
+ rescue JSON::ParserError
42
+ @logger.warn "Error decoding receiver response: #{response.content}"
43
+ return
44
+ end
45
+
46
+ # log a warning with the present keys
47
+ response_keys = ["id", "code", "status", "message", "errors"]
48
+ log_params = []
49
+ response_keys.each do |key|
50
+ if response_map.has_key?(key) then
51
+ value = response_map[key]
52
+ log_params.append("#{key}: #{value}")
53
+ end
54
+ end
55
+ log_params_str = log_params.join(", ")
56
+ @logger.warn "There was an issue sending data: #{log_params_str}"
33
57
  end
34
58
 
35
59
  def request_headers(source_host, source_category, source_name, data_type, metric_data_format, collected_fields, dimensions)
@@ -133,6 +157,15 @@ class Fluent::Plugin::Sumologic < Fluent::Plugin::Output
133
157
  config_param :timestamp_key, :string, :default => 'timestamp'
134
158
  config_param :proxy_uri, :string, :default => nil
135
159
  config_param :disable_cookies, :bool, :default => false
160
+
161
+ config_param :use_internal_retry, :bool, :default => false
162
+ config_param :retry_timeout, :time, :default => 72 * 3600 # 72h
163
+ config_param :retry_max_times, :integer, :default => 0
164
+ config_param :retry_min_interval, :time, :default => 1 # 1s
165
+ config_param :retry_max_interval, :time, :default => 5*60 # 5m
166
+
167
+ config_param :max_request_size, :size, :default => 0
168
+
136
169
  # https://help.sumologic.com/Manage/Fields
137
170
  desc 'Fields string (eg "cluster=payment, service=credit_card") which is going to be added to every log record.'
138
171
  config_param :custom_fields, :string, :default => nil
@@ -218,8 +251,13 @@ class Fluent::Plugin::Sumologic < Fluent::Plugin::Output
218
251
  conf['disable_cookies'],
219
252
  conf['sumo_client'],
220
253
  conf['compress'],
221
- conf['compress_encoding']
254
+ conf['compress_encoding'],
255
+ log,
222
256
  )
257
+
258
+ if !conf['max_request_size'].nil? && conf['max_request_size'].to_i <= 0
259
+ conf['max_request_size'] = '0'
260
+ end
223
261
  super
224
262
  end
225
263
 
@@ -360,6 +398,7 @@ class Fluent::Plugin::Sumologic < Fluent::Plugin::Output
360
398
 
361
399
  end
362
400
 
401
+ chunk_id = "##{chunk.dump_unique_id_hex(chunk.unique_id)}"
363
402
  # Push logs to sumo
364
403
  messages_list.each do |key, messages|
365
404
  source_name, source_category, source_host, fields = key[:source_name], key[:source_category],
@@ -372,16 +411,78 @@ class Fluent::Plugin::Sumologic < Fluent::Plugin::Output
372
411
  fields = [fields,@custom_fields].compact.join(",")
373
412
  end
374
413
 
375
- @sumo_conn.publish(
376
- messages.join("\n"),
377
- source_host =source_host,
378
- source_category =source_category,
379
- source_name =source_name,
380
- data_type =@data_type,
381
- metric_data_format =@metric_data_format,
382
- collected_fields =fields,
383
- dimensions =@custom_dimensions
384
- )
414
+ if @max_request_size <= 0
415
+ messages_to_send = [messages]
416
+ else
417
+ messages_to_send = []
418
+ current_message = []
419
+ current_length = 0
420
+ messages.each do |message|
421
+ current_message.push message
422
+ current_length += message.length
423
+
424
+ if current_length > @max_request_size
425
+ messages_to_send.push(current_message)
426
+ current_message = []
427
+ current_length = 0
428
+ end
429
+ current_length += 1 # this is for newline
430
+ end
431
+ if current_message.length > 0
432
+ messages_to_send.push(current_message)
433
+ end
434
+ end
435
+
436
+ messages_to_send.each_with_index do |message, i|
437
+ retries = 0
438
+ start_time = Time.now
439
+ sleep_time = @retry_min_interval
440
+
441
+ while true
442
+ common_log_part = "#{@data_type} records with source category '#{source_category}', source host '#{source_host}', source name '#{source_name}', chunk #{chunk_id}, try #{retries}, batch #{i}"
443
+
444
+ begin
445
+ @log.debug { "Sending #{message.count}; #{common_log_part}" }
446
+
447
+ @sumo_conn.publish(
448
+ message.join("\n"),
449
+ source_host =source_host,
450
+ source_category =source_category,
451
+ source_name =source_name,
452
+ data_type =@data_type,
453
+ metric_data_format =@metric_data_format,
454
+ collected_fields =fields,
455
+ dimensions =@custom_dimensions
456
+ )
457
+ break
458
+ rescue => e
459
+ if !@use_internal_retry
460
+ raise e
461
+ end
462
+ # increment retries
463
+ retries += 1
464
+
465
+ log.warn "error while sending request to sumo: #{e}; #{common_log_part}"
466
+ log.warn_backtrace e.backtrace
467
+
468
+ # drop data if
469
+ # - we reached out the @retry_max_times retries
470
+ # - or we exceeded @retry_timeout
471
+ if (retries >= @retry_max_times && @retry_max_times > 0) || (Time.now > start_time + @retry_timeout && @retry_timeout > 0)
472
+ log.warn "dropping records; #{common_log_part}"
473
+ break
474
+ end
475
+
476
+ log.info "going to retry to send data at #{Time.now + sleep_time}; #{common_log_part}"
477
+ sleep sleep_time
478
+
479
+ sleep_time *= 2
480
+ if sleep_time > @retry_max_interval
481
+ sleep_time = @retry_max_interval
482
+ end
483
+ end
484
+ end
485
+ end
385
486
  end
386
487
 
387
488
  end
@@ -795,6 +795,187 @@ class SumologicOutput < Test::Unit::TestCase
795
795
  headers: {'X-Sumo-Category'=>'test', 'X-Sumo-Client'=>'fluentd-output', 'X-Sumo-Host'=>'test', 'X-Sumo-Name'=>'test'},
796
796
  body: /\A{"timestamp":\d+.,"message":"\\"foo\\\\\\": \\\\\\"bar\\\\\\", \\\\\\"mess"}\z/,
797
797
  times:1
798
- end
798
+ end
799
+
800
+ def test_warning_response_from_receiver
801
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
802
+ config = %{
803
+ endpoint #{endpoint}
804
+ }
805
+ testdata = [
806
+ [
807
+ '{"id":"1TIRY-KGIVX-TPQRJ","errors":[{"code":"internal.error","message":"Internal server error."}]}',
808
+ 'There was an issue sending data: id: 1TIRY-KGIVX-TPQRJ, errors: [{"code"=>"internal.error", "message"=>"Internal server error."}]'
809
+ ],
810
+ [
811
+ '{"id":"1TIRY-KGIVX-TPQRX","code": 200, "status": "Fields dropped", "message": "Dropped fields above the 30 field limit"}',
812
+ 'There was an issue sending data: id: 1TIRY-KGIVX-TPQRX, code: 200, status: Fields dropped, message: Dropped fields above the 30 field limit'
813
+ ],
814
+ ]
815
+ time = event_time
816
+
817
+ testdata.each do |data, log|
818
+ driver = create_driver(config)
819
+ stub_request(:post, endpoint).to_return(body: data, headers: {content_type: 'application/json'})
820
+ driver.run do
821
+ driver.feed("test", time, {"message": "test"})
822
+ end
823
+ assert_equal driver.logs.length, 1
824
+ assert driver.logs[0].end_with?(log + "\n")
825
+ end
826
+ end
827
+
828
+ def test_resend
829
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
830
+ config = %{
831
+ endpoint #{endpoint}
832
+ retry_min_interval 0s
833
+ retry_max_times 3
834
+ use_internal_retry true
835
+ }
836
+ time = event_time
837
+
838
+ driver = create_driver(config)
839
+ stub_request(:post, endpoint).to_return(
840
+ {status: 500, headers: {content_type: 'application/json'}},
841
+ {status: 200, headers: {content_type: 'application/json'}}
842
+ )
843
+ driver.run do
844
+ driver.feed("test", time, {"message": "test"})
845
+ end
846
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
847
+ body: /\A{"timestamp":\d+.,"message":"test"}\z/,
848
+ times:2
849
+ end
850
+
851
+ def test_resend_failed
852
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
853
+ config = %{
854
+ endpoint #{endpoint}
855
+ retry_min_interval 0s
856
+ retry_max_times 15
857
+ use_internal_retry true
858
+ }
859
+ time = event_time
860
+
861
+ driver = create_driver(config)
862
+ stub_request(:post, endpoint).to_return(
863
+ status: 500, headers: {content_type: 'application/json'}
864
+ )
865
+ driver.run do
866
+ driver.feed("test", time, {"message": "test"})
867
+ end
868
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
869
+ body: /\A{"timestamp":\d+.,"message":"test"}\z/,
870
+ times:15
871
+ end
872
+
873
+ def test_resend_forever
874
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
875
+ config = %{
876
+ endpoint #{endpoint}
877
+ retry_min_interval 0s
878
+ retry_max_times 0
879
+ retry_timeout 0s
880
+ use_internal_retry true
881
+ }
882
+ time = event_time
883
+
884
+ driver = create_driver(config)
885
+ stub_request(:post, endpoint).to_return(
886
+ *[{status: 500, headers: {content_type: 'application/json'}}]*123,
887
+ {status: 200, headers: {content_type: 'application/json'}}
888
+ )
889
+ driver.run do
890
+ driver.feed("test", time, {"message": "test"})
891
+ end
892
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
893
+ body: /\A{"timestamp":\d+.,"message":"test"}\z/,
894
+ times:124
895
+ end
896
+
897
+ def test_skip_retry
898
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
899
+ config = %{
900
+ endpoint #{endpoint}
901
+ }
902
+ time = event_time
903
+
904
+ driver = create_driver(config)
905
+ stub_request(:post, endpoint).to_return(status: 500, headers: {content_type: 'application/json'})
906
+
907
+ exception = assert_raise(RuntimeError) {
908
+ driver.run do
909
+ driver.feed("test", time, {"message": "test"})
910
+ end
911
+ }
912
+ assert_equal("Failed to send data to HTTP Source. 500 - ", exception.message)
913
+ end
914
+
915
+ def test_split_negative_or_zero
916
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
917
+
918
+ configs = [
919
+ %{
920
+ endpoint #{endpoint}
921
+ max_request_size -5
922
+ },
923
+ %{
924
+ endpoint #{endpoint}
925
+ max_request_size 0
926
+ }
927
+ ]
928
+
929
+ time = event_time
930
+
931
+ configs.each do |config|
932
+ WebMock.reset_executed_requests!
933
+ driver = create_driver(config)
934
+ stub_request(:post, endpoint).to_return(status: 200, headers: {content_type: 'application/json'})
935
+
936
+ driver.run do
937
+ driver.feed("test", time, {"message": "test"})
938
+ driver.feed("test", time, {"message": "test"})
939
+ driver.feed("test", time, {"message": "test"})
940
+ end
941
+
942
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
943
+ body: /\A{"timestamp":\d+.,"message":"test"}\n{"timestamp":\d+.,"message":"test"}\n{"timestamp":\d+.,"message":"test"}\z/,
944
+ times:1
945
+ end
946
+ end
947
+
948
+ def test_split
949
+ endpoint = "https://collectors.sumologic.com/v1/receivers/http/1234"
950
+
951
+ config = %{
952
+ endpoint #{endpoint}
953
+ max_request_size 80
954
+ }
955
+
956
+ time = event_time
957
+
958
+ WebMock.reset_executed_requests!
959
+ driver = create_driver(config)
960
+ stub_request(:post, endpoint).to_return(status: 200, headers: {content_type: 'application/json'})
961
+
962
+ driver.run do
963
+ driver.feed("test", time, {"message": "test1"})
964
+ driver.feed("test", time, {"message": "test2"})
965
+ driver.feed("test", time, {"message": "test3"})
966
+ driver.feed("test", time, {"message": "test4"})
967
+ driver.feed("test", time, {"message": "test5"})
968
+ end
969
+
970
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
971
+ body: /\A{"timestamp":\d+.,"message":"test1"}\n{"timestamp":\d+.,"message":"test2"}\z/,
972
+ times:1
973
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
974
+ body: /\A{"timestamp":\d+.,"message":"test3"}\n{"timestamp":\d+.,"message":"test4"}\z/,
975
+ times:1
976
+ assert_requested :post, "https://collectors.sumologic.com/v1/receivers/http/1234",
977
+ body: /\A{"timestamp":\d+.,"message":"test5"}\z/,
978
+ times:1
979
+ end
799
980
 
800
981
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-sumologic_output
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.3
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Adams
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-10-19 00:00:00.000000000 Z
12
+ date: 2022-04-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler