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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07de29b14ab7bb4183d662138f847ee9b75e94ee1b4eabeb65bb30725baea31a
4
- data.tar.gz: 26261270771892d09baa36c4c7625492b69748c2f908dc9d1595fad609adae6a
3
+ metadata.gz: 1e738c523b1cea2d7aa0976064cb4fa85379b63795f1230a965da934033bc619
4
+ data.tar.gz: '09241b0eabc17074c5b4effb5f53ba47d183bb97ad724a064d6e9cfa9d5e8641'
5
5
  SHA512:
6
- metadata.gz: b2de1513bda2040d60c4f7ccfeb9a44f1c5a8965601833b7f656851d3fe7107e8070b42b6e0f7dca47a31827285670b01317020cea272f8186f56eaeaa97e1e7
7
- data.tar.gz: c07a6f90ffda567a2518a5d7de0b7c0af9d0320535ff3b080a1a23d40861a01a5a8193d3c44c03ea97ba0ddb2361f39d012f4dffb766e8e0c9d7be78ddfe703d
6
+ metadata.gz: 238b972ec9340a013d473911c05955e0a39d03f706d215167f2b3837bf46610917d2741680fd698c5d1075d1f7a307debd3c7cf4111ebba07280110bff2239dc
7
+ data.tar.gz: 1c42dfccbfa8963652198b3b2c0da102feda2b1052370b3296c26c781f5ab1fcd0126b513f87139e055c437c82347c597fe930f3739b6f61a4f8ac7d1764996a
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
5
  gemspec
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](http://docs.fluentd.org/articles/filter_record_transformer).
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 bz2
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 1048576
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 100k
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
- ####Scalyr specific options
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 */etc/ssl/certs/ca-bundle.crt*.
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 1,048,576 (1MB). Fluentd chunks that generate JSON requests larger than the max_request_buffer will be split in to multiple separate requests. **Note:** If you set this value too large Scalyr may reject your requests.
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
- ####Buffer options
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 *100KB*. **Note:** if you set this value too large, then Scalyr may reject your requests. Requests smaller than 1MB will typically be accepted by Scalyr, but note that the 1MB limit also includes the entire request body and all associated JSON keys and punctuation, which may be considerably larger than the raw log data.
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
- require 'bundler'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
- require 'rake/testtask'
6
+ require "rake/testtask"
5
7
 
6
- Rake::TestTask.new do |t|
7
- t.libs << "test" << "lib"
8
- t.pattern = 'test/**/test_*.rb'
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 :default => [:build]
15
+ task default: [:build]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.7
1
+ 0.8.12
@@ -1,4 +1,6 @@
1
- $:.push File.expand_path('../lib', __FILE__)
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['AUTHORS', 'Gemfile', 'LICENSE', 'README.md', 'Rakefile', 'VERSION', 'fluent-plugin-scalyr.gemspec', 'fluent.conf.sample', 'lib/**/*', 'test/**/*']
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{ |f| File.basename(f) }
17
- gem.require_paths = ['lib']
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
@@ -1,7 +1,7 @@
1
1
  <match scalyr.*>
2
2
  @type scalyr
3
3
  api_write_token YOUR_WRITE_LOGS_API_TOKEN
4
- compression_type bz2
4
+ compression_type deflate
5
5
 
6
6
  ##Scalyr specific options
7
7
  # server_attributes {
@@ -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 'fluent/plugin/output'
20
- require 'fluent/plugin/scalyr-exceptions'
21
- require 'fluent/plugin_helper/compat_parameters'
22
- require 'json'
23
- require 'net/http'
24
- require 'net/https'
25
- require 'rbzip2'
26
- require 'stringio'
27
- require 'zlib'
28
- require 'securerandom'
29
- require 'thread'
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( 'scalyr', self )
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, :default => nil
39
- config_param :scalyr_server, :string, :default => "https://agent.scalyr.com/"
40
- config_param :ssl_ca_bundle_path, :string, :default => "/etc/ssl/certs/ca-bundle.crt"
41
- config_param :ssl_verify_peer, :bool, :default => true
42
- config_param :ssl_verify_depth, :integer, :default => 5
43
- config_param :message_field, :string, :default => "message"
44
- config_param :max_request_buffer, :integer, :default => 1024*1024
45
- config_param :force_message_encoding, :string, :default => nil
46
- config_param :replace_invalid_utf8, :bool, :default => false
47
- config_param :compression_type, :string, :default => nil #Valid options are bz2, deflate or None. Defaults to None.
48
- config_param :compression_level, :integer, :default => 9 #An int containing the compression level of compression to use, from 1-9. Defaults to 9 (max)
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, 30 #wait a maximum of 30 seconds per retry
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, 1024*100 #default chunk size of 100k
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 configure( conf )
70
+ def multi_workers_ready?
71
+ true
72
+ end
69
73
 
70
- if conf.elements('buffer').empty?
71
- $log.warn "Pre 0.14.0 configuration file detected. Please consider updating your configuration file"
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( conf, default_chunk_key: '' )
79
+ compat_parameters_buffer(conf, default_chunk_key: "")
75
80
 
76
81
  super
77
82
 
78
- if @buffer.chunk_limit_size > 1024*1024
79
- $log.warn "Buffer chunk size is greater than 1Mb. This may result in requests being rejected by Scalyr"
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 > (1024*1024*3)
83
- $log.warn "Maximum request buffer > 3Mb. This may result in requests being rejected by Scalyr"
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( @force_message_encoding )
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
- if value.is_a?( String )
101
- m = /^\#{(.*)}$/.match( value )
102
- if m
103
- new_attributes[key] = eval( m[1] )
104
- else
105
- new_attributes[key] = value
106
- end
107
- end
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
- @scalyr_server << '/' unless @scalyr_server.end_with?('/')
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
- raise Fluent::ConfigError, "num_threads is currently limited to 1. You specified #{num_threads}." if num_threads > 1
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
- $log.info "Scalyr Fluentd Plugin ID - #{self.plugin_id()}"
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
- @sync = Mutex.new
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( tag, time, record )
137
- begin
138
-
139
- if time.nil?
140
- time = Fluent::Engine.now
141
- end
142
-
143
- # handle timestamps that are not EventTime types
144
- if time.is_a?( Integer )
145
- time = Fluent::EventTime.new( time )
146
- elsif time.is_a?( Float )
147
- components = time.divmod 1 #get integer and decimal components
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
- if @message_field != "message"
154
- if record.key? @message_field
155
- if record.key? "message"
156
- $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."
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
- if @message_encoding and record.key? "message" and record["message"]
164
- if @replace_invalid_utf8 and @message_encoding == Encoding::UTF_8
165
- record["message"] = record["message"].encode("UTF-8", :invalid => :replace, :undef => :replace, :replace => "<?>").force_encoding('UTF-8')
166
- else
167
- record["message"].force_encoding( @message_encoding )
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( chunk )
180
- begin
181
- $log.debug "Size of chunk is: #{chunk.size}"
182
- requests = self.build_add_events_body( chunk )
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
- rescue JSON::GeneratorError
203
- $log.warn "Unable to format message due to JSON::GeneratorError."
204
- raise
205
- end
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
- #explicit function to convert to nanoseconds
210
- #will make things easier to maintain if/when fluentd supports higher than second resolutions
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( timestamp )
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( uri, body )
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 == 'deflate'
238
- encoding = 'deflate'
241
+ if @compression_type == "deflate"
242
+ encoding = "deflate"
239
243
  body = Zlib::Deflate.deflate(body, @compression_level)
240
- elsif @compression_type == 'bz2'
241
- encoding = 'bz2'
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( 'Content-Type', 'application/json' )
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( post )
260
-
261
+ https.request(post)
261
262
  end
262
263
 
263
- def handle_response( 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 = Hash.new
268
+ response_hash = {}
268
269
 
269
270
  begin
270
- response_hash = JSON.parse( response.body )
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
- if !response_hash.key? "status"
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"/client/"i
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( chunk )
303
-
304
- #requests
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 = Hash.new
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 = Array.new
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 = 0
317
+ thread_id = tag
320
318
 
321
- @sync.synchronize {
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
- if !record.key? "logfile"
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 = { :thread => thread_id.to_s,
346
- :ts => timestamp,
347
- :attrs => record
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( sec, nsec )
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
- $log.debug "\t#{key} (#{value.encoding.name}): '#{value}'"
363
- event[:attrs][key] = value.encode("UTF-8", :invalid => :replace, :undef => :replace, :replace => "<?>").force_encoding('UTF-8')
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.size == 0
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 = self.create_request( events, current_threads )
362
+ request = create_request(events, current_threads)
377
363
  requests << request
378
364
 
379
365
  total_bytes = 0
380
- current_threads = Hash.new
381
- events = Array.new
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 = self.create_request( events, current_threads )
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( events, current_threads )
400
- #build the scalyr thread objects
401
- threads = Array.new
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 << { :id => id.to_s,
404
- :name => "Fluentd: #{tag}"
405
- }
387
+ threads << {id: id.to_s,
388
+ name: "Fluentd: #{tag}"}
406
389
  end
407
390
 
408
- current_time = self.to_millis( Fluent::Engine.now )
391
+ current_time = to_millis(Fluent::Engine.now)
409
392
 
410
- body = { :token => @api_write_token,
411
- :client_timestamp => current_time.to_s,
412
- :session => @session,
413
- :events => events,
414
- :threads => threads
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
- { :body => body.to_json, :record_count => events.size }
402
+ {body: body.to_json, record_count: events.size}
423
403
  end
424
-
425
404
  end
426
405
  end