logstash-output-dynatrace 0.2.1 → 0.3.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: 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