fluent-plugin-splunk-hec 1.1.2 → 1.2.4
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/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 +69 -24
- 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
|