fluent-plugin-splunk-hec 1.1.0 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +101 -29
- data/LICENSE +73 -5
- data/README.md +130 -77
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/fluent-plugin-splunk-hec.gemspec +10 -5
- data/lib/fluent/plugin/out_splunk.rb +313 -0
- data/lib/fluent/plugin/{out_splunk_hec → out_splunk}/match_formatter.rb +5 -3
- data/lib/fluent/plugin/out_splunk/version.rb +3 -0
- data/lib/fluent/plugin/out_splunk_hec.rb +144 -194
- data/lib/fluent/plugin/out_splunk_hec/version.rb +2 -0
- data/lib/fluent/plugin/out_splunk_ingest_api.rb +112 -0
- data/test/fluent/plugin/out_splunk_hec_test.rb +227 -225
- data/test/fluent/plugin/out_splunk_ingest_api_test.rb +244 -0
- data/test/test_helper.rb +10 -7
- metadata +73 -28
- data/test/lib/webmock/http_lib_adapters/httpclient_adapter.rb +0 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$LOAD_PATH.unshift(File.expand_path('..', __dir__))
|
3
|
+
require 'fluent/plugin/out_splunk'
|
4
|
+
require 'openid_connect'
|
5
|
+
require 'rack/oauth2'
|
6
|
+
require 'multi_json'
|
7
|
+
|
8
|
+
module Fluent::Plugin
|
9
|
+
class SplunkIngestApiOutput < SplunkOutput
|
10
|
+
Fluent::Plugin.register_output('splunk_ingest_api', self)
|
11
|
+
|
12
|
+
desc 'Service Client Identifier'
|
13
|
+
config_param :service_client_identifier, :string, default: nil
|
14
|
+
|
15
|
+
desc 'Service Client Secret Key'
|
16
|
+
config_param :service_client_secret_key, :string, default: nil
|
17
|
+
|
18
|
+
desc 'Token Endpoint'
|
19
|
+
config_param :token_endpoint, :string, default: '/token'
|
20
|
+
|
21
|
+
desc 'Token Auth Hostname'
|
22
|
+
config_param :ingest_auth_host, :string, default: 'auth.scp.splunk.com'
|
23
|
+
|
24
|
+
desc 'Ingest Api Hostname'
|
25
|
+
config_param :ingest_api_host, :string, default: 'api.scp.splunk.com'
|
26
|
+
|
27
|
+
desc 'Ingest API Tenant Name'
|
28
|
+
config_param :ingest_api_tenant, :string
|
29
|
+
|
30
|
+
desc 'Ingest API Events Endpoint'
|
31
|
+
config_param :ingest_api_events_endpoint, :string, default: '/ingest/v1beta2/events'
|
32
|
+
|
33
|
+
desc 'Debug the HTTP transport'
|
34
|
+
config_param :debug_http, :bool, default: false
|
35
|
+
|
36
|
+
def prefer_buffer_processing
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def configure(conf)
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
def construct_api
|
45
|
+
uri = "https://#{@ingest_api_host}/#{@ingest_api_tenant}#{@ingest_api_events_endpoint}"
|
46
|
+
URI(uri)
|
47
|
+
rescue StandardError
|
48
|
+
raise Fluent::ConfigError, "URI #{uri} is invalid"
|
49
|
+
end
|
50
|
+
|
51
|
+
def format(tag, time, record)
|
52
|
+
format_event(tag, time, record)
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_event(tag, time, record)
|
56
|
+
event = prepare_event_payload(tag, time, record)
|
57
|
+
# Unsure how to drop a record. So append the empty string
|
58
|
+
if event[:body].nil? || event[:body].strip.empty?
|
59
|
+
''
|
60
|
+
else
|
61
|
+
MultiJson.dump(event) + ','
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def prepare_event_payload(tag, time, record)
|
66
|
+
payload = super(tag, time, record)
|
67
|
+
payload[:attributes] = payload.delete(:fields) || {}
|
68
|
+
payload[:attributes][:index] = payload.delete(:index) if payload[:index]
|
69
|
+
payload[:body] = payload.delete(:event)
|
70
|
+
payload.delete(:time)
|
71
|
+
payload[:timestamp] = (time.to_f * 1000).to_i
|
72
|
+
payload[:nanos] = time.nsec / 100_000
|
73
|
+
|
74
|
+
payload
|
75
|
+
end
|
76
|
+
|
77
|
+
def process_response(response, request_body)
|
78
|
+
super
|
79
|
+
if response.code.to_s == '401'
|
80
|
+
@conn = new_connection
|
81
|
+
raise 'Auth Error recived. New token has been fetched.'
|
82
|
+
elsif response.code.to_s == '429'
|
83
|
+
raise "Throttle error from server. #{response.body}"
|
84
|
+
elsif /INVALID_DATA/.match?(response.body)
|
85
|
+
log.error "#{self.class}: POST Body #{request_body}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def new_connection
|
90
|
+
Rack::OAuth2.debugging = true if @debug_http
|
91
|
+
client = OpenIDConnect::Client.new(
|
92
|
+
token_endpoint: @token_endpoint,
|
93
|
+
identifier: @service_client_identifier,
|
94
|
+
secret: @service_client_secret_key,
|
95
|
+
redirect_uri: 'http://localhost:8080/', # Not used
|
96
|
+
host: @ingest_auth_host,
|
97
|
+
scheme: 'https'
|
98
|
+
)
|
99
|
+
|
100
|
+
client.access_token!(client_auth_method: 'other')
|
101
|
+
end
|
102
|
+
|
103
|
+
def write_to_splunk(chunk)
|
104
|
+
log.trace "#{self.class}: In write() with #{chunk.size_of_events} records and #{chunk.bytesize} bytes "
|
105
|
+
# ingest API is an array of json objects
|
106
|
+
body = "[#{chunk.read.chomp(',')}]"
|
107
|
+
@conn ||= new_connection
|
108
|
+
response = @conn.post("https://#{@ingest_api_host}/#{@ingest_api_tenant}#{@ingest_api_events_endpoint}", body: body)
|
109
|
+
process_response(response, body)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -1,334 +1,336 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
2
4
|
|
3
5
|
describe Fluent::Plugin::SplunkHecOutput do
|
4
6
|
include Fluent::Test::Helpers
|
5
7
|
include PluginTestHelper
|
6
8
|
|
7
9
|
before { Fluent::Test.setup } # setup router and others
|
8
|
-
|
10
|
+
|
9
11
|
it { expect(::Fluent::Plugin::SplunkHecOutput::VERSION).wont_be_nil }
|
10
12
|
|
11
|
-
describe
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
13
|
+
describe 'config param tests' do
|
14
|
+
it 'should require https protocol' do
|
15
|
+
expect(create_hec_output_driver('hec_host protocol').instance.protocol).must_equal :https
|
16
|
+
end
|
17
|
+
it 'should require hec_host' do
|
18
|
+
expect(create_hec_output_driver('hec_host hec_host').instance.hec_host).must_equal 'hec_host'
|
19
|
+
end
|
20
|
+
it 'should require hec_port' do
|
21
|
+
expect(create_hec_output_driver('hec_host hec_port').instance.hec_port).must_equal 8088
|
22
|
+
end
|
23
|
+
it 'should require hec_token' do
|
24
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.hec_token).must_equal 'some-token'
|
25
|
+
end
|
26
|
+
it 'should define client_cert as nil initially' do
|
27
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.client_cert)
|
28
|
+
end
|
29
|
+
it 'should define client_key as nil (string) initially' do
|
30
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.client_key)
|
31
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.client_key).is_a? String
|
32
|
+
end
|
33
|
+
it 'should define ca_file as nil (string) initially' do
|
34
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.ca_file)
|
35
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.ca_file).is_a? String
|
36
|
+
end
|
37
|
+
it 'should define ca_path as nil (string) initially' do
|
38
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.ca_path)
|
39
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.ca_path).is_a? String
|
40
|
+
end
|
41
|
+
it 'should define ssl_ciphers as nil (array) initially' do
|
42
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.ssl_ciphers)
|
43
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.ssl_ciphers).is_a? Array
|
44
|
+
end
|
45
|
+
it 'should not allow an insecure ssl connection' do
|
46
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.insecure_ssl).must_equal false
|
47
|
+
end
|
48
|
+
it 'should allow both event (default) and metric to be sent to splunk' do
|
49
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.data_type).must_equal :event
|
50
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.data_type = :metric).must_equal :metric
|
51
|
+
end
|
52
|
+
it 'should define Splunk index to index (string) as nil initially' do
|
53
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.index)
|
54
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.index).is_a? String
|
55
|
+
end
|
56
|
+
it 'should define field names to include Splunk index_key as nil (string) initially' do
|
57
|
+
assert_nil(create_hec_output_driver('hec_host hec_token').instance.index_key)
|
58
|
+
expect(create_hec_output_driver('hec_host hec_token').instance.index_key).is_a? String
|
59
|
+
end
|
58
60
|
end
|
59
61
|
|
60
|
-
describe
|
61
|
-
describe
|
62
|
-
it
|
63
|
-
|
62
|
+
describe 'hec_host validation' do
|
63
|
+
describe 'invalid host' do
|
64
|
+
it 'should require hec_host' do
|
65
|
+
expect { create_hec_output_driver }.must_raise Fluent::ConfigError
|
64
66
|
end
|
65
67
|
|
66
|
-
it { expect{
|
68
|
+
it { expect { create_hec_output_driver('hec_host %bad-host%') }.must_raise Fluent::ConfigError }
|
67
69
|
end
|
68
70
|
|
69
|
-
describe
|
71
|
+
describe 'good host' do
|
70
72
|
it {
|
71
|
-
|
73
|
+
expect(create_hec_output_driver('hec_host splunk.com').instance.hec_host).must_equal 'splunk.com'
|
72
74
|
}
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
|
-
it
|
77
|
-
req = verify_sent_events
|
78
|
+
it 'should send request to Splunk' do
|
79
|
+
req = verify_sent_events do |batch|
|
78
80
|
expect(batch.size).must_equal 2
|
79
|
-
|
81
|
+
end
|
80
82
|
expect(req).must_be_requested times: 1
|
81
83
|
end
|
82
84
|
|
83
|
-
it
|
84
|
-
verify_sent_events
|
85
|
+
it 'should use string for event time, and the value of the string should be a float' do
|
86
|
+
verify_sent_events do |batch|
|
85
87
|
batch.each do |item|
|
86
88
|
expect(item['time']).must_be_instance_of String
|
87
89
|
expect(item['time']).must_match /^\d+\.\d+$/
|
88
90
|
end
|
89
|
-
|
91
|
+
end
|
90
92
|
end
|
91
93
|
|
92
94
|
# it "should contain splunk event time field via fluentd, as nil" do
|
93
|
-
# expect(
|
95
|
+
# expect(create_hec_output_driver('hec_host splunk.com').instance.time_key).must_equal nil
|
94
96
|
# end
|
95
97
|
#
|
96
|
-
it
|
97
|
-
|
98
|
-
|
98
|
+
it 'should contain splunk event time field via fluentd, as nil' do
|
99
|
+
test_driver = create_hec_output_driver('hec_host splunk.com')
|
100
|
+
assert_nil(test_driver.instance.time_key)
|
99
101
|
end
|
100
102
|
|
101
103
|
it "should use host machine's hostname for event host by default" do
|
102
|
-
verify_sent_events
|
104
|
+
verify_sent_events do |batch|
|
103
105
|
batch.each do |item|
|
104
|
-
|
106
|
+
expect(item['host']).must_equal Socket.gethostname
|
105
107
|
end
|
106
|
-
|
108
|
+
end
|
107
109
|
end
|
108
110
|
|
109
111
|
%w[index source sourcetype].each do |field|
|
110
112
|
it "should not set #{field} by default" do
|
111
|
-
verify_sent_events
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
verify_sent_events do |batch|
|
114
|
+
batch.each do |item|
|
115
|
+
expect(item).wont_include field
|
116
|
+
end
|
117
|
+
end
|
116
118
|
end
|
117
119
|
end
|
118
120
|
|
119
|
-
it
|
120
|
-
verify_sent_events(<<~CONF)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
121
|
+
it 'should support ${tag}' do
|
122
|
+
verify_sent_events(<<~CONF) do |batch|
|
123
|
+
index ${tag}
|
124
|
+
host ${tag}
|
125
|
+
source ${tag}
|
126
|
+
sourcetype ${tag}
|
125
127
|
CONF
|
126
128
|
batch.each do |item|
|
127
|
-
|
128
|
-
|
129
|
-
|
129
|
+
%w[index host source sourcetype].each do |field|
|
130
|
+
expect(%w[tag.event1 tag.event2]).must_include item[field]
|
131
|
+
end
|
130
132
|
end
|
131
|
-
|
133
|
+
end
|
132
134
|
end
|
133
135
|
|
134
|
-
it
|
135
|
-
verify_sent_events(<<~CONF)
|
136
|
+
it 'should support *_key' do
|
137
|
+
verify_sent_events(<<~CONF) do |batch|
|
136
138
|
index_key level
|
137
139
|
host_key from
|
138
140
|
source_key file
|
139
141
|
sourcetype_key agent.name
|
140
142
|
CONF
|
141
|
-
batch.each
|
143
|
+
batch.each do |item|
|
142
144
|
expect(item['index']).must_equal 'info'
|
143
145
|
expect(item['host']).must_equal 'my_machine'
|
144
146
|
expect(item['source']).must_equal 'cool.log'
|
145
147
|
expect(item['sourcetype']).must_equal 'test'
|
146
148
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
149
|
+
JSON.load(item['event']).tap do |event|
|
150
|
+
%w[level from file].each { |field| expect(event).wont_include field }
|
151
|
+
expect(event['agent']).wont_include 'name'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
153
155
|
end
|
154
156
|
|
155
|
-
it
|
156
|
-
verify_sent_events(<<~CONF)
|
157
|
+
it 'should remove nil fileds.' do
|
158
|
+
verify_sent_events(<<~CONF) do |batch|
|
157
159
|
index_key nonexist
|
158
160
|
host_key nonexist
|
159
161
|
source_key nonexist
|
160
162
|
sourcetype_key nonexist
|
161
163
|
CONF
|
162
|
-
batch.each
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
164
|
+
batch.each do |item|
|
165
|
+
expect(item).wont_be :has_key?, 'index'
|
166
|
+
expect(item).wont_be :has_key?, 'host'
|
167
|
+
expect(item).wont_be :has_key?, 'source'
|
168
|
+
expect(item).wont_be :has_key?, 'sourcetype'
|
169
|
+
end
|
170
|
+
end
|
169
171
|
end
|
170
172
|
|
171
173
|
describe 'formatter' do
|
172
|
-
it
|
173
|
-
verify_sent_events(<<~CONF)
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
174
|
+
it 'should support replace the default json formater' do
|
175
|
+
verify_sent_events(<<~CONF) do |batch|
|
176
|
+
<format>
|
177
|
+
@type single_value
|
178
|
+
message_key log
|
179
|
+
add_newline false
|
180
|
+
</format>
|
179
181
|
CONF
|
180
|
-
|
181
|
-
|
182
|
-
|
182
|
+
batch.map { |item| item['event'] }
|
183
|
+
.each { |event| expect(event).must_equal 'everything is good' }
|
184
|
+
end
|
183
185
|
end
|
184
186
|
|
185
|
-
it
|
186
|
-
verify_sent_events(<<~CONF)
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
187
|
+
it 'should support multiple formatters' do
|
188
|
+
verify_sent_events(<<~CONF) do |batch|
|
189
|
+
source ${tag}
|
190
|
+
<format tag.event1>
|
191
|
+
@type single_value
|
192
|
+
message_key log
|
193
|
+
add_newline false
|
194
|
+
</format>
|
193
195
|
CONF
|
194
|
-
|
195
|
-
|
196
|
-
|
196
|
+
expect(batch.find { |item| item['source'] == 'tag.event1' }['event']).must_equal 'everything is good'
|
197
|
+
expect(batch.find { |item| item['source'] == 'tag.event2' }['event']).must_be_instance_of Hash
|
198
|
+
end
|
197
199
|
end
|
198
200
|
end
|
199
201
|
|
200
|
-
it
|
201
|
-
verify_sent_events(<<~CONF)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
202
|
+
it 'should support fields for indexed field extraction' do
|
203
|
+
verify_sent_events(<<~CONF) do |batch|
|
204
|
+
<fields>
|
205
|
+
from
|
206
|
+
logLevel level
|
207
|
+
nonexist
|
208
|
+
</fields>
|
207
209
|
CONF
|
208
210
|
batch.each do |item|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
211
|
+
JSON.load(item['event']).tap do |event|
|
212
|
+
expect(event).wont_include 'from'
|
213
|
+
expect(event).wont_include 'level'
|
214
|
+
end
|
215
|
+
|
216
|
+
expect(item['fields']['from']).must_equal 'my_machine'
|
217
|
+
expect(item['fields']['logLevel']).must_equal 'info'
|
218
|
+
expect(item['fields']).wont_be :has_key?, 'nonexist'
|
217
219
|
end
|
218
|
-
|
220
|
+
end
|
219
221
|
end
|
220
222
|
|
221
|
-
describe 'metric'do
|
223
|
+
describe 'metric' do
|
222
224
|
it 'should check related configs' do
|
223
225
|
expect(
|
224
|
-
|
226
|
+
create_hec_output_driver('hec_host somehost', 'data_type metric')
|
225
227
|
).wont_be_nil
|
226
228
|
|
227
|
-
expect
|
228
|
-
|
229
|
-
|
229
|
+
expect do
|
230
|
+
create_hec_output_driver('hec_host somehost', 'data_type metric', 'metrics_from_event false')
|
231
|
+
end.must_raise Fluent::ConfigError
|
230
232
|
|
231
|
-
expect
|
232
|
-
|
233
|
-
|
233
|
+
expect do
|
234
|
+
create_hec_output_driver('hec_host somehost', 'data_type metric', 'metric_name_key x')
|
235
|
+
end.must_raise Fluent::ConfigError
|
234
236
|
|
235
237
|
expect(
|
236
|
-
|
238
|
+
create_hec_output_driver('hec_host somehost', 'data_type metric', 'metric_name_key x', 'metric_value_key y')
|
237
239
|
).wont_be_nil
|
238
240
|
end
|
239
241
|
|
240
242
|
it 'should have "metric" as event, and have proper fields' do
|
241
|
-
verify_sent_events(<<~CONF)
|
242
|
-
|
243
|
-
|
244
|
-
|
243
|
+
verify_sent_events(<<~CONF) do |batch|
|
244
|
+
data_type metric
|
245
|
+
metric_name_key from
|
246
|
+
metric_value_key value
|
245
247
|
CONF
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
248
|
+
batch.each do |item|
|
249
|
+
expect(item['event']).must_equal 'metric'
|
250
|
+
expect(item['fields']['metric_name']).must_equal 'my_machine'
|
251
|
+
expect(item['fields']['_value']).must_equal 100
|
252
|
+
expect(item['fields']['log']).must_equal 'everything is good'
|
253
|
+
expect(item['fields']['level']).must_equal 'info'
|
254
|
+
expect(item['fields']['file']).must_equal 'cool.log'
|
255
|
+
end
|
256
|
+
end
|
255
257
|
end
|
256
258
|
|
257
259
|
it 'should handle empty fields' do
|
258
|
-
verify_sent_events(<<~CONF)
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
260
|
+
verify_sent_events(<<~CONF) do |batch|
|
261
|
+
data_type metric
|
262
|
+
metric_name_key from
|
263
|
+
metric_value_key value
|
264
|
+
<fields>
|
265
|
+
</fields>
|
264
266
|
CONF
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
267
|
+
batch.each do |item|
|
268
|
+
# only "metric_name" and "_value"
|
269
|
+
expect(item['fields'].keys.size).must_equal 2
|
270
|
+
end
|
271
|
+
end
|
270
272
|
end
|
271
273
|
|
272
274
|
it 'should handle custom fields' do
|
273
|
-
verify_sent_events(<<~CONF)
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
275
|
+
verify_sent_events(<<~CONF) do |batch|
|
276
|
+
data_type metric
|
277
|
+
metric_name_key from
|
278
|
+
metric_value_key value
|
279
|
+
<fields>
|
280
|
+
level
|
281
|
+
filePath file
|
282
|
+
username
|
283
|
+
</fields>
|
282
284
|
CONF
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
285
|
+
batch.each do |item|
|
286
|
+
expect(item['fields'].keys.size).must_equal 4
|
287
|
+
expect(item['fields']['level']).must_equal 'info'
|
288
|
+
expect(item['fields']['filePath']).must_equal 'cool.log'
|
289
|
+
# null fields should be removed
|
290
|
+
expect(item['fields']).wont_be :has_key?, 'username'
|
291
|
+
end
|
292
|
+
end
|
291
293
|
end
|
292
294
|
|
293
295
|
it 'should treat each key-value in event as a metric' do
|
294
296
|
metrics = [
|
295
|
-
['tag', event_time, {'cup': 0.5, 'memory': 100}],
|
296
|
-
['tag', event_time, {'cup': 0.6, 'memory': 200}]
|
297
|
+
['tag', event_time, { 'cup': 0.5, 'memory': 100 }],
|
298
|
+
['tag', event_time, { 'cup': 0.6, 'memory': 200 }]
|
297
299
|
]
|
298
|
-
with_stub_hec(events: metrics, conf: 'data_type metric')
|
299
|
-
|
300
|
-
|
300
|
+
with_stub_hec(events: metrics, conf: 'data_type metric') do |batch|
|
301
|
+
expect(batch.size).must_equal 4
|
302
|
+
end
|
301
303
|
end
|
302
304
|
end
|
303
305
|
|
304
306
|
describe 'timeout params' do
|
305
|
-
|
306
|
-
|
307
|
-
|
307
|
+
it 'should reset unused connection after 5 seconds' do
|
308
|
+
expect(create_hec_output_driver('hec_host splunk.com', 'idle_timeout 5').instance.idle_timeout).must_equal 5
|
309
|
+
end
|
308
310
|
|
309
|
-
|
310
|
-
|
311
|
-
|
311
|
+
it 'should allow custom setting between reading chunks from the socket' do
|
312
|
+
expect(create_hec_output_driver('hec_host splunk.com', 'read_timeout 5').instance.read_timeout).must_equal 5
|
313
|
+
end
|
312
314
|
|
313
|
-
|
314
|
-
|
315
|
-
|
315
|
+
it 'should allow custom setting a connection to be opened' do
|
316
|
+
expect(create_hec_output_driver('hec_host splunk.com', 'open_timeout 5').instance.open_timeout).must_equal 5
|
317
|
+
end
|
316
318
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
end
|
319
|
+
it 'should check default values are created correctly for timeout params' do
|
320
|
+
test_driver = create_hec_output_driver('hec_host splunk.com')
|
321
|
+
expect(test_driver.instance.idle_timeout).must_equal 5
|
322
|
+
assert_nil(test_driver.instance.read_timeout)
|
323
|
+
assert_nil(test_driver.instance.open_timeout)
|
323
324
|
end
|
325
|
+
end
|
324
326
|
|
325
|
-
def with_stub_hec(events:, conf: ''
|
326
|
-
host =
|
327
|
-
@driver =
|
327
|
+
def with_stub_hec(events:, conf: '')
|
328
|
+
host = 'hec.splunk.com'
|
329
|
+
@driver = create_hec_output_driver("hec_host #{host}", conf)
|
328
330
|
|
329
|
-
hec_req = stub_hec_request("https://#{host}:8088").with
|
330
|
-
|
331
|
-
|
331
|
+
hec_req = stub_hec_request("https://#{host}:8088").with do |r|
|
332
|
+
yield r.body.split(/(?={)\s*(?<=})/).map { |item| JSON.load item }
|
333
|
+
end
|
332
334
|
|
333
335
|
@driver.run do
|
334
336
|
events.each { |evt| @driver.feed *evt }
|
@@ -339,19 +341,19 @@ describe Fluent::Plugin::SplunkHecOutput do
|
|
339
341
|
|
340
342
|
def verify_sent_events(conf = '', &blk)
|
341
343
|
event = {
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
344
|
+
'log' => 'everything is good',
|
345
|
+
'level' => 'info',
|
346
|
+
'from' => 'my_machine',
|
347
|
+
'file' => 'cool.log',
|
348
|
+
'value' => 100,
|
349
|
+
'agent' => {
|
350
|
+
'name' => 'test',
|
351
|
+
'version' => '1.0.0'
|
350
352
|
}
|
351
353
|
}
|
352
354
|
events = [
|
353
|
-
[
|
354
|
-
[
|
355
|
+
['tag.event1', event_time, { 'id' => '1st' }.merge(Marshal.load(Marshal.dump(event)))],
|
356
|
+
['tag.event2', event_time, { 'id' => '2nd' }.merge(Marshal.load(Marshal.dump(event)))]
|
355
357
|
]
|
356
358
|
|
357
359
|
with_stub_hec conf: conf, events: events, &blk
|