fluent-plugin-scalyr 0.8.10 → 0.8.11
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/README.md +20 -1
- data/Rakefile +9 -6
- data/VERSION +1 -1
- data/fluent-plugin-scalyr.gemspec +11 -7
- data/lib/fluent/plugin/out_scalyr.rb +185 -215
- data/lib/fluent/plugin/{scalyr-exceptions.rb → scalyr_exceptions.rb} +2 -2
- data/test/helper.rb +12 -6
- data/test/test_config.rb +19 -30
- data/test/test_events.rb +156 -154
- data/test/test_handle_response.rb +34 -35
- data/test/test_ssl_verify.rb +101 -10
- metadata +47 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d9fa05bade6ec51a39896e90bce7ddfdf233c8888d5ca2ac41393966c8a924b
|
4
|
+
data.tar.gz: b6406c122b1180faa48294b47ed2ebb1b61aad08c64c0310385a00c0ae8a044a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b083816314fdf992eaca0cb0216795e2c7a6c1cc7e2f32669452201c45b29d6254dcbc2f69a665d6a0852242423bd87cdf62a88feda5f463e6f774b142d0520
|
7
|
+
data.tar.gz: 8a94b4b649cfd45fa1143e6866d119811236771980f508d0643b0b51b1d6bbdb2e241233a469086a0b5a3e22529c08d1c23b7825966fe78e367defd4672d20c2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -116,7 +116,7 @@ directory.
|
|
116
116
|
|
117
117
|
***scalyr_server*** - the Scalyr server to send API requests to. This value is optional and defaults to https://agent.scalyr.com/
|
118
118
|
|
119
|
-
***ssl_ca_bundle_path*** - a path on your server pointing to a valid certificate bundle. This value is optional and defaults to
|
119
|
+
***ssl_ca_bundle_path*** - a path on your server pointing to a valid certificate bundle. This value is optional and defaults to *nil*, which means it will look for a valid certificate bundle on its own.
|
120
120
|
|
121
121
|
**Note:** if the certificate bundle does not contain a certificate chain that verifies the Scalyr SSL certificate then all requests to Scalyr will fail unless ***ssl_verify_peer*** is set to false. If you suspect logging to Scalyr is failing due to an invalid certificate chain, you can grep through the Fluentd output for warnings that contain the message 'certificate verification failed'. The full text of such warnings will look something like this:
|
122
122
|
|
@@ -180,3 +180,22 @@ Which builds the gem and puts it in the pkg directory, then install the Gem usin
|
|
180
180
|
```
|
181
181
|
fluent-gem install pkg/fluent-plugin-scalyr-<VERSION>.gem
|
182
182
|
```
|
183
|
+
|
184
|
+
Publishing a new release to RubyGems
|
185
|
+
------------------------------------
|
186
|
+
|
187
|
+
(for project maintainers)
|
188
|
+
|
189
|
+
To publish a new version to RubyGems, simply make your changes, make sure all the lint checks and
|
190
|
+
tests pass and merge your changes into master.
|
191
|
+
|
192
|
+
After that's done, bump a version in ``VERSION`` file, update ``CHANGELOG.md`` file, add a tag
|
193
|
+
which matches a version in VERSION file (e.g. ``v0.8.10``) and push that tag to the remote:
|
194
|
+
|
195
|
+
```bash
|
196
|
+
git tag v0.8.10
|
197
|
+
git push origin v0.8.10
|
198
|
+
```
|
199
|
+
|
200
|
+
Push of this tag will trigger a Circle CI job which will build the latest version of the gem and
|
201
|
+
publish it to RubyGems.
|
data/Rakefile
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
2
4
|
Bundler::GemHelper.install_tasks
|
3
5
|
|
4
|
-
require
|
6
|
+
require "rake/testtask"
|
5
7
|
|
6
|
-
Rake::TestTask.new do |
|
7
|
-
|
8
|
-
|
8
|
+
Rake::TestTask.new(:test) do |test|
|
9
|
+
test.libs << "lib" << "test"
|
10
|
+
test.test_files = FileList["test/test_*.rb"]
|
11
|
+
test.verbose = true
|
9
12
|
end
|
10
13
|
|
11
|
-
task :
|
14
|
+
task default: [:build]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.
|
1
|
+
0.8.11
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path("lib", __dir__)
|
2
4
|
|
3
5
|
Gem::Specification.new do |gem|
|
4
6
|
gem.name = "fluent-plugin-scalyr"
|
@@ -10,16 +12,18 @@ Gem::Specification.new do |gem|
|
|
10
12
|
gem.licenses = ["Apache-2.0"]
|
11
13
|
gem.email = "imron@scalyr.com"
|
12
14
|
gem.platform = Gem::Platform::RUBY
|
13
|
-
gem.files = Dir[
|
15
|
+
gem.files = Dir["AUTHORS", "Gemfile", "LICENSE", "README.md", "Rakefile", "VERSION",
|
16
|
+
"fluent-plugin-scalyr.gemspec", "fluent.conf.sample", "lib/**/*", "test/**/*"]
|
14
17
|
gem.test_files = Dir.glob("{test,spec,features}/**/*")
|
15
|
-
gem.executables = Dir.glob("bin/*").map{
|
16
|
-
gem.require_paths = [
|
17
|
-
gem.add_dependency "fluentd", [">= 0.14.0", "< 2"]
|
18
|
+
gem.executables = Dir.glob("bin/*").map {|f| File.basename(f) }
|
19
|
+
gem.require_paths = ["lib"]
|
18
20
|
gem.add_dependency "ffi", "1.9.25"
|
21
|
+
gem.add_dependency "fluentd", [">= 0.14.0", "< 2"]
|
19
22
|
gem.add_dependency "rbzip2", "0.3.0"
|
20
23
|
gem.add_dependency "zlib"
|
24
|
+
gem.add_development_dependency "bundler", "~> 1.9"
|
25
|
+
gem.add_development_dependency "flexmock", "~> 1.2"
|
21
26
|
gem.add_development_dependency "rake", "~> 0.9"
|
27
|
+
gem.add_development_dependency "rubocop", "~> 0.4"
|
22
28
|
gem.add_development_dependency "test-unit", "~> 3.0"
|
23
|
-
gem.add_development_dependency "flexmock", "~> 1.2"
|
24
|
-
gem.add_development_dependency "bundler", "~> 1.9"
|
25
29
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#
|
2
4
|
# Scalyr Output Plugin for Fluentd
|
3
5
|
#
|
@@ -15,47 +17,44 @@
|
|
15
17
|
# See the License for the specific language governing permissions and
|
16
18
|
# limitations under the License.
|
17
19
|
|
18
|
-
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require 'socket'
|
30
|
-
require 'thread'
|
31
|
-
|
20
|
+
require "fluent/plugin/output"
|
21
|
+
require "fluent/plugin/scalyr_exceptions"
|
22
|
+
require "fluent/plugin_helper/compat_parameters"
|
23
|
+
require "json"
|
24
|
+
require "net/http"
|
25
|
+
require "net/https"
|
26
|
+
require "rbzip2"
|
27
|
+
require "stringio"
|
28
|
+
require "zlib"
|
29
|
+
require "securerandom"
|
30
|
+
require "socket"
|
32
31
|
module Scalyr
|
33
32
|
class ScalyrOut < Fluent::Plugin::Output
|
34
|
-
Fluent::Plugin.register_output(
|
33
|
+
Fluent::Plugin.register_output("scalyr", self)
|
35
34
|
helpers :compat_parameters
|
36
35
|
helpers :event_emitter
|
37
36
|
|
38
37
|
config_param :api_write_token, :string
|
39
|
-
config_param :server_attributes, :hash, :
|
40
|
-
config_param :use_hostname_for_serverhost, :bool, :
|
41
|
-
config_param :scalyr_server, :string, :
|
42
|
-
config_param :ssl_ca_bundle_path, :string, :
|
43
|
-
config_param :ssl_verify_peer, :bool, :
|
44
|
-
config_param :ssl_verify_depth, :integer, :
|
45
|
-
config_param :message_field, :string, :
|
46
|
-
config_param :max_request_buffer, :integer, :
|
47
|
-
config_param :force_message_encoding, :string, :
|
48
|
-
config_param :replace_invalid_utf8, :bool, :
|
49
|
-
config_param :compression_type, :string, :
|
50
|
-
config_param :compression_level, :integer, :
|
38
|
+
config_param :server_attributes, :hash, default: nil
|
39
|
+
config_param :use_hostname_for_serverhost, :bool, default: true
|
40
|
+
config_param :scalyr_server, :string, default: "https://agent.scalyr.com/"
|
41
|
+
config_param :ssl_ca_bundle_path, :string, default: nil
|
42
|
+
config_param :ssl_verify_peer, :bool, default: true
|
43
|
+
config_param :ssl_verify_depth, :integer, default: 5
|
44
|
+
config_param :message_field, :string, default: "message"
|
45
|
+
config_param :max_request_buffer, :integer, default: 5_500_000
|
46
|
+
config_param :force_message_encoding, :string, default: nil
|
47
|
+
config_param :replace_invalid_utf8, :bool, default: false
|
48
|
+
config_param :compression_type, :string, default: nil # Valid options are bz2, deflate or None. Defaults to None.
|
49
|
+
config_param :compression_level, :integer, default: 6 # An int containing the compression level of compression to use, from 1-9. Defaults to 6
|
51
50
|
|
52
51
|
config_section :buffer do
|
53
|
-
config_set_default :retry_max_times, 40 #try a maximum of 40 times before discarding
|
54
|
-
config_set_default :retry_max_interval,
|
55
|
-
config_set_default :retry_wait, 5 #wait a minimum of 5 seconds per retry
|
56
|
-
config_set_default :flush_interval, 5 #default flush interval of 5 seconds
|
57
|
-
config_set_default :chunk_limit_size,
|
58
|
-
config_set_default :queue_limit_length, 1024 #default queue size of 1024
|
52
|
+
config_set_default :retry_max_times, 40 # try a maximum of 40 times before discarding
|
53
|
+
config_set_default :retry_max_interval, 30 # wait a maximum of 30 seconds per retry
|
54
|
+
config_set_default :retry_wait, 5 # wait a minimum of 5 seconds per retry
|
55
|
+
config_set_default :flush_interval, 5 # default flush interval of 5 seconds
|
56
|
+
config_set_default :chunk_limit_size, 2_500_000 # default chunk size of 2.5mb
|
57
|
+
config_set_default :queue_limit_length, 1024 # default queue size of 1024
|
59
58
|
end
|
60
59
|
|
61
60
|
# support for version 0.14.0:
|
@@ -71,46 +70,45 @@ module Scalyr
|
|
71
70
|
true
|
72
71
|
end
|
73
72
|
|
74
|
-
def configure(
|
75
|
-
|
76
|
-
|
77
|
-
$log.warn "Pre 0.14.0 configuration file detected. Please consider updating your configuration file"
|
73
|
+
def configure(conf)
|
74
|
+
if conf.elements("buffer").empty?
|
75
|
+
$log.warn "Pre 0.14.0 configuration file detected. Please consider updating your configuration file" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
78
76
|
end
|
79
77
|
|
80
|
-
compat_parameters_buffer(
|
78
|
+
compat_parameters_buffer(conf, default_chunk_key: "")
|
81
79
|
|
82
80
|
super
|
83
81
|
|
84
|
-
if @buffer.chunk_limit_size >
|
85
|
-
$log.warn "Buffer chunk size is greater than 6Mb. This may result in requests being rejected by Scalyr"
|
82
|
+
if @buffer.chunk_limit_size > 6_000_000
|
83
|
+
$log.warn "Buffer chunk size is greater than 6Mb. This may result in requests being rejected by Scalyr" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
86
84
|
end
|
87
85
|
|
88
|
-
if @max_request_buffer >
|
89
|
-
$log.warn "Maximum request buffer > 6Mb. This may result in requests being rejected by Scalyr"
|
86
|
+
if @max_request_buffer > 6_000_000
|
87
|
+
$log.warn "Maximum request buffer > 6Mb. This may result in requests being rejected by Scalyr" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
90
88
|
end
|
91
89
|
|
92
90
|
@message_encoding = nil
|
93
|
-
if @force_message_encoding.to_s !=
|
91
|
+
if @force_message_encoding.to_s != ""
|
94
92
|
begin
|
95
|
-
@message_encoding = Encoding.find(
|
93
|
+
@message_encoding = Encoding.find(@force_message_encoding)
|
96
94
|
$log.debug "Forcing message encoding to '#{@force_message_encoding}'"
|
97
95
|
rescue ArgumentError
|
98
96
|
$log.warn "No encoding '#{@force_message_encoding}' found. Ignoring"
|
99
97
|
end
|
100
98
|
end
|
101
99
|
|
102
|
-
#evaluate any statements in string value of the server_attributes object
|
100
|
+
# evaluate any statements in string value of the server_attributes object
|
103
101
|
if @server_attributes
|
104
102
|
new_attributes = {}
|
105
103
|
@server_attributes.each do |key, value|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
104
|
+
next unless value.is_a?(String)
|
105
|
+
|
106
|
+
m = /^\#{(.*)}$/.match(value)
|
107
|
+
new_attributes[key] = if m
|
108
|
+
eval(m[1]) # rubocop:disable Security/Eval
|
109
|
+
else
|
110
|
+
value
|
111
|
+
end
|
114
112
|
end
|
115
113
|
@server_attributes = new_attributes
|
116
114
|
end
|
@@ -119,143 +117,131 @@ module Scalyr
|
|
119
117
|
if @use_hostname_for_serverhost
|
120
118
|
|
121
119
|
# ensure server_attributes is not nil
|
122
|
-
if @server_attributes.nil?
|
123
|
-
@server_attributes = {}
|
124
|
-
end
|
120
|
+
@server_attributes = {} if @server_attributes.nil?
|
125
121
|
|
126
122
|
# only set serverHost if it doesn't currently exist in server_attributes
|
127
123
|
# Note: Use strings rather than symbols for the key, because keys coming
|
128
124
|
# from the config file will be strings
|
129
|
-
|
130
|
-
@server_attributes[
|
125
|
+
unless @server_attributes.key? "serverHost"
|
126
|
+
@server_attributes["serverHost"] = Socket.gethostname
|
131
127
|
end
|
132
128
|
end
|
133
129
|
|
134
|
-
@scalyr_server <<
|
130
|
+
@scalyr_server << "/" unless @scalyr_server.end_with?("/")
|
135
131
|
|
136
132
|
@add_events_uri = URI @scalyr_server + "addEvents"
|
137
133
|
|
138
134
|
num_threads = @buffer_config.flush_thread_count
|
139
135
|
|
140
|
-
#forcibly limit the number of threads to 1 for now, to ensure requests always have incrementing timestamps
|
141
|
-
|
136
|
+
# forcibly limit the number of threads to 1 for now, to ensure requests always have incrementing timestamps
|
137
|
+
if num_threads > 1
|
138
|
+
raise Fluent::ConfigError, "num_threads is currently limited to 1. You specified #{num_threads}."
|
139
|
+
end
|
142
140
|
end
|
143
141
|
|
144
142
|
def start
|
145
143
|
super
|
146
|
-
#Generate a session id. This will be called once for each <match> in fluent.conf that uses scalyr
|
144
|
+
# Generate a session id. This will be called once for each <match> in fluent.conf that uses scalyr
|
147
145
|
@session = SecureRandom.uuid
|
148
146
|
|
149
|
-
$log.info "Scalyr Fluentd Plugin ID id=#{
|
150
|
-
|
147
|
+
$log.info "Scalyr Fluentd Plugin ID id=#{plugin_id} worker=#{fluentd_worker_id} session=#{@session}" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
151
148
|
end
|
152
149
|
|
153
|
-
def format(
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
sec = components[0].to_i
|
166
|
-
nsec = (components[1] * 10**9).to_i
|
167
|
-
time = Fluent::EventTime.new( sec, nsec )
|
168
|
-
end
|
150
|
+
def format(tag, time, record)
|
151
|
+
time = Fluent::Engine.now if time.nil?
|
152
|
+
|
153
|
+
# handle timestamps that are not EventTime types
|
154
|
+
if time.is_a?(Integer)
|
155
|
+
time = Fluent::EventTime.new(time)
|
156
|
+
elsif time.is_a?(Float)
|
157
|
+
components = time.divmod 1 # get integer and decimal components
|
158
|
+
sec = components[0].to_i
|
159
|
+
nsec = (components[1] * 10**9).to_i
|
160
|
+
time = Fluent::EventTime.new(sec, nsec)
|
161
|
+
end
|
169
162
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
end
|
175
|
-
record["message"] = record[@message_field]
|
176
|
-
record.delete( @message_field )
|
163
|
+
if @message_field != "message"
|
164
|
+
if record.key? @message_field
|
165
|
+
if record.key? "message"
|
166
|
+
$log.warn "Overwriting log record field 'message'. You are seeing this warning because in your fluentd config file you have configured the '#{@message_field}' field to be converted to the 'message' field, but the log record already contains a field called 'message' and this is now being overwritten." # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
177
167
|
end
|
168
|
+
record["message"] = record[@message_field]
|
169
|
+
record.delete(@message_field)
|
178
170
|
end
|
171
|
+
end
|
179
172
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
end
|
173
|
+
if @message_encoding && record.key?("message") && record["message"]
|
174
|
+
if @replace_invalid_utf8 && (@message_encoding == Encoding::UTF_8)
|
175
|
+
record["message"] = record["message"].encode("UTF-8", invalid: :replace, undef: :replace, replace: "<?>").force_encoding("UTF-8") # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
176
|
+
else
|
177
|
+
record["message"].force_encoding(@message_encoding)
|
186
178
|
end
|
187
|
-
[tag, time.sec, time.nsec, record].to_msgpack
|
188
|
-
|
189
|
-
rescue JSON::GeneratorError
|
190
|
-
$log.warn "Unable to format message due to JSON::GeneratorError. Record is:\n\t#{record.to_s}"
|
191
|
-
raise
|
192
179
|
end
|
180
|
+
[tag, time.sec, time.nsec, record].to_msgpack
|
181
|
+
rescue JSON::GeneratorError
|
182
|
+
$log.warn "Unable to format message due to JSON::GeneratorError. Record is:\n\t#{record}"
|
183
|
+
raise
|
193
184
|
end
|
194
185
|
|
195
|
-
#called by fluentd when a chunk of log messages is ready
|
196
|
-
def write(
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
$log.debug "Chunk split into #{requests.size} request(s)."
|
201
|
-
|
202
|
-
requests.each_with_index { |request, index|
|
203
|
-
$log.debug "Request #{index + 1}/#{requests.size}: #{request[:body].bytesize} bytes"
|
204
|
-
begin
|
205
|
-
response = self.post_request( @add_events_uri, request[:body] )
|
206
|
-
self.handle_response( response )
|
207
|
-
rescue OpenSSL::SSL::SSLError => e
|
208
|
-
if e.message.include? "certificate verify failed"
|
209
|
-
$log.warn "SSL certificate verification failed. Please make sure your certificate bundle is configured correctly and points to a valid file. You can configure this with the ssl_ca_bundle_path configuration option. The current value of ssl_ca_bundle_path is '#{@ssl_ca_bundle_path}'"
|
210
|
-
end
|
211
|
-
$log.warn e.message
|
212
|
-
$log.warn "Discarding buffer chunk without retrying or logging to <secondary>"
|
213
|
-
rescue Scalyr::Client4xxError => e
|
214
|
-
$log.warn "4XX status code received for request #{index + 1}/#{requests.size}. Discarding buffer without retrying or logging.\n\t#{response.code} - #{e.message}\n\tChunk Size: #{chunk.size}\n\tLog messages this request: #{request[:record_count]}\n\tJSON payload size: #{request[:body].bytesize}\n\tSample: #{request[:body][0,1024]}..."
|
186
|
+
# called by fluentd when a chunk of log messages is ready
|
187
|
+
def write(chunk)
|
188
|
+
$log.debug "Size of chunk is: #{chunk.size}"
|
189
|
+
requests = build_add_events_body(chunk)
|
190
|
+
$log.debug "Chunk split into #{requests.size} request(s)."
|
215
191
|
|
192
|
+
requests.each_with_index {|request, index|
|
193
|
+
$log.debug "Request #{index + 1}/#{requests.size}: #{request[:body].bytesize} bytes"
|
194
|
+
begin
|
195
|
+
response = post_request(@add_events_uri, request[:body])
|
196
|
+
handle_response(response)
|
197
|
+
rescue OpenSSL::SSL::SSLError => e
|
198
|
+
if e.message.include? "certificate verify failed"
|
199
|
+
$log.warn "SSL certificate verification failed. Please make sure your certificate bundle is configured correctly and points to a valid file. You can configure this with the ssl_ca_bundle_path configuration option. The current value of ssl_ca_bundle_path is '#{@ssl_ca_bundle_path}'" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
216
200
|
end
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
201
|
+
$log.warn e.message
|
202
|
+
$log.warn "Discarding buffer chunk without retrying or logging to <secondary>"
|
203
|
+
rescue Scalyr::Client4xxError => e
|
204
|
+
$log.warn "4XX status code received for request #{index + 1}/#{requests.size}. Discarding buffer without retrying or logging.\n\t#{response.code} - #{e.message}\n\tChunk Size: #{chunk.size}\n\tLog messages this request: #{request[:record_count]}\n\tJSON payload size: #{request[:body].bytesize}\n\tSample: #{request[:body][0, 1024]}..."
|
205
|
+
end
|
206
|
+
}
|
207
|
+
rescue JSON::GeneratorError
|
208
|
+
$log.warn "Unable to format message due to JSON::GeneratorError."
|
209
|
+
raise
|
223
210
|
end
|
224
211
|
|
225
|
-
|
226
|
-
#
|
227
|
-
|
228
|
-
def to_nanos( seconds, nsec )
|
212
|
+
# explicit function to convert to nanoseconds
|
213
|
+
# will make things easier to maintain if/when fluentd supports higher than second resolutions
|
214
|
+
def to_nanos(seconds, nsec)
|
229
215
|
(seconds * 10**9) + nsec
|
230
216
|
end
|
231
217
|
|
232
|
-
#explicit function to convert to milliseconds
|
233
|
-
#will make things easier to maintain if/when fluentd supports higher than second resolutions
|
234
|
-
def to_millis(
|
218
|
+
# explicit function to convert to milliseconds
|
219
|
+
# will make things easier to maintain if/when fluentd supports higher than second resolutions
|
220
|
+
def to_millis(timestamp)
|
235
221
|
(timestamp.sec * 10**3) + (timestamp.nsec / 10**6)
|
236
222
|
end
|
237
223
|
|
238
|
-
def post_request(
|
239
|
-
|
240
|
-
https = Net::HTTP.new( uri.host, uri.port )
|
224
|
+
def post_request(uri, body)
|
225
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
241
226
|
https.use_ssl = true
|
242
227
|
|
243
|
-
#verify peers to prevent potential MITM attacks
|
228
|
+
# verify peers to prevent potential MITM attacks
|
244
229
|
if @ssl_verify_peer
|
245
|
-
https.ca_file = @ssl_ca_bundle_path
|
230
|
+
https.ca_file = @ssl_ca_bundle_path unless @ssl_ca_bundle_path.nil?
|
231
|
+
https.ssl_version = :TLSv1_2
|
246
232
|
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
247
233
|
https.verify_depth = @ssl_verify_depth
|
248
234
|
end
|
249
235
|
|
250
|
-
#use compression if enabled
|
236
|
+
# use compression if enabled
|
251
237
|
encoding = nil
|
252
238
|
|
253
239
|
if @compression_type
|
254
|
-
if @compression_type ==
|
255
|
-
encoding =
|
240
|
+
if @compression_type == "deflate"
|
241
|
+
encoding = "deflate"
|
256
242
|
body = Zlib::Deflate.deflate(body, @compression_level)
|
257
|
-
elsif @compression_type ==
|
258
|
-
encoding =
|
243
|
+
elsif @compression_type == "bz2"
|
244
|
+
encoding = "bz2"
|
259
245
|
io = StringIO.new
|
260
246
|
bz2 = RBzip2.default_adapter::Compressor.new io
|
261
247
|
bz2.write body
|
@@ -265,91 +251,82 @@ module Scalyr
|
|
265
251
|
end
|
266
252
|
|
267
253
|
post = Net::HTTP::Post.new uri.path
|
268
|
-
post.add_field(
|
254
|
+
post.add_field("Content-Type", "application/json")
|
269
255
|
|
270
|
-
if @compression_type
|
271
|
-
post.add_field( 'Content-Encoding', encoding )
|
272
|
-
end
|
256
|
+
post.add_field("Content-Encoding", encoding) if @compression_type
|
273
257
|
|
274
258
|
post.body = body
|
275
259
|
|
276
|
-
https.request(
|
277
|
-
|
260
|
+
https.request(post)
|
278
261
|
end
|
279
262
|
|
280
|
-
def handle_response(
|
263
|
+
def handle_response(response)
|
281
264
|
$log.debug "Response Code: #{response.code}"
|
282
265
|
$log.debug "Response Body: #{response.body}"
|
283
266
|
|
284
|
-
response_hash =
|
267
|
+
response_hash = {}
|
285
268
|
|
286
269
|
begin
|
287
|
-
response_hash = JSON.parse(
|
288
|
-
rescue
|
270
|
+
response_hash = JSON.parse(response.body)
|
271
|
+
rescue StandardError
|
289
272
|
response_hash["status"] = "Invalid JSON response from server"
|
290
273
|
end
|
291
274
|
|
292
|
-
#make sure the JSON reponse has a "status" field
|
293
|
-
|
275
|
+
# make sure the JSON reponse has a "status" field
|
276
|
+
unless response_hash.key? "status"
|
294
277
|
$log.debug "JSON response does not contain status message"
|
295
278
|
raise Scalyr::ServerError.new "JSON response does not contain status message"
|
296
279
|
end
|
297
280
|
|
298
281
|
status = response_hash["status"]
|
299
282
|
|
300
|
-
#4xx codes are handled separately
|
283
|
+
# 4xx codes are handled separately
|
301
284
|
if response.code =~ /^4\d\d/
|
302
285
|
raise Scalyr::Client4xxError.new status
|
303
286
|
else
|
304
|
-
if status != "success"
|
287
|
+
if status != "success" # rubocop:disable Style/IfInsideElse
|
305
288
|
if status =~ /discardBuffer/
|
306
289
|
$log.warn "Received 'discardBuffer' message from server. Buffer dropped."
|
307
|
-
elsif status =~ %r
|
290
|
+
elsif status =~ %r{/client/}i
|
308
291
|
raise Scalyr::ClientError.new status
|
309
|
-
else #don't check specifically for server, we assume all non-client errors are server errors
|
292
|
+
else # don't check specifically for server, we assume all non-client errors are server errors
|
310
293
|
raise Scalyr::ServerError.new status
|
311
294
|
end
|
312
|
-
elsif !response.code.include? "200" #response code is a string not an int
|
295
|
+
elsif !response.code.include? "200" # response code is a string not an int
|
313
296
|
raise Scalyr::ServerError
|
314
297
|
end
|
315
298
|
end
|
316
|
-
|
317
299
|
end
|
318
300
|
|
319
|
-
def build_add_events_body(
|
320
|
-
|
321
|
-
|
322
|
-
requests = Array.new
|
301
|
+
def build_add_events_body(chunk)
|
302
|
+
# requests
|
303
|
+
requests = []
|
323
304
|
|
324
|
-
#set of unique scalyr threads for this chunk
|
325
|
-
current_threads =
|
305
|
+
# set of unique scalyr threads for this chunk
|
306
|
+
current_threads = {}
|
326
307
|
|
327
|
-
#byte count
|
308
|
+
# byte count
|
328
309
|
total_bytes = 0
|
329
310
|
|
330
|
-
#create a Scalyr event object for each record in the chunk
|
331
|
-
events =
|
332
|
-
chunk.msgpack_each {|(tag, sec, nsec, record)|
|
333
|
-
|
334
|
-
timestamp = self.to_nanos( sec, nsec )
|
311
|
+
# create a Scalyr event object for each record in the chunk
|
312
|
+
events = []
|
313
|
+
chunk.msgpack_each {|(tag, sec, nsec, record)| # rubocop:disable Metrics/BlockLength
|
314
|
+
timestamp = to_nanos(sec, nsec)
|
335
315
|
|
336
316
|
thread_id = tag
|
337
317
|
|
338
|
-
#then update the map of threads for this chunk
|
318
|
+
# then update the map of threads for this chunk
|
339
319
|
current_threads[tag] = thread_id
|
340
320
|
|
341
|
-
#add a logfile field if one doesn't exist
|
342
|
-
|
343
|
-
record["logfile"] = "/fluentd/#{tag}"
|
344
|
-
end
|
321
|
+
# add a logfile field if one doesn't exist
|
322
|
+
record["logfile"] = "/fluentd/#{tag}" unless record.key? "logfile"
|
345
323
|
|
346
|
-
#append to list of events
|
347
|
-
event = {
|
348
|
-
|
349
|
-
|
350
|
-
}
|
324
|
+
# append to list of events
|
325
|
+
event = {thread: thread_id.to_s,
|
326
|
+
ts: timestamp,
|
327
|
+
attrs: record}
|
351
328
|
|
352
|
-
#get json string of event to keep track of how many bytes we are sending
|
329
|
+
# get json string of event to keep track of how many bytes we are sending
|
353
330
|
|
354
331
|
begin
|
355
332
|
event_json = event.to_json
|
@@ -357,72 +334,65 @@ module Scalyr
|
|
357
334
|
$log.warn "#{e.class}: #{e.message}"
|
358
335
|
|
359
336
|
# Send the faulty event to a label @ERROR block and allow to handle it there (output to exceptions file for ex)
|
360
|
-
time = Fluent::EventTime.new(
|
337
|
+
time = Fluent::EventTime.new(sec, nsec)
|
361
338
|
router.emit_error_event(tag, time, record, e)
|
362
339
|
|
363
340
|
event[:attrs].each do |key, value|
|
364
341
|
$log.debug "\t#{key} (#{value.encoding.name}): '#{value}'"
|
365
|
-
event[:attrs][key] = value.encode("UTF-8", :
|
342
|
+
event[:attrs][key] = value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "<?>").force_encoding("UTF-8") # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
366
343
|
end
|
367
344
|
event_json = event.to_json
|
368
345
|
end
|
369
346
|
|
370
|
-
#generate new request if json size of events in the array exceed maximum request buffer size
|
347
|
+
# generate new request if json size of events in the array exceed maximum request buffer size
|
371
348
|
append_event = true
|
372
349
|
if total_bytes + event_json.bytesize > @max_request_buffer
|
373
|
-
#make sure we always have at least one event
|
374
|
-
if events.
|
350
|
+
# make sure we always have at least one event
|
351
|
+
if events.empty?
|
375
352
|
events << event
|
376
353
|
append_event = false
|
377
354
|
end
|
378
|
-
request =
|
355
|
+
request = create_request(events, current_threads)
|
379
356
|
requests << request
|
380
357
|
|
381
358
|
total_bytes = 0
|
382
|
-
current_threads =
|
383
|
-
events =
|
359
|
+
current_threads = {}
|
360
|
+
events = []
|
384
361
|
end
|
385
362
|
|
386
|
-
#if we haven't consumed the current event already
|
387
|
-
#add it to the end of our array and keep track of the json bytesize
|
363
|
+
# if we haven't consumed the current event already
|
364
|
+
# add it to the end of our array and keep track of the json bytesize
|
388
365
|
if append_event
|
389
366
|
events << event
|
390
367
|
total_bytes += event_json.bytesize
|
391
368
|
end
|
392
|
-
|
393
369
|
}
|
394
370
|
|
395
|
-
#create a final request with any left over events
|
396
|
-
request =
|
371
|
+
# create a final request with any left over events
|
372
|
+
request = create_request(events, current_threads)
|
397
373
|
requests << request
|
398
|
-
|
399
374
|
end
|
400
375
|
|
401
|
-
def create_request(
|
402
|
-
#build the scalyr thread objects
|
403
|
-
threads =
|
376
|
+
def create_request(events, current_threads)
|
377
|
+
# build the scalyr thread objects
|
378
|
+
threads = []
|
404
379
|
current_threads.each do |tag, id|
|
405
|
-
threads << {
|
406
|
-
|
407
|
-
}
|
380
|
+
threads << {id: id.to_s,
|
381
|
+
name: "Fluentd: #{tag}"}
|
408
382
|
end
|
409
383
|
|
410
|
-
current_time =
|
384
|
+
current_time = to_millis(Fluent::Engine.now)
|
411
385
|
|
412
|
-
body = {
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
}
|
386
|
+
body = {token: @api_write_token,
|
387
|
+
client_timestamp: current_time.to_s,
|
388
|
+
session: @session,
|
389
|
+
events: events,
|
390
|
+
threads: threads}
|
418
391
|
|
419
|
-
#add server_attributes hash if it exists
|
420
|
-
if @server_attributes
|
421
|
-
body[:sessionInfo] = @server_attributes
|
422
|
-
end
|
392
|
+
# add server_attributes hash if it exists
|
393
|
+
body[:sessionInfo] = @server_attributes if @server_attributes
|
423
394
|
|
424
|
-
{
|
395
|
+
{body: body.to_json, record_count: events.size}
|
425
396
|
end
|
426
|
-
|
427
397
|
end
|
428
398
|
end
|