logstash-output-dynatrace 0.6.0 → 0.7.1

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: 9281b9e498b9986e45d25eaadc74358f95547551baf97132483533997edf134d
4
- data.tar.gz: 27bc3095ec54575becfaf75eb2b7bca7d186f95b2ab239713e83f8b9f58b363b
3
+ metadata.gz: 81b4a8c2170a81fb234ff285214b21e531d8b33a304ccbe25b643a10d05a7c28
4
+ data.tar.gz: 70e9196d107abd6655ea8394fac223713bd8120c5305c5f22075d402f897d65d
5
5
  SHA512:
6
- metadata.gz: f4863a8fae10fe23afc85d4a6e31290ba3c0628d974c57c8cec6306adc2ba1da5c73905c7597c0954cb095ea1a24c6e182d028d8d7289f05951f77c214255b48
7
- data.tar.gz: 53281ec7c6a7a8f6ac5dffae9cde5babe61d0a1b962d5c903e7e60975bb00d0528efc57c21c3b35d6f9bfc18b84c1549221c18dbe8ca3ae75e8cde764a94d7b7
6
+ metadata.gz: 6c67bc08595859467e6c32da87b60e0da67e99f3df4eb8b59d1ecedd8b96676429fe908b4e0678ea4f74736445fab3880a25461d439ef8fef3ce6100b9bc7ada
7
+ data.tar.gz: c0c78831f7436a02ae3faafab913a725af81d9bf8a14111b7de5d4e46bac3b473f22b325ef27280f5bb4262279a86dadf6ec2b19250b89819e90b7e7f5b8c32b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.7.1
2
+
3
+ - Fix an error where max payload size was calculated using character count instead of bytes
4
+
5
+ ## 0.7.0
6
+
7
+ - Add new development dependency `rackup` for logstash 8.x compatibility
8
+ - Enable `compression` configuration to compress payloads using `gzip`
9
+ - Document `proxy` configuration
10
+
1
11
  ## 0.6.0
2
12
 
3
13
  - Disable cookie processing by default
data/README.md CHANGED
@@ -15,6 +15,7 @@
15
15
  - [`ingest_endpoint_url`](#ingest_endpoint_url)
16
16
  - [`api_key`](#api_key)
17
17
  - [`ssl_verify_none`](#ssl_verify_none)
18
+ - [`proxy`](#proxy)
18
19
  - [`enable_metric`](#enable_metric)
19
20
  - [`id`](#id)
20
21
  - [Troubleshooting issues](#troubleshooting-issues)
@@ -62,6 +63,7 @@ The following configuration options are supported by the Dynatrace output plugin
62
63
  | [`ingest_endpoint_url`](#ingest_endpoint_url) | [String](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#string) | Yes |
63
64
  | [`api_key`](#api_key) | [String](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#string) | Yes |
64
65
  | [`ssl_verify_none`](#ssl_verify_none) | [Boolean](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#boolean) | No |
66
+ | [`http_compression`](#http_compression) | [Boolean](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#boolean) | No |
65
67
 
66
68
 
67
69
  ### Common Options
@@ -70,7 +72,8 @@ The following configuration options are supported by all output plugins:
70
72
 
71
73
  | Setting | Input type | Required |
72
74
  | --------------------------------- | ----------------------------------------------------------------------------------------------------- | -------- |
73
- | [`codec`](#codec) | [Codec](https://www.elastic.co/guide/en/logstash/7.16/configuration-file-structure.html#codec) | No |
75
+ | [`proxy`](#proxy) | [String](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#string) or [Hash](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#hash) | No |
76
+ | `codec` | [Codec](https://www.elastic.co/guide/en/logstash/7.16/configuration-file-structure.html#codec) | No |
74
77
  | [`enable_metric`](#enable_metric) | [Boolean](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#boolean) | No |
75
78
  | [`id`](#id) | [String](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#string) | No |
76
79
 
@@ -106,9 +109,26 @@ This option may be required if you are using a self-signed certificate, an expir
106
109
  > NOTE: Starting in plugin version `0.5.0`, this option has no effect in versions of Logstash older than `8.1.0`.
107
110
  > If this functionality is required, it is recommended to update Logstash or stay at plugin version `0.4.x` or older.
108
111
 
112
+ ### `proxy`
113
+
114
+ * Value type is [string](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#string) or [hash](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#hash)
115
+ * Optional
116
+ * No default value
117
+ * Introduced in `logstash-output-dynatrace` version `0.5.0` (not available in older versions)
118
+
119
+ This setting configures an HTTP proxy to route exported data through.
120
+
121
+ The supported configuration options and their syntax are the same as for the [proxy option](https://www.elastic.co/guide/en/logstash/current/plugins-outputs-http.html#plugins-outputs-http-proxy) in the HTTP output plugin:
122
+
123
+ 1. `proxy => http://example.org:1234`
124
+ 2. `proxy => { host => "example.org" port => 80 scheme => 'http' user => 'username@host' password => 'password' }`
125
+ 3. `proxy => { url => 'http://example.org:1234' user => 'username@host' password => 'password' }`
126
+
127
+ Note that [Hashes](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#hash) in the Logstash configuration file format use spaces as delimiters between entries, not commas.
128
+
109
129
  ### `enable_metric`
110
130
 
111
- * Value type is [boolean](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#boolean)
131
+ * Value type is [boolean](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#boolean)
112
132
  * Default value is true
113
133
 
114
134
  Disable or enable metric logging for this specific plugin instance. By default we record all the metrics we can, but you can disable metrics collection for a specific plugin.
@@ -116,7 +136,7 @@ Disable or enable metric logging for this specific plugin instance. By default w
116
136
  ### `id`
117
137
 
118
138
  * Value type is string
119
- * There is no default value for this setting.
139
+ * No default value
120
140
 
121
141
  Add a unique ID to the plugin configuration. If no ID is specified, Logstash will generate one. It is strongly recommended to set this ID in your configuration. This is particularly useful when you have two or more plugins of the same type. For example, if you have 2 dynatrace outputs. Adding a named ID in this case will help in monitoring Logstash when using the monitoring APIs.
122
142
 
@@ -128,6 +148,15 @@ output {
128
148
  }
129
149
  ```
130
150
 
151
+ ### `http_compression`
152
+
153
+ * Value type is [boolean](https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html#boolean)
154
+ * Optional
155
+ * Default value is `false`
156
+
157
+ Setting `http_compression` to `true` causes the output plugin to compress all requests to the Dynatrace API using `gzip`.
158
+ This can result in a reduction in size and transmission time of network requests at the expense of some additional CPU and memory consumption.
159
+
131
160
  ## Troubleshooting issues
132
161
 
133
162
  When troubleshooting, always try to reduce the configuration as much as possible.
@@ -21,6 +21,7 @@ require 'logstash/version'
21
21
  require 'dynatrace/version'
22
22
  require 'uri'
23
23
  require 'logstash/plugin_mixins/http_client'
24
+ require 'zlib'
24
25
 
25
26
  # These constants came from the http plugin config but we don't want them configurable
26
27
  # If encountered as response codes this plugin will retry these requests
@@ -80,6 +81,9 @@ module LogStash
80
81
  # Disable cookie support. Overridden default value from LogStash::PluginMixins::HttpClient
81
82
  config :cookies, :validate => :boolean, :default => false
82
83
 
84
+ # Set this to true if you want to enable gzip compression for your http requests
85
+ config :http_compression, :validate => :boolean, :default => false
86
+
83
87
  def register
84
88
  # ssl_verification_mode config is from mixin but ssl_verify_none is our documented config
85
89
  @ssl_verification_mode = 'none' if @ssl_verify_none
@@ -118,11 +122,11 @@ module LogStash
118
122
 
119
123
  def offer(serialized_event)
120
124
  # 2 square brackets, the length of all previously serialized strings, commas, and the current event size
121
- batch_size_bytes = 2 + @batch_events_size + @serialized_events.length + serialized_event.length
125
+ batch_size_bytes = 2 + @batch_events_size + @serialized_events.length + serialized_event.bytesize
122
126
  return false if batch_size_bytes > @max_batch_size
123
127
 
124
128
  @serialized_events.push(serialized_event)
125
- @batch_events_size += serialized_event.length
129
+ @batch_events_size += serialized_event.bytesize
126
130
  true
127
131
  end
128
132
 
@@ -174,8 +178,8 @@ module LogStash
174
178
 
175
179
  events.each do |event|
176
180
  serialized_event = LogStash::Json.dump(event.to_hash)
177
- if serialized_event.length > @max_payload_size
178
- log_params = { size: serialized_event.length }
181
+ if serialized_event.bytesize > @max_payload_size
182
+ log_params = { size: serialized_event.bytesize }
179
183
  log_params[:body] = serialized_event if @debug_include_body
180
184
  log_warning('Event larger than max_payload_size dropped', log_params)
181
185
  next
@@ -253,6 +257,12 @@ module LogStash
253
257
  def send_event(event, attempt)
254
258
  headers = make_headers
255
259
 
260
+ # Compress the body and add appropriate header
261
+ if @http_compression == true
262
+ headers["Content-Encoding"] = "gzip"
263
+ event = gzip(event)
264
+ end
265
+
256
266
  # Create an async request
257
267
  response = client.post(ingest_endpoint_url, body: event, headers: headers)
258
268
 
@@ -331,6 +341,16 @@ module LogStash
331
341
  def log_warning(message, opts)
332
342
  @logger.warn(message, opts)
333
343
  end
344
+
345
+ # gzip data
346
+ def gzip(data)
347
+ gz = StringIO.new
348
+ gz.set_encoding("BINARY")
349
+ z = Zlib::GzipWriter.new(gz)
350
+ z.write(data)
351
+ z.close
352
+ gz.string
353
+ end
334
354
  end
335
355
  end
336
356
  end
@@ -52,4 +52,7 @@ Gem::Specification.new do |s|
52
52
 
53
53
  s.add_development_dependency 'rubocop', '1.9.1'
54
54
  s.add_development_dependency 'rubocop-rake', '0.5.1'
55
+
56
+ # Pin to avoid using new Fiber-based implementation that breaks tests here
57
+ s.add_development_dependency 'rackup', "< 2.1.0"
55
58
  end
@@ -232,17 +232,38 @@ describe LogStash::Outputs::Dynatrace do
232
232
  end
233
233
  end
234
234
 
235
- context 'max_payload_size 2MB' do
236
- let(:config) { { 'ingest_endpoint_url' => ingest_endpoint_url, 'api_key' => api_key, 'max_payload_size' => 2_000_000 } }
235
+ context 'max_payload_size 500' do
236
+ let(:max_payload_size) { 500 }
237
+ let(:config) { { 'ingest_endpoint_url' => ingest_endpoint_url, 'api_key' => api_key, 'max_payload_size' => max_payload_size } }
237
238
  subject { LogStash::Outputs::Dynatrace.new(config) }
238
239
 
239
240
  before do
240
241
  allow(subject).to receive(:send_event) { |e, att| [:success, e, att] }
241
- subject.multi_receive([1, 2].map { |n| LogStash::Event.new({ 'n' => n.to_s * 1_250_000 }) })
242
242
  end
243
243
 
244
- it 'should split the chunk into multiple requests' do
245
- expect(subject).to have_received(:send_event).exactly(2).times
244
+ it 'should send 2 400B messages in multiple requests' do
245
+ subject.multi_receive([1, 2].map { |n| LogStash::Event.new({ 'n' => n.to_s * 400 }) })
246
+ expect(subject).to have_received(:send_event).exactly(2).times do |s|
247
+ expect(s.bytesize).to be <= max_payload_size
248
+ end
249
+ end
250
+
251
+ it 'should send 2 100B messages in a single request' do
252
+ subject.multi_receive([1, 2].map { |n| LogStash::Event.new({ 'n' => n.to_s * 100 }) })
253
+ expect(subject).to have_received(:send_event).exactly(1).times do |s|
254
+ expect(s.bytesize).to be <= max_payload_size
255
+ end
256
+ end
257
+
258
+ it 'should drop messages larger than max_payload_size' do
259
+ subject.multi_receive([
260
+ LogStash::Event.new({ 'event' => '🤣' * 400 }),
261
+ LogStash::Event.new({ 'event' => 'n' * 600 }),
262
+ LogStash::Event.new({ 'event' => 'n' * 400 }),
263
+ ])
264
+ expect(subject).to have_received(:send_event).exactly(1).times do |s|
265
+ expect(s.bytesize).to be <= max_payload_size
266
+ end
246
267
  end
247
268
  end
248
269
 
@@ -347,7 +368,7 @@ describe LogStash::Outputs::Dynatrace do
347
368
  end
348
369
 
349
370
  let(:last_request) { TestApp.last_request }
350
- let(:body) { last_request.body.read }
371
+ let(:body) { read_last_request_body(last_request) }
351
372
  let(:content_type) { last_request.env['CONTENT_TYPE'] }
352
373
 
353
374
  it 'should receive the request' do
@@ -409,11 +430,11 @@ describe LogStash::Outputs::Dynatrace do
409
430
  include_examples('integration tests')
410
431
  end
411
432
 
412
- # describe "integration test with gzip compression" do
413
- # include_examples("integration tests") do
414
- # let(:base_config) { { "http_compression" => true } }
415
- # end
416
- # end
433
+ describe "integration test with gzip compression" do
434
+ include_examples("integration tests") do
435
+ let(:base_config) { { "http_compression" => true } }
436
+ end
437
+ end
417
438
 
418
439
  describe 'retryable error in termination' do
419
440
  let(:ingest_endpoint_url) { "http://localhost:#{port - 1}/invalid" }
@@ -437,10 +458,10 @@ describe LogStash::Outputs::Dynatrace do
437
458
  end
438
459
 
439
460
  RSpec.describe LogStash::Outputs::Dynatrace do # different block as we're starting web server with TLS
440
- @@default_server_settings = TestApp.server_settings.dup
461
+ let(:default_server_settings) { TestApp.server_settings.dup }
441
462
 
442
463
  before do
443
- TestApp.server_settings = @@default_server_settings.merge(webrick_config)
464
+ TestApp.server_settings = default_server_settings.merge(webrick_config)
444
465
 
445
466
  TestApp.last_request = nil
446
467
 
@@ -465,7 +486,7 @@ RSpec.describe LogStash::Outputs::Dynatrace do # different block as we're starti
465
486
  rescue StandardError
466
487
  nil
467
488
  end
468
- TestApp.server_settings = @@default_server_settings
489
+ TestApp.server_settings = default_server_settings
469
490
  end
470
491
 
471
492
  let(:ssl_cert_host) { 'localhost' }
@@ -483,7 +504,7 @@ RSpec.describe LogStash::Outputs::Dynatrace do # different block as we're starti
483
504
  after { subject.close }
484
505
 
485
506
  let(:last_request) { TestApp.last_request }
486
- let(:last_request_body) { last_request.body.read }
507
+ let(:last_request_body) { read_last_request_body(last_request) }
487
508
 
488
509
  let(:event) { LogStash::Event.new('message' => 'hello!') }
489
510
 
@@ -550,3 +571,10 @@ RSpec.describe LogStash::Outputs::Dynatrace do # different block as we're starti
550
571
  end
551
572
  end
552
573
  end
574
+
575
+ # Pre-emptively rewind the retrieval of `last_request.body` - for form based endpoints, the body
576
+ # is placed in params, and body is empty, requiring a `rewind` for the body to be available for comparison
577
+ def read_last_request_body(last_request)
578
+ last_request.body.rewind
579
+ last_request.body.read
580
+ end
data/spec/spec_helper.rb CHANGED
@@ -23,6 +23,8 @@ require 'webrick'
23
23
  require 'webrick/https'
24
24
  require 'openssl'
25
25
 
26
+ require "supports/compressed_requests"
27
+
26
28
  PORT = rand(65_535 - 1024) + 1025
27
29
 
28
30
  module LogStash
@@ -52,6 +54,9 @@ end
52
54
  # == Sinatra has ended his set (crowd applauds)
53
55
  #
54
56
  class TestApp < Sinatra::Base
57
+ # on the fly uncompress gzip content
58
+ use CompressedRequests
59
+
55
60
  set :environment, :production
56
61
  set :sessions, false
57
62
 
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ #
3
+ # based on relistan's rack handler
4
+ # out of the box rack only gives use the rack deflater handler to return compressed
5
+ # response, this gist offer the inverse and should work on all rack based app like sinatra or rails.
6
+ #
7
+ # original source: https://gist.github.com/relistan/2109707
8
+ require "zlib"
9
+
10
+ class CompressedRequests
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def method_handled?(env)
16
+ ['POST', 'PUT'].include? env['REQUEST_METHOD']
17
+ end
18
+
19
+ def encoding_handled?(env)
20
+ ['gzip', 'deflate'].include? env['HTTP_CONTENT_ENCODING']
21
+ end
22
+
23
+ def call(env)
24
+ if method_handled?(env) && encoding_handled?(env)
25
+ extracted = decode(env['rack.input'], env['HTTP_CONTENT_ENCODING'])
26
+
27
+ env.delete('HTTP_CONTENT_ENCODING')
28
+ env['CONTENT_LENGTH'] = extracted.bytesize
29
+ env['rack.input'] = StringIO.new(extracted)
30
+ end
31
+
32
+ status, headers, response = @app.call(env)
33
+ return [status, headers, response]
34
+ end
35
+
36
+ def decode(input, content_encoding)
37
+ case content_encoding
38
+ when 'gzip' then Zlib::GzipReader.new(input).read
39
+ when 'deflate' then Zlib::Inflate.inflate(input.read)
40
+ end
41
+ end
42
+ end
data/version.yaml CHANGED
@@ -1 +1 @@
1
- logstash-output-dynatrace: '0.6.0'
1
+ logstash-output-dynatrace: '0.7.1'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-dynatrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dynatrace Open Source Engineering
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-27 00:00:00.000000000 Z
11
+ date: 2025-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logstash-codec-json
@@ -148,6 +148,20 @@ dependencies:
148
148
  - - '='
149
149
  - !ruby/object:Gem::Version
150
150
  version: 0.5.1
151
+ - !ruby/object:Gem::Dependency
152
+ name: rackup
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "<"
156
+ - !ruby/object:Gem::Version
157
+ version: 2.1.0
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "<"
163
+ - !ruby/object:Gem::Version
164
+ version: 2.1.0
151
165
  description: |2
152
166
  This gem is a Logstash plugin required to be installed on top of the Logstash
153
167
  core pipeline using `$LS_HOME/bin/logstash-plugin install logstash-output-dynatrace`.
@@ -168,6 +182,7 @@ files:
168
182
  - logstash-output-dynatrace.gemspec
169
183
  - spec/outputs/dynatrace_spec.rb
170
184
  - spec/spec_helper.rb
185
+ - spec/supports/compressed_requests.rb
171
186
  - version.yaml
172
187
  homepage: https://github.com/dynatrace-oss/logstash-output-dynatrace
173
188
  licenses:
@@ -175,7 +190,7 @@ licenses:
175
190
  metadata:
176
191
  logstash_plugin: 'true'
177
192
  logstash_group: output
178
- post_install_message:
193
+ post_install_message:
179
194
  rdoc_options: []
180
195
  require_paths:
181
196
  - lib
@@ -191,10 +206,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
206
  version: '0'
192
207
  requirements: []
193
208
  rubygems_version: 3.1.6
194
- signing_key:
209
+ signing_key:
195
210
  specification_version: 4
196
211
  summary: A logstash output plugin for sending logs to the Dynatrace Generic log ingest
197
212
  API v2
198
213
  test_files:
199
214
  - spec/outputs/dynatrace_spec.rb
200
215
  - spec/spec_helper.rb
216
+ - spec/supports/compressed_requests.rb