fluentd 0.14.5-x86-mingw32 → 0.14.7-x86-mingw32

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.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +55 -0
  3. data/bin/fluent-binlog-reader +7 -0
  4. data/example/in_dummy_with_compression.conf +23 -0
  5. data/lib/fluent/agent.rb +8 -12
  6. data/lib/fluent/command/binlog_reader.rb +234 -0
  7. data/lib/fluent/command/fluentd.rb +17 -1
  8. data/lib/fluent/compat/file_util.rb +1 -1
  9. data/lib/fluent/compat/output.rb +5 -1
  10. data/lib/fluent/config/configure_proxy.rb +18 -3
  11. data/lib/fluent/config/element.rb +1 -1
  12. data/lib/fluent/config/section.rb +1 -1
  13. data/lib/fluent/config/v1_parser.rb +1 -1
  14. data/lib/fluent/env.rb +1 -0
  15. data/lib/fluent/event.rb +49 -2
  16. data/lib/fluent/event_router.rb +6 -2
  17. data/lib/fluent/label.rb +8 -0
  18. data/lib/fluent/log.rb +30 -1
  19. data/lib/fluent/plugin.rb +1 -1
  20. data/lib/fluent/plugin/base.rb +3 -0
  21. data/lib/fluent/plugin/buf_file.rb +2 -2
  22. data/lib/fluent/plugin/buf_memory.rb +1 -1
  23. data/lib/fluent/plugin/buffer.rb +12 -2
  24. data/lib/fluent/plugin/buffer/chunk.rb +68 -7
  25. data/lib/fluent/plugin/buffer/file_chunk.rb +4 -4
  26. data/lib/fluent/plugin/buffer/memory_chunk.rb +4 -4
  27. data/lib/fluent/plugin/compressable.rb +91 -0
  28. data/lib/fluent/plugin/filter_grep.rb +4 -4
  29. data/lib/fluent/plugin/formatter.rb +2 -2
  30. data/lib/fluent/plugin/formatter_json.rb +2 -1
  31. data/lib/fluent/plugin/formatter_out_file.rb +3 -30
  32. data/lib/fluent/plugin/in_forward.rb +6 -5
  33. data/lib/fluent/plugin/in_monitor_agent.rb +7 -21
  34. data/lib/fluent/plugin/in_syslog.rb +1 -1
  35. data/lib/fluent/plugin/in_tail.rb +11 -2
  36. data/lib/fluent/plugin/multi_output.rb +63 -3
  37. data/lib/fluent/plugin/out_exec.rb +1 -1
  38. data/lib/fluent/plugin/out_file.rb +5 -1
  39. data/lib/fluent/plugin/out_forward.rb +17 -5
  40. data/lib/fluent/plugin/out_stdout.rb +2 -1
  41. data/lib/fluent/plugin/output.rb +205 -19
  42. data/lib/fluent/plugin/parser.rb +5 -49
  43. data/lib/fluent/plugin/parser_apache2.rb +1 -1
  44. data/lib/fluent/plugin/parser_json.rb +4 -4
  45. data/lib/fluent/plugin/parser_multiline.rb +5 -5
  46. data/lib/fluent/plugin/parser_regexp.rb +1 -2
  47. data/lib/fluent/plugin/parser_syslog.rb +2 -2
  48. data/lib/fluent/plugin/storage_local.rb +2 -1
  49. data/lib/fluent/plugin_helper.rb +1 -0
  50. data/lib/fluent/plugin_helper/compat_parameters.rb +39 -21
  51. data/lib/fluent/plugin_helper/extract.rb +92 -0
  52. data/lib/fluent/plugin_helper/inject.rb +10 -12
  53. data/lib/fluent/plugin_helper/thread.rb +23 -3
  54. data/lib/fluent/registry.rb +1 -1
  55. data/lib/fluent/root_agent.rb +2 -1
  56. data/lib/fluent/supervisor.rb +28 -8
  57. data/lib/fluent/test/base.rb +0 -7
  58. data/lib/fluent/test/driver/base.rb +1 -0
  59. data/lib/fluent/test/driver/output.rb +3 -0
  60. data/lib/fluent/test/helpers.rb +18 -0
  61. data/lib/fluent/test/input_test.rb +4 -2
  62. data/lib/fluent/test/log.rb +3 -1
  63. data/lib/fluent/time.rb +232 -1
  64. data/lib/fluent/timezone.rb +1 -1
  65. data/lib/fluent/version.rb +1 -1
  66. data/test/command/test_binlog_reader.rb +351 -0
  67. data/test/config/test_config_parser.rb +6 -0
  68. data/test/config/test_configurable.rb +47 -1
  69. data/test/helper.rb +0 -1
  70. data/test/plugin/test_buffer.rb +22 -2
  71. data/test/plugin/test_buffer_chunk.rb +34 -4
  72. data/test/plugin/test_buffer_file_chunk.rb +73 -0
  73. data/test/plugin/test_buffer_memory_chunk.rb +73 -0
  74. data/test/plugin/test_compressable.rb +81 -0
  75. data/test/plugin/test_formatter_json.rb +14 -1
  76. data/test/plugin/test_in_forward.rb +67 -3
  77. data/test/plugin/test_in_monitor_agent.rb +17 -1
  78. data/test/plugin/test_in_tail.rb +8 -8
  79. data/test/plugin/test_out_file.rb +0 -8
  80. data/test/plugin/test_out_forward.rb +85 -0
  81. data/test/plugin/test_out_secondary_file.rb +20 -12
  82. data/test/plugin/test_out_stdout.rb +11 -10
  83. data/test/plugin/test_output.rb +234 -0
  84. data/test/plugin/test_output_as_buffered.rb +223 -0
  85. data/test/plugin/test_output_as_buffered_compress.rb +165 -0
  86. data/test/plugin/test_parser_json.rb +8 -0
  87. data/test/plugin/test_parser_regexp.rb +1 -1
  88. data/test/plugin_helper/test_child_process.rb +2 -2
  89. data/test/plugin_helper/test_extract.rb +195 -0
  90. data/test/plugin_helper/test_inject.rb +0 -7
  91. data/test/scripts/fluent/plugin/formatter1/formatter_test1.rb +7 -0
  92. data/test/scripts/fluent/plugin/formatter2/formatter_test2.rb +7 -0
  93. data/test/test_event.rb +186 -0
  94. data/test/test_event_router.rb +1 -1
  95. data/test/test_formatter.rb +0 -7
  96. data/test/test_log.rb +121 -0
  97. data/test/test_plugin_classes.rb +62 -0
  98. data/test/test_root_agent.rb +125 -0
  99. data/test/test_supervisor.rb +25 -2
  100. data/test/test_time_formatter.rb +103 -7
  101. data/test/test_time_parser.rb +211 -0
  102. metadata +22 -4
  103. data/test/plugin/test_parser_time.rb +0 -46
@@ -15,8 +15,9 @@
15
15
  #
16
16
 
17
17
  require 'fluent/event'
18
- require 'time'
18
+ require 'fluent/time'
19
19
  require 'fluent/configurable'
20
+ require 'socket'
20
21
 
21
22
  module Fluent
22
23
  module PluginHelper
@@ -66,11 +67,13 @@ module Fluent
66
67
  config_param :hostname, :string, default: nil
67
68
  config_param :tag_key, :string, default: nil
68
69
  config_param :time_key, :string, default: nil
70
+
71
+ # To avoid defining :time_type twice
69
72
  config_param :time_type, :enum, list: [:float, :unixtime, :string], default: :float
70
- config_param :time_format, :string, default: nil
71
- config_param :localtime, :bool, default: true # if localtime is false and timezone is nil, then utc
72
- config_param :utc, :bool, default: false # placeholder to turn localtime to false
73
- config_param :timezone, :string, default: nil
73
+
74
+ Fluent::TimeMixin::TIME_PARAMETERS.each do |name, type, opts|
75
+ config_param name, type, opts
76
+ end
74
77
  end
75
78
  end
76
79
 
@@ -89,12 +92,6 @@ module Fluent
89
92
  end
90
93
 
91
94
  def configure(conf)
92
- conf.elements('inject').each do |e|
93
- if e.has_key?('utc') && Fluent::Config.bool_value(e['utc'])
94
- e['localtime'] = 'false'
95
- end
96
- end
97
-
98
95
  super
99
96
 
100
97
  if @inject_config
@@ -113,7 +110,8 @@ module Fluent
113
110
  when :float then ->(time){ time.to_r.truncate(+6).to_f } # microsecond floating point value
114
111
  when :unixtime then ->(time){ time.to_i }
115
112
  else
116
- Fluent::TimeFormatter.new(@inject_config.time_format, @inject_config.localtime, @inject_config.timezone)
113
+ localtime = @inject_config.localtime && !@inject_config.utc
114
+ Fluent::TimeFormatter.new(@inject_config.time_format, localtime, @inject_config.timezone)
117
115
  end
118
116
  end
119
117
 
@@ -70,7 +70,7 @@ module Fluent
70
70
  thread_exit = true
71
71
  raise
72
72
  ensure
73
- unless thread_exit
73
+ if ::Thread.current.alive? && !thread_exit
74
74
  log.warn "thread doesn't exit correctly (killed or other reason)", plugin: self.class, title: title, thread: ::Thread.current, error: $!
75
75
  end
76
76
  @_threads_mutex.synchronize do
@@ -110,11 +110,31 @@ module Fluent
110
110
 
111
111
  def stop
112
112
  super
113
+ wakeup_threads = []
113
114
  @_threads_mutex.synchronize do
114
- @_threads.each_pair do |obj_id, thread|
115
+ @_threads.each_value do |thread|
115
116
  thread[:_fluentd_plugin_helper_thread_running] = false
117
+ wakeup_threads << thread if thread.alive? && thread.status == "sleep"
116
118
  end
117
119
  end
120
+ wakeup_threads.each do |thread|
121
+ if thread.alive?
122
+ thread.wakeup
123
+ end
124
+ end
125
+ end
126
+
127
+ def after_shutdown
128
+ super
129
+ wakeup_threads = []
130
+ @_threads_mutex.synchronize do
131
+ @_threads.each_value do |thread|
132
+ wakeup_threads << thread if thread.alive? && thread.status == "sleep"
133
+ end
134
+ end
135
+ wakeup_threads.each do |thread|
136
+ thread.wakeup if thread.alive?
137
+ end
118
138
  end
119
139
 
120
140
  def close
@@ -132,7 +152,7 @@ module Fluent
132
152
  super
133
153
  @_threads_mutex.synchronize{ @_threads.keys }.each do |obj_id|
134
154
  thread = @_threads[obj_id]
135
- log.warn "killing existing thead", thread: thread
155
+ log.warn "killing existing thread", thread: thread
136
156
  thread.kill if thread
137
157
  end
138
158
  @_threads_mutex.synchronize{ @_threads.keys }.each do |obj_id|
@@ -28,7 +28,7 @@ module Fluent
28
28
  @paths = [DEFAULT_PLUGIN_PATH]
29
29
  end
30
30
 
31
- attr_reader :kind, :paths
31
+ attr_reader :kind, :paths, :map, :dir_search_prefix
32
32
 
33
33
  def register(type, value)
34
34
  type = type.to_sym
@@ -163,7 +163,7 @@ module Fluent
163
163
  end
164
164
 
165
165
  def shutdown # Fluentd's shutdown sequence is stop, before_shutdown, shutdown, after_shutdown, close, terminate for plugins
166
- # Thesee method callers does `rescue Exception` to call methods of shutdown sequence as far as possible
166
+ # These method callers does `rescue Exception` to call methods of shutdown sequence as far as possible
167
167
  # if plugin methods does something like infinite recursive call, `exit`, unregistering signal handlers or others.
168
168
  # Plugins should be separated and be in sandbox to protect data in each plugins/buffers.
169
169
 
@@ -242,6 +242,7 @@ module Fluent
242
242
 
243
243
  def add_label(name)
244
244
  label = Label.new(name, log: log)
245
+ raise ConfigError, "Section <label #{name}> appears twice" if @labels[name]
245
246
  label.root_agent = self
246
247
  @labels[name] = label
247
248
  end
@@ -230,11 +230,17 @@ module Fluent
230
230
  log_path = params['log_path']
231
231
  chuser = params['chuser']
232
232
  chgroup = params['chgroup']
233
+ log_rotate_age = params['log_rotate_age']
234
+ log_rotate_size = params['log_rotate_size']
233
235
  rpc_endpoint = system_config.rpc_endpoint
234
236
  enable_get_dump = system_config.enable_get_dump
235
237
 
236
238
  log_opts = {suppress_repeated_stacktrace: suppress_repeated_stacktrace}
237
- logger_initializer = Supervisor::LoggerInitializer.new(log_path, log_level, chuser, chgroup, log_opts)
239
+ logger_initializer = Supervisor::LoggerInitializer.new(
240
+ log_path, log_level, chuser, chgroup, log_opts,
241
+ log_rotate_age: log_rotate_age,
242
+ log_rotate_size: log_rotate_size
243
+ )
238
244
  # this #init sets initialized logger to $log
239
245
  logger_initializer.init
240
246
  logger = $log
@@ -295,42 +301,48 @@ module Fluent
295
301
  end
296
302
 
297
303
  class LoggerInitializer
298
- def initialize(path, level, chuser, chgroup, opts)
304
+ def initialize(path, level, chuser, chgroup, opts, log_rotate_age: nil, log_rotate_size: nil)
299
305
  @path = path
300
306
  @level = level
301
307
  @chuser = chuser
302
308
  @chgroup = chgroup
303
309
  @opts = opts
310
+ @log_rotate_age = log_rotate_age
311
+ @log_rotate_size = log_rotate_size
304
312
  end
305
313
 
306
314
  def init
307
315
  if @path && @path != "-"
308
- @io = File.open(@path, "a")
316
+ @logdev = if @log_rotate_age || @log_rotate_size
317
+ Fluent::LogDeviceIO.new(@path, shift_age: @log_rotate_age, shift_size: @log_rotate_size)
318
+ else
319
+ File.open(@path, "a")
320
+ end
309
321
  if @chuser || @chgroup
310
322
  chuid = @chuser ? ServerEngine::Privilege.get_etc_passwd(@chuser).uid : nil
311
323
  chgid = @chgroup ? ServerEngine::Privilege.get_etc_group(@chgroup).gid : nil
312
324
  File.chown(chuid, chgid, @path)
313
325
  end
314
326
  else
315
- @io = STDOUT
327
+ @logdev = STDOUT
316
328
  end
317
329
 
318
330
  dl_opts = {}
319
331
  # subtract 1 to match serverengine daemon logger side logging severity.
320
332
  dl_opts[:log_level] = @level - 1
321
- logger = ServerEngine::DaemonLogger.new(@io, dl_opts)
333
+ logger = ServerEngine::DaemonLogger.new(@logdev, dl_opts)
322
334
  $log = Fluent::Log.new(logger, @opts)
323
335
  $log.enable_color(false) if @path
324
336
  $log.enable_debug if @level <= Fluent::Log::LEVEL_DEBUG
325
337
  end
326
338
 
327
339
  def stdout?
328
- @io == STDOUT
340
+ @logdev == STDOUT
329
341
  end
330
342
 
331
343
  def reopen!
332
344
  if @path && @path != "-"
333
- @io.reopen(@path, "a")
345
+ @logdev.reopen(@path, "a")
334
346
  end
335
347
  self
336
348
  end
@@ -381,6 +393,8 @@ module Fluent
381
393
  @process_name = nil
382
394
 
383
395
  @log_level = opt[:log_level]
396
+ @log_rotate_age = opt[:log_rotate_age]
397
+ @log_rotate_size = opt[:log_rotate_size]
384
398
  @suppress_interval = opt[:suppress_interval]
385
399
  @suppress_config_dump = opt[:suppress_config_dump]
386
400
  @without_source = opt[:without_source]
@@ -388,7 +402,11 @@ module Fluent
388
402
 
389
403
  @suppress_repeated_stacktrace = opt[:suppress_repeated_stacktrace]
390
404
  log_opts = {suppress_repeated_stacktrace: @suppress_repeated_stacktrace}
391
- @log = LoggerInitializer.new(@log_path, @log_level, @chuser, @chgroup, log_opts)
405
+ @log = LoggerInitializer.new(
406
+ @log_path, @log_level, @chuser, @chgroup, log_opts,
407
+ log_rotate_age: @log_rotate_age,
408
+ log_rotate_size: @log_rotate_size
409
+ )
392
410
  @finished = false
393
411
  end
394
412
 
@@ -512,6 +530,8 @@ module Fluent
512
530
  params['inline_config'] = @inline_config
513
531
  params['log_path'] = @log_path
514
532
  params['log_level'] = @log_level
533
+ params['log_rotate_age'] = @log_rotate_age
534
+ params['log_rotate_size'] = @log_rotate_size
515
535
  params['chuser'] = @chuser
516
536
  params['chgroup'] = @chgroup
517
537
  params['use_v1_config'] = @use_v1_config
@@ -76,10 +76,3 @@ module Fluent
76
76
  end
77
77
  end
78
78
  end
79
-
80
- Test::Unit::Assertions.module_eval do
81
- def assert_equal_event_time(a, b)
82
- assert_equal(a.sec, b.sec)
83
- assert_equal(a.nsec, b.nsec)
84
- end
85
- end
@@ -40,6 +40,7 @@ module Fluent
40
40
  else
41
41
  @instance = klass
42
42
  end
43
+ @instance.under_plugin_development = true
43
44
 
44
45
  @logs = []
45
46
 
@@ -18,6 +18,7 @@ require 'fluent/test/driver/base_owner'
18
18
  require 'fluent/test/driver/event_feeder'
19
19
 
20
20
  require 'fluent/plugin/output'
21
+ require 'timeout'
21
22
 
22
23
  module Fluent
23
24
  module Test
@@ -43,6 +44,7 @@ module Fluent
43
44
  super(**kwargs, &block)
44
45
  if @flush_buffer_at_cleanup
45
46
  @instance.force_flush
47
+ Timeout.timeout(10){ sleep 0.1 until !@instance.buffer || @instance.buffer.queue.size == 0 }
46
48
  end
47
49
  end
48
50
 
@@ -52,6 +54,7 @@ module Fluent
52
54
 
53
55
  def flush
54
56
  @instance.force_flush
57
+ Timeout.timeout(10){ sleep 0.1 until !@instance.buffer || @instance.buffer.queue.size == 0 }
55
58
  end
56
59
 
57
60
  def instance_hook_after_started
@@ -21,6 +21,17 @@ require 'fluent/time'
21
21
  module Fluent
22
22
  module Test
23
23
  module Helpers
24
+ # See "Example Custom Assertion: http://test-unit.github.io/test-unit/en/Test/Unit/Assertions.html
25
+ def assert_equal_event_time(expected, actual, message = nil)
26
+ message = build_message(message, <<EOT, expected, actual)
27
+ <?> expected but was
28
+ <?>.
29
+ EOT
30
+ assert_block(message) do
31
+ expected.is_a?(Fluent::EventTime) && actual.is_a?(Fluent::EventTime) && expected.sec == actual.sec && expected.nsec == actual.nsec
32
+ end
33
+ end
34
+
24
35
  def config_element(name = 'test', argument = '', params = {}, elements = [])
25
36
  Fluent::Config::Element.new(name, argument, params, elements)
26
37
  end
@@ -37,6 +48,13 @@ module Fluent
37
48
  end
38
49
  end
39
50
 
51
+ def with_timezone(tz)
52
+ oldtz, ENV['TZ'] = ENV['TZ'], tz
53
+ yield
54
+ ensure
55
+ ENV['TZ'] = oldtz
56
+ end
57
+
40
58
  def time2str(time, localtime: false, format: nil)
41
59
  if format
42
60
  if localtime
@@ -24,6 +24,7 @@ module Fluent
24
24
  def initialize(klass, &block)
25
25
  super(klass, &block)
26
26
  @emit_streams = []
27
+ @event_streams = []
27
28
  @expects = nil
28
29
  # for checking only the number of emitted records during run
29
30
  @expected_emits_length = nil
@@ -42,7 +43,7 @@ module Fluent
42
43
 
43
44
  attr_accessor :expected_emits_length
44
45
  attr_accessor :run_timeout
45
- attr_reader :emit_streams
46
+ attr_reader :emit_streams, :event_streams
46
47
 
47
48
  def emits
48
49
  all = []
@@ -135,7 +136,7 @@ module Fluent
135
136
  end
136
137
  end
137
138
 
138
- # Set runnning timeout to avoid infinite loop caused by some errors.
139
+ # Set running timeout to avoid infinite loop caused by some errors.
139
140
  started_at = Time.now
140
141
  register_run_breaking_condition do
141
142
  Time.now >= started_at + @run_timeout
@@ -165,6 +166,7 @@ module Fluent
165
166
 
166
167
  private
167
168
  def emit_stream(tag, es)
169
+ @event_streams << es
168
170
  @emit_streams << [tag, es.to_a]
169
171
  end
170
172
  end
@@ -21,13 +21,15 @@ module Fluent
21
21
  module Test
22
22
  class DummyLogDevice
23
23
  attr_reader :logs
24
+ attr_accessor :flush_logs
24
25
 
25
26
  def initialize
26
27
  @logs = []
28
+ @flush_logs = true
27
29
  end
28
30
 
29
31
  def reset
30
- @logs = []
32
+ @logs = [] if @flush_logs
31
33
  end
32
34
 
33
35
  def tty?
@@ -16,7 +16,10 @@
16
16
 
17
17
  require 'time'
18
18
  require 'msgpack'
19
+ require 'strptime'
19
20
  require 'fluent/timezone'
21
+ require 'fluent/configurable'
22
+ require 'fluent/config/error'
20
23
 
21
24
  module Fluent
22
25
  class EventTime
@@ -103,8 +106,207 @@ module Fluent
103
106
  end
104
107
  end
105
108
 
109
+ module TimeMixin
110
+ TIME_PARAMETERS = [
111
+ [:time_format, :string, {default: nil}],
112
+ [:localtime, :bool, {default: true}], # UTC if :localtime is false and :timezone is nil
113
+ [:utc, :bool, {default: false}], # to turn :localtime false
114
+ [:timezone, :string, {default: nil}],
115
+ ]
116
+ TIME_FULL_PARAMETERS = [
117
+ # To avoid to define :time_type twice (in plugin_helper/inject)
118
+ [:time_type, :enum, {default: :string, list: [:string, :unixtime, :float]}],
119
+ ] + TIME_PARAMETERS
120
+
121
+ module TimeParameters
122
+ include Fluent::Configurable
123
+ TIME_FULL_PARAMETERS.each do |name, type, opts|
124
+ config_param name, type, opts
125
+ end
126
+
127
+ def configure(conf)
128
+ if conf.has_key?('localtime') || conf.has_key?('utc')
129
+ if conf.has_key?('localtime') && conf.has_key?('utc')
130
+ raise Fluent::ConfigError, "both of utc and localtime are specified, use only one of them"
131
+ elsif conf.has_key?('localtime')
132
+ conf['localtime'] = Fluent::Config.bool_value(conf['localtime'])
133
+ elsif conf.has_key?('utc')
134
+ conf['localtime'] = !(Fluent::Config.bool_value(conf['utc']))
135
+ # Specifying "localtime false" means using UTC in TimeFormatter
136
+ # And specifying "utc" is different from specifying "timezone +0000"(it's not always UTC).
137
+ # There are difference between "Z" and "+0000" in timezone formatting.
138
+ # TODO: add kwargs to TimeFormatter to specify "using localtime", "using UTC" or "using specified timezone" in more explicit way
139
+ end
140
+ end
141
+
142
+ super
143
+
144
+ Fluent::Timezone.validate!(@timezone) if @timezone
145
+ end
146
+ end
147
+
148
+ module Parser
149
+ def self.included(mod)
150
+ mod.include TimeParameters
151
+ end
152
+
153
+ def time_parser_create(type: @time_type, format: @time_format, timezone: @timezone, force_localtime: false)
154
+ return NumericTimeParser.new(type) if type != :string
155
+ return TimeParser.new(format, true, nil) if force_localtime
156
+
157
+ localtime = @localtime && (timezone.nil? && !@utc)
158
+ TimeParser.new(format, localtime, timezone)
159
+ end
160
+ end
161
+
162
+ module Formatter
163
+ def self.included(mod)
164
+ mod.include TimeParameters
165
+ end
166
+
167
+ def time_formatter_create(type: @time_type, format: @time_format, timezone: @timezone, force_localtime: false)
168
+ return NumericTimeFormatter.new(type) if type != :string
169
+ return TimeFormatter.new(format, true, nil) if force_localtime
170
+
171
+ localtime = @localtime && (timezone.nil? && !@utc)
172
+ TimeFormatter.new(format, localtime, timezone)
173
+ end
174
+ end
175
+ end
176
+
177
+ class TimeParser
178
+ class TimeParseError < StandardError; end
179
+
180
+ def initialize(format = nil, localtime = true, timezone = nil)
181
+ if format.nil? && (timezone || !localtime)
182
+ raise Fluent::ConfigError, "specifying timezone requires time format"
183
+ end
184
+
185
+ @cache1_key = nil
186
+ @cache1_time = nil
187
+ @cache2_key = nil
188
+ @cache2_time = nil
189
+
190
+ format_with_timezone = format && (format.include?("%z") || format.include?("%Z"))
191
+
192
+ # unixtime_in_expected_tz = unixtime_in_localtime + offset_diff
193
+ offset_diff = case
194
+ when format_with_timezone then nil
195
+ when timezone then Time.now.localtime.utc_offset - Time.zone_offset(timezone)
196
+ when localtime then 0
197
+ else Time.now.localtime.utc_offset # utc
198
+ end
199
+
200
+ strptime = format && (Strptime.new(time_format) rescue nil)
201
+
202
+ @parse = case
203
+ when format_with_timezone && strptime then ->(v){ Fluent::EventTime.from_time(strptime.exec(v)) }
204
+ when format_with_timezone then ->(v){ Fluent::EventTime.from_time(Time.strptime(v, format)) }
205
+ when strptime then ->(v){ t = strptime.exec(v); Fluent::EventTime.new(t.to_i + offset_diff, t.nsec) }
206
+ when format then ->(v){ t = Time.strptime(v, format); Fluent::EventTime.new(t.to_i + offset_diff, t.nsec) }
207
+ else ->(v){ Fluent::EventTime.parse(v) }
208
+ end
209
+ end
210
+
211
+ # TODO: new cache mechanism using format string
212
+ def parse(value)
213
+ unless value.is_a?(String)
214
+ raise TimeParseError, "value must be string: #{value}"
215
+ end
216
+
217
+ if @cache1_key == value
218
+ return @cache1_time
219
+ elsif @cache2_key == value
220
+ return @cache2_time
221
+ else
222
+ begin
223
+ time = @parse.call(value)
224
+ rescue => e
225
+ raise TimeParseError, "invalid time format: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
226
+ end
227
+ @cache1_key = @cache2_key
228
+ @cache1_time = @cache2_time
229
+ @cache2_key = value
230
+ @cache2_time = time
231
+ return time
232
+ end
233
+ end
234
+ alias :call :parse
235
+ end
236
+
237
+ class NumericTimeParser < TimeParser # to include TimeParseError
238
+ def initialize(type, localtime = nil, timezone = nil)
239
+ @cache1_key = @cache1_time = @cache2_key = @cache2_time = nil
240
+
241
+ if type == :unixtime
242
+ define_singleton_method(:parse, method(:parse_unixtime))
243
+ define_singleton_method(:call, method(:parse_unixtime))
244
+ else # :float
245
+ define_singleton_method(:parse, method(:parse_float))
246
+ define_singleton_method(:call, method(:parse_float))
247
+ end
248
+ end
249
+
250
+ def parse_unixtime(value)
251
+ unless value.is_a?(String)
252
+ raise TimeParseError, "value must be a string: #{value}"
253
+ end
254
+
255
+ if @cache1_key == value
256
+ return @cache1_time
257
+ elsif @cache2_key == value
258
+ return @cache2_time
259
+ end
260
+
261
+ begin
262
+ time = Fluent::EventTime.new(value.to_i)
263
+ rescue => e
264
+ raise TimeParseError, "invalid time format: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
265
+ end
266
+ @cache1_key = @cache2_key
267
+ @cache1_time = @cache2_time
268
+ @cache2_key = value
269
+ @cache2_time = time
270
+ time
271
+ end
272
+
273
+ # rough benchmark result to compare handmade parser vs Fluent::EventTime.from_time(Time.at(value.to_r))
274
+ # full: with 9-digits of nsec after dot
275
+ # msec: with 3-digits of msec after dot
276
+ # 10_000_000 times loop on MacBookAir
277
+ ## parse_by_myself(full): 12.162475 sec
278
+ ## parse_by_myself(msec): 15.050435 sec
279
+ ## parse_by_to_r (full): 28.722362 sec
280
+ ## parse_by_to_r (msec): 28.232856 sec
281
+ def parse_float(value)
282
+ unless value.is_a?(String)
283
+ raise TimeParseError, "value must be a string: #{value}"
284
+ end
285
+
286
+ if @cache1_key == value
287
+ return @cache1_time
288
+ elsif @cache2_key == value
289
+ return @cache2_time
290
+ end
291
+
292
+ begin
293
+ sec_s, nsec_s, _ = value.split('.', 3) # throw away second-dot and later
294
+ nsec_s = nsec_s[0..9]
295
+ nsec_s += '0' * (9 - nsec_s.size) if nsec_s.size < 9
296
+ time = Fluent::EventTime.new(sec_s.to_i, nsec_s.to_i)
297
+ rescue => e
298
+ raise TimeParseError, "invalid time format: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
299
+ end
300
+ @cache1_key = @cache2_key
301
+ @cache1_time = @cache2_time
302
+ @cache2_key = value
303
+ @cache2_time = time
304
+ time
305
+ end
306
+ end
307
+
106
308
  class TimeFormatter
107
- def initialize(format, localtime, timezone = nil)
309
+ def initialize(format = nil, localtime = true, timezone = nil)
108
310
  @tc1 = 0
109
311
  @tc1_str = nil
110
312
  @tc2 = 0
@@ -172,4 +374,33 @@ module Fluent
172
374
  @format_nocache.call(time)
173
375
  end
174
376
  end
377
+
378
+ class NumericTimeFormatter < TimeFormatter
379
+ def initialize(type, localtime = nil, timezone = nil)
380
+ @cache1_key = @cache1_time = @cache2_key = @cache2_time = nil
381
+
382
+ if type == :unixtime
383
+ define_singleton_method(:format, method(:format_unixtime))
384
+ define_singleton_method(:call, method(:format_unixtime))
385
+ else # :float
386
+ define_singleton_method(:format, method(:format_float))
387
+ define_singleton_method(:call, method(:format_float))
388
+ end
389
+ end
390
+
391
+ def format_unixtime(time)
392
+ time.to_i.to_s
393
+ end
394
+
395
+ def format_float(time)
396
+ if time.is_a?(Fluent::EventTime) || time.is_a?(Time)
397
+ # 10.015 secs for 10_000_000 times call on MacBookAir
398
+ nsec_s = time.nsec.to_s
399
+ nsec_s = '0' * (9 - nsec_s.size) if nsec_s.size < 9
400
+ "#{time.sec}.#{nsec_s}"
401
+ else # integer (or float?)
402
+ time.to_f.to_s
403
+ end
404
+ end
405
+ end
175
406
  end