logstash-output-dynatrace 0.2.1 → 0.3.0

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: 0a8f5b5423d993317f8f73d5416c7c17b2961c833ca95da455f2f4742bcbe81b
4
- data.tar.gz: 249eaf43d553a14a30ee84328514d6771acba98c8ef4da3bee738169b73008ec
3
+ metadata.gz: aefa283dfb7437921963fafa7f068d82363dd421506ebab8b146026f5ca7e30c
4
+ data.tar.gz: d8843b75b161044bbc20cd6ee4b6f28777ff98bd2cab487e669240d662ce3e4a
5
5
  SHA512:
6
- metadata.gz: bdb3df6d43d9e2361958b925830af0ec1f686fa02d2d9adbd6488e063e890885aae42589cf5d60e4a2b53c89b6d4a121e9dff1b3500fe5773c1a1cf8203d415a
7
- data.tar.gz: 20525e9f0cea6acb310656de596404e086ac755508dfa6d603a0d7b6f1dfb030c36ec32db60cf89fbe7942732c14dd2c0fbea375df5874c981d5805c16ce67a3
6
+ metadata.gz: 4ef069a726a4765d9d4395156ab4bfa13ddaf5aa83fe6934ffd8e610af6330f6ecca44a16de538d85551b7c416b8fd97340907a5b6ad6cdf1c8a0abf0b5fb2a2
7
+ data.tar.gz: 244956ca7a924de908e05987eb5d13392c007c864f4462b51914ff766db8784aefc7e1b24eb52ff68a747e530f22f5233bb1af155a4ca461f8adce61dee605e9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.3.0
2
+ - Log response bodies on client errors
3
+
1
4
  ## 0.2.0
2
5
  - Add retries with exponential backoff (#8)
3
6
 
@@ -19,7 +19,7 @@ require 'logstash/outputs/base'
19
19
  require 'logstash/json'
20
20
 
21
21
  MAX_RETRIES = 5
22
- PLUGIN_VERSION = '0.2.1'
22
+ PLUGIN_VERSION = '0.3.0'
23
23
 
24
24
  module LogStash
25
25
  module Outputs
@@ -61,7 +61,7 @@ module LogStash
61
61
 
62
62
  def headers
63
63
  {
64
- 'User-Agent' => "logstash-output-dynatrace v#{PLUGIN_VERSION}",
64
+ 'User-Agent' => "logstash-output-dynatrace/#{PLUGIN_VERSION}",
65
65
  'Content-Type' => 'application/json; charset=utf-8',
66
66
  'Authorization' => "Api-Token #{@api_key}"
67
67
  }
@@ -76,29 +76,26 @@ module LogStash
76
76
  begin
77
77
  request = Net::HTTP::Post.new(uri, headers)
78
78
  request.body = "#{LogStash::Json.dump(events.map(&:to_hash)).chomp}\n"
79
- response = send(request)
80
- return if response.is_a? Net::HTTPSuccess
81
-
82
- failure_message = "Dynatrace returned #{response.code} #{response.message}."
83
-
84
- if response.is_a? Net::HTTPServerError
85
- raise RetryableError.new failure_message
86
- end
87
-
88
- if response.is_a? Net::HTTPNotFound
89
- @logger.error("#{failure_message} Please check that log ingest is enabled and your API token has the `logs.ingest` (Ingest Logs) scope.")
90
- return
79
+ response = @client.request(request)
80
+
81
+ case response
82
+ when Net::HTTPSuccess
83
+ @logger.debug("successfully sent #{events.length} events#{" with #{retries} retries" if retries > 0}")
84
+ when Net::HTTPServerError
85
+ @logger.error("Encountered an HTTP server error", :message => response.message, :code => response.code, :body => response.body) if retries == 0
86
+ when Net::HTTPNotFound
87
+ @logger.error("Encountered a 404 Not Found error. Please check that log ingest is enabled and your API token has the `logs.ingest` (Ingest Logs) scope.", :message => response.message, :code => response.code)
88
+ when Net::HTTPClientError
89
+ @logger.error("Encountered an HTTP client error", :message => response.message, :code => response.code, :body => response.body)
90
+ else
91
+ @logger.error("Encountered an unexpected response code", :message => response.message, :code => response.code)
91
92
  end
92
93
 
93
- if response.is_a? Net::HTTPClientError
94
- @logger.error(failure_message)
95
- return
96
- end
94
+ raise RetryableError.new "code #{response.code}" if retryable(response)
97
95
 
98
- @logger.debug("successfully sent #{events.length} events")
99
96
  rescue Net::HTTPBadResponse, RetryableError => e
100
97
  # indicates a protocol error
101
- if retries < MAX_RETRIES
98
+ if retries < MAX_RETRIES
102
99
  sleep_seconds = 2 ** retries
103
100
  @logger.warn("Failed to contact dynatrace: #{e.message}. Trying again after #{sleep_seconds} seconds.")
104
101
  sleep sleep_seconds
@@ -109,12 +106,10 @@ module LogStash
109
106
  return
110
107
  end
111
108
  end
112
-
113
- @logger.debug("Successfully exported #{events.length} events with #{retries} retries")
114
109
  end
115
110
 
116
- def send(request)
117
- @client.request(request)
111
+ def retryable(response)
112
+ return response.is_a? Net::HTTPServerError
118
113
  end
119
114
  end
120
115
  end
@@ -43,11 +43,8 @@ Gem::Specification.new do |s|
43
43
  s.add_runtime_dependency 'logstash-codec-json'
44
44
  s.add_runtime_dependency 'logstash-core-plugin-api', '>= 2.0.0', '< 3'
45
45
 
46
- s.add_development_dependency 'insist'
47
46
  s.add_development_dependency 'logstash-devutils'
48
47
  s.add_development_dependency 'logstash-input-generator'
49
- s.add_development_dependency 'sinatra'
50
- s.add_development_dependency 'webrick'
51
48
 
52
49
  s.add_development_dependency 'rubocop', '1.9.1'
53
50
  s.add_development_dependency 'rubocop-rake', '0.5.1'
@@ -18,8 +18,6 @@ require_relative '../spec_helper'
18
18
  require_relative '../../version'
19
19
  require 'logstash/codecs/plain'
20
20
  require 'logstash/event'
21
- require 'sinatra'
22
- require 'insist'
23
21
  require 'net/http'
24
22
  require 'json'
25
23
 
@@ -32,96 +30,109 @@ describe LogStash::Outputs::Dynatrace do
32
30
  end
33
31
  let(:url) { "http://localhost/good" }
34
32
  let(:key) { 'api.key' }
33
+
35
34
  let(:subject) { LogStash::Outputs::Dynatrace.new({ 'api_key' => key, 'ingest_endpoint_url' => url }) }
35
+ let(:client) { subject.instance_variable_get(:@client) }
36
+
37
+ let(:ok) { Net::HTTPOK.new "1.1", "200", "OK" }
38
+ let(:server_error) { Net::HTTPServerError.new "1.1", "500", "Internal Server Error" }
39
+ let(:client_error) { Net::HTTPClientError.new("1.1", '400', 'Client error') }
40
+ let(:not_found) { Net::HTTPNotFound.new "1.1", "404", "Not Found" }
41
+
42
+ let(:body) { "this is a failure" }
36
43
 
37
44
  before do
38
45
  subject.register
39
46
  end
40
47
 
41
48
  it 'does not send empty events' do
42
- allow(subject).to receive(:send)
49
+ expect(client).to_not receive(:request)
43
50
  subject.multi_receive([])
44
- expect(subject).to_not have_received(:send)
45
51
  end
46
52
 
47
53
  context 'server response success' do
48
54
  it 'sends events' do
49
- allow(subject).to receive(:send) do |req|
55
+ expect(client).to receive(:request) do |req|
50
56
  body = JSON.parse(req.body)
51
57
  expect(body.length).to eql(2)
52
58
  expect(body[0]['message']).to eql('message 1')
53
59
  expect(body[0]['@timestamp']).to eql('2021-06-25T15:46:45.693Z')
54
60
  expect(body[1]['message']).to eql('message 2')
55
61
  expect(body[1]['@timestamp']).to eql('2021-06-25T15:46:46.693Z')
56
- Net::HTTPOK.new "1.1", "200", "OK"
62
+ ok
57
63
  end
58
64
  subject.multi_receive(events)
59
- expect(subject).to have_received(:send)
60
65
  end
61
66
 
62
67
  it 'includes authorization header' do
63
- allow(subject).to receive(:send) do |req|
68
+ expect(client).to receive(:request) do |req|
64
69
  expect(req['Authorization']).to eql("Api-Token #{key}")
65
- Net::HTTPOK.new "1.1", "200", "OK"
70
+ ok
66
71
  end
67
72
  subject.multi_receive(events)
68
- expect(subject).to have_received(:send)
69
73
  end
70
74
 
71
75
  it 'includes content type header' do
72
- allow(subject).to receive(:send) do |req|
76
+ expect(client).to receive(:request) do |req|
73
77
  expect(req['Content-Type']).to eql('application/json; charset=utf-8')
74
- Net::HTTPOK.new "1.1", "200", "OK"
78
+ ok
75
79
  end
76
80
  subject.multi_receive(events)
77
- expect(subject).to have_received(:send)
78
81
  end
79
82
 
80
83
  it 'includes user agent' do
81
- allow(subject).to receive(:send) do |req|
82
- expect(req['User-Agent']).to eql("logstash-output-dynatrace v#{::DynatraceConstants::VERSION}")
83
- Net::HTTPOK.new "1.1", "200", "OK"
84
+ expect(client).to receive(:request) do |req|
85
+ expect(req['User-Agent']).to eql("logstash-output-dynatrace/#{::DynatraceConstants::VERSION}")
86
+ ok
84
87
  end
85
88
  subject.multi_receive(events)
86
- expect(subject).to have_received(:send)
87
89
  end
88
90
 
89
91
  it 'does not log on success' do
90
92
  allow(subject.logger).to receive(:debug)
91
- allow(subject.logger).to receive(:info) { raise "should not log" }
92
- allow(subject.logger).to receive(:error) { raise "should not log" }
93
- allow(subject.logger).to receive(:warn) { raise "should not log" }
94
- allow(subject).to receive(:send) do |req|
95
- Net::HTTPOK.new "1.1", "200", "OK"
96
- end
93
+ expect(subject.logger).to_not receive(:info)
94
+ expect(subject.logger).to_not receive(:error)
95
+ expect(subject.logger).to_not receive(:warn)
96
+ expect(client).to receive(:request) { ok }
97
97
  subject.multi_receive(events)
98
- expect(subject).to have_received(:send)
99
98
  end
100
99
  end
101
100
 
102
- context 'with bad client request' do
103
- it 'does not retry on 404' do
104
- allow(subject).to receive(:send) { Net::HTTPNotFound.new "1.1", "404", "Not Found" }
101
+ context 'with server error' do
102
+ it 'retries 5 times with exponential backoff' do
103
+ # This prevents the elusive "undefined method `close' for nil:NilClass" error.
104
+ expect(server_error).to receive(:body) { body }.once
105
+ expect(subject.logger).to receive(:error).with("Encountered an HTTP server error", {:body=>body, :code=>"500", :message=> "Internal Server Error"}).once
106
+ expect(client).to receive(:request) { server_error }.exactly(6).times
107
+
108
+
109
+ expect(subject).to receive(:sleep).with(1).ordered
110
+ expect(subject).to receive(:sleep).with(2).ordered
111
+ expect(subject).to receive(:sleep).with(4).ordered
112
+ expect(subject).to receive(:sleep).with(8).ordered
113
+ expect(subject).to receive(:sleep).with(16).ordered
114
+
115
+ expect(subject.logger).to receive(:error).with("Failed to export logs to Dynatrace.")
105
116
  subject.multi_receive(events)
106
- expect(subject).to have_received(:send).once
107
117
  end
108
118
  end
109
119
 
110
- context 'with server error' do
111
- it 'retries 5 times with exponential backoff' do
112
- allow(subject).to receive(:sleep)
113
- allow(subject).to receive(:send) { Net::HTTPInternalServerError.new "1.1", "500", "Internal Server Error" }
114
-
120
+ context 'with client error' do
121
+ it 'does not retry on 404' do
122
+ allow(subject.logger).to receive(:error)
123
+ expect(client).to receive(:request) { not_found }.once
115
124
  subject.multi_receive(events)
125
+ end
116
126
 
117
- expect(subject).to have_received(:sleep).with(1).ordered
118
- expect(subject).to have_received(:sleep).with(2).ordered
119
- expect(subject).to have_received(:sleep).with(4).ordered
120
- expect(subject).to have_received(:sleep).with(8).ordered
121
- expect(subject).to have_received(:sleep).with(16).ordered
127
+ it 'logs the response body' do
128
+ expect(client).to receive(:request) { client_error }
129
+ # This prevents the elusive "undefined method `close' for nil:NilClass" error.
130
+ expect(client_error).to receive(:body) { body }
122
131
 
123
- expect(subject).to have_received(:sleep).exactly(5).times
124
- expect(subject).to have_received(:send).exactly(6).times
132
+ expect(subject.logger).to receive(:error).with("Encountered an HTTP client error",
133
+ {:body=>body, :code=>"400", :message=> "Client error"})
134
+
135
+ subject.multi_receive(events)
125
136
  end
126
137
  end
127
138
  end
data/version.rb CHANGED
@@ -16,5 +16,5 @@
16
16
 
17
17
  module DynatraceConstants
18
18
  # Also required to change the version in lib/logstash/outputs/dynatrace.rb
19
- VERSION = '0.2.1'
19
+ VERSION = '0.3.0'
20
20
  end
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dynatrace Open Source Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-14 00:00:00.000000000 Z
11
+ date: 2022-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logstash-codec-json
@@ -44,20 +44,6 @@ dependencies:
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '3'
47
- - !ruby/object:Gem::Dependency
48
- name: insist
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
47
  - !ruby/object:Gem::Dependency
62
48
  name: logstash-devutils
63
49
  requirement: !ruby/object:Gem::Requirement
@@ -86,34 +72,6 @@ dependencies:
86
72
  - - ">="
87
73
  - !ruby/object:Gem::Version
88
74
  version: '0'
89
- - !ruby/object:Gem::Dependency
90
- name: sinatra
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: '0'
96
- type: :development
97
- prerelease: false
98
- version_requirements: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - ">="
101
- - !ruby/object:Gem::Version
102
- version: '0'
103
- - !ruby/object:Gem::Dependency
104
- name: webrick
105
- requirement: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- type: :development
111
- prerelease: false
112
- version_requirements: !ruby/object:Gem::Requirement
113
- requirements:
114
- - - ">="
115
- - !ruby/object:Gem::Version
116
- version: '0'
117
75
  - !ruby/object:Gem::Dependency
118
76
  name: rubocop
119
77
  requirement: !ruby/object:Gem::Requirement