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 +4 -4
- data/CHANGELOG.md +3 -0
- data/lib/logstash/outputs/dynatrace.rb +19 -24
- data/logstash-output-dynatrace.gemspec +0 -3
- data/spec/outputs/dynatrace_spec.rb +51 -40
- data/version.rb +1 -1
- metadata +2 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aefa283dfb7437921963fafa7f068d82363dd421506ebab8b146026f5ca7e30c
|
4
|
+
data.tar.gz: d8843b75b161044bbc20cd6ee4b6f28777ff98bd2cab487e669240d662ce3e4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ef069a726a4765d9d4395156ab4bfa13ddaf5aa83fe6934ffd8e610af6330f6ecca44a16de538d85551b7c416b8fd97340907a5b6ad6cdf1c8a0abf0b5fb2a2
|
7
|
+
data.tar.gz: 244956ca7a924de908e05987eb5d13392c007c864f4462b51914ff766db8784aefc7e1b24eb52ff68a747e530f22f5233bb1af155a4ca461f8adce61dee605e9
|
data/CHANGELOG.md
CHANGED
@@ -19,7 +19,7 @@ require 'logstash/outputs/base'
|
|
19
19
|
require 'logstash/json'
|
20
20
|
|
21
21
|
MAX_RETRIES = 5
|
22
|
-
PLUGIN_VERSION = '0.
|
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
|
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 =
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
@logger.error("
|
90
|
-
|
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
|
-
|
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
|
117
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
68
|
+
expect(client).to receive(:request) do |req|
|
64
69
|
expect(req['Authorization']).to eql("Api-Token #{key}")
|
65
|
-
|
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
|
-
|
76
|
+
expect(client).to receive(:request) do |req|
|
73
77
|
expect(req['Content-Type']).to eql('application/json; charset=utf-8')
|
74
|
-
|
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
|
-
|
82
|
-
expect(req['User-Agent']).to eql("logstash-output-dynatrace
|
83
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
103
|
-
it '
|
104
|
-
|
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
|
111
|
-
it '
|
112
|
-
allow(subject).to receive(:
|
113
|
-
|
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
|
-
|
118
|
-
expect(
|
119
|
-
|
120
|
-
expect(
|
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
|
124
|
-
|
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
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.
|
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-
|
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
|