fluentd 1.9.3 → 1.11.1

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/issue-auto-closer.yml +12 -0
  3. data/CHANGELOG.md +182 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/README.md +4 -0
  6. data/docs/SECURITY_AUDIT.pdf +0 -0
  7. data/lib/fluent/command/debug.rb +1 -0
  8. data/lib/fluent/command/fluentd.rb +25 -1
  9. data/lib/fluent/config.rb +1 -0
  10. data/lib/fluent/daemonizer.rb +88 -0
  11. data/lib/fluent/log.rb +45 -6
  12. data/lib/fluent/match.rb +1 -1
  13. data/lib/fluent/msgpack_factory.rb +13 -6
  14. data/lib/fluent/plugin/buffer.rb +2 -2
  15. data/lib/fluent/plugin/in_dummy.rb +3 -3
  16. data/lib/fluent/plugin/in_forward.rb +2 -2
  17. data/lib/fluent/plugin/in_gc_stat.rb +16 -0
  18. data/lib/fluent/plugin/in_http.rb +146 -75
  19. data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
  20. data/lib/fluent/plugin/in_syslog.rb +4 -4
  21. data/lib/fluent/plugin/in_tail.rb +40 -31
  22. data/lib/fluent/plugin/in_tail/position_file.rb +23 -6
  23. data/lib/fluent/plugin/in_unix.rb +77 -77
  24. data/lib/fluent/plugin/out_copy.rb +1 -1
  25. data/lib/fluent/plugin/out_file.rb +1 -1
  26. data/lib/fluent/plugin/out_forward.rb +25 -22
  27. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +4 -0
  28. data/lib/fluent/plugin/out_forward/load_balancer.rb +1 -1
  29. data/lib/fluent/plugin/out_http.rb +15 -2
  30. data/lib/fluent/plugin/parser_multiline.rb +1 -1
  31. data/lib/fluent/plugin/parser_syslog.rb +303 -62
  32. data/lib/fluent/plugin/sd_file.rb +1 -0
  33. data/lib/fluent/plugin/sd_srv.rb +135 -0
  34. data/lib/fluent/plugin_helper/cert_option.rb +15 -2
  35. data/lib/fluent/plugin_helper/child_process.rb +3 -2
  36. data/lib/fluent/plugin_helper/record_accessor.rb +14 -0
  37. data/lib/fluent/plugin_helper/server.rb +3 -1
  38. data/lib/fluent/plugin_helper/service_discovery.rb +7 -0
  39. data/lib/fluent/plugin_helper/service_discovery/manager.rb +8 -0
  40. data/lib/fluent/plugin_helper/socket.rb +20 -2
  41. data/lib/fluent/plugin_helper/socket_option.rb +21 -3
  42. data/lib/fluent/supervisor.rb +21 -9
  43. data/lib/fluent/system_config.rb +2 -1
  44. data/lib/fluent/test/filter_test.rb +2 -2
  45. data/lib/fluent/test/output_test.rb +3 -3
  46. data/lib/fluent/version.rb +1 -1
  47. data/test/command/test_fluentd.rb +71 -12
  48. data/test/config/test_system_config.rb +2 -0
  49. data/test/helper.rb +2 -2
  50. data/test/plugin/in_tail/test_fifo.rb +121 -0
  51. data/test/plugin/in_tail/test_io_handler.rb +132 -0
  52. data/test/plugin/in_tail/test_position_file.rb +25 -1
  53. data/test/plugin/out_forward/test_handshake_protocol.rb +10 -1
  54. data/test/plugin/out_forward/test_load_balancer.rb +46 -0
  55. data/test/plugin/test_buf_file.rb +3 -1
  56. data/test/plugin/test_buffer.rb +20 -0
  57. data/test/plugin/test_compressable.rb +7 -4
  58. data/test/plugin/test_in_dummy.rb +12 -14
  59. data/test/plugin/test_in_forward.rb +2 -2
  60. data/test/plugin/test_in_gc_stat.rb +24 -1
  61. data/test/plugin/test_in_http.rb +57 -0
  62. data/test/plugin/test_in_syslog.rb +16 -1
  63. data/test/plugin/test_in_tail.rb +43 -20
  64. data/test/plugin/test_in_unix.rb +128 -73
  65. data/test/plugin/test_out_forward.rb +39 -3
  66. data/test/plugin/test_out_http.rb +38 -0
  67. data/test/plugin/test_out_null.rb +1 -1
  68. data/test/plugin/test_output_as_buffered_retries.rb +12 -4
  69. data/test/plugin/test_output_as_buffered_secondary.rb +9 -1
  70. data/test/plugin/test_parser_syslog.rb +106 -46
  71. data/test/plugin/test_sd_file.rb +17 -0
  72. data/test/plugin/test_sd_srv.rb +230 -0
  73. data/test/plugin_helper/data/cert/cert-with-CRLF.pem +19 -0
  74. data/test/plugin_helper/data/cert/cert_chains/ca-cert-key.pem +27 -0
  75. data/test/plugin_helper/data/cert/cert_chains/ca-cert.pem +20 -0
  76. data/test/plugin_helper/data/cert/cert_chains/cert-key.pem +27 -0
  77. data/test/plugin_helper/data/cert/cert_chains/cert.pem +40 -0
  78. data/test/plugin_helper/data/cert/generate_cert.rb +38 -0
  79. data/test/plugin_helper/http_server/test_app.rb +1 -1
  80. data/test/plugin_helper/http_server/test_route.rb +1 -1
  81. data/test/plugin_helper/test_cert_option.rb +2 -0
  82. data/test/plugin_helper/test_child_process.rb +20 -3
  83. data/test/plugin_helper/test_http_server_helper.rb +2 -2
  84. data/test/plugin_helper/test_record_accessor.rb +41 -0
  85. data/test/plugin_helper/test_server.rb +1 -1
  86. data/test/plugin_helper/test_service_discovery.rb +37 -4
  87. data/test/plugin_helper/test_socket.rb +131 -0
  88. data/test/test_daemonizer.rb +91 -0
  89. data/test/test_log.rb +44 -0
  90. data/test/test_msgpack_factory.rb +18 -0
  91. metadata +28 -2
@@ -61,7 +61,7 @@ module Fluent::Plugin
61
61
  output.emit_events(tag, @copy_proc ? @copy_proc.call(es) : es)
62
62
  rescue => e
63
63
  if @ignore_errors[i]
64
- log.error "ignore emit error", error: e
64
+ log.error "ignore emit error in #{output.plugin_id}", error: e
65
65
  else
66
66
  raise e
67
67
  end
@@ -155,7 +155,7 @@ module Fluent::Plugin
155
155
  dummy_record_keys = get_placeholders_keys(@path_template) || ['message']
156
156
  dummy_record = Hash[dummy_record_keys.zip(['data'] * dummy_record_keys.size)]
157
157
 
158
- test_chunk1 = chunk_for_test(dummy_tag, Fluent::Engine.now, dummy_record)
158
+ test_chunk1 = chunk_for_test(dummy_tag, Fluent::EventTime.now, dummy_record)
159
159
  test_path = extract_placeholders(@path_template, test_chunk1)
160
160
  unless ::Fluent::FileUtil.writable_p?(test_path)
161
161
  raise Fluent::ConfigError, "out_file: `#{test_path}` is not writable"
@@ -324,7 +324,7 @@ module Fluent::Plugin
324
324
  end
325
325
  end
326
326
 
327
- if @keepalive && @keepalive_timeout
327
+ if @keepalive
328
328
  timer_execute(:out_forward_keep_alived_socket_watcher, @keep_alive_watcher_interval, &method(:on_purge_obsolete_socks))
329
329
  end
330
330
  end
@@ -382,8 +382,11 @@ module Fluent::Plugin
382
382
  cert_logical_store_name: @tls_cert_logical_store_name,
383
383
  cert_use_enterprise_store: @tls_cert_use_enterprise_store,
384
384
 
385
- # Enabling SO_LINGER causes data loss on Windows
386
- # https://github.com/fluent/fluentd/issues/1968
385
+ # Enabling SO_LINGER causes tcp port exhaustion on Windows.
386
+ # This is because dynamic ports are only 16384 (from 49152 to 65535) and
387
+ # expiring SO_LINGER enabled ports should wait 4 minutes
388
+ # where set by TcpTimeDelay. Its default value is 4 minutes.
389
+ # So, we should disable SO_LINGER on Windows to prevent flood of waiting ports.
387
390
  linger_timeout: Fluent.windows? ? nil : @send_timeout,
388
391
  send_timeout: @send_timeout,
389
392
  recv_timeout: @ack_response_timeout,
@@ -510,7 +513,7 @@ module Fluent::Plugin
510
513
 
511
514
  class Node
512
515
  extend Forwardable
513
- def_delegators :@server, :discovery_id, :host, :port, :name, :weight, :standby, :username, :password, :shared_key
516
+ def_delegators :@server, :discovery_id, :host, :port, :name, :weight, :standby
514
517
 
515
518
  # @param connection_manager [Fluent::Plugin::ForwardOutput::ConnectionManager]
516
519
  # @param ack_handler [Fluent::Plugin::ForwardOutput::AckHandler]
@@ -542,8 +545,8 @@ module Fluent::Plugin
542
545
  log: @log,
543
546
  hostname: sender.security && sender.security.self_hostname,
544
547
  shared_key: server.shared_key || (sender.security && sender.security.shared_key) || '',
545
- password: server.password,
546
- username: server.username,
548
+ password: server.password || '',
549
+ username: server.username || '',
547
550
  )
548
551
 
549
552
  @unpacker = Fluent::MessagePackFactory.msgpack_unpacker
@@ -580,12 +583,7 @@ module Fluent::Plugin
580
583
 
581
584
  def verify_connection
582
585
  connect do |sock, ri|
583
- if ri.state != :established
584
- establish_connection(sock, ri)
585
- if ri.state != :established
586
- raise "Failed to establish connection to #{@host}:#{@port}"
587
- end
588
- end
586
+ ensure_established_connection(sock, ri)
589
587
  end
590
588
  end
591
589
 
@@ -654,14 +652,7 @@ module Fluent::Plugin
654
652
  def send_data(tag, chunk)
655
653
  ack = @ack_handler && @ack_handler.create_ack(chunk.unique_id, self)
656
654
  connect(nil, ack: ack) do |sock, ri|
657
- if ri.state != :established
658
- establish_connection(sock, ri)
659
-
660
- if ri.state != :established
661
- raise ConnectionClosedError, "failed to establish connection with node #{@name}"
662
- end
663
- end
664
-
655
+ ensure_established_connection(sock, ri)
665
656
  send_data_actual(sock, tag, chunk)
666
657
  end
667
658
 
@@ -686,7 +677,9 @@ module Fluent::Plugin
686
677
 
687
678
  case @sender.heartbeat_type
688
679
  when :transport
689
- connect(dest_addr) do |_ri, _sock|
680
+ connect(dest_addr) do |sock, ri|
681
+ ensure_established_connection(sock, ri)
682
+
690
683
  ## don't send any data to not cause a compatibility problem
691
684
  # sock.write FORWARD_TCP_HEARTBEAT_DATA
692
685
 
@@ -716,7 +709,7 @@ module Fluent::Plugin
716
709
  @resolved_host ||= resolve_dns!
717
710
 
718
711
  else
719
- now = Fluent::Engine.now
712
+ now = Fluent::EventTime.now
720
713
  rh = @resolved_host
721
714
  if !rh || now - @resolved_time >= @sender.expire_dns_cache
722
715
  rh = @resolved_host = resolve_dns!
@@ -778,6 +771,16 @@ module Fluent::Plugin
778
771
 
779
772
  private
780
773
 
774
+ def ensure_established_connection(sock, request_info)
775
+ if request_info.state != :established
776
+ establish_connection(sock, request_info)
777
+
778
+ if request_info.state != :established
779
+ raise ConnectionClosedError, "failed to establish connection with node #{@name}"
780
+ end
781
+ end
782
+ end
783
+
781
784
  def connect(host = nil, ack: false, &block)
782
785
  @connection_manager.connect(host: host || resolved_host, port: port, hostname: @hostname, ack: ack, &block)
783
786
  end
@@ -105,6 +105,10 @@ module Fluent::Plugin
105
105
  .hexdigest
106
106
  ping = ['PING', @hostname, @shared_key_salt, shared_key_hexdigest]
107
107
  if !ri.auth.empty?
108
+ if @username.nil? || @password.nil?
109
+ raise PingpongError, "username and password are required"
110
+ end
111
+
108
112
  password_hexdigest = Digest::SHA512.new.update(ri.auth).update(@username).update(@password).hexdigest
109
113
  ping.push(@username, password_hexdigest)
110
114
  else
@@ -56,7 +56,7 @@ module Fluent::Plugin
56
56
  end
57
57
 
58
58
  def rebuild_weight_array(nodes)
59
- standby_nodes, regular_nodes = nodes.partition {|n|
59
+ standby_nodes, regular_nodes = nodes.select { |e| e.weight > 0 }.partition {|n|
60
60
  n.standby?
61
61
  }
62
62
 
@@ -37,6 +37,8 @@ module Fluent::Plugin
37
37
  config_param :proxy, :string, default: ENV['HTTP_PROXY'] || ENV['http_proxy']
38
38
  desc 'Content-Type for HTTP request'
39
39
  config_param :content_type, :string, default: nil
40
+ desc 'JSON array data format for HTTP request body'
41
+ config_param :json_array, :bool, default: false
40
42
  desc 'Additional headers for HTTP request'
41
43
  config_param :headers, :hash, default: nil
42
44
 
@@ -100,6 +102,13 @@ module Fluent::Plugin
100
102
  @proxy_uri = URI.parse(@proxy) if @proxy
101
103
  @formatter = formatter_create
102
104
  @content_type = setup_content_type unless @content_type
105
+
106
+ if @json_array
107
+ if @formatter_configs.first[:@type] != "json"
108
+ raise Fluent::ConfigError, "json_array option could be used with json formatter only"
109
+ end
110
+ define_singleton_method(:format, method(:format_json_array))
111
+ end
103
112
  end
104
113
 
105
114
  def multi_workers_ready?
@@ -114,6 +123,10 @@ module Fluent::Plugin
114
123
  @formatter.format(tag, time, record)
115
124
  end
116
125
 
126
+ def format_json_array(tag, time, record)
127
+ @formatter.format(tag, time, record) << ","
128
+ end
129
+
117
130
  def write(chunk)
118
131
  uri = parse_endpoint(chunk)
119
132
  req = create_request(chunk, uri)
@@ -128,7 +141,7 @@ module Fluent::Plugin
128
141
  def setup_content_type
129
142
  case @formatter_configs.first[:@type]
130
143
  when 'json'
131
- 'application/x-ndjson'
144
+ @json_array ? 'application/json' : 'application/x-ndjson'
132
145
  when 'csv'
133
146
  'text/csv'
134
147
  when 'tsv', 'ltsv'
@@ -202,7 +215,7 @@ module Fluent::Plugin
202
215
  req.basic_auth(@auth.username, @auth.password)
203
216
  end
204
217
  set_headers(req)
205
- req.body = chunk.read
218
+ req.body = @json_array ? "[#{chunk.read.chop!}]" : chunk.read
206
219
  req
207
220
  end
208
221
 
@@ -104,7 +104,7 @@ module Fluent
104
104
  end
105
105
 
106
106
  def firstline?(text)
107
- @firstline_regex.match(text)
107
+ @firstline_regex.match?(text)
108
108
  end
109
109
 
110
110
  private
@@ -23,6 +23,7 @@ module Fluent
23
23
  class SyslogParser < Parser
24
24
  Plugin.register_parser('syslog', self)
25
25
 
26
+ # TODO: Remove them since these regexps are no longer needed. but keep them for compatibility for now
26
27
  # From existence TextParser pattern
27
28
  REGEXP = /^(?<time>[^ ]*\s*[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[^ :\[]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/
28
29
  # From in_syslog default pattern
@@ -36,7 +37,16 @@ module Fluent
36
37
  REGEXP_RFC5424_WITH_PRI = Regexp.new(<<~'EOS'.chomp % REGEXP_RFC5424, Regexp::MULTILINE)
37
38
  \A<(?<pri>[0-9]{1,3})\>[1-9]\d{0,2} %s\z
38
39
  EOS
39
- REGEXP_DETECT_RFC5424 = /^\<.*\>[1-9]\d{0,2}/
40
+
41
+ REGEXP_DETECT_RFC5424 = /^\<[0-9]{1,3}\>[1-9]\d{0,2}/
42
+
43
+ RFC3164_WITHOUT_TIME_AND_PRI_REGEXP = /(?<host>[^ ]*) (?<ident>[^ :\[]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/
44
+ RFC3164_CAPTURES = RFC3164_WITHOUT_TIME_AND_PRI_REGEXP.names.freeze
45
+ RFC3164_PRI_REGEXP = /^<(?<pri>[0-9]{1,3})>/
46
+
47
+ RFC5424_WITHOUT_TIME_AND_PRI_REGEXP = /(?<host>[!-~]{1,255}) (?<ident>[!-~]{1,48}) (?<pid>[!-~]{1,128}) (?<msgid>[!-~]{1,32}) (?<extradata>(?:\-|(?:\[.*?(?<!\\)\])+))(?: (?<message>.+))?\z/m
48
+ RFC5424_CAPTURES = RFC5424_WITHOUT_TIME_AND_PRI_REGEXP.names.freeze
49
+ RFC5424_PRI_REGEXP = /^<(?<pri>\d{1,3})>\d\d{0,2}\s/
40
50
 
41
51
  config_set_default :time_format, "%b %d %H:%M:%S"
42
52
  desc 'If the incoming logs have priority prefix, e.g. <9>, set true'
@@ -53,46 +63,80 @@ module Fluent
53
63
  def initialize
54
64
  super
55
65
  @mutex = Mutex.new
66
+ @regexp = nil
67
+ @regexp3164 = nil
68
+ @regexp5424 = nil
69
+ @regexp_parser = nil
70
+ @time_parser_rfc3164 = nil
71
+ @time_parser_rfc5424 = nil
72
+ @space_count_rfc3164 = nil
73
+ @space_count_rfc5424 = nil
74
+ @skip_space_count_rfc3164 = false
75
+ @skip_space_count_rfc5424 = false
76
+ @time_parser_rfc5424_without_subseconds = nil
56
77
  end
57
78
 
58
79
  def configure(conf)
59
80
  super
60
81
 
61
- @time_parser_rfc3164 = @time_parser_rfc5424 = nil
62
- @time_parser_rfc5424_without_subseconds = nil
63
- @support_rfc5424_without_subseconds = false
64
82
  @regexp_parser = @parser_type == :regexp
65
83
  @regexp = case @message_format
66
84
  when :rfc3164
67
85
  if @regexp_parser
68
86
  class << self
69
- alias_method :parse, :parse_plain
87
+ alias_method :parse, :parse_rfc3164_regex
70
88
  end
71
89
  else
72
90
  class << self
73
91
  alias_method :parse, :parse_rfc3164
74
92
  end
75
93
  end
76
- @with_priority ? REGEXP_WITH_PRI : REGEXP
94
+ setup_time_parser_3164(@time_format)
95
+ RFC3164_WITHOUT_TIME_AND_PRI_REGEXP
77
96
  when :rfc5424
78
- class << self
79
- alias_method :parse, :parse_plain
97
+ if @regexp_parser
98
+ class << self
99
+ alias_method :parse, :parse_rfc5424_regex
100
+ end
101
+ else
102
+ class << self
103
+ alias_method :parse, :parse_rfc5424
104
+ end
80
105
  end
81
106
  @time_format = @rfc5424_time_format unless conf.has_key?('time_format')
82
- @support_rfc5424_without_subseconds = true
83
- @with_priority ? REGEXP_RFC5424_WITH_PRI : REGEXP_RFC5424_NO_PRI
107
+ setup_time_parser_5424(@time_format)
108
+ RFC5424_WITHOUT_TIME_AND_PRI_REGEXP
84
109
  when :auto
85
110
  class << self
86
111
  alias_method :parse, :parse_auto
87
112
  end
88
- @time_parser_rfc3164 = time_parser_create(format: @time_format)
89
- @time_parser_rfc5424 = time_parser_create(format: @rfc5424_time_format)
113
+ setup_time_parser_3164(@time_format)
114
+ setup_time_parser_5424(@rfc5424_time_format)
90
115
  nil
91
116
  end
92
- @time_parser = time_parser_create
117
+
118
+ if @regexp_parser
119
+ @regexp3164 = RFC3164_WITHOUT_TIME_AND_PRI_REGEXP
120
+ @regexp5424 = RFC5424_WITHOUT_TIME_AND_PRI_REGEXP
121
+ end
122
+ end
123
+
124
+ def setup_time_parser_3164(time_fmt)
125
+ @time_parser_rfc3164 = time_parser_create(format: time_fmt)
126
+ if ['%b %d %H:%M:%S', '%b %d %H:%M:%S.%N'].include?(time_fmt)
127
+ @skip_space_count_rfc3164 = true
128
+ end
129
+ @space_count_rfc3164 = time_fmt.squeeze(' ').count(' ') + 1
130
+ end
131
+
132
+ def setup_time_parser_5424(time_fmt)
133
+ @time_parser_rfc5424 = time_parser_create(format: time_fmt)
93
134
  @time_parser_rfc5424_without_subseconds = time_parser_create(format: "%Y-%m-%dT%H:%M:%S%z")
135
+ @skip_space_count_rfc5424 = time_fmt.count(' ').zero?
136
+ @space_count_rfc5424 = time_fmt.squeeze(' ').count(' ') + 1
94
137
  end
95
138
 
139
+ # this method is for tests
96
140
  def patterns
97
141
  {'format' => @regexp, 'time_format' => @time_format}
98
142
  end
@@ -102,52 +146,113 @@ module Fluent
102
146
  end
103
147
 
104
148
  def parse_auto(text, &block)
105
- if REGEXP_DETECT_RFC5424.match(text)
106
- @regexp = @with_priority ? REGEXP_RFC5424_WITH_PRI : REGEXP_RFC5424_NO_PRI
107
- @time_parser = @time_parser_rfc5424
108
- @support_rfc5424_without_subseconds = true
109
- parse_plain(text, &block)
149
+ if REGEXP_DETECT_RFC5424.match?(text)
150
+ if @regexp_parser
151
+ parse_rfc5424_regex(text, &block)
152
+ else
153
+ parse_rfc5424(text, &block)
154
+ end
110
155
  else
111
- @regexp = @with_priority ? REGEXP_WITH_PRI : REGEXP
112
- @time_parser = @time_parser_rfc3164
113
156
  if @regexp_parser
114
- parse_plain(text, &block)
157
+ parse_rfc3164_regex(text, &block)
115
158
  else
116
159
  parse_rfc3164(text, &block)
117
160
  end
118
161
  end
119
162
  end
120
163
 
121
- def parse_plain(text, &block)
122
- m = @regexp.match(text)
123
- unless m
124
- yield nil, nil
125
- return
164
+ SPLIT_CHAR = ' '.freeze
165
+
166
+ def parse_rfc3164_regex(text, &block)
167
+ idx = 0
168
+ record = {}
169
+
170
+ if @with_priority
171
+ if RFC3164_PRI_REGEXP.match?(text)
172
+ v = text.index('>')
173
+ record['pri'] = text[1..v].to_i # trim `<` and ``>
174
+ idx = v + 1
175
+ else
176
+ yield(nil, nil)
177
+ return
178
+ end
179
+ end
180
+
181
+ i = idx - 1
182
+ sq = false
183
+ @space_count_rfc3164.times do
184
+ while text[i + 1] == SPLIT_CHAR
185
+ sq = true
186
+ i += 1
187
+ end
188
+
189
+ i = text.index(SPLIT_CHAR, i + 1)
126
190
  end
127
191
 
128
- time = nil
192
+ time_str = sq ? text.slice(idx, i - idx).squeeze(SPLIT_CHAR) : text.slice(idx, i - idx)
193
+ time = @mutex.synchronize { @time_parser_rfc3164.parse(time_str) }
194
+ if @keep_time_key
195
+ record['time'] = time_str
196
+ end
197
+
198
+ parse_plain(@regexp3164, time, text, i + 1, record, RFC3164_CAPTURES, &block)
199
+ end
200
+
201
+ def parse_rfc5424_regex(text, &block)
202
+ idx = 0
129
203
  record = {}
130
204
 
131
- m.names.each { |name|
132
- if value = m[name]
205
+ if @with_priority
206
+ if (m = RFC5424_PRI_REGEXP.match(text))
207
+ record['pri'] = m['pri'].to_i
208
+ idx = m.end(0)
209
+ else
210
+ yield(nil, nil)
211
+ return
212
+ end
213
+ end
214
+
215
+ i = idx - 1
216
+ sq = false
217
+ @space_count_rfc5424.times {
218
+ while text[i + 1] == SPLIT_CHAR
219
+ sq = true
220
+ i += 1
221
+ end
222
+
223
+ i = text.index(SPLIT_CHAR, i + 1)
224
+ }
225
+
226
+ time_str = sq ? text.slice(idx, i - idx).squeeze(SPLIT_CHAR) : text.slice(idx, i - idx)
227
+ time = @mutex.synchronize do
228
+ begin
229
+ @time_parser_rfc5424.parse(time_str)
230
+ rescue Fluent::TimeParser::TimeParseError => e
231
+ log.trace(e)
232
+ @time_parser_rfc5424_without_subseconds.parse(time_str)
233
+ end
234
+ end
235
+
236
+ if @keep_time_key
237
+ record['time'] = time_str
238
+ end
239
+ parse_plain(@regexp5424, time, text, i + 1, record, RFC5424_CAPTURES, &block)
240
+ end
241
+
242
+ # @param time [EventTime]
243
+ # @param idx [Integer] note: this argument is needed to avoid string creation
244
+ # @param record [Hash]
245
+ # @param capture_list [Array] for performance
246
+ def parse_plain(re, time, text, idx, record, capture_list, &block)
247
+ m = re.match(text, idx)
248
+ if m.nil?
249
+ yield nil, nil
250
+ return
251
+ end
252
+
253
+ capture_list.each { |name|
254
+ if value = (m[name] rescue nil)
133
255
  case name
134
- when "pri"
135
- record['pri'] = value.to_i
136
- when "time"
137
- time = @mutex.synchronize do
138
- time_str = value.squeeze(' ')
139
- begin
140
- @time_parser.parse(time_str)
141
- rescue Fluent::TimeParser::TimeParseError => e
142
- if @support_rfc5424_without_subseconds
143
- log.trace(e)
144
- @time_parser_rfc5424_without_subseconds.parse(time_str)
145
- else
146
- raise
147
- end
148
- end
149
- end
150
- record[name] = value if @keep_time_key
151
256
  when "message"
152
257
  value.chomp!
153
258
  record[name] = value
@@ -164,8 +269,6 @@ module Fluent
164
269
  yield time, record
165
270
  end
166
271
 
167
- SPLIT_CHAR = ' '.freeze
168
-
169
272
  def parse_rfc3164(text, &block)
170
273
  pri = nil
171
274
  cursor = 0
@@ -184,20 +287,35 @@ module Fluent
184
287
  end
185
288
  end
186
289
 
187
- # header part
188
- time_size = 15 # skip Mmm dd hh:mm:ss
189
- time_end = text[cursor + time_size]
190
- if time_end == SPLIT_CHAR
191
- time_str = text.slice(cursor, time_size)
192
- cursor += 16 # time + ' '
193
- elsif time_end == '.'.freeze
194
- # support subsecond time
195
- i = text.index(SPLIT_CHAR, time_size)
196
- time_str = text.slice(cursor, i - cursor)
197
- cursor = i + 1
290
+ if @skip_space_count_rfc3164
291
+ # header part
292
+ time_size = 15 # skip Mmm dd hh:mm:ss
293
+ time_end = text[cursor + time_size]
294
+ if time_end == SPLIT_CHAR
295
+ time_str = text.slice(cursor, time_size)
296
+ cursor += 16 # time + ' '
297
+ elsif time_end == '.'.freeze
298
+ # support subsecond time
299
+ i = text.index(SPLIT_CHAR, time_size)
300
+ time_str = text.slice(cursor, i - cursor)
301
+ cursor = i + 1
302
+ else
303
+ yield nil, nil
304
+ return
305
+ end
198
306
  else
199
- yield nil, nil
200
- return
307
+ i = cursor - 1
308
+ sq = false
309
+ @space_count_rfc3164.times do
310
+ while text[i + 1] == SPLIT_CHAR
311
+ sq = true
312
+ i += 1
313
+ end
314
+ i = text.index(SPLIT_CHAR, i + 1)
315
+ end
316
+
317
+ time_str = sq ? text.slice(idx, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
318
+ cursor = i + 1
201
319
  end
202
320
 
203
321
  i = text.index(SPLIT_CHAR, cursor)
@@ -245,7 +363,130 @@ module Fluent
245
363
  msg.chomp!
246
364
  record['message'] = msg
247
365
 
248
- time = @time_parser.parse(time_str)
366
+ time = @time_parser_rfc3164.parse(time_str)
367
+ record['time'] = time_str if @keep_time_key
368
+
369
+ yield time, record
370
+ end
371
+
372
+ NILVALUE = '-'.freeze
373
+
374
+ def parse_rfc5424(text, &block)
375
+ pri = nil
376
+ cursor = 0
377
+ if @with_priority
378
+ if text.start_with?('<'.freeze)
379
+ i = text.index('>'.freeze, 1)
380
+ if i < 2
381
+ yield nil, nil
382
+ return
383
+ end
384
+ pri = text.slice(1, i - 1).to_i
385
+ i = text.index(SPLIT_CHAR, i)
386
+ cursor = i + 1
387
+ else
388
+ yield nil, nil
389
+ return
390
+ end
391
+ end
392
+
393
+ # timestamp part
394
+ if @skip_space_count_rfc5424
395
+ i = text.index(SPLIT_CHAR, cursor)
396
+ time_str = text.slice(cursor, i - cursor)
397
+ cursor = i + 1
398
+ else
399
+ i = cursor - 1
400
+ sq = false
401
+ @space_count_rfc5424.times do
402
+ while text[i + 1] == SPLIT_CHAR
403
+ sq = true
404
+ i += 1
405
+ end
406
+ i = text.index(SPLIT_CHAR, i + 1)
407
+ end
408
+
409
+ time_str = sq ? text.slice(idx, i - cursor).squeeze(SPLIT_CHAR) : text.slice(cursor, i - cursor)
410
+ cursor = i + 1
411
+ end
412
+
413
+ # Repeat same code for the performance
414
+
415
+ # host part
416
+ i = text.index(SPLIT_CHAR, cursor)
417
+ unless i
418
+ yield nil, nil
419
+ return
420
+ end
421
+ slice_size = i - cursor
422
+ host = text.slice(cursor, slice_size)
423
+ cursor += slice_size + 1
424
+
425
+ # ident part
426
+ i = text.index(SPLIT_CHAR, cursor)
427
+ unless i
428
+ yield nil, nil
429
+ return
430
+ end
431
+ slice_size = i - cursor
432
+ ident = text.slice(cursor, slice_size)
433
+ cursor += slice_size + 1
434
+
435
+ # pid part
436
+ i = text.index(SPLIT_CHAR, cursor)
437
+ unless i
438
+ yield nil, nil
439
+ return
440
+ end
441
+ slice_size = i - cursor
442
+ pid = text.slice(cursor, slice_size)
443
+ cursor += slice_size + 1
444
+
445
+ # msgid part
446
+ i = text.index(SPLIT_CHAR, cursor)
447
+ unless i
448
+ yield nil, nil
449
+ return
450
+ end
451
+ slice_size = i - cursor
452
+ msgid = text.slice(cursor, slice_size)
453
+ cursor += slice_size + 1
454
+
455
+ record = {'host' => host, 'ident' => ident, 'pid' => pid, 'msgid' => msgid}
456
+ record['pri'] = pri if pri
457
+
458
+ # extradata part
459
+ ed_start = text[cursor]
460
+ if ed_start == NILVALUE
461
+ record['extradata'] = NILVALUE
462
+ cursor += 1
463
+ else
464
+ start = cursor
465
+ i = text.index('] '.freeze, cursor)
466
+ extradata = if i
467
+ diff = i + 1 - start # calculate ']' position
468
+ cursor += diff
469
+ text.slice(start, diff)
470
+ else # No message part case
471
+ cursor = text.bytesize
472
+ text.slice(start, cursor)
473
+ end
474
+ extradata.tr!("\\".freeze, ''.freeze)
475
+ record['extradata'] = extradata
476
+ end
477
+
478
+ # message part
479
+ if cursor != text.bytesize
480
+ msg = text.slice(cursor + 1, text.bytesize)
481
+ msg.chomp!
482
+ record['message'] = msg
483
+ end
484
+
485
+ time = begin
486
+ @time_parser_rfc5424.parse(time_str)
487
+ rescue Fluent::TimeParser::TimeParseError => e
488
+ @time_parser_rfc5424_without_subseconds.parse(time_str)
489
+ end
249
490
  record['time'] = time_str if @keep_time_key
250
491
 
251
492
  yield time, record