fluent-plugin-vmware-log-intelligence 2.0.5 → 2.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +10 -10
- data/LICENSE.txt +15 -15
- data/README.rdoc +113 -106
- data/Rakefile +15 -15
- data/VERSION +1 -1
- data/fluent-plugin-vmware-log-intelligence.gemspec +53 -53
- data/lib/fluent/plugin/http_client.rb +72 -72
- data/lib/fluent/plugin/out_vmware_log_intelligence.rb +205 -179
- data/test/helper.rb +31 -31
- data/test/plugin/test_http_client.rb +95 -95
- data/test/plugin/test_out_vmware_log_intelligence.rb +153 -153
- metadata +5 -5
@@ -1,179 +1,205 @@
|
|
1
|
-
# Copyright (c) 2013 ablagoev
|
2
|
-
# Copyright 2018 VMware, Inc.
|
3
|
-
# SPDX-License-Identifier: MIT
|
4
|
-
|
5
|
-
|
6
|
-
require
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
config_param :
|
15
|
-
config_param :
|
16
|
-
|
17
|
-
config_param :
|
18
|
-
config_param :
|
19
|
-
|
20
|
-
#
|
21
|
-
config_param :
|
22
|
-
|
23
|
-
|
24
|
-
#
|
25
|
-
config_param :
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
if
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
1
|
+
# Copyright (c) 2013 ablagoev
|
2
|
+
# Copyright 2018 VMware, Inc.
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
|
6
|
+
require 'zlib'
|
7
|
+
require "fluent/plugin/output"
|
8
|
+
require "fluent/plugin/http_client"
|
9
|
+
|
10
|
+
module Fluent::Plugin
|
11
|
+
class LogIntelligenceOutput < Output
|
12
|
+
Fluent::Plugin.register_output('vmware_log_intelligence', self)
|
13
|
+
|
14
|
+
config_param :http_compress, :bool, :default => false
|
15
|
+
config_param :endpoint_url, :string
|
16
|
+
config_param :bearer_token, :string, default: ''
|
17
|
+
config_param :http_retry_statuses, :string, default: ''
|
18
|
+
config_param :read_timeout, :integer, default: 60
|
19
|
+
config_param :open_timeout, :integer, default: 60
|
20
|
+
# config_param :use_ssl, :bool, :default => false
|
21
|
+
config_param :verify_ssl, :bool, :default => true
|
22
|
+
config_param :rate_limit_msec, :integer, :default => 0
|
23
|
+
# Keys from log event whose values should be added as log message/text
|
24
|
+
# to log-intelligence. Note these key/value pairs won't be added as metadata/fields
|
25
|
+
config_param :log_text_keys, :array, default: ["log", "message", "msg"], value_type: :string
|
26
|
+
# Flatten hashes to create one key/val pair w/o losing log data
|
27
|
+
config_param :flatten_hashes, :bool, :default => true
|
28
|
+
# Seperator to use for joining flattened keys
|
29
|
+
config_param :flatten_hashes_separator, :string, :default => "_"
|
30
|
+
|
31
|
+
config_section :buffer do
|
32
|
+
config_set_default :@type, "memory"
|
33
|
+
config_set_default :chunk_keys, []
|
34
|
+
config_set_default :timekey_use_utc, true
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
super
|
39
|
+
require 'http'
|
40
|
+
require 'uri'
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_uri(uri_string)
|
44
|
+
unless uri_string =~ /^#{URI.regexp}$/
|
45
|
+
fail Fluent::ConfigError, 'endpoint_url invalid'
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
@uri = URI.parse(uri_string)
|
50
|
+
rescue URI::InvalidURIError
|
51
|
+
raise Fluent::ConfigError, 'endpoint_url invalid'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def retrieve_headers(conf)
|
56
|
+
headers = {}
|
57
|
+
conf.elements.each do |element|
|
58
|
+
if @http_compress
|
59
|
+
set_gzip_header(element)
|
60
|
+
end
|
61
|
+
if element.name == 'headers'
|
62
|
+
if @bearer_token != ''
|
63
|
+
element['Authorization'] = 'Bearer ' + @bearer_token
|
64
|
+
end
|
65
|
+
headers = element.to_hash
|
66
|
+
end
|
67
|
+
end
|
68
|
+
headers
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_gzip_header(element)
|
72
|
+
element['Content-Encoding'] = 'gzip'
|
73
|
+
element
|
74
|
+
end
|
75
|
+
|
76
|
+
def shorten_key(key)
|
77
|
+
# LINT doesn't allow some characters in field 'name'
|
78
|
+
# like '/', '-', '\', '.', etc. so replace them with @flatten_hashes_separator
|
79
|
+
key = key.gsub(/[\/\.\-\\]/,@flatten_hashes_separator).downcase
|
80
|
+
key
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_lint_event(record)
|
84
|
+
flattened_records = {}
|
85
|
+
merged_records = {}
|
86
|
+
if @flatten_hashes
|
87
|
+
flattened_records = flatten_record(record, [])
|
88
|
+
else
|
89
|
+
flattened_records = record
|
90
|
+
end
|
91
|
+
|
92
|
+
keys = []
|
93
|
+
log = ''
|
94
|
+
flattened_records.each do |key, value|
|
95
|
+
begin
|
96
|
+
next if value.nil?
|
97
|
+
# LINT doesn't support duplicate fields, make unique names by appending underscore
|
98
|
+
key = shorten_key(key)
|
99
|
+
if keys.include?(key)
|
100
|
+
value = merged_records[key] + " " + value
|
101
|
+
end
|
102
|
+
keys.push(key)
|
103
|
+
key.force_encoding("utf-8")
|
104
|
+
|
105
|
+
if value.is_a?(String)
|
106
|
+
value.force_encoding("utf-8")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if @log_text_keys.include?(key)
|
111
|
+
if log != "#{value}"
|
112
|
+
if log.empty?
|
113
|
+
log = "#{value}"
|
114
|
+
else
|
115
|
+
log += " #{value}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
else
|
119
|
+
merged_records[key] = value
|
120
|
+
end
|
121
|
+
end
|
122
|
+
merged_records["text"] = log
|
123
|
+
|
124
|
+
if log == "\\n"
|
125
|
+
{}
|
126
|
+
else
|
127
|
+
merged_records
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def flatten_record(record, prefix=[])
|
132
|
+
ret = {}
|
133
|
+
|
134
|
+
case record
|
135
|
+
when Hash
|
136
|
+
record.each do |key, value|
|
137
|
+
if @log_text_keys.include?(key)
|
138
|
+
ret.merge!({key.to_s => value})
|
139
|
+
else
|
140
|
+
ret.merge! flatten_record(value, prefix + [key.to_s])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
when Array
|
144
|
+
record.each do |value|
|
145
|
+
ret.merge! flatten_record(value, prefix)
|
146
|
+
end
|
147
|
+
else
|
148
|
+
return {prefix.join(@flatten_hashes_separator) => record}
|
149
|
+
end
|
150
|
+
ret
|
151
|
+
end
|
152
|
+
|
153
|
+
def configure(conf)
|
154
|
+
super
|
155
|
+
validate_uri(@endpoint_url)
|
156
|
+
|
157
|
+
@statuses = @http_retry_statuses.split(',').map { |status| status.to_i }
|
158
|
+
@statuses = [] if @statuses.nil?
|
159
|
+
|
160
|
+
@headers = retrieve_headers(conf)
|
161
|
+
|
162
|
+
@http_client = Fluent::Plugin::HttpClient.new(
|
163
|
+
@endpoint_url, @verify_ssl, @headers, @statuses,
|
164
|
+
@open_timeout, @read_timeout, @log)
|
165
|
+
end
|
166
|
+
|
167
|
+
def multi_workers_ready?
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
def start
|
172
|
+
super
|
173
|
+
end
|
174
|
+
|
175
|
+
def shutdown
|
176
|
+
super
|
177
|
+
begin
|
178
|
+
@http_client.close if @http_client
|
179
|
+
rescue
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def write(chunk)
|
184
|
+
is_rate_limited = (@rate_limit_msec != 0 and not @last_request_time.nil?)
|
185
|
+
if is_rate_limited and ((Time.now.to_f - @last_request_time) * 1000.0 < @rate_limit_msec)
|
186
|
+
@log.info('Dropped request due to rate limiting')
|
187
|
+
return
|
188
|
+
end
|
189
|
+
|
190
|
+
data = []
|
191
|
+
chunk.each do |time, record|
|
192
|
+
data << create_lint_event(record)
|
193
|
+
end
|
194
|
+
|
195
|
+
if @http_compress
|
196
|
+
gzip_body = Zlib::GzipWriter.new(StringIO.new)
|
197
|
+
gzip_body << data.to_json
|
198
|
+
@http_client.post(gzip_body.close.string)
|
199
|
+
else
|
200
|
+
@last_request_time = Time.now.to_f
|
201
|
+
@http_client.post(JSON.dump(data))
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
-
# Copyright (c) 2013 ablagoev
|
2
|
-
# Copyright 2018 VMware, Inc.
|
3
|
-
# SPDX-License-Identifier: MIT
|
4
|
-
|
5
|
-
require 'coveralls'
|
6
|
-
Coveralls.wear!
|
7
|
-
|
8
|
-
require 'rubygems'
|
9
|
-
require 'bundler'
|
10
|
-
|
11
|
-
begin
|
12
|
-
Bundler.setup(:default, :development)
|
13
|
-
rescue Bundler::BundlerError => e
|
14
|
-
$stderr.puts e.message
|
15
|
-
$stderr.puts 'Run `bundle install` to install missing gems'
|
16
|
-
exit e.status_code
|
17
|
-
end
|
18
|
-
|
19
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
21
|
-
|
22
|
-
require 'test-unit'
|
23
|
-
require 'fluent/test'
|
24
|
-
require "fluent/test/driver/output"
|
25
|
-
require "fluent/test/helpers"
|
26
|
-
Test::Unit::TestCase.include(Fluent::Test::Helpers)
|
27
|
-
Test::Unit::TestCase.extend(Fluent::Test::Helpers)
|
28
|
-
|
29
|
-
require 'fluent/plugin/out_vmware_log_intelligence'
|
30
|
-
require 'webmock/test_unit'
|
31
|
-
WebMock.disable_net_connect!
|
1
|
+
# Copyright (c) 2013 ablagoev
|
2
|
+
# Copyright 2018 VMware, Inc.
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
require 'coveralls'
|
6
|
+
Coveralls.wear!
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'bundler'
|
10
|
+
|
11
|
+
begin
|
12
|
+
Bundler.setup(:default, :development)
|
13
|
+
rescue Bundler::BundlerError => e
|
14
|
+
$stderr.puts e.message
|
15
|
+
$stderr.puts 'Run `bundle install` to install missing gems'
|
16
|
+
exit e.status_code
|
17
|
+
end
|
18
|
+
|
19
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
21
|
+
|
22
|
+
require 'test-unit'
|
23
|
+
require 'fluent/test'
|
24
|
+
require "fluent/test/driver/output"
|
25
|
+
require "fluent/test/helpers"
|
26
|
+
Test::Unit::TestCase.include(Fluent::Test::Helpers)
|
27
|
+
Test::Unit::TestCase.extend(Fluent::Test::Helpers)
|
28
|
+
|
29
|
+
require 'fluent/plugin/out_vmware_log_intelligence'
|
30
|
+
require 'webmock/test_unit'
|
31
|
+
WebMock.disable_net_connect!
|
@@ -1,95 +1,95 @@
|
|
1
|
-
# Copyright 2018 VMware, Inc.
|
2
|
-
# SPDX-License-Identifier: MIT
|
3
|
-
|
4
|
-
require 'helper'
|
5
|
-
|
6
|
-
class HttpClientTest < Test::Unit::TestCase
|
7
|
-
DEFAULT_URL = "https://local.endpoint:3000/dummy/xyz"
|
8
|
-
|
9
|
-
def stub_server_out_of_quota(url=DEFAULT_URL)
|
10
|
-
stub_request(:post, url).to_return(:status => [429, "User out of ingestion quota."])
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_check_quota
|
14
|
-
http_client = create_http_client()
|
15
|
-
assert_equal http_client.check_quota, true
|
16
|
-
|
17
|
-
stub_server_out_of_quota
|
18
|
-
http_client.post(JSON.dump(sample_record()))
|
19
|
-
assert_equal http_client.check_quota, false
|
20
|
-
end
|
21
|
-
|
22
|
-
def stub_server_unavailable(url=DEFAULT_URL)
|
23
|
-
stub_request(:post, url).to_return(:status => [503, "Service Unavailable"])
|
24
|
-
end
|
25
|
-
|
26
|
-
def stub_server_returns_500(url=DEFAULT_URL)
|
27
|
-
stub_request(:post, url).to_return(:status => [500, "Internal service error"])
|
28
|
-
end
|
29
|
-
|
30
|
-
def stub_server_raise_error(url=DEFAULT_URL)
|
31
|
-
stub_request(:post, url).with do |req|
|
32
|
-
raise IOError
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def stub_post_logs(url=DEFAULT_URL)
|
37
|
-
stub_request(:post, url)
|
38
|
-
.with(
|
39
|
-
body: "[{\"field1\":26,\"field2\":\"value26\"},{\"field1\":27,\"field2\":\"value27\"},{\"field1\":28,\"field2\":\"value28\"}]",
|
40
|
-
headers: {
|
41
|
-
'Authorization'=>'Bearer EdaNNN68y',
|
42
|
-
'Connection'=>'Keep-Alive',
|
43
|
-
'Content-Type'=>'application/json',
|
44
|
-
'Format'=>'syslog',
|
45
|
-
'Structure'=>'simple'
|
46
|
-
})
|
47
|
-
.to_return(:status => 200, :body => "ok")
|
48
|
-
end
|
49
|
-
|
50
|
-
def sample_record()
|
51
|
-
[
|
52
|
-
{'field1' => 26, 'field2' => 'value26'},
|
53
|
-
{'field1' => 27, 'field2' => 'value27'},
|
54
|
-
{'field1' => 28, 'field2' => 'value28'}
|
55
|
-
]
|
56
|
-
end
|
57
|
-
|
58
|
-
def create_http_client
|
59
|
-
Fluent::Plugin::HttpClient.new(
|
60
|
-
DEFAULT_URL, true,
|
61
|
-
{
|
62
|
-
'Authorization'=>'Bearer EdaNNN68y',
|
63
|
-
'Connection'=>'Keep-Alive',
|
64
|
-
'Content-Type'=>'application/json',
|
65
|
-
'Format'=>'syslog',
|
66
|
-
'Host'=>'local.endpoint:3000',
|
67
|
-
'Structure'=>'simple'
|
68
|
-
}, [500, 510], 60, 60, Logger.new(STDOUT))
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_retry_on_response_status_code
|
72
|
-
http_client = create_http_client()
|
73
|
-
stub_server_returns_500
|
74
|
-
assert_raise RuntimeError do
|
75
|
-
http_client.post(JSON.dump(sample_record()))
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def test_server_raise_error
|
80
|
-
http_client = create_http_client()
|
81
|
-
stub_server_raise_error
|
82
|
-
assert_raise IOError do
|
83
|
-
http_client.post(JSON.dump(sample_record()))
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def test_post_logs
|
88
|
-
stub_post_logs
|
89
|
-
http_client = create_http_client()
|
90
|
-
http_client.post(JSON.dump(sample_record()))
|
91
|
-
|
92
|
-
stub_post_logs
|
93
|
-
http_client.post(JSON.dump(sample_record()))
|
94
|
-
end
|
95
|
-
end
|
1
|
+
# Copyright 2018 VMware, Inc.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require 'helper'
|
5
|
+
|
6
|
+
class HttpClientTest < Test::Unit::TestCase
|
7
|
+
DEFAULT_URL = "https://local.endpoint:3000/dummy/xyz"
|
8
|
+
|
9
|
+
def stub_server_out_of_quota(url=DEFAULT_URL)
|
10
|
+
stub_request(:post, url).to_return(:status => [429, "User out of ingestion quota."])
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_check_quota
|
14
|
+
http_client = create_http_client()
|
15
|
+
assert_equal http_client.check_quota, true
|
16
|
+
|
17
|
+
stub_server_out_of_quota
|
18
|
+
http_client.post(JSON.dump(sample_record()))
|
19
|
+
assert_equal http_client.check_quota, false
|
20
|
+
end
|
21
|
+
|
22
|
+
def stub_server_unavailable(url=DEFAULT_URL)
|
23
|
+
stub_request(:post, url).to_return(:status => [503, "Service Unavailable"])
|
24
|
+
end
|
25
|
+
|
26
|
+
def stub_server_returns_500(url=DEFAULT_URL)
|
27
|
+
stub_request(:post, url).to_return(:status => [500, "Internal service error"])
|
28
|
+
end
|
29
|
+
|
30
|
+
def stub_server_raise_error(url=DEFAULT_URL)
|
31
|
+
stub_request(:post, url).with do |req|
|
32
|
+
raise IOError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stub_post_logs(url=DEFAULT_URL)
|
37
|
+
stub_request(:post, url)
|
38
|
+
.with(
|
39
|
+
body: "[{\"field1\":26,\"field2\":\"value26\"},{\"field1\":27,\"field2\":\"value27\"},{\"field1\":28,\"field2\":\"value28\"}]",
|
40
|
+
headers: {
|
41
|
+
'Authorization'=>'Bearer EdaNNN68y',
|
42
|
+
'Connection'=>'Keep-Alive',
|
43
|
+
'Content-Type'=>'application/json',
|
44
|
+
'Format'=>'syslog',
|
45
|
+
'Structure'=>'simple'
|
46
|
+
})
|
47
|
+
.to_return(:status => 200, :body => "ok")
|
48
|
+
end
|
49
|
+
|
50
|
+
def sample_record()
|
51
|
+
[
|
52
|
+
{'field1' => 26, 'field2' => 'value26'},
|
53
|
+
{'field1' => 27, 'field2' => 'value27'},
|
54
|
+
{'field1' => 28, 'field2' => 'value28'}
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_http_client
|
59
|
+
Fluent::Plugin::HttpClient.new(
|
60
|
+
DEFAULT_URL, true,
|
61
|
+
{
|
62
|
+
'Authorization'=>'Bearer EdaNNN68y',
|
63
|
+
'Connection'=>'Keep-Alive',
|
64
|
+
'Content-Type'=>'application/json',
|
65
|
+
'Format'=>'syslog',
|
66
|
+
'Host'=>'local.endpoint:3000',
|
67
|
+
'Structure'=>'simple'
|
68
|
+
}, [500, 510], 60, 60, Logger.new(STDOUT))
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_retry_on_response_status_code
|
72
|
+
http_client = create_http_client()
|
73
|
+
stub_server_returns_500
|
74
|
+
assert_raise RuntimeError do
|
75
|
+
http_client.post(JSON.dump(sample_record()))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_server_raise_error
|
80
|
+
http_client = create_http_client()
|
81
|
+
stub_server_raise_error
|
82
|
+
assert_raise IOError do
|
83
|
+
http_client.post(JSON.dump(sample_record()))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_post_logs
|
88
|
+
stub_post_logs
|
89
|
+
http_client = create_http_client()
|
90
|
+
http_client.post(JSON.dump(sample_record()))
|
91
|
+
|
92
|
+
stub_post_logs
|
93
|
+
http_client.post(JSON.dump(sample_record()))
|
94
|
+
end
|
95
|
+
end
|