fluentd 0.14.13-x86-mingw32 → 0.14.17-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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -5
  3. data/ChangeLog +106 -0
  4. data/MAINTAINERS.md +5 -0
  5. data/README.md +25 -0
  6. data/example/worker_section.conf +36 -0
  7. data/fluentd.gemspec +1 -1
  8. data/lib/fluent/agent.rb +5 -2
  9. data/lib/fluent/command/binlog_reader.rb +1 -0
  10. data/lib/fluent/command/fluentd.rb +28 -12
  11. data/lib/fluent/command/plugin_config_formatter.rb +0 -1
  12. data/lib/fluent/command/plugin_generator.rb +1 -1
  13. data/lib/fluent/compat/detach_process_mixin.rb +8 -0
  14. data/lib/fluent/compat/input.rb +0 -10
  15. data/lib/fluent/compat/output.rb +0 -10
  16. data/lib/fluent/config/element.rb +22 -0
  17. data/lib/fluent/config/literal_parser.rb +2 -0
  18. data/lib/fluent/config/types.rb +2 -2
  19. data/lib/fluent/engine.rb +27 -10
  20. data/lib/fluent/env.rb +3 -3
  21. data/lib/fluent/log.rb +4 -1
  22. data/lib/fluent/plugin/base.rb +3 -0
  23. data/lib/fluent/plugin/filter.rb +2 -2
  24. data/lib/fluent/plugin/filter_parser.rb +17 -6
  25. data/lib/fluent/plugin/in_forward.rb +1 -1
  26. data/lib/fluent/plugin/in_http.rb +4 -0
  27. data/lib/fluent/plugin/in_monitor_agent.rb +8 -3
  28. data/lib/fluent/plugin/in_syslog.rb +3 -2
  29. data/lib/fluent/plugin/in_tail.rb +14 -3
  30. data/lib/fluent/plugin/in_udp.rb +6 -2
  31. data/lib/fluent/plugin/out_file.rb +5 -0
  32. data/lib/fluent/plugin/out_forward.rb +5 -2
  33. data/lib/fluent/plugin/output.rb +13 -8
  34. data/lib/fluent/plugin/parser_apache2.rb +1 -1
  35. data/lib/fluent/plugin/parser_syslog.rb +40 -1
  36. data/lib/fluent/plugin_helper/cert_option.rb +2 -2
  37. data/lib/fluent/plugin_helper/compat_parameters.rb +1 -1
  38. data/lib/fluent/plugin_helper/storage.rb +1 -1
  39. data/lib/fluent/root_agent.rb +36 -4
  40. data/lib/fluent/supervisor.rb +37 -6
  41. data/lib/fluent/system_config.rb +7 -0
  42. data/lib/fluent/time.rb +1 -0
  43. data/lib/fluent/version.rb +1 -1
  44. data/lib/fluent/winsvc.rb +25 -11
  45. data/test/command/test_fluentd.rb +253 -4
  46. data/test/config/test_element.rb +63 -0
  47. data/test/config/test_literal_parser.rb +1 -1
  48. data/test/config/test_system_config.rb +36 -6
  49. data/test/config/test_types.rb +19 -0
  50. data/test/plugin/test_filter_parser.rb +35 -0
  51. data/test/plugin/test_in_http.rb +58 -4
  52. data/test/plugin/test_in_monitor_agent.rb +90 -9
  53. data/test/plugin/test_in_tail.rb +16 -0
  54. data/test/plugin/test_in_udp.rb +11 -1
  55. data/test/plugin/test_out_file.rb +9 -0
  56. data/test/plugin/test_out_forward.rb +45 -0
  57. data/test/plugin/test_output.rb +15 -15
  58. data/test/plugin/test_output_as_buffered.rb +30 -2
  59. data/test/plugin/test_parser_apache2.rb +8 -0
  60. data/test/plugin/test_parser_syslog.rb +176 -0
  61. data/test/plugin_helper/test_server.rb +37 -31
  62. data/test/plugin_helper/test_storage.rb +9 -0
  63. data/test/test_log.rb +6 -0
  64. data/test/test_plugin_classes.rb +50 -0
  65. data/test/test_root_agent.rb +245 -14
  66. data/test/test_time_parser.rb +12 -0
  67. metadata +13 -5
@@ -35,10 +35,13 @@ module Fluent
35
35
 
36
36
  # it's global logger, not plugin logger: deprecated message should be global warning, not plugin level.
37
37
  @logger = defined?($log) ? $log : nil
38
+
39
+ @target_worker_id = nil
38
40
  end
39
41
 
40
42
  attr_accessor :name, :arg, :unused, :v1_config, :corresponding_proxies, :unused_in
41
43
  attr_writer :elements
44
+ attr_reader :target_worker_id
42
45
 
43
46
  RESERVED_PARAMETERS_COMPAT = {
44
47
  '@type' => 'type',
@@ -213,6 +216,25 @@ module Fluent
213
216
  v.each_char { |c| result << LiteralParser.unescape_char(c) }
214
217
  result
215
218
  end
219
+
220
+ def set_target_worker_id(worker_id)
221
+ @target_worker_id = worker_id
222
+ @elements.each { |e|
223
+ e.set_target_worker_id(worker_id)
224
+ }
225
+ end
226
+
227
+ def for_every_workers?
228
+ @target_worker_id == nil
229
+ end
230
+
231
+ def for_this_worker?
232
+ @target_worker_id == Fluent::Engine.worker_id
233
+ end
234
+
235
+ def for_another_worker?
236
+ @target_worker_id != nil && @target_worker_id != Fluent::Engine.worker_id
237
+ end
216
238
  end
217
239
  end
218
240
  end
@@ -181,6 +181,8 @@ module Fluent
181
181
  "\f"
182
182
  when "b"
183
183
  "\b"
184
+ when "0"
185
+ "\0"
184
186
  when /[a-zA-Z0-9]/
185
187
  parse_error! "unexpected back-slash escape character '#{c}'"
186
188
  else # symbols
@@ -64,7 +64,7 @@ module Fluent
64
64
  end
65
65
  end
66
66
 
67
- STRING_TYPE = Proc.new { |val, opts| val.to_s.encode(Encoding::UTF_8) }
67
+ STRING_TYPE = Proc.new { |val, opts| val.to_s.force_encoding(Encoding::UTF_8) }
68
68
  ENUM_TYPE = Proc.new { |val, opts|
69
69
  s = val.to_sym
70
70
  list = opts[:list]
@@ -85,7 +85,7 @@ module Fluent
85
85
  value
86
86
  else
87
87
  case type
88
- when :string then value.to_s.encode(Encoding::UTF_8)
88
+ when :string then value.to_s.force_encoding(Encoding::UTF_8)
89
89
  when :integer then value.to_i
90
90
  when :float then value.to_f
91
91
  when :size then Config.size_value(value)
@@ -31,6 +31,7 @@ module Fluent
31
31
  @root_agent = nil
32
32
  @default_loop = nil
33
33
  @engine_stopped = false
34
+ @_worker_id = nil
34
35
 
35
36
  @log_event_router = nil
36
37
  @log_emit_thread = nil
@@ -42,6 +43,8 @@ module Fluent
42
43
  @suppress_config_dump = false
43
44
 
44
45
  @system_config = SystemConfig.new
46
+
47
+ @dry_run_mode = false
45
48
  end
46
49
 
47
50
  MAINLOOP_SLEEP_INTERVAL = 0.3
@@ -53,6 +56,8 @@ module Fluent
53
56
  attr_reader :matches, :sources
54
57
  attr_reader :system_config
55
58
 
59
+ attr_accessor :dry_run_mode
60
+
56
61
  def init(system_config)
57
62
  @system_config = system_config
58
63
 
@@ -97,12 +102,21 @@ module Fluent
97
102
  else
98
103
  "section <#{e.name}> is not used in <#{parent_name}>"
99
104
  end
100
- $log.warn :worker0, message
105
+ if e.for_every_workers?
106
+ $log.warn :worker0, message
107
+ elsif e.for_this_worker?
108
+ $log.warn message
109
+ end
101
110
  next
102
111
  end
103
112
  unless e.name == 'system'
104
113
  unless @without_source && e.name == 'source'
105
- $log.warn :worker0, "parameter '#{key}' in #{e.to_s.strip} is not used."
114
+ message = "parameter '#{key}' in #{e.to_s.strip} is not used."
115
+ if e.for_every_workers?
116
+ $log.warn :worker0, message
117
+ elsif e.for_this_worker?
118
+ $log.warn message
119
+ end
106
120
  end
107
121
  end
108
122
  }
@@ -128,7 +142,7 @@ module Fluent
128
142
 
129
143
  unmatched_tags = Fluent::Log.event_tags.select{|t| !@log_event_router.match?(t) }
130
144
  unless unmatched_tags.empty?
131
- $log.warn :worker0, "match for some tags of log events are not defined (to be ignored)", tags: unmatched_tags
145
+ $log.warn "match for some tags of log events are not defined (to be ignored)", tags: unmatched_tags
132
146
  end
133
147
  rescue ArgumentError # ArgumentError "#{label_name} label not found"
134
148
  # use default event router if <label @FLUENT_LOG> is missing in configuration
@@ -139,7 +153,7 @@ module Fluent
139
153
 
140
154
  unmatched_tags = Fluent::Log.event_tags.select{|t| !@log_event_router.match?(t) }
141
155
  unless unmatched_tags.empty?
142
- $log.warn :worker0, "match for some tags of log events are not defined (to be ignored)", tags: unmatched_tags
156
+ $log.warn "match for some tags of log events are not defined (to be ignored)", tags: unmatched_tags
143
157
  end
144
158
  end
145
159
  end
@@ -147,7 +161,7 @@ module Fluent
147
161
  $log.enable_event(true) if @log_event_router
148
162
 
149
163
  unless @suppress_config_dump
150
- $log.info :worker0, "using configuration file: #{conf.to_s.rstrip}"
164
+ $log.info :supervisor, "using configuration file: #{conf.to_s.rstrip}"
151
165
  end
152
166
  end
153
167
 
@@ -200,10 +214,6 @@ module Fluent
200
214
  end
201
215
 
202
216
  def run
203
- # if ENV doesn't have SERVERENGINE_WORKER_ID, it is a worker under --no-supervisor or in tests
204
- # so it's (almost) a single worker, worker_id=0
205
- worker_id = (ENV['SERVERENGINE_WORKER_ID'] || 0).to_i
206
-
207
217
  begin
208
218
  $log.info "starting fluentd worker", pid: Process.pid, ppid: Process.ppid, worker: worker_id
209
219
  start
@@ -251,8 +261,15 @@ module Fluent
251
261
  @log_event_queue.push([tag, time, record])
252
262
  end
253
263
 
254
- private
264
+ def worker_id
265
+ return @_worker_id if @_worker_id
266
+ # if ENV doesn't have SERVERENGINE_WORKER_ID, it is a worker under --no-supervisor or in tests
267
+ # so it's (almost) a single worker, worker_id=0
268
+ @_worker_id = (ENV['SERVERENGINE_WORKER_ID'] || 0).to_i
269
+ @_worker_id
270
+ end
255
271
 
272
+ private
256
273
  def start
257
274
  @root_agent.start
258
275
  end
@@ -14,15 +14,15 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
+ require 'serverengine/utils'
18
+
17
19
  module Fluent
18
20
  DEFAULT_CONFIG_PATH = ENV['FLUENT_CONF'] || '/etc/fluent/fluent.conf'
19
21
  DEFAULT_PLUGIN_DIR = ENV['FLUENT_PLUGIN'] || '/etc/fluent/plugin'
20
22
  DEFAULT_SOCKET_PATH = ENV['FLUENT_SOCKET'] || '/var/run/fluent/fluent.sock'
21
23
  DEFAULT_OJ_OPTIONS = {bigdecimal_load: :float, mode: :compat, use_to_json: true}
22
- IS_WINDOWS = /mswin|mingw/ === RUBY_PLATFORM
23
- private_constant :IS_WINDOWS
24
24
 
25
25
  def self.windows?
26
- IS_WINDOWS
26
+ ServerEngine.windows?
27
27
  end
28
28
  end
@@ -357,6 +357,9 @@ module Fluent
357
357
  def write(data)
358
358
  @out.write(data)
359
359
  end
360
+ # We need `#<<` method to use this logger class with other
361
+ # libraries such as aws-sdk
362
+ alias << write
360
363
 
361
364
  def flush
362
365
  @out.flush
@@ -469,7 +472,7 @@ module Fluent
469
472
  extend Forwardable
470
473
  def_delegators '@logger', :enable_color?, :enable_debug, :enable_event,
471
474
  :disable_events, :log_event_enabled, :log_event_enamed=, :time_format, :time_format=,
472
- :event, :caller_line, :puts, :write, :flush, :reset, :out, :out=,
475
+ :event, :caller_line, :puts, :write, :<<, :flush, :reset, :out, :out=,
473
476
  :optional_header, :optional_header=, :optional_attrs, :optional_attrs=
474
477
  end
475
478
 
@@ -51,6 +51,9 @@ module Fluent
51
51
  end
52
52
 
53
53
  def configure(conf)
54
+ if conf.respond_to?(:for_this_worker?) && conf.for_this_worker?
55
+ system_config_override(workers: 1)
56
+ end
54
57
  super
55
58
  @_state ||= State.new(false, false, false, false, false, false, false, false, false)
56
59
  @_state.configure = true
@@ -79,13 +79,13 @@ module Fluent
79
79
  return nil if implmented_methods.include?(:filter_stream)
80
80
  case
81
81
  when [:filter, :filter_with_time].all? { |e| implmented_methods.include?(e) }
82
- raise "BUG: Filter plugins MUST be implemented either `filter` or `filter_with_time`"
82
+ raise "BUG: Filter plugins MUST implement either `filter` or `filter_with_time`"
83
83
  when implmented_methods.include?(:filter)
84
84
  false
85
85
  when implmented_methods.include?(:filter_with_time)
86
86
  true
87
87
  else
88
- raise NotImplementedError, "BUG: Filter plugins MUST be implmented either `filter` or `filter_with_time`"
88
+ raise NotImplementedError, "BUG: Filter plugins MUST implement either `filter` or `filter_with_time`"
89
89
  end
90
90
  end
91
91
  end
@@ -32,6 +32,7 @@ module Fluent::Plugin
32
32
  config_param :inject_key_prefix, :string, default: nil
33
33
  config_param :replace_invalid_sequence, :bool, default: false
34
34
  config_param :hash_value_field, :string, default: nil
35
+ config_param :emit_invalid_record_to_error, :bool, default: true
35
36
 
36
37
  attr_reader :parser
37
38
 
@@ -49,7 +50,9 @@ module Fluent::Plugin
49
50
  def filter_with_time(tag, time, record)
50
51
  raw_value = record[@key_name]
51
52
  if raw_value.nil?
52
- router.emit_error_event(tag, time, record, ArgumentError.new("#{@key_name} does not exist"))
53
+ if @emit_invalid_record_to_error
54
+ router.emit_error_event(tag, time, record, ArgumentError.new("#{@key_name} does not exist"))
55
+ end
53
56
  if @reserve_data
54
57
  return time, handle_parsed(tag, record, time, {})
55
58
  else
@@ -67,7 +70,9 @@ module Fluent::Plugin
67
70
  r = handle_parsed(tag, record, t, values)
68
71
  return t, r
69
72
  else
70
- router.emit_error_event(tag, time, record, Fluent::Plugin::Parser::ParserError.new("pattern not match with data '#{raw_value}'"))
73
+ if @emit_invalid_record_to_error
74
+ router.emit_error_event(tag, time, record, Fluent::Plugin::Parser::ParserError.new("pattern not match with data '#{raw_value}'"))
75
+ end
71
76
  if @reserve_data
72
77
  t = time
73
78
  r = handle_parsed(tag, record, time, {})
@@ -78,8 +83,11 @@ module Fluent::Plugin
78
83
  end
79
84
  end
80
85
  rescue Fluent::Plugin::Parser::ParserError => e
81
- router.emit_error_event(tag, time, record, e)
82
- return FAILED_RESULT
86
+ if @emit_invalid_record_to_error
87
+ raise e
88
+ else
89
+ return FAILED_RESULT
90
+ end
83
91
  rescue ArgumentError => e
84
92
  raise unless @replace_invalid_sequence
85
93
  raise unless e.message.index("invalid byte sequence in") == 0
@@ -87,8 +95,11 @@ module Fluent::Plugin
87
95
  raw_value = raw_value.scrub(REPLACE_CHAR)
88
96
  retry
89
97
  rescue => e
90
- router.emit_error_event(tag, time, record, Fluent::Plugin::Parser::ParserError.new("parse failed #{e.message}"))
91
- return FAILED_RESULT
98
+ if @emit_invalid_record_to_error
99
+ raise Fluent::Plugin::Parser::ParserError, "parse failed #{e.message}"
100
+ else
101
+ return FAILED_RESULT
102
+ end
92
103
  end
93
104
  end
94
105
 
@@ -211,7 +211,7 @@ module Fluent::Plugin
211
211
  options = on_message(msg, chunk_size, conn)
212
212
  if options && r = response(options)
213
213
  log.trace "sent response to fluent socket", address: conn.remote_addr, response: r
214
- conn.on_write_complete{ conn.close } if @deny_keepalive
214
+ conn.on(:write_complete) { |c| c.close } if @deny_keepalive
215
215
  send_data.call(serializer, r)
216
216
  else
217
217
  if @deny_keepalive
@@ -319,6 +319,8 @@ module Fluent::Plugin
319
319
  when /Origin/i
320
320
  @origin = v
321
321
  when /X-Forwarded-For/i
322
+ # For multiple X-Forwarded-For headers. Use first header value.
323
+ v = v.first if v.is_a?(Array)
322
324
  @remote_addr = v.split(",").first
323
325
  end
324
326
  }
@@ -373,6 +375,8 @@ module Fluent::Plugin
373
375
  params.update WEBrick::HTTPUtils.parse_form_data(@body, boundary)
374
376
  elsif @content_type =~ /^application\/json/
375
377
  params['json'] = @body
378
+ elsif @content_type =~ /^application\/msgpack/
379
+ params['msgpack'] = @body
376
380
  end
377
381
  path_info = uri.path
378
382
 
@@ -226,6 +226,11 @@ module Fluent::Plugin
226
226
  @first_warn = false
227
227
  end
228
228
 
229
+ def configure(conf)
230
+ super
231
+ @port += fluentd_worker_id
232
+ end
233
+
229
234
  def multi_workers_ready?
230
235
  true
231
236
  end
@@ -233,7 +238,7 @@ module Fluent::Plugin
233
238
  def start
234
239
  super
235
240
 
236
- log.debug "listening monitoring http server on http://#{@bind}:#{@port}/api/plugins"
241
+ log.debug "listening monitoring http server on http://#{@bind}:#{@port}/api/plugins for worker#{fluentd_worker_id}"
237
242
  @srv = WEBrick::HTTPServer.new({
238
243
  BindAddress: @bind,
239
244
  Port: @port,
@@ -273,8 +278,8 @@ module Fluent::Plugin
273
278
 
274
279
  MONITOR_INFO = {
275
280
  'output_plugin' => ->(){ is_a?(::Fluent::Plugin::Output) },
276
- 'buffer_queue_length' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil?; @buffer.queue.size },
277
- 'buffer_total_queued_size' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil?; @buffer.stage_size },
281
+ 'buffer_queue_length' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil? && @buffer.is_a?(::Fluent::Plugin::Buffer); @buffer.queue.size },
282
+ 'buffer_total_queued_size' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil? && @buffer.is_a?(::Fluent::Plugin::Buffer); @buffer.stage_size },
278
283
  'retry_count' => ->(){ instance_variable_defined?(:@num_errors) ? @num_errors : nil },
279
284
  }
280
285
 
@@ -85,15 +85,16 @@ module Fluent::Plugin
85
85
  config_param :source_hostname_key, :string, default: nil
86
86
  desc 'The field name of source address of sender.'
87
87
  config_param :source_address_key, :string, default: nil
88
-
89
88
  desc 'The field name of the priority.'
90
89
  config_param :priority_key, :string, default: nil
91
90
  desc 'The field name of the facility.'
92
91
  config_param :facility_key, :string, default: nil
93
92
 
94
- config_param :blocking_timeout, :time, default: 0.5
93
+ desc "The max bytes of message"
95
94
  config_param :message_length_limit, :size, default: 2048
96
95
 
96
+ config_param :blocking_timeout, :time, default: 0.5
97
+
97
98
  config_section :parse do
98
99
  config_set_default :@type, DEFAULT_PARSER
99
100
  config_param :with_priority, :bool, default: true
@@ -42,6 +42,7 @@ module Fluent::Plugin
42
42
  @tails = {}
43
43
  @pf_file = nil
44
44
  @pf = nil
45
+ @ignore_list = []
45
46
  end
46
47
 
47
48
  desc 'The paths to read. Multiple paths can be specified, separated by comma.'
@@ -79,6 +80,10 @@ module Fluent::Plugin
79
80
  config_param :open_on_every_update, :bool, default: false
80
81
  desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
81
82
  config_param :limit_recently_modified, :time, default: nil
83
+ desc 'Enable the option to skip the refresh of watching list on startup.'
84
+ config_param :skip_refresh_on_startup, :bool, default: false
85
+ desc 'Ignore repeated permission error logs'
86
+ config_param :ignore_repeated_permission_error, :bool, default: false
82
87
 
83
88
  attr_reader :paths
84
89
 
@@ -160,7 +165,7 @@ module Fluent::Plugin
160
165
  @pf = PositionFile.parse(@pf_file)
161
166
  end
162
167
 
163
- refresh_watchers
168
+ refresh_watchers unless @skip_refresh_on_startup
164
169
  timer_execute(:in_tail_refresh_watchers, @refresh_interval, &method(:refresh_watchers))
165
170
  end
166
171
 
@@ -187,14 +192,20 @@ module Fluent::Plugin
187
192
  path = date.strftime(path)
188
193
  if path.include?('*')
189
194
  paths += Dir.glob(path).select { |p|
190
- if File.readable?(p) && !File.directory?(p)
195
+ is_file = !File.directory?(p)
196
+ if File.readable?(p) && is_file
191
197
  if @limit_recently_modified && File.mtime(p) < (date - @limit_recently_modified)
192
198
  false
193
199
  else
194
200
  true
195
201
  end
196
202
  else
197
- log.warn "#{p} unreadable. It is excluded and would be examined next time."
203
+ if is_file
204
+ unless @ignore_list.include?(path)
205
+ log.warn "#{p} unreadable. It is excluded and would be examined next time."
206
+ @ignore_list << path if @ignore_repeated_permission_error
207
+ end
208
+ end
198
209
  false
199
210
  end
200
211
  }
@@ -34,7 +34,10 @@ module Fluent::Plugin
34
34
  desc "The field name of the client's hostname."
35
35
  config_param :source_hostname_key, :string, default: nil
36
36
 
37
- config_param :body_size_limit, :size, default: 4096
37
+ desc "Deprecated parameter. Use message_length_limit instead"
38
+ config_param :body_size_limit, :size, default: nil, deprecated: "use message_length_limit instead."
39
+ desc "The max bytes of message"
40
+ config_param :message_length_limit, :size, default: 4096
38
41
 
39
42
  config_param :blocking_timeout, :time, default: 0.5
40
43
 
@@ -43,6 +46,7 @@ module Fluent::Plugin
43
46
  super
44
47
  @_event_loop_blocking_timeout = @blocking_timeout
45
48
  @source_hostname_key ||= @source_host_key if @source_host_key
49
+ @message_length_limit = @body_size_limit if @body_size_limit
46
50
 
47
51
  @parser = parser_create
48
52
  end
@@ -55,7 +59,7 @@ module Fluent::Plugin
55
59
  super
56
60
 
57
61
  log.info "listening udp socket", bind: @bind, port: @port
58
- server_create(:in_udp_server, @port, proto: :udp, bind: @bind, max_bytes: @body_size_limit) do |data, sock|
62
+ server_create(:in_udp_server, @port, proto: :udp, bind: @bind, max_bytes: @message_length_limit) do |data, sock|
59
63
  data.chomp!
60
64
  begin
61
65
  @parser.parse(data) do |time, record|