fluentd 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 599f19a7ae06ad5cfe37bfa47a6b3d5d4c59070c
4
- data.tar.gz: 949bcb1356ad507607487ffb072bd73fbce75d85
3
+ metadata.gz: f7457e2a191772c11ca520188030ea3ef06c211d
4
+ data.tar.gz: 2ae8a9864a31245d3fca6756cb4f39c82aed53fc
5
5
  SHA512:
6
- metadata.gz: 714ef7ba99c73efd0f82a57323a829f3c771ad94595bc809406bc43959cd39b6300b82f50d3ecd4292425dc76f55ff4fc9e1207656e0dad4f35c8e33dcc1d941
7
- data.tar.gz: 5374ef1b34a077aab0d929256eceb883881bf1b92312bace5b01452cad8c50bd59cc9b5e2d81dfb5461e4057e497f860d07e2a88febd093112668493422a44ed
6
+ metadata.gz: 6ad103cd892630b1c15452a4749ddba854b2ca73e87bbe252bdf75a7a85964589801e21d002bf6140b23d107dbed207e856a0eaee275ea04c895ebb5df31d361
7
+ data.tar.gz: 44cfc8efcc83ac52e55fb35afcc93486dbb44ed9ee84ac20fa8da335a011812f04eb256bdb55d741facc8bdbb5e87728d75c11f118fe370f7690b553b082223a
@@ -31,8 +31,14 @@ matrix:
31
31
  osx_image: xcode8.3 # OSX 10.12
32
32
  - rvm: ruby-head
33
33
  os: osx
34
- osx_image: xcode 8.3 # OSX 10.12
34
+ osx_image: xcode8.3 # OSX 10.12
35
35
  allow_failures:
36
+ - rvm: 2.1.10
37
+ os: osx
38
+ osx_image: xcode8.3
39
+ - rvm: 2.4.1
40
+ os: osx
41
+ osx_image: xcode8.3
36
42
  - rvm: ruby-head
37
43
 
38
44
  branches:
@@ -1,5 +1,31 @@
1
1
  # v1.0
2
2
 
3
+ ## Release v1.1.0 - 2018/01/17
4
+
5
+ ### New features / Enhancements
6
+
7
+ * config: Add hostname and worker_id short-cut
8
+ https://github.com/fluent/fluentd/pull/1814
9
+ * parser_ltsv: Add delimiter_pattern parameter
10
+ https://github.com/fluent/fluentd/pull/1802
11
+ * record_accessor helper: Support nested field deletion
12
+ https://github.com/fluent/fluentd/pull/1800
13
+ * record_accessor helper: Expose internal instance `@keys` variable
14
+ https://github.com/fluent/fluentd/pull/1808
15
+ * log: Improve Log#on_xxx API performance
16
+ https://github.com/fluent/fluentd/pull/1809
17
+ * time: Improve time formatting performance
18
+ https://github.com/fluent/fluentd/pull/1796
19
+ * command: Port certificates generating command from secure-forward
20
+ https://github.com/fluent/fluentd/pull/1818
21
+
22
+ ### Bug fixes
23
+
24
+ * server helper: Fix TCP + TLS degradation
25
+ https://github.com/fluent/fluentd/pull/1805
26
+ * time: Fix the method for TimeFormatter#call
27
+ https://github.com/fluent/fluentd/pull/1813
28
+
3
29
  ## Release v1.0.2 - 2017/12/17
4
30
 
5
31
  ### New features / Enhancements
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.join(__dir__, 'lib'))
4
+ require 'fluent/command/ca_generate'
5
+
6
+ Fluent::CaGenerate.new.call
@@ -27,7 +27,7 @@ Gem::Specification.new do |gem|
27
27
  gem.add_runtime_dependency("sigdump", ["~> 0.2.2"])
28
28
  gem.add_runtime_dependency("tzinfo", ["~> 1.0"])
29
29
  gem.add_runtime_dependency("tzinfo-data", ["~> 1.0"])
30
- gem.add_runtime_dependency("strptime", ["~> 0.1"])
30
+ gem.add_runtime_dependency("strptime", [">= 0.2.2", "< 1.0.0"])
31
31
  gem.add_runtime_dependency("dig_rb", ["~> 1.0.0"])
32
32
 
33
33
  # build gem for a certain platform. see also Rakefile
@@ -0,0 +1,179 @@
1
+ require 'openssl'
2
+ require 'optparse'
3
+ require 'fileutils'
4
+
5
+ module Fluent
6
+ class CaGenerate
7
+ DEFAULT_OPTIONS = {
8
+ private_key_length: 2048,
9
+ cert_country: 'US',
10
+ cert_state: 'CA',
11
+ cert_locality: 'Mountain View',
12
+ cert_common_name: 'Fluentd Forward CA',
13
+ }
14
+ HELP_TEXT = <<HELP
15
+ Usage: fluent-ca-genrate DIR_PATH PRIVATE_KEY_PASSPHRASE [--country COUNTRY] [--state STATE] [--locality LOCALITY] [--common-name COMMON_NAME]
16
+ HELP
17
+
18
+ def initialize(argv = ARGV)
19
+ @argv = argv
20
+ @options = {}
21
+ @opt_parser = OptionParser.new
22
+ configure_option_parser
23
+ @options.merge!(DEFAULT_OPTIONS)
24
+ parse_options!
25
+ end
26
+
27
+ def usage(msg = nil)
28
+ puts HELP_TEXT
29
+ puts "Error: #{msg}" if msg
30
+ exit 1
31
+ end
32
+
33
+ def call
34
+ ca_dir, passphrase, = @argv[0..1]
35
+
36
+ unless ca_dir && passphrase
37
+ puts "#{HELP_TEXT}"
38
+ puts ''
39
+ exit 1
40
+ end
41
+
42
+ FileUtils.mkdir_p(ca_dir)
43
+
44
+ cert, key = Fluent::CaGenerate.generate_ca_pair(@options)
45
+
46
+ key_data = key.export(OpenSSL::Cipher.new('aes256'), passphrase)
47
+ File.open(File.join(ca_dir, 'ca_key.pem'), 'w') do |file|
48
+ file.write key_data
49
+ end
50
+ File.open(File.join(ca_dir, 'ca_cert.pem'), 'w') do |file|
51
+ file.write cert.to_pem
52
+ end
53
+
54
+ puts "successfully generated: ca_key.pem, ca_cert.pem"
55
+ puts "copy and use ca_cert.pem to client(out_forward)"
56
+ end
57
+
58
+ def self.certificates_from_file(path)
59
+ data = File.read(path)
60
+ pattern = Regexp.compile('-+BEGIN CERTIFICATE-+\n(?:[^-]*\n)+-+END CERTIFICATE-+\n', Regexp::MULTILINE)
61
+ list = []
62
+ data.scan(pattern){|match| list << OpenSSL::X509::Certificate.new(match)}
63
+ list
64
+ end
65
+
66
+ def self.generate_ca_pair(opts={})
67
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
68
+
69
+ issuer = subject = OpenSSL::X509::Name.new
70
+ subject.add_entry('C', opts[:cert_country])
71
+ subject.add_entry('ST', opts[:cert_state])
72
+ subject.add_entry('L', opts[:cert_locality])
73
+ subject.add_entry('CN', opts[:cert_common_name])
74
+
75
+ digest = OpenSSL::Digest::SHA256.new
76
+
77
+ cert = OpenSSL::X509::Certificate.new
78
+ cert.not_before = Time.at(0)
79
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
80
+ cert.public_key = key
81
+ cert.serial = 1
82
+ cert.issuer = issuer
83
+ cert.subject = subject
84
+ cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(true)]))
85
+ cert.sign(key, digest)
86
+
87
+ return cert, key
88
+ end
89
+
90
+ def self.generate_server_pair(opts={})
91
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
92
+
93
+ ca_key = OpenSSL::PKey::RSA.new(File.read(opts[:ca_key_path]), opts[:ca_key_passphrase])
94
+ ca_cert = OpenSSL::X509::Certificate.new(File.read(opts[:ca_cert_path]))
95
+ issuer = ca_cert.issuer
96
+
97
+ subject = OpenSSL::X509::Name.new
98
+ subject.add_entry('C', opts[:country])
99
+ subject.add_entry('ST', opts[:state])
100
+ subject.add_entry('L', opts[:locality])
101
+ subject.add_entry('CN', opts[:common_name])
102
+
103
+ digest = OpenSSL::Digest::SHA256.new
104
+
105
+ cert = OpenSSL::X509::Certificate.new
106
+ cert.not_before = Time.at(0)
107
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
108
+ cert.public_key = key
109
+ cert.serial = 2
110
+ cert.issuer = issuer
111
+ cert.subject = subject
112
+
113
+ cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(false)]))
114
+ cert.add_extension OpenSSL::X509::Extension.new('nsCertType', 'server')
115
+
116
+ cert.sign ca_key, digest
117
+
118
+ return cert, key
119
+ end
120
+
121
+ def self.generate_self_signed_server_pair(opts={})
122
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
123
+
124
+ issuer = subject = OpenSSL::X509::Name.new
125
+ subject.add_entry('C', opts[:country])
126
+ subject.add_entry('ST', opts[:state])
127
+ subject.add_entry('L', opts[:locality])
128
+ subject.add_entry('CN', opts[:common_name])
129
+
130
+ digest = OpenSSL::Digest::SHA256.new
131
+
132
+ cert = OpenSSL::X509::Certificate.new
133
+ cert.not_before = Time.at(0)
134
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
135
+ cert.public_key = key
136
+ cert.serial = 1
137
+ cert.issuer = issuer
138
+ cert.subject = subject
139
+ cert.sign(key, digest)
140
+
141
+ return cert, key
142
+ end
143
+
144
+ private
145
+
146
+ def configure_option_parser
147
+ @opt_parser.banner = HELP_TEXT
148
+
149
+ @opt_parser.on('--key-length [KEY_LENGTH]',
150
+ "configure key length. (default: #{DEFAULT_OPTIONS[:private_key_length]})") do |v|
151
+ @options[:private_key_length] = v.to_i
152
+ end
153
+
154
+ @opt_parser.on('--country [COUNTRY]',
155
+ "configure country. (default: #{DEFAULT_OPTIONS[:cert_country]})") do |v|
156
+ @options[:cert_country] = v.upcase
157
+ end
158
+
159
+ @opt_parser.on('--state [STATE]',
160
+ "configure state. (default: #{DEFAULT_OPTIONS[:cert_state]})") do |v|
161
+ @options[:cert_state] = v
162
+ end
163
+
164
+ @opt_parser.on('--locality [LOCALITY]',
165
+ "configure locality. (default: #{DEFAULT_OPTIONS[:cert_locality]})") do |v|
166
+ @options[:cert_locality] = v
167
+ end
168
+
169
+ @opt_parser.on('--common-name [COMMON_NAME]',
170
+ "configure common name (default: #{DEFAULT_OPTIONS[:cert_common_name]})") do |v|
171
+ @options[:cert_common_name] = v
172
+ end
173
+ end
174
+
175
+ def parse_options!
176
+ @opt_parser.parse!(@argv)
177
+ end
178
+ end
179
+ end
@@ -18,6 +18,7 @@ require 'stringio'
18
18
 
19
19
  require 'json'
20
20
  require 'yajl'
21
+ require 'socket'
21
22
  require 'irb/ruby-lex' # RubyLex
22
23
 
23
24
  require 'fluent/config/basic_parser'
@@ -162,6 +163,11 @@ module Fluent
162
163
  if @eval_context.nil?
163
164
  parse_error! "embedded code is not allowed in this file"
164
165
  end
166
+ # Add hostname and worker_id to code for preventing unused warnings
167
+ code = <<EOM + code
168
+ hostname = Socket.gethostname
169
+ worker_id = ENV['SERVERENGINE_WORKER_ID'] || ''
170
+ EOM
165
171
  @eval_context.instance_eval(code)
166
172
  end
167
173
 
@@ -163,10 +163,12 @@ module Fluent
163
163
  def format=(fmt)
164
164
  return if @format == fmt
165
165
 
166
+ @time_format = '%Y-%m-%d %H:%M:%S %z'
167
+ @time_formatter = Strftime.new(@time_format) rescue nil
168
+
166
169
  case fmt
167
170
  when :text
168
171
  @format = :text
169
- @time_format = '%Y-%m-%d %H:%M:%S %z'
170
172
  @formatter = Proc.new { |type, time, level, msg|
171
173
  r = caller_line(type, time, @depth_offset, level)
172
174
  r << msg
@@ -174,10 +176,9 @@ module Fluent
174
176
  }
175
177
  when :json
176
178
  @format = :json
177
- @time_format = '%Y-%m-%d %H:%M:%S %z'
178
179
  @formatter = Proc.new { |type, time, level, msg|
179
180
  r = {
180
- 'time' => time.strftime(@time_format),
181
+ 'time' => format_time(time),
181
182
  'level' => LEVEL_TEXT[level],
182
183
  'message' => msg
183
184
  }
@@ -191,6 +192,11 @@ module Fluent
191
192
  nil
192
193
  end
193
194
 
195
+ def time_format=(time_fmt)
196
+ @time_format = time_fmt
197
+ @time_formatter = Strftime.new(@time_format) rescue nil
198
+ end
199
+
194
200
  def reopen!
195
201
  # do nothing in @logger.reopen! because it's already reopened in Supervisor.load_config
196
202
  @logger.reopen! if @logger
@@ -261,9 +267,9 @@ module Fluent
261
267
  end
262
268
  end
263
269
 
264
- def on_trace(&block)
270
+ def on_trace
265
271
  return if @level > LEVEL_TRACE
266
- block.call if block
272
+ yield
267
273
  end
268
274
 
269
275
  def trace(*args, &block)
@@ -282,9 +288,9 @@ module Fluent
282
288
  dump_stacktrace(type, backtrace, LEVEL_TRACE)
283
289
  end
284
290
 
285
- def on_debug(&block)
291
+ def on_debug
286
292
  return if @level > LEVEL_DEBUG
287
- block.call if block
293
+ yield
288
294
  end
289
295
 
290
296
  def debug(*args, &block)
@@ -302,9 +308,9 @@ module Fluent
302
308
  dump_stacktrace(type, backtrace, LEVEL_DEBUG)
303
309
  end
304
310
 
305
- def on_info(&block)
311
+ def on_info
306
312
  return if @level > LEVEL_INFO
307
- block.call if block
313
+ yield
308
314
  end
309
315
 
310
316
  def info(*args, &block)
@@ -322,9 +328,9 @@ module Fluent
322
328
  dump_stacktrace(type, backtrace, LEVEL_INFO)
323
329
  end
324
330
 
325
- def on_warn(&block)
331
+ def on_warn
326
332
  return if @level > LEVEL_WARN
327
- block.call if block
333
+ yield
328
334
  end
329
335
 
330
336
  def warn(*args, &block)
@@ -342,9 +348,9 @@ module Fluent
342
348
  dump_stacktrace(type, backtrace, LEVEL_WARN)
343
349
  end
344
350
 
345
- def on_error(&block)
351
+ def on_error
346
352
  return if @level > LEVEL_ERROR
347
- block.call if block
353
+ yield
348
354
  end
349
355
 
350
356
  def error(*args, &block)
@@ -362,9 +368,9 @@ module Fluent
362
368
  dump_stacktrace(type, backtrace, LEVEL_ERROR)
363
369
  end
364
370
 
365
- def on_fatal(&block)
371
+ def on_fatal
366
372
  return if @level > LEVEL_FATAL
367
- block.call if block
373
+ yield
368
374
  end
369
375
 
370
376
  def fatal(*args, &block)
@@ -423,7 +429,7 @@ module Fluent
423
429
  end
424
430
  else
425
431
  r = {
426
- 'time' => time.strftime(@time_format),
432
+ 'time' => format_time(time),
427
433
  'level' => LEVEL_TEXT[level],
428
434
  }
429
435
  if wid = get_worker_id(type)
@@ -491,7 +497,7 @@ module Fluent
491
497
  else
492
498
  "".freeze
493
499
  end
494
- log_msg = "#{time.strftime(@time_format)} [#{LEVEL_TEXT[level]}]: #{worker_id_part}"
500
+ log_msg = "#{format_time(time)} [#{LEVEL_TEXT[level]}]: #{worker_id_part}"
495
501
  if @debug_mode
496
502
  line = caller(depth+1)[0]
497
503
  if match = /^(.+?):(\d+)(?::in `(.*)')?/.match(line)
@@ -503,6 +509,10 @@ module Fluent
503
509
  end
504
510
  return log_msg
505
511
  end
512
+
513
+ def format_time(time)
514
+ @time_formatter ? @time_formatter.exec(time) : time.strftime(@time_format)
515
+ end
506
516
  end
507
517
 
508
518
 
@@ -545,8 +555,8 @@ module Fluent
545
555
  extend Forwardable
546
556
  def_delegators '@logger', :get_worker_id, :enable_color?, :enable_debug, :enable_event,
547
557
  :disable_events, :log_event_enabled, :log_event_enamed=, :time_format, :time_format=,
548
- :event, :caller_line, :puts, :write, :<<, :flush, :reset, :out, :out=,
549
- :optional_header, :optional_header=, :optional_attrs, :optional_attrs=
558
+ :time_formatter, :time_formatter=, :event, :caller_line, :puts, :write, :<<, :flush,
559
+ :reset, :out, :out=, :optional_header, :optional_header=, :optional_attrs, :optional_attrs=
550
560
  end
551
561
 
552
562
 
@@ -240,7 +240,8 @@ module Fluent
240
240
  end
241
241
 
242
242
  def add_metadata(metadata)
243
- log.trace "adding metadata", instance: self.object_id, metadata: metadata
243
+ log.on_trace { log.trace "adding metadata", instance: self.object_id, metadata: metadata }
244
+
244
245
  synchronize do
245
246
  if i = @metadata_list.index(metadata)
246
247
  @metadata_list[i]
@@ -263,7 +264,7 @@ module Fluent
263
264
  return if metadata_and_data.size < 1
264
265
  raise BufferOverflowError, "buffer space has too many data" unless storable?
265
266
 
266
- log.trace "writing events into buffer", instance: self.object_id, metadata_size: metadata_and_data.size
267
+ log.on_trace { log.trace "writing events into buffer", instance: self.object_id, metadata_size: metadata_and_data.size }
267
268
 
268
269
  staged_bytesize = 0
269
270
  operated_chunks = []
@@ -382,7 +383,8 @@ module Fluent
382
383
  end
383
384
 
384
385
  def enqueue_chunk(metadata)
385
- log.trace "enqueueing chunk", instance: self.object_id, metadata: metadata
386
+ log.on_trace { log.trace "enqueueing chunk", instance: self.object_id, metadata: metadata }
387
+
386
388
  chunk = synchronize do
387
389
  @stage.delete(metadata)
388
390
  end
@@ -406,7 +408,8 @@ module Fluent
406
408
  end
407
409
 
408
410
  def enqueue_unstaged_chunk(chunk)
409
- log.trace "enqueueing unstaged chunk", instance: self.object_id, metadata: chunk.metadata
411
+ log.on_trace { log.trace "enqueueing unstaged chunk", instance: self.object_id, metadata: chunk.metadata }
412
+
410
413
  synchronize do
411
414
  chunk.synchronize do
412
415
  metadata = chunk.metadata
@@ -419,7 +422,8 @@ module Fluent
419
422
  end
420
423
 
421
424
  def enqueue_all
422
- log.trace "enqueueing all chunks in buffer", instance: self.object_id
425
+ log.on_trace { log.trace "enqueueing all chunks in buffer", instance: self.object_id }
426
+
423
427
  if block_given?
424
428
  synchronize{ @stage.keys }.each do |metadata|
425
429
  # NOTE: The following line might cause data race depending on Ruby implementations except CRuby
@@ -438,7 +442,8 @@ module Fluent
438
442
 
439
443
  def dequeue_chunk
440
444
  return nil if @queue.empty?
441
- log.trace "dequeueing a chunk", instance: self.object_id
445
+ log.on_trace { log.trace "dequeueing a chunk", instance: self.object_id }
446
+
442
447
  synchronize do
443
448
  chunk = @queue.shift
444
449
 
@@ -453,7 +458,8 @@ module Fluent
453
458
  end
454
459
 
455
460
  def takeback_chunk(chunk_id)
456
- log.trace "taking back a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id)
461
+ log.on_trace { log.trace "taking back a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id) }
462
+
457
463
  synchronize do
458
464
  chunk = @dequeued.delete(chunk_id)
459
465
  return false unless chunk # already purged by other thread
@@ -470,7 +476,8 @@ module Fluent
470
476
  return nil unless chunk # purged by other threads
471
477
 
472
478
  metadata = chunk.metadata
473
- log.trace "purging a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata
479
+ log.on_trace { log.trace "purging a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata }
480
+
474
481
  begin
475
482
  bytesize = chunk.bytesize
476
483
  chunk.purge
@@ -489,7 +496,8 @@ module Fluent
489
496
  end
490
497
 
491
498
  def clear_queue!
492
- log.trace "clearing queue", instance: self.object_id
499
+ log.on_trace { log.trace "clearing queue", instance: self.object_id }
500
+
493
501
  synchronize do
494
502
  until @queue.empty?
495
503
  begin
@@ -27,6 +27,8 @@ module Fluent::Plugin
27
27
  class RecordTransformerFilter < Fluent::Plugin::Filter
28
28
  Fluent::Plugin.register_filter('record_transformer', self)
29
29
 
30
+ helpers :record_accessor
31
+
30
32
  desc 'A comma-delimited list of keys to delete.'
31
33
  config_param :remove_keys, :array, default: nil
32
34
  desc 'A comma-delimited list of keys to keep.'
@@ -56,6 +58,10 @@ module Fluent::Plugin
56
58
  raise Fluent::ConfigError, "`renew_record` must be true to use `keep_keys`" unless @renew_record
57
59
  end
58
60
 
61
+ @key_deleters = if @remove_keys
62
+ @remove_keys.map { |k| record_accessor_create(k) }
63
+ end
64
+
59
65
  placeholder_expander_params = {
60
66
  log: log,
61
67
  auto_typecast: @auto_typecast,
@@ -96,8 +102,7 @@ module Fluent::Plugin
96
102
  if @renew_time_key && new_record.has_key?(@renew_time_key)
97
103
  time = Fluent::EventTime.from_time(Time.at(new_record[@renew_time_key].to_f))
98
104
  end
99
- @remove_keys.each {|k| new_record.delete(k) } if @remove_keys
100
-
105
+ @key_deleters.each { |deleter| deleter.delete(new_record) } if @key_deleters
101
106
  new_es.add(time, new_record)
102
107
  rescue => e
103
108
  router.emit_error_event(tag, time, record, e)
@@ -21,13 +21,14 @@ module Fluent
21
21
  class StdoutFormatter < Formatter
22
22
  Plugin.register_formatter('stdout', self)
23
23
 
24
- TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%9N %z'
24
+ TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%N %z'
25
25
 
26
26
  config_param :output_type, :string, default: 'json'
27
27
 
28
28
  def configure(conf)
29
29
  super
30
30
 
31
+ @time_formatter = Strftime.new(TIME_FORMAT)
31
32
  @sub_formatter = Plugin.new_formatter(@output_type, parent: self.owner)
32
33
  @sub_formatter.configure(conf)
33
34
  end
@@ -38,7 +39,7 @@ module Fluent
38
39
  end
39
40
 
40
41
  def format(tag, time, record)
41
- "#{Time.at(time).localtime.strftime(TIME_FORMAT)} #{tag}: #{@sub_formatter.format(tag, time, record).chomp}\n"
42
+ "#{@time_formatter.exec(Time.at(time).localtime)} #{tag}: #{@sub_formatter.format(tag, time, record).chomp}\n"
42
43
  end
43
44
 
44
45
  def stop
@@ -24,7 +24,6 @@ module Fluent::Plugin
24
24
 
25
25
  DEFAULT_LINE_FORMAT_TYPE = 'stdout'
26
26
  DEFAULT_FORMAT_TYPE = 'json'
27
- TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%9N %z'
28
27
 
29
28
  config_section :buffer do
30
29
  config_set_default :chunk_keys, ['tag']
@@ -971,7 +971,8 @@ module Fluent
971
971
  end
972
972
 
973
973
  def commit_write(chunk_id, delayed: @delayed_commit, secondary: false)
974
- log.trace "committing write operation to a chunk", chunk: dump_unique_id_hex(chunk_id), delayed: delayed
974
+ log.on_trace { log.trace "committing write operation to a chunk", chunk: dump_unique_id_hex(chunk_id), delayed: delayed }
975
+
975
976
  if delayed
976
977
  @dequeued_chunks_mutex.synchronize do
977
978
  @dequeued_chunks.delete_if{ |info| info.chunk_id == chunk_id }
@@ -1057,7 +1058,7 @@ module Fluent
1057
1058
  chunk = @buffer.dequeue_chunk
1058
1059
  return unless chunk
1059
1060
 
1060
- log.trace "trying flush for a chunk", chunk: dump_unique_id_hex(chunk.unique_id)
1061
+ log.on_trace { log.trace "trying flush for a chunk", chunk: dump_unique_id_hex(chunk.unique_id) }
1061
1062
 
1062
1063
  output = self
1063
1064
  using_secondary = false
@@ -23,11 +23,20 @@ module Fluent
23
23
 
24
24
  desc 'The delimiter character (or string) of TSV values'
25
25
  config_param :delimiter, :string, default: "\t"
26
+ desc 'The delimiter pattern of TSV values'
27
+ config_param :delimiter_pattern, default: nil do |value|
28
+ Regexp.compile(value[1..-2]) if value
29
+ end
26
30
  desc 'The delimiter character between field name and value'
27
31
  config_param :label_delimiter, :string, default: ":"
28
32
 
29
33
  config_set_default :time_key, 'time'
30
34
 
35
+ def configure(conf)
36
+ super
37
+ @delimiter = @delimiter_pattern || @delimiter
38
+ end
39
+
31
40
  def parse(text)
32
41
  r = {}
33
42
  text.split(@delimiter).each do |pair|
@@ -32,13 +32,25 @@ module Fluent
32
32
  end
33
33
 
34
34
  class Accessor
35
+ attr_reader :keys
36
+
35
37
  def initialize(param)
36
38
  @keys = Accessor.parse_parameter(param)
37
39
 
38
- # Call [] for single key to reduce dig overhead
39
- m = method(@keys.is_a?(Array) ? :call_dig : :call_index)
40
+ if @keys.is_a?(Array)
41
+ @last_key = @keys.last
42
+ @dig_keys = @keys[0..-2]
43
+ mcall = method(:call_dig)
44
+ mdelete = method(:delete_nest)
45
+ else
46
+ # Call [] for single key to reduce dig overhead
47
+ mcall = method(:call_index)
48
+ mdelete = method(:delete_top)
49
+ end
50
+
40
51
  singleton_class.module_eval do
41
- define_method(:call, m)
52
+ define_method(:call, mcall)
53
+ define_method(:delete, mdelete)
42
54
  end
43
55
  end
44
56
 
@@ -55,6 +67,25 @@ module Fluent
55
67
  r[@keys]
56
68
  end
57
69
 
70
+ def delete(r)
71
+ end
72
+
73
+ def delete_nest(r)
74
+ if target = r.dig(*@dig_keys)
75
+ if target.is_a?(Array)
76
+ target.delete_at(@last_key)
77
+ else
78
+ target.delete(@last_key)
79
+ end
80
+ end
81
+ rescue
82
+ nil
83
+ end
84
+
85
+ def delete_top(r)
86
+ r.delete(@keys)
87
+ end
88
+
58
89
  def self.parse_parameter(param)
59
90
  if param.start_with?('$.')
60
91
  parse_dot_notation(param)
@@ -446,9 +446,12 @@ module Fluent
446
446
  class TLSCallbackSocket < CallbackSocket
447
447
  ENABLED_EVENTS = [:data, :write_complete, :close]
448
448
 
449
+ attr_accessor :buffer
450
+
449
451
  def initialize(sock)
450
452
  super("tls", sock, ENABLED_EVENTS)
451
453
  @peeraddr = (@sock.to_io.peeraddr rescue PEERADDR_FAILED)
454
+ @buffer = ''
452
455
  end
453
456
 
454
457
  def write(data)
@@ -319,21 +319,24 @@ module Fluent
319
319
  @tc2 = 0
320
320
  @tc2_str = nil
321
321
 
322
+ strftime = format && (Strftime.new(format) rescue nil)
322
323
  if format && format =~ /(^|[^%])(%%)*%L|(^|[^%])(%%)*%\d*N/
323
324
  define_singleton_method(:format, method(:format_with_subsec))
324
325
  define_singleton_method(:call, method(:format_with_subsec))
325
326
  else
326
327
  define_singleton_method(:format, method(:format_without_subsec))
327
- define_singleton_method(:call, method(:format_with_subsec))
328
+ define_singleton_method(:call, method(:format_without_subsec))
328
329
  end
329
330
 
330
- formatter = Fluent::Timezone.formatter(timezone, format)
331
+ formatter = Fluent::Timezone.formatter(timezone, strftime ? strftime : format)
331
332
  @format_nocache = case
332
- when formatter then formatter
333
- when format && localtime then ->(time){ Time.at(time).strftime(format) }
334
- when format then ->(time){ Time.at(time).utc.strftime(format) }
335
- when localtime then ->(time){ Time.at(time).iso8601 }
336
- else ->(time){ Time.at(time).utc.iso8601 }
333
+ when formatter then formatter
334
+ when strftime && localtime then ->(time){ strftime.exec(Time.at(time)) }
335
+ when format && localtime then ->(time){ Time.at(time).strftime(format) }
336
+ when strftime then ->(time){ strftime.exec(Time.at(time).utc) }
337
+ when format then ->(time){ Time.at(time).utc.strftime(format) }
338
+ when localtime then ->(time){ Time.at(time).iso8601 }
339
+ else ->(time){ Time.at(time).utc.iso8601 }
337
340
  end
338
341
  end
339
342
 
@@ -97,10 +97,15 @@ module Fluent
97
97
  if NUMERIC_PATTERN === timezone
98
98
  offset = Time.zone_offset(timezone)
99
99
 
100
- if format
100
+ case
101
+ when format.is_a?(String)
101
102
  return Proc.new {|time|
102
103
  Time.at(time).localtime(offset).strftime(format)
103
104
  }
105
+ when format.is_a?(Strftime)
106
+ return Proc.new {|time|
107
+ format.exec(Time.at(time).localtime(offset))
108
+ }
104
109
  else
105
110
  return Proc.new {|time|
106
111
  Time.at(time).localtime(offset).iso8601
@@ -116,10 +121,15 @@ module Fluent
116
121
  return nil
117
122
  end
118
123
 
119
- if format
124
+ case
125
+ when format.is_a?(String)
120
126
  return Proc.new {|time|
121
127
  Time.at(time).localtime(tz.period_for_utc(time).utc_total_offset).strftime(format)
122
128
  }
129
+ when format.is_a?(Strftime)
130
+ return Proc.new {|time|
131
+ format.exec(Time.at(time).localtime(tz.period_for_utc(time).utc_total_offset))
132
+ }
123
133
  else
124
134
  return Proc.new {|time|
125
135
  Time.at(time).localtime(tz.period_for_utc(time).utc_total_offset).iso8601
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.0.2'
19
+ VERSION = '1.1.0'
20
20
 
21
21
  end
@@ -0,0 +1,70 @@
1
+ require_relative '../helper'
2
+
3
+ require 'yajl'
4
+ require 'flexmock/test_unit'
5
+ require 'tmpdir'
6
+
7
+ require 'fluent/command/ca_generate'
8
+ require 'fluent/event'
9
+
10
+ class TestFluentCaGenerate < ::Test::Unit::TestCase
11
+ def test_generate_ca_pair
12
+ cert, key = Fluent::CaGenerate.generate_ca_pair(Fluent::CaGenerate::DEFAULT_OPTIONS)
13
+ assert_equal(OpenSSL::X509::Certificate, cert.class)
14
+ assert_true(key.private?)
15
+ end
16
+
17
+ def test_ca_generate
18
+ dumped_output = capture_stdout do
19
+ Dir.mktmpdir do |dir|
20
+ Fluent::CaGenerate.new([dir, "fluentd"]).call
21
+ assert_true(File.exist?(File.join(dir, "ca_key.pem")))
22
+ assert_true(File.exist?(File.join(dir, "ca_cert.pem")))
23
+ end
24
+ end
25
+ expected = <<TEXT
26
+ successfully generated: ca_key.pem, ca_cert.pem
27
+ copy and use ca_cert.pem to client(out_forward)
28
+ TEXT
29
+ assert_equal(expected, dumped_output)
30
+ end
31
+
32
+ sub_test_case "configure options" do
33
+ test "should respond multiple options" do
34
+ dumped_output = capture_stdout do
35
+ Dir.mktmpdir do |dir|
36
+ Fluent::CaGenerate.new([dir, "fluentd",
37
+ "--country", "JP", "--key-length", "4096",
38
+ "--state", "Tokyo", "--locality", "Chiyoda-ku",
39
+ "--common-name", "Forward CA"]).call
40
+ assert_true(File.exist?(File.join(dir, "ca_key.pem")))
41
+ assert_true(File.exist?(File.join(dir, "ca_cert.pem")))
42
+ end
43
+ end
44
+ expected = <<TEXT
45
+ successfully generated: ca_key.pem, ca_cert.pem
46
+ copy and use ca_cert.pem to client(out_forward)
47
+ TEXT
48
+ assert_equal(expected, dumped_output)
49
+ end
50
+
51
+ test "invalid options" do
52
+ Dir.mktmpdir do |dir|
53
+ assert_raise(OptionParser::InvalidOption) do
54
+ Fluent::CaGenerate.new([dir, "fluentd",
55
+ "--invalid"]).call
56
+ end
57
+ assert_false(File.exist?(File.join(dir, "ca_key.pem")))
58
+ assert_false(File.exist?(File.join(dir, "ca_cert.pem")))
59
+ end
60
+ end
61
+
62
+ test "empty options" do
63
+ assert_raise(SystemExit) do
64
+ capture_stdout do
65
+ Fluent::CaGenerate.new([]).call
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -494,14 +494,14 @@ CONF
494
494
  end
495
495
 
496
496
  test 'success to start the number of workers specified in configuration' do
497
- conf = <<CONF
497
+ conf = <<'CONF'
498
498
  <system>
499
499
  workers 2
500
500
  root_dir #{@root_path}
501
501
  </system>
502
502
  <source>
503
503
  @type dummy
504
- @id dummy
504
+ @id "dummy#{worker_id}" # check worker_id works or not with actual command
505
505
  @label @dummydata
506
506
  tag dummy
507
507
  dummy {"message": "yay!"}
@@ -232,6 +232,14 @@ module Fluent::Config
232
232
  test("\"\#{\n=begin\n}\"") { assert_parse_error("\"\#{\n=begin\n}\"") } # error in embedded ruby code
233
233
  test('"#{v1}foo#{v2}"') { assert_text_parsed_as("#{v1}foo#{v2}", '"#{v1}foo#{v2}"') }
234
234
  test('"#{1+1}foo#{2+2}bar"') { assert_text_parsed_as("#{1+1}foo#{2+2}bar", '"#{1+1}foo#{2+2}bar"') }
235
+ test('"foo#{hostname}"') { assert_text_parsed_as("foo#{Socket.gethostname}", '"foo#{hostname}"') }
236
+ test('"foo#{worker_id}"') {
237
+ ENV.delete('SERVERENGINE_WORKER_ID')
238
+ assert_text_parsed_as("foo", '"foo#{worker_id}"')
239
+ ENV['SERVERENGINE_WORKER_ID'] = '1'
240
+ assert_text_parsed_as("foo1", '"foo#{worker_id}"')
241
+ ENV.delete('SERVERENGINE_WORKER_ID')
242
+ }
235
243
  end
236
244
 
237
245
  sub_test_case 'array parsing' do
@@ -44,7 +44,7 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
44
44
  d = create_driver(config)
45
45
  d.run {
46
46
  msgs.each { |msg|
47
- d.feed(@tag, @time, {'foo' => 'bar', 'message' => msg})
47
+ d.feed(@tag, @time, {'foo' => 'bar', 'message' => msg, 'nest' => {'k1' => 'v1', 'k2' => 'v2'}})
48
48
  }
49
49
  }
50
50
  d.filtered
@@ -68,6 +68,7 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
68
68
  assert_equal(@tag, r['tag'])
69
69
  assert_equal(Time.at(@time).localtime.to_s, r['time'])
70
70
  assert_equal("#{@hostname} #{@tag_parts[-1]} #{msgs[i]}", r['message'])
71
+ assert_equal({'k1' => 'v1', 'k2' => 'v2'}, r['nest'])
71
72
  end
72
73
  end
73
74
 
@@ -83,6 +84,14 @@ class RecordTransformerFilterTest < Test::Unit::TestCase
83
84
  end
84
85
  end
85
86
 
87
+ test 'remove_keys with nested key' do
88
+ config = CONFIG + %[remove_keys $.nest.k1]
89
+ filtered = filter(config)
90
+ filtered.each_with_index do |(_t, r), i|
91
+ assert_not_include(r['nest'], 'k1')
92
+ end
93
+ end
94
+
86
95
  test 'renew_record' do
87
96
  config = CONFIG + %[renew_record true]
88
97
  msgs = ['1', '2']
@@ -125,4 +125,21 @@ class LabeledTSVParserTest < ::Test::Unit::TestCase
125
125
  assert_equal record['b'], ' '
126
126
  end
127
127
  end
128
+
129
+ data("single space" => ["k1=v1 k2=v2", { "k1" => "v1", "k2" => "v2" }],
130
+ "multiple space" => ["k1=v1 k2=v2", { "k1" => "v1", "k2" => "v2" }],
131
+ "reverse" => ["k2=v2 k1=v1", { "k1" => "v1", "k2" => "v2" }],
132
+ "tab" => ["k2=v2\tk1=v1", { "k1" => "v1", "k2" => "v2" }],
133
+ "tab and space" => ["k2=v2\t k1=v1", { "k1" => "v1", "k2" => "v2" }])
134
+ def test_parse_with_delimiter_pattern(data)
135
+ text, expected = data
136
+ parser = Fluent::Test::Driver::Parser.new(Fluent::Plugin::LabeledTSVParser)
137
+ parser.configure(
138
+ 'delimiter_pattern' => '/\s+/',
139
+ 'label_delimiter' => '='
140
+ )
141
+ parser.instance.parse(text) do |_time, record|
142
+ assert_equal(expected, record)
143
+ end
144
+ end
128
145
  end
@@ -62,6 +62,40 @@ class RecordAccessorHelperTest < Test::Unit::TestCase
62
62
  end
63
63
  end
64
64
 
65
+ sub_test_case 'attr_reader :keys' do
66
+ setup do
67
+ @d = Dummy.new
68
+ end
69
+
70
+ data('normal' => 'key1',
71
+ 'space' => 'ke y2',
72
+ 'dot key' => 'this.is.key3')
73
+ test 'access single key' do |param|
74
+ accessor = @d.record_accessor_create(param)
75
+ assert_equal param, accessor.keys
76
+ end
77
+
78
+ test "nested bracket keys with dot" do
79
+ accessor = @d.record_accessor_create("$['key1']['this.is.key3']")
80
+ assert_equal ['key1','this.is.key3'], accessor.keys
81
+ end
82
+
83
+ data('dot' => '$.key1.key2[0]',
84
+ 'bracket' => "$['key1']['key2'][0]",
85
+ 'bracket w/ double quotes' => '$["key1"]["key2"][0]')
86
+ test "nested keys ['key1', 'key2', 0]" do |param|
87
+ accessor = @d.record_accessor_create(param)
88
+ assert_equal ['key1', 'key2', 0], accessor.keys
89
+ end
90
+
91
+ data('bracket' => "$['key1'][0]['ke y2']",
92
+ 'bracket w/ double quotes' => '$["key1"][0]["ke y2"]')
93
+ test "nested keys ['key1', 0, 'ke y2']" do |param|
94
+ accessor = @d.record_accessor_create(param)
95
+ assert_equal ['key1', 0, 'ke y2'], accessor.keys
96
+ end
97
+ end
98
+
65
99
  sub_test_case Fluent::PluginHelper::RecordAccessor::Accessor do
66
100
  setup do
67
101
  @d = Dummy.new
@@ -114,4 +148,37 @@ class RecordAccessorHelperTest < Test::Unit::TestCase
114
148
  end
115
149
  end
116
150
  end
151
+
152
+ sub_test_case 'Fluent::PluginHelper::RecordAccessor::Accessor#delete' do
153
+ setup do
154
+ @d = Dummy.new
155
+ end
156
+
157
+ data('normal' => 'key1',
158
+ 'space' => 'ke y2',
159
+ 'dot key' => 'this.is.key3')
160
+ test 'delete top key' do |param|
161
+ r = {'key1' => 'v1', 'ke y2' => 'v2', 'this.is.key3' => 'v3'}
162
+ accessor = @d.record_accessor_create(param)
163
+ accessor.delete(r)
164
+ assert_not_include(r, param)
165
+ end
166
+
167
+ data('bracket' => "$['key1'][0]['ke y2']",
168
+ 'bracket w/ double quotes' => '$["key1"][0]["ke y2"]')
169
+ test "delete nested keys ['key1', 0, 'ke y2']" do |param|
170
+ r = {'key1' => [{'ke y2' => "value"}]}
171
+ accessor = @d.record_accessor_create(param)
172
+ accessor.delete(r)
173
+ assert_not_include(r['key1'][0], 'ke y2')
174
+ end
175
+
176
+ test "don't raise an error when unexpected record is coming" do
177
+ r = {'key1' => [{'key3' => "value"}]}
178
+ accessor = @d.record_accessor_create("$['key1']['key2']['key3']")
179
+ assert_nothing_raised do
180
+ assert_nil accessor.delete(r)
181
+ end
182
+ end
183
+ end
117
184
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluentd
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sadayuki Furuhashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-18 00:00:00.000000000 Z
11
+ date: 2018-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -150,16 +150,22 @@ dependencies:
150
150
  name: strptime
151
151
  requirement: !ruby/object:Gem::Requirement
152
152
  requirements:
153
- - - "~>"
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: 0.2.2
156
+ - - "<"
154
157
  - !ruby/object:Gem::Version
155
- version: '0.1'
158
+ version: 1.0.0
156
159
  type: :runtime
157
160
  prerelease: false
158
161
  version_requirements: !ruby/object:Gem::Requirement
159
162
  requirements:
160
- - - "~>"
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: 0.2.2
166
+ - - "<"
161
167
  - !ruby/object:Gem::Version
162
- version: '0.1'
168
+ version: 1.0.0
163
169
  - !ruby/object:Gem::Dependency
164
170
  name: dig_rb
165
171
  requirement: !ruby/object:Gem::Requirement
@@ -306,6 +312,7 @@ email:
306
312
  - frsyuki@gmail.com
307
313
  executables:
308
314
  - fluent-binlog-reader
315
+ - fluent-ca-generate
309
316
  - fluent-cat
310
317
  - fluent-debug
311
318
  - fluent-gem
@@ -332,6 +339,7 @@ files:
332
339
  - Vagrantfile
333
340
  - appveyor.yml
334
341
  - bin/fluent-binlog-reader
342
+ - bin/fluent-ca-generate
335
343
  - bin/fluent-cat
336
344
  - bin/fluent-debug
337
345
  - bin/fluent-gem
@@ -379,6 +387,7 @@ files:
379
387
  - lib/fluent/clock.rb
380
388
  - lib/fluent/command/binlog_reader.rb
381
389
  - lib/fluent/command/bundler_injection.rb
390
+ - lib/fluent/command/ca_generate.rb
382
391
  - lib/fluent/command/cat.rb
383
392
  - lib/fluent/command/debug.rb
384
393
  - lib/fluent/command/fluentd.rb
@@ -580,6 +589,7 @@ files:
580
589
  - templates/plugin_config_formatter/param.md.erb
581
590
  - templates/plugin_config_formatter/section.md.erb
582
591
  - test/command/test_binlog_reader.rb
592
+ - test/command/test_ca_generate.rb
583
593
  - test/command/test_fluentd.rb
584
594
  - test/command/test_plugin_config_formatter.rb
585
595
  - test/command/test_plugin_generator.rb
@@ -748,6 +758,7 @@ specification_version: 4
748
758
  summary: Fluentd event collector
749
759
  test_files:
750
760
  - test/command/test_binlog_reader.rb
761
+ - test/command/test_ca_generate.rb
751
762
  - test/command/test_fluentd.rb
752
763
  - test/command/test_plugin_config_formatter.rb
753
764
  - test/command/test_plugin_generator.rb