fluent-plugin-scalyr 0.8.7 → 0.8.12
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 +38 -11
- data/Rakefile +10 -6
- data/VERSION +1 -1
- data/fluent-plugin-scalyr.gemspec +11 -8
- data/fluent.conf.sample +1 -1
- data/lib/fluent/plugin/out_scalyr.rb +209 -230
- data/lib/fluent/plugin/{scalyr-exceptions.rb → scalyr_exceptions.rb} +2 -2
- data/lib/fluent/plugin/scalyr_utils.rb +65 -0
- data/test/helper.rb +12 -6
- data/test/test_config.rb +24 -21
- data/test/test_events.rb +226 -142
- data/test/test_handle_response.rb +34 -35
- data/test/test_ssl_verify.rb +101 -10
- data/test/test_utils.rb +100 -0
- metadata +49 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e738c523b1cea2d7aa0976064cb4fa85379b63795f1230a965da934033bc619
|
4
|
+
data.tar.gz: '09241b0eabc17074c5b4effb5f53ba47d183bb97ad724a064d6e9cfa9d5e8641'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 238b972ec9340a013d473911c05955e0a39d03f706d215167f2b3837bf46610917d2741680fd698c5d1075d1f7a307debd3c7cf4111ebba07280110bff2239dc
|
7
|
+
data.tar.gz: 1c42dfccbfa8963652198b3b2c0da102feda2b1052370b3296c26c781f5ab1fcd0126b513f87139e055c437c82347c597fe930f3739b6f61a4f8ac7d1764996a
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
Scalyr output plugin for Fluentd
|
2
|
-
|
2
|
+
================================
|
3
3
|
|
4
4
|
**Note:** Fluentd introduced breaking changes to their plugin API between
|
5
5
|
version 0.12 and 0.14.
|
@@ -24,7 +24,7 @@ Fluentd may format log messages into json or some other format. If you want to
|
|
24
24
|
format none
|
25
25
|
```
|
26
26
|
|
27
|
-
The Scalyr output plugin assigns a unique Scalyr session id for each Fluentd <match> block. It is recommended that a single machine doesn't create too many simultaneous Scalyr sessions, so if possible you should try to have a single match for all logs you wish to send to Scalyr.
|
27
|
+
The Scalyr output plugin assigns a unique Scalyr session id for each Fluentd <match> block, or for each worker. It is recommended that a single machine doesn't create too many simultaneous Scalyr sessions, so if possible you should try to have a single match for all logs you wish to send to Scalyr.
|
28
28
|
|
29
29
|
This can be done by specifying tags such as scalyr.apache, scalyr.maillog etc and matching on scalyr.\*
|
30
30
|
|
@@ -33,7 +33,7 @@ Fluentd tag names will be used for the logfile name in Scalyr.
|
|
33
33
|
Scalyr Parsers and Custom Fields
|
34
34
|
--------------------------------
|
35
35
|
|
36
|
-
You may also need to specify a Scalyr parser for your log message or add custom fields to each log event. This can be done using Fluentd's filter mechanism, in particular the [record_transformer filter](
|
36
|
+
You may also need to specify a Scalyr parser for your log message or add custom fields to each log event. This can be done using Fluentd's filter mechanism, in particular the [record_transformer filter](https://docs.fluentd.org/filter/record_transformer).
|
37
37
|
|
38
38
|
For example, if you want to use Scalyr's ```accessLog``` parser for all events with the ```scalyr.access``` tag you would add the following to your fluent.conf file:
|
39
39
|
|
@@ -66,7 +66,9 @@ The following configuration options are also supported:
|
|
66
66
|
|
67
67
|
#scalyr specific options
|
68
68
|
api_write_token YOUR_SCALYR_WRITE_TOKEN
|
69
|
-
compression_type
|
69
|
+
compression_type deflate
|
70
|
+
compression_level 6
|
71
|
+
use_hostname_for_serverhost true
|
70
72
|
server_attributes {
|
71
73
|
"serverHost": "front-1",
|
72
74
|
"serverType": "frontend",
|
@@ -79,7 +81,7 @@ The following configuration options are also supported:
|
|
79
81
|
ssl_verify_depth 5
|
80
82
|
message_field message
|
81
83
|
|
82
|
-
max_request_buffer
|
84
|
+
max_request_buffer 5500000
|
83
85
|
|
84
86
|
force_message_encoding nil
|
85
87
|
replace_invalid_utf8 false
|
@@ -91,14 +93,18 @@ The following configuration options are also supported:
|
|
91
93
|
retry_max_interval 30s
|
92
94
|
flush_interval 5s
|
93
95
|
flush_thread_count 1
|
94
|
-
chunk_limit_size
|
96
|
+
chunk_limit_size 2.5m
|
95
97
|
queue_limit_length 1024
|
96
98
|
</buffer>
|
97
99
|
|
98
100
|
</match>
|
99
101
|
```
|
100
102
|
|
101
|
-
|
103
|
+
For some additional examples of configuration for different setups, please refer to the
|
104
|
+
[examples/configs/](https://github.com/scalyr/scalyr-fluentd/tree/master/examples/configs/)
|
105
|
+
directory.
|
106
|
+
|
107
|
+
### Scalyr specific options
|
102
108
|
|
103
109
|
***compression_type*** - compress Scalyr traffic to reduce network traffic. Options are `bz2` and `deflate`. See [here](https://www.scalyr.com/help/scalyr-agent#compressing) for more details. This feature is optional.
|
104
110
|
|
@@ -106,9 +112,11 @@ The following configuration options are also supported:
|
|
106
112
|
|
107
113
|
***server_attributes*** - a JSON hash containing custom server attributes you want to include with each log request. This value is optional and defaults to *nil*.
|
108
114
|
|
115
|
+
***use_hostname_for_serverhost*** - if `true` then if `server_attributes` is nil or it does *not* include a field called `serverHost` then the plugin will add the `serverHost` field with the value set to the hostname that fluentd is running on. Defaults to `true`.
|
116
|
+
|
109
117
|
***scalyr_server*** - the Scalyr server to send API requests to. This value is optional and defaults to https://agent.scalyr.com/
|
110
118
|
|
111
|
-
***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.
|
112
120
|
|
113
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:
|
114
122
|
|
@@ -126,13 +134,13 @@ The cURL project maintains CA certificate bundles automatically converted from m
|
|
126
134
|
|
127
135
|
***message_field*** - Scalyr expects all log events to have a 'message' field containing the contents of a log message. If your event has the log message stored in another field, you can specify the field name here, and the plugin will rename that field to 'message' before sending the data to Scalyr. **Note:** this will override any existing 'message' field if the log record contains both a 'message' field and the field specified by this config option.
|
128
136
|
|
129
|
-
***max_request_buffer*** - The maximum size in bytes of each request to send to Scalyr. Defaults to
|
137
|
+
***max_request_buffer*** - The maximum size in bytes of each request to send to Scalyr. Defaults to 5,500,000 (5.5MB). Fluentd chunks that generate JSON requests larger than the max_request_buffer will be split in to multiple separate requests. **Note:** The maximum size the Scalyr servers accept for this value is 6MB and requests containing data larger than this will be rejected.
|
130
138
|
|
131
139
|
***force_message_encoding*** - Set a specific encoding for all your log messages (defaults to nil). If your log messages are not in UTF-8, this can cause problems when converting the message to JSON in order to send to the Scalyr server. You can avoid these problems by setting an encoding for your log messages so they can be correctly converted.
|
132
140
|
|
133
141
|
***replace_invalid_utf8*** - If this value is true and ***force_message_encoding*** is set to 'UTF-8' then all invalid UTF-8 sequences in log messages will be replaced with <?>. Defaults to false. This flag has no effect if ***force_message_encoding*** is not set to 'UTF-8'.
|
134
142
|
|
135
|
-
|
143
|
+
### Buffer options
|
136
144
|
|
137
145
|
***retry_max_times*** - the maximum number of times to retry a failed post request before giving up. Defaults to *40*.
|
138
146
|
|
@@ -144,7 +152,7 @@ The cURL project maintains CA certificate bundles automatically converted from m
|
|
144
152
|
|
145
153
|
***flush_thread_count*** - the number of threads to use to upload logs. This is currently fixed to 1 will cause fluentd to fail with a ConfigError if set to anything greater.
|
146
154
|
|
147
|
-
***chunk_limit_size*** - the maximum amount of log data to send to Scalyr in a single request. Defaults to *
|
155
|
+
***chunk_limit_size*** - the maximum amount of log data to send to Scalyr in a single request. Defaults to *2.5MB*. **Note:** if you set this value too large, then Scalyr may reject your requests. Requests smaller than 6 MB will typically be accepted by Scalyr, but note that the 6 MB limit also includes the entire request body and all associated JSON keys and punctuation, which may be considerably larger than the raw log data. This value should be set lower than the `max_request_buffer` option.
|
148
156
|
|
149
157
|
***queue_limit_length*** - the maximum number of chunks to buffer before dropping new log requests. Defaults to *1024*. Combines with ***chunk_limit_size*** to give you the total amount of buffer to use in the event of request failures before dropping requests.
|
150
158
|
|
@@ -172,3 +180,22 @@ Which builds the gem and puts it in the pkg directory, then install the Gem usin
|
|
172
180
|
```
|
173
181
|
fluent-gem install pkg/fluent-plugin-scalyr-<VERSION>.gem
|
174
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,15 @@
|
|
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
|
12
|
+
test.options = "--verbose=verbose"
|
9
13
|
end
|
10
14
|
|
11
|
-
task :
|
15
|
+
task default: [:build]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.
|
1
|
+
0.8.12
|
@@ -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"
|
@@ -9,18 +11,19 @@ Gem::Specification.new do |gem|
|
|
9
11
|
gem.authors = ["Imron Alston"]
|
10
12
|
gem.licenses = ["Apache-2.0"]
|
11
13
|
gem.email = "imron@scalyr.com"
|
12
|
-
gem.has_rdoc = false
|
13
14
|
gem.platform = Gem::Platform::RUBY
|
14
|
-
gem.files = Dir[
|
15
|
+
gem.files = Dir["AUTHORS", "Gemfile", "LICENSE", "README.md", "Rakefile", "VERSION",
|
16
|
+
"fluent-plugin-scalyr.gemspec", "fluent.conf.sample", "lib/**/*", "test/**/*"]
|
15
17
|
gem.test_files = Dir.glob("{test,spec,features}/**/*")
|
16
|
-
gem.executables = Dir.glob("bin/*").map{
|
17
|
-
gem.require_paths = [
|
18
|
-
gem.add_dependency "fluentd", [">= 0.14.0", "< 2"]
|
18
|
+
gem.executables = Dir.glob("bin/*").map {|f| File.basename(f) }
|
19
|
+
gem.require_paths = ["lib"]
|
19
20
|
gem.add_dependency "ffi", "1.9.25"
|
21
|
+
gem.add_dependency "fluentd", [">= 0.14.0", "< 2"]
|
20
22
|
gem.add_dependency "rbzip2", "0.3.0"
|
21
23
|
gem.add_dependency "zlib"
|
24
|
+
gem.add_development_dependency "bundler", "~> 1.9"
|
25
|
+
gem.add_development_dependency "flexmock", "~> 1.2"
|
22
26
|
gem.add_development_dependency "rake", "~> 0.9"
|
27
|
+
gem.add_development_dependency "rubocop", "~> 0.4"
|
23
28
|
gem.add_development_dependency "test-unit", "~> 3.0"
|
24
|
-
gem.add_development_dependency "flexmock", "~> 1.2"
|
25
|
-
gem.add_development_dependency "bundler", "~> 1.9"
|
26
29
|
end
|
data/fluent.conf.sample
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#
|
2
4
|
# Scalyr Output Plugin for Fluentd
|
3
5
|
#
|
@@ -15,45 +17,45 @@
|
|
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
|
30
|
-
|
20
|
+
require "fluent/plugin/output"
|
21
|
+
require "fluent/plugin/scalyr_exceptions"
|
22
|
+
require "fluent/plugin/scalyr_utils"
|
23
|
+
require "fluent/plugin_helper/compat_parameters"
|
24
|
+
require "json"
|
25
|
+
require "net/http"
|
26
|
+
require "net/https"
|
27
|
+
require "rbzip2"
|
28
|
+
require "stringio"
|
29
|
+
require "zlib"
|
30
|
+
require "securerandom"
|
31
|
+
require "socket"
|
31
32
|
module Scalyr
|
32
33
|
class ScalyrOut < Fluent::Plugin::Output
|
33
|
-
Fluent::Plugin.register_output(
|
34
|
+
Fluent::Plugin.register_output("scalyr", self)
|
34
35
|
helpers :compat_parameters
|
35
36
|
helpers :event_emitter
|
36
37
|
|
37
38
|
config_param :api_write_token, :string
|
38
|
-
config_param :server_attributes, :hash, :
|
39
|
-
config_param :
|
40
|
-
config_param :
|
41
|
-
config_param :
|
42
|
-
config_param :
|
43
|
-
config_param :
|
44
|
-
config_param :
|
45
|
-
config_param :
|
46
|
-
config_param :
|
47
|
-
config_param :
|
48
|
-
config_param :
|
39
|
+
config_param :server_attributes, :hash, default: nil
|
40
|
+
config_param :use_hostname_for_serverhost, :bool, default: true
|
41
|
+
config_param :scalyr_server, :string, default: "https://agent.scalyr.com/"
|
42
|
+
config_param :ssl_ca_bundle_path, :string, default: nil
|
43
|
+
config_param :ssl_verify_peer, :bool, default: true
|
44
|
+
config_param :ssl_verify_depth, :integer, default: 5
|
45
|
+
config_param :message_field, :string, default: "message"
|
46
|
+
config_param :max_request_buffer, :integer, default: 5_500_000
|
47
|
+
config_param :force_message_encoding, :string, default: nil
|
48
|
+
config_param :replace_invalid_utf8, :bool, default: false
|
49
|
+
config_param :compression_type, :string, default: nil # Valid options are bz2, deflate or None. Defaults to None.
|
50
|
+
config_param :compression_level, :integer, default: 6 # An int containing the compression level of compression to use, from 1-9. Defaults to 6
|
49
51
|
|
50
52
|
config_section :buffer do
|
51
|
-
config_set_default :retry_max_times, 40 #try a maximum of 40 times before discarding
|
52
|
-
config_set_default :retry_max_interval,
|
53
|
-
config_set_default :retry_wait, 5 #wait a minimum of 5 seconds per retry
|
54
|
-
config_set_default :flush_interval, 5 #default flush interval of 5 seconds
|
55
|
-
config_set_default :chunk_limit_size,
|
56
|
-
config_set_default :queue_limit_length, 1024 #default queue size of 1024
|
53
|
+
config_set_default :retry_max_times, 40 # try a maximum of 40 times before discarding
|
54
|
+
config_set_default :retry_max_interval, 30 # wait a maximum of 30 seconds per retry
|
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, 2_500_000 # default chunk size of 2.5mb
|
58
|
+
config_set_default :queue_limit_length, 1024 # default queue size of 1024
|
57
59
|
end
|
58
60
|
|
59
61
|
# support for version 0.14.0:
|
@@ -65,180 +67,182 @@ module Scalyr
|
|
65
67
|
true
|
66
68
|
end
|
67
69
|
|
68
|
-
def
|
70
|
+
def multi_workers_ready?
|
71
|
+
true
|
72
|
+
end
|
69
73
|
|
70
|
-
|
71
|
-
|
74
|
+
def configure(conf)
|
75
|
+
if conf.elements("buffer").empty?
|
76
|
+
$log.warn "Pre 0.14.0 configuration file detected. Please consider updating your configuration file" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
72
77
|
end
|
73
78
|
|
74
|
-
compat_parameters_buffer(
|
79
|
+
compat_parameters_buffer(conf, default_chunk_key: "")
|
75
80
|
|
76
81
|
super
|
77
82
|
|
78
|
-
if @buffer.chunk_limit_size >
|
79
|
-
$log.warn "Buffer chunk size is greater than
|
83
|
+
if @buffer.chunk_limit_size > 6_000_000
|
84
|
+
$log.warn "Buffer chunk size is greater than 6Mb. This may result in requests being rejected by Scalyr" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
80
85
|
end
|
81
86
|
|
82
|
-
if @max_request_buffer >
|
83
|
-
$log.warn "Maximum request buffer >
|
87
|
+
if @max_request_buffer > 6_000_000
|
88
|
+
$log.warn "Maximum request buffer > 6Mb. This may result in requests being rejected by Scalyr" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
84
89
|
end
|
85
90
|
|
86
91
|
@message_encoding = nil
|
87
|
-
if @force_message_encoding.to_s !=
|
92
|
+
if @force_message_encoding.to_s != ""
|
88
93
|
begin
|
89
|
-
@message_encoding = Encoding.find(
|
94
|
+
@message_encoding = Encoding.find(@force_message_encoding)
|
90
95
|
$log.debug "Forcing message encoding to '#{@force_message_encoding}'"
|
91
96
|
rescue ArgumentError
|
92
97
|
$log.warn "No encoding '#{@force_message_encoding}' found. Ignoring"
|
93
98
|
end
|
94
99
|
end
|
95
100
|
|
96
|
-
#evaluate any statements in string value of the server_attributes object
|
101
|
+
# evaluate any statements in string value of the server_attributes object
|
97
102
|
if @server_attributes
|
98
103
|
new_attributes = {}
|
99
104
|
@server_attributes.each do |key, value|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
105
|
+
next unless value.is_a?(String)
|
106
|
+
|
107
|
+
m = /^\#{(.*)}$/.match(value)
|
108
|
+
new_attributes[key] = if m
|
109
|
+
eval(m[1]) # rubocop:disable Security/Eval
|
110
|
+
else
|
111
|
+
value
|
112
|
+
end
|
108
113
|
end
|
109
114
|
@server_attributes = new_attributes
|
110
115
|
end
|
111
116
|
|
112
|
-
|
117
|
+
# See if we should use the hostname as the server_attributes.serverHost
|
118
|
+
if @use_hostname_for_serverhost
|
119
|
+
|
120
|
+
# ensure server_attributes is not nil
|
121
|
+
@server_attributes = {} if @server_attributes.nil?
|
122
|
+
|
123
|
+
# only set serverHost if it doesn't currently exist in server_attributes
|
124
|
+
# Note: Use strings rather than symbols for the key, because keys coming
|
125
|
+
# from the config file will be strings
|
126
|
+
unless @server_attributes.key? "serverHost"
|
127
|
+
@server_attributes["serverHost"] = Socket.gethostname
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
@scalyr_server << "/" unless @scalyr_server.end_with?("/")
|
113
132
|
|
114
133
|
@add_events_uri = URI @scalyr_server + "addEvents"
|
115
134
|
|
116
135
|
num_threads = @buffer_config.flush_thread_count
|
117
136
|
|
118
|
-
#forcibly limit the number of threads to 1 for now, to ensure requests always have incrementing timestamps
|
119
|
-
|
137
|
+
# forcibly limit the number of threads to 1 for now, to ensure requests always have incrementing timestamps
|
138
|
+
if num_threads > 1
|
139
|
+
raise Fluent::ConfigError, "num_threads is currently limited to 1. You specified #{num_threads}."
|
140
|
+
end
|
120
141
|
end
|
121
142
|
|
122
143
|
def start
|
123
144
|
super
|
124
|
-
|
125
|
-
#Generate a session id. This will be called once for each <match> in fluent.conf that uses scalyr
|
145
|
+
# Generate a session id. This will be called once for each <match> in fluent.conf that uses scalyr
|
126
146
|
@session = SecureRandom.uuid
|
127
147
|
|
128
|
-
@
|
129
|
-
#the following variables are all under the control of the above mutex
|
130
|
-
@thread_ids = Hash.new #hash of tags -> id
|
131
|
-
@next_id = 1 #incrementing thread id for the session
|
132
|
-
@last_timestamp = 0 #timestamp of most recent event in nanoseconds since epoch
|
133
|
-
|
148
|
+
$log.info "Scalyr Fluentd Plugin ID id=#{plugin_id} worker=#{fluentd_worker_id} session=#{@session}" # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
134
149
|
end
|
135
150
|
|
136
|
-
def format(
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
sec = components[0].to_i
|
149
|
-
nsec = (components[1] * 10**9).to_i
|
150
|
-
time = Fluent::EventTime.new( sec, nsec )
|
151
|
-
end
|
151
|
+
def format(tag, time, record)
|
152
|
+
time = Fluent::Engine.now if time.nil?
|
153
|
+
|
154
|
+
# handle timestamps that are not EventTime types
|
155
|
+
if time.is_a?(Integer)
|
156
|
+
time = Fluent::EventTime.new(time)
|
157
|
+
elsif time.is_a?(Float)
|
158
|
+
components = time.divmod 1 # get integer and decimal components
|
159
|
+
sec = components[0].to_i
|
160
|
+
nsec = (components[1] * 10**9).to_i
|
161
|
+
time = Fluent::EventTime.new(sec, nsec)
|
162
|
+
end
|
152
163
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
record["message"] = record[@message_field]
|
159
|
-
record.delete( @message_field )
|
164
|
+
if @message_field != "message"
|
165
|
+
if record.key? @message_field
|
166
|
+
if record.key? "message"
|
167
|
+
$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
|
160
168
|
end
|
169
|
+
record["message"] = record[@message_field]
|
170
|
+
record.delete(@message_field)
|
161
171
|
end
|
172
|
+
end
|
162
173
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
174
|
+
if @message_encoding && record.key?("message") && record["message"]
|
175
|
+
if @replace_invalid_utf8 && (@message_encoding == Encoding::UTF_8)
|
176
|
+
record["message"] = record["message"].encode("UTF-8", invalid: :replace, undef: :replace, replace: "<?>").force_encoding("UTF-8") # rubocop:disable Layout/LineLength, Lint/RedundantCopDisableDirective
|
177
|
+
else
|
178
|
+
record["message"].force_encoding(@message_encoding)
|
169
179
|
end
|
170
|
-
[tag, time.sec, time.nsec, record].to_msgpack
|
171
|
-
|
172
|
-
rescue JSON::GeneratorError
|
173
|
-
$log.warn "Unable to format message due to JSON::GeneratorError. Record is:\n\t#{record.to_s}"
|
174
|
-
raise
|
175
180
|
end
|
181
|
+
[tag, time.sec, time.nsec, record].to_msgpack
|
182
|
+
rescue JSON::GeneratorError
|
183
|
+
$log.warn "Unable to format message due to JSON::GeneratorError. Record is:\n\t#{record}"
|
184
|
+
raise
|
176
185
|
end
|
177
186
|
|
178
|
-
#called by fluentd when a chunk of log messages is ready
|
179
|
-
def write(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
$log.debug "Chunk split into #{requests.size} request(s)."
|
184
|
-
|
185
|
-
requests.each_with_index { |request, index|
|
186
|
-
$log.debug "Request #{index + 1}/#{requests.size}: #{request[:body].bytesize} bytes"
|
187
|
-
begin
|
188
|
-
response = self.post_request( @add_events_uri, request[:body] )
|
189
|
-
self.handle_response( response )
|
190
|
-
rescue OpenSSL::SSL::SSLError => e
|
191
|
-
if e.message.include? "certificate verify failed"
|
192
|
-
$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}'"
|
193
|
-
end
|
194
|
-
$log.warn e.message
|
195
|
-
$log.warn "Discarding buffer chunk without retrying or logging to <secondary>"
|
196
|
-
rescue Scalyr::Client4xxError => e
|
197
|
-
$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]}..."
|
187
|
+
# called by fluentd when a chunk of log messages is ready
|
188
|
+
def write(chunk)
|
189
|
+
$log.debug "Size of chunk is: #{chunk.size}"
|
190
|
+
requests = build_add_events_body(chunk)
|
191
|
+
$log.debug "Chunk split into #{requests.size} request(s)."
|
198
192
|
|
193
|
+
requests.each_with_index {|request, index|
|
194
|
+
$log.debug "Request #{index + 1}/#{requests.size}: #{request[:body].bytesize} bytes"
|
195
|
+
begin
|
196
|
+
response = post_request(@add_events_uri, request[:body])
|
197
|
+
handle_response(response)
|
198
|
+
rescue OpenSSL::SSL::SSLError => e
|
199
|
+
if e.message.include? "certificate verify failed"
|
200
|
+
$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
|
199
201
|
end
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
202
|
+
$log.warn e.message
|
203
|
+
$log.warn "Discarding buffer chunk without retrying or logging to <secondary>"
|
204
|
+
rescue Scalyr::Client4xxError => e
|
205
|
+
$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]}..."
|
206
|
+
end
|
207
|
+
}
|
208
|
+
rescue JSON::GeneratorError
|
209
|
+
$log.warn "Unable to format message due to JSON::GeneratorError."
|
210
|
+
raise
|
206
211
|
end
|
207
212
|
|
208
|
-
|
209
|
-
#
|
210
|
-
|
211
|
-
def to_nanos( seconds, nsec )
|
213
|
+
# explicit function to convert to nanoseconds
|
214
|
+
# will make things easier to maintain if/when fluentd supports higher than second resolutions
|
215
|
+
def to_nanos(seconds, nsec)
|
212
216
|
(seconds * 10**9) + nsec
|
213
217
|
end
|
214
218
|
|
215
|
-
#explicit function to convert to milliseconds
|
216
|
-
#will make things easier to maintain if/when fluentd supports higher than second resolutions
|
217
|
-
def to_millis(
|
219
|
+
# explicit function to convert to milliseconds
|
220
|
+
# will make things easier to maintain if/when fluentd supports higher than second resolutions
|
221
|
+
def to_millis(timestamp)
|
218
222
|
(timestamp.sec * 10**3) + (timestamp.nsec / 10**6)
|
219
223
|
end
|
220
224
|
|
221
|
-
def post_request(
|
222
|
-
|
223
|
-
https = Net::HTTP.new( uri.host, uri.port )
|
225
|
+
def post_request(uri, body)
|
226
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
224
227
|
https.use_ssl = true
|
225
228
|
|
226
|
-
#verify peers to prevent potential MITM attacks
|
229
|
+
# verify peers to prevent potential MITM attacks
|
227
230
|
if @ssl_verify_peer
|
228
|
-
https.ca_file = @ssl_ca_bundle_path
|
231
|
+
https.ca_file = @ssl_ca_bundle_path unless @ssl_ca_bundle_path.nil?
|
232
|
+
https.ssl_version = :TLSv1_2
|
229
233
|
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
230
234
|
https.verify_depth = @ssl_verify_depth
|
231
235
|
end
|
232
236
|
|
233
|
-
#use compression if enabled
|
237
|
+
# use compression if enabled
|
234
238
|
encoding = nil
|
235
239
|
|
236
240
|
if @compression_type
|
237
|
-
if @compression_type ==
|
238
|
-
encoding =
|
241
|
+
if @compression_type == "deflate"
|
242
|
+
encoding = "deflate"
|
239
243
|
body = Zlib::Deflate.deflate(body, @compression_level)
|
240
|
-
elsif @compression_type ==
|
241
|
-
encoding =
|
244
|
+
elsif @compression_type == "bz2"
|
245
|
+
encoding = "bz2"
|
242
246
|
io = StringIO.new
|
243
247
|
bz2 = RBzip2.default_adapter::Compressor.new io
|
244
248
|
bz2.write body
|
@@ -248,179 +252,154 @@ module Scalyr
|
|
248
252
|
end
|
249
253
|
|
250
254
|
post = Net::HTTP::Post.new uri.path
|
251
|
-
post.add_field(
|
255
|
+
post.add_field("Content-Type", "application/json")
|
252
256
|
|
253
|
-
if @compression_type
|
254
|
-
post.add_field( 'Content-Encoding', encoding )
|
255
|
-
end
|
257
|
+
post.add_field("Content-Encoding", encoding) if @compression_type
|
256
258
|
|
257
259
|
post.body = body
|
258
260
|
|
259
|
-
https.request(
|
260
|
-
|
261
|
+
https.request(post)
|
261
262
|
end
|
262
263
|
|
263
|
-
def handle_response(
|
264
|
+
def handle_response(response)
|
264
265
|
$log.debug "Response Code: #{response.code}"
|
265
266
|
$log.debug "Response Body: #{response.body}"
|
266
267
|
|
267
|
-
response_hash =
|
268
|
+
response_hash = {}
|
268
269
|
|
269
270
|
begin
|
270
|
-
response_hash = JSON.parse(
|
271
|
-
rescue
|
271
|
+
response_hash = JSON.parse(response.body)
|
272
|
+
rescue StandardError
|
272
273
|
response_hash["status"] = "Invalid JSON response from server"
|
273
274
|
end
|
274
275
|
|
275
|
-
#make sure the JSON reponse has a "status" field
|
276
|
-
|
276
|
+
# make sure the JSON reponse has a "status" field
|
277
|
+
unless response_hash.key? "status"
|
277
278
|
$log.debug "JSON response does not contain status message"
|
278
279
|
raise Scalyr::ServerError.new "JSON response does not contain status message"
|
279
280
|
end
|
280
281
|
|
281
282
|
status = response_hash["status"]
|
282
283
|
|
283
|
-
#4xx codes are handled separately
|
284
|
+
# 4xx codes are handled separately
|
284
285
|
if response.code =~ /^4\d\d/
|
285
286
|
raise Scalyr::Client4xxError.new status
|
286
287
|
else
|
287
|
-
if status != "success"
|
288
|
+
if status != "success" # rubocop:disable Style/IfInsideElse
|
288
289
|
if status =~ /discardBuffer/
|
289
290
|
$log.warn "Received 'discardBuffer' message from server. Buffer dropped."
|
290
|
-
elsif status =~ %r
|
291
|
+
elsif status =~ %r{/client/}i
|
291
292
|
raise Scalyr::ClientError.new status
|
292
|
-
else #don't check specifically for server, we assume all non-client errors are server errors
|
293
|
+
else # don't check specifically for server, we assume all non-client errors are server errors
|
293
294
|
raise Scalyr::ServerError.new status
|
294
295
|
end
|
295
|
-
elsif !response.code.include? "200" #response code is a string not an int
|
296
|
+
elsif !response.code.include? "200" # response code is a string not an int
|
296
297
|
raise Scalyr::ServerError
|
297
298
|
end
|
298
299
|
end
|
299
|
-
|
300
300
|
end
|
301
301
|
|
302
|
-
def build_add_events_body(
|
303
|
-
|
304
|
-
|
305
|
-
requests = Array.new
|
302
|
+
def build_add_events_body(chunk)
|
303
|
+
# requests
|
304
|
+
requests = []
|
306
305
|
|
307
|
-
#set of unique scalyr threads for this chunk
|
308
|
-
current_threads =
|
306
|
+
# set of unique scalyr threads for this chunk
|
307
|
+
current_threads = {}
|
309
308
|
|
310
|
-
#byte count
|
309
|
+
# byte count
|
311
310
|
total_bytes = 0
|
312
311
|
|
313
|
-
#create a Scalyr event object for each record in the chunk
|
314
|
-
events =
|
315
|
-
chunk.msgpack_each {|(tag, sec, nsec, record)|
|
316
|
-
|
317
|
-
timestamp = self.to_nanos( sec, nsec )
|
312
|
+
# create a Scalyr event object for each record in the chunk
|
313
|
+
events = []
|
314
|
+
chunk.msgpack_each {|(tag, sec, nsec, record)| # rubocop:disable Metrics/BlockLength
|
315
|
+
timestamp = to_nanos(sec, nsec)
|
318
316
|
|
319
|
-
thread_id =
|
317
|
+
thread_id = tag
|
320
318
|
|
321
|
-
|
322
|
-
#ensure timestamp is at least 1 nanosecond greater than the last one
|
323
|
-
timestamp = [timestamp, @last_timestamp + 1].max
|
324
|
-
@last_timestamp = timestamp
|
325
|
-
|
326
|
-
#get thread id or add a new one if we haven't seen this tag before
|
327
|
-
if @thread_ids.key? tag
|
328
|
-
thread_id = @thread_ids[tag]
|
329
|
-
else
|
330
|
-
thread_id = @next_id
|
331
|
-
@thread_ids[tag] = thread_id
|
332
|
-
@next_id += 1
|
333
|
-
end
|
334
|
-
}
|
335
|
-
|
336
|
-
#then update the map of threads for this chunk
|
319
|
+
# then update the map of threads for this chunk
|
337
320
|
current_threads[tag] = thread_id
|
338
321
|
|
339
|
-
#add a logfile field if one doesn't exist
|
340
|
-
|
341
|
-
record["logfile"] = "/fluentd/#{tag}"
|
342
|
-
end
|
322
|
+
# add a logfile field if one doesn't exist
|
323
|
+
record["logfile"] = "/fluentd/#{tag}" unless record.key? "logfile"
|
343
324
|
|
344
|
-
#append to list of events
|
345
|
-
event = {
|
346
|
-
|
347
|
-
|
348
|
-
}
|
325
|
+
# append to list of events
|
326
|
+
event = {thread: thread_id.to_s,
|
327
|
+
ts: timestamp,
|
328
|
+
attrs: record}
|
349
329
|
|
350
|
-
#get json string of event to keep track of how many bytes we are sending
|
330
|
+
# get json string of event to keep track of how many bytes we are sending
|
351
331
|
|
352
332
|
begin
|
353
333
|
event_json = event.to_json
|
354
334
|
rescue JSON::GeneratorError, Encoding::UndefinedConversionError => e
|
355
|
-
$log.warn "#{e.class}: #{e.message}"
|
335
|
+
$log.warn "JSON serialization of the event failed: #{e.class}: #{e.message}"
|
356
336
|
|
357
337
|
# Send the faulty event to a label @ERROR block and allow to handle it there (output to exceptions file for ex)
|
358
|
-
time = Fluent::EventTime.new(
|
338
|
+
time = Fluent::EventTime.new(sec, nsec)
|
359
339
|
router.emit_error_event(tag, time, record, e)
|
360
340
|
|
341
|
+
# Print attribute values for debugging / troubleshooting purposes
|
342
|
+
$log.debug "Event attributes:"
|
343
|
+
|
361
344
|
event[:attrs].each do |key, value|
|
362
|
-
|
363
|
-
|
345
|
+
# NOTE: value doesn't always value.encoding attribute so we use .class which is always available
|
346
|
+
$log.debug "\t#{key} (#{value.class}): '#{value}'"
|
364
347
|
end
|
348
|
+
|
349
|
+
# Recursively re-encode and sanitize potentially bad string values
|
350
|
+
event[:attrs] = sanitize_and_reencode_value(event[:attrs])
|
365
351
|
event_json = event.to_json
|
366
352
|
end
|
367
353
|
|
368
|
-
#generate new request if json size of events in the array exceed maximum request buffer size
|
354
|
+
# generate new request if json size of events in the array exceed maximum request buffer size
|
369
355
|
append_event = true
|
370
356
|
if total_bytes + event_json.bytesize > @max_request_buffer
|
371
|
-
#make sure we always have at least one event
|
372
|
-
if events.
|
357
|
+
# make sure we always have at least one event
|
358
|
+
if events.empty?
|
373
359
|
events << event
|
374
360
|
append_event = false
|
375
361
|
end
|
376
|
-
request =
|
362
|
+
request = create_request(events, current_threads)
|
377
363
|
requests << request
|
378
364
|
|
379
365
|
total_bytes = 0
|
380
|
-
current_threads =
|
381
|
-
events =
|
366
|
+
current_threads = {}
|
367
|
+
events = []
|
382
368
|
end
|
383
369
|
|
384
|
-
#if we haven't consumed the current event already
|
385
|
-
#add it to the end of our array and keep track of the json bytesize
|
370
|
+
# if we haven't consumed the current event already
|
371
|
+
# add it to the end of our array and keep track of the json bytesize
|
386
372
|
if append_event
|
387
373
|
events << event
|
388
374
|
total_bytes += event_json.bytesize
|
389
375
|
end
|
390
|
-
|
391
376
|
}
|
392
377
|
|
393
|
-
#create a final request with any left over events
|
394
|
-
request =
|
378
|
+
# create a final request with any left over events
|
379
|
+
request = create_request(events, current_threads)
|
395
380
|
requests << request
|
396
|
-
|
397
381
|
end
|
398
382
|
|
399
|
-
def create_request(
|
400
|
-
#build the scalyr thread objects
|
401
|
-
threads =
|
383
|
+
def create_request(events, current_threads)
|
384
|
+
# build the scalyr thread objects
|
385
|
+
threads = []
|
402
386
|
current_threads.each do |tag, id|
|
403
|
-
threads << {
|
404
|
-
|
405
|
-
}
|
387
|
+
threads << {id: id.to_s,
|
388
|
+
name: "Fluentd: #{tag}"}
|
406
389
|
end
|
407
390
|
|
408
|
-
current_time =
|
391
|
+
current_time = to_millis(Fluent::Engine.now)
|
409
392
|
|
410
|
-
body = {
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
}
|
393
|
+
body = {token: @api_write_token,
|
394
|
+
client_timestamp: current_time.to_s,
|
395
|
+
session: @session,
|
396
|
+
events: events,
|
397
|
+
threads: threads}
|
416
398
|
|
417
|
-
#add server_attributes hash if it exists
|
418
|
-
if @server_attributes
|
419
|
-
body[:sessionInfo] = @server_attributes
|
420
|
-
end
|
399
|
+
# add server_attributes hash if it exists
|
400
|
+
body[:sessionInfo] = @server_attributes if @server_attributes
|
421
401
|
|
422
|
-
{
|
402
|
+
{body: body.to_json, record_count: events.size}
|
423
403
|
end
|
424
|
-
|
425
404
|
end
|
426
405
|
end
|