fluentd 1.13.3 → 1.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +2 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +2 -2
  4. data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +1 -0
  5. data/.github/workflows/stale-actions.yml +11 -9
  6. data/.github/workflows/test.yml +32 -0
  7. data/CHANGELOG.md +490 -10
  8. data/CONTRIBUTING.md +2 -2
  9. data/MAINTAINERS.md +7 -5
  10. data/README.md +3 -23
  11. data/Rakefile +1 -1
  12. data/SECURITY.md +14 -0
  13. data/fluentd.gemspec +7 -8
  14. data/lib/fluent/command/cat.rb +13 -3
  15. data/lib/fluent/command/ctl.rb +6 -3
  16. data/lib/fluent/command/fluentd.rb +73 -65
  17. data/lib/fluent/command/plugin_config_formatter.rb +1 -1
  18. data/lib/fluent/compat/output.rb +9 -6
  19. data/lib/fluent/config/dsl.rb +1 -1
  20. data/lib/fluent/config/error.rb +12 -0
  21. data/lib/fluent/config/literal_parser.rb +2 -2
  22. data/lib/fluent/config/parser.rb +1 -1
  23. data/lib/fluent/config/v1_parser.rb +3 -3
  24. data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
  25. data/lib/fluent/config/yaml_parser/loader.rb +108 -0
  26. data/lib/fluent/config/yaml_parser/parser.rb +166 -0
  27. data/lib/fluent/config/yaml_parser/section_builder.rb +107 -0
  28. data/lib/fluent/config/yaml_parser.rb +56 -0
  29. data/lib/fluent/config.rb +14 -1
  30. data/lib/fluent/counter/server.rb +1 -1
  31. data/lib/fluent/counter/validator.rb +3 -3
  32. data/lib/fluent/daemon.rb +2 -4
  33. data/lib/fluent/engine.rb +1 -1
  34. data/lib/fluent/env.rb +4 -0
  35. data/lib/fluent/error.rb +3 -0
  36. data/lib/fluent/event.rb +8 -4
  37. data/lib/fluent/event_router.rb +47 -2
  38. data/lib/fluent/file_wrapper.rb +137 -0
  39. data/lib/fluent/log/console_adapter.rb +66 -0
  40. data/lib/fluent/log.rb +44 -5
  41. data/lib/fluent/match.rb +1 -1
  42. data/lib/fluent/msgpack_factory.rb +6 -1
  43. data/lib/fluent/oj_options.rb +1 -2
  44. data/lib/fluent/plugin/bare_output.rb +49 -8
  45. data/lib/fluent/plugin/base.rb +26 -9
  46. data/lib/fluent/plugin/buf_file.rb +34 -5
  47. data/lib/fluent/plugin/buf_file_single.rb +32 -3
  48. data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
  49. data/lib/fluent/plugin/buffer.rb +216 -70
  50. data/lib/fluent/plugin/filter.rb +35 -1
  51. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  52. data/lib/fluent/plugin/in_forward.rb +2 -2
  53. data/lib/fluent/plugin/in_http.rb +39 -10
  54. data/lib/fluent/plugin/in_monitor_agent.rb +4 -2
  55. data/lib/fluent/plugin/in_sample.rb +1 -1
  56. data/lib/fluent/plugin/in_syslog.rb +13 -1
  57. data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
  58. data/lib/fluent/plugin/in_tail/position_file.rb +33 -33
  59. data/lib/fluent/plugin/in_tail.rb +216 -84
  60. data/lib/fluent/plugin/in_tcp.rb +47 -2
  61. data/lib/fluent/plugin/input.rb +39 -1
  62. data/lib/fluent/plugin/metrics.rb +119 -0
  63. data/lib/fluent/plugin/metrics_local.rb +96 -0
  64. data/lib/fluent/plugin/multi_output.rb +43 -6
  65. data/lib/fluent/plugin/out_copy.rb +1 -1
  66. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  67. data/lib/fluent/plugin/out_file.rb +20 -2
  68. data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
  69. data/lib/fluent/plugin/out_forward/socket_cache.rb +2 -0
  70. data/lib/fluent/plugin/out_forward.rb +17 -9
  71. data/lib/fluent/plugin/out_secondary_file.rb +39 -22
  72. data/lib/fluent/plugin/output.rb +167 -78
  73. data/lib/fluent/plugin/parser.rb +3 -4
  74. data/lib/fluent/plugin/parser_apache2.rb +1 -1
  75. data/lib/fluent/plugin/parser_json.rb +1 -1
  76. data/lib/fluent/plugin/parser_syslog.rb +1 -1
  77. data/lib/fluent/plugin/storage_local.rb +3 -5
  78. data/lib/fluent/plugin.rb +10 -1
  79. data/lib/fluent/plugin_helper/child_process.rb +3 -0
  80. data/lib/fluent/plugin_helper/event_emitter.rb +8 -1
  81. data/lib/fluent/plugin_helper/event_loop.rb +2 -2
  82. data/lib/fluent/plugin_helper/http_server/server.rb +2 -1
  83. data/lib/fluent/plugin_helper/metrics.rb +129 -0
  84. data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
  85. data/lib/fluent/plugin_helper/retry_state.rb +14 -4
  86. data/lib/fluent/plugin_helper/server.rb +35 -6
  87. data/lib/fluent/plugin_helper/service_discovery.rb +2 -2
  88. data/lib/fluent/plugin_helper/socket.rb +13 -2
  89. data/lib/fluent/plugin_helper/thread.rb +3 -3
  90. data/lib/fluent/plugin_helper.rb +1 -0
  91. data/lib/fluent/plugin_id.rb +3 -2
  92. data/lib/fluent/registry.rb +2 -1
  93. data/lib/fluent/root_agent.rb +6 -0
  94. data/lib/fluent/rpc.rb +4 -3
  95. data/lib/fluent/supervisor.rb +283 -259
  96. data/lib/fluent/system_config.rb +13 -3
  97. data/lib/fluent/test/driver/base.rb +11 -5
  98. data/lib/fluent/test/driver/filter.rb +4 -0
  99. data/lib/fluent/test/startup_shutdown.rb +6 -8
  100. data/lib/fluent/time.rb +21 -20
  101. data/lib/fluent/version.rb +1 -1
  102. data/lib/fluent/win32api.rb +38 -0
  103. data/lib/fluent/winsvc.rb +5 -8
  104. data/templates/new_gem/test/helper.rb.erb +0 -1
  105. data/test/command/test_cat.rb +31 -2
  106. data/test/command/test_ctl.rb +1 -2
  107. data/test/command/test_fluentd.rb +209 -24
  108. data/test/command/test_plugin_config_formatter.rb +0 -1
  109. data/test/compat/test_parser.rb +6 -6
  110. data/test/config/test_system_config.rb +13 -11
  111. data/test/config/test_types.rb +1 -1
  112. data/test/log/test_console_adapter.rb +110 -0
  113. data/test/plugin/in_tail/test_io_handler.rb +26 -8
  114. data/test/plugin/in_tail/test_position_file.rb +48 -59
  115. data/test/plugin/out_forward/test_ack_handler.rb +39 -0
  116. data/test/plugin/out_forward/test_socket_cache.rb +26 -1
  117. data/test/plugin/test_bare_output.rb +14 -1
  118. data/test/plugin/test_base.rb +133 -1
  119. data/test/plugin/test_buf_file.rb +62 -23
  120. data/test/plugin/test_buf_file_single.rb +65 -0
  121. data/test/plugin/test_buffer.rb +267 -3
  122. data/test/plugin/test_buffer_chunk.rb +11 -0
  123. data/test/plugin/test_filter.rb +12 -1
  124. data/test/plugin/test_filter_parser.rb +1 -1
  125. data/test/plugin/test_filter_stdout.rb +2 -2
  126. data/test/plugin/test_in_forward.rb +9 -11
  127. data/test/plugin/test_in_http.rb +65 -3
  128. data/test/plugin/test_in_monitor_agent.rb +216 -11
  129. data/test/plugin/test_in_object_space.rb +9 -3
  130. data/test/plugin/test_in_syslog.rb +35 -0
  131. data/test/plugin/test_in_tail.rb +1393 -385
  132. data/test/plugin/test_in_tcp.rb +87 -2
  133. data/test/plugin/test_in_udp.rb +28 -0
  134. data/test/plugin/test_in_unix.rb +2 -2
  135. data/test/plugin/test_input.rb +12 -1
  136. data/test/plugin/test_metrics.rb +294 -0
  137. data/test/plugin/test_metrics_local.rb +96 -0
  138. data/test/plugin/test_multi_output.rb +25 -1
  139. data/test/plugin/test_out_exec.rb +6 -4
  140. data/test/plugin/test_out_exec_filter.rb +6 -2
  141. data/test/plugin/test_out_file.rb +34 -17
  142. data/test/plugin/test_out_forward.rb +78 -77
  143. data/test/plugin/test_out_http.rb +1 -0
  144. data/test/plugin/test_out_stdout.rb +2 -2
  145. data/test/plugin/test_output.rb +297 -12
  146. data/test/plugin/test_output_as_buffered.rb +44 -44
  147. data/test/plugin/test_output_as_buffered_compress.rb +32 -18
  148. data/test/plugin/test_output_as_buffered_retries.rb +54 -7
  149. data/test/plugin/test_output_as_buffered_secondary.rb +4 -4
  150. data/test/plugin/test_parser_regexp.rb +1 -6
  151. data/test/plugin/test_parser_syslog.rb +1 -1
  152. data/test/plugin_helper/test_cert_option.rb +1 -1
  153. data/test/plugin_helper/test_child_process.rb +38 -16
  154. data/test/plugin_helper/test_event_emitter.rb +29 -0
  155. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  156. data/test/plugin_helper/test_metrics.rb +137 -0
  157. data/test/plugin_helper/test_retry_state.rb +602 -38
  158. data/test/plugin_helper/test_server.rb +78 -6
  159. data/test/plugin_helper/test_timer.rb +2 -2
  160. data/test/test_config.rb +191 -24
  161. data/test/test_event_router.rb +17 -0
  162. data/test/test_file_wrapper.rb +53 -0
  163. data/test/test_formatter.rb +24 -21
  164. data/test/test_log.rb +122 -40
  165. data/test/test_msgpack_factory.rb +32 -0
  166. data/test/test_plugin_classes.rb +102 -0
  167. data/test/test_root_agent.rb +30 -1
  168. data/test/test_supervisor.rb +477 -257
  169. data/test/test_time_parser.rb +22 -0
  170. metadata +55 -34
  171. data/.drone.yml +0 -35
  172. data/.github/workflows/issue-auto-closer.yml +0 -12
  173. data/.github/workflows/linux-test.yaml +0 -36
  174. data/.github/workflows/macos-test.yaml +0 -30
  175. data/.github/workflows/windows-test.yaml +0 -46
  176. data/.gitlab-ci.yml +0 -103
  177. data/lib/fluent/plugin/file_wrapper.rb +0 -187
  178. data/test/plugin/test_file_wrapper.rb +0 -126
  179. data/test/test_logger_initializer.rb +0 -46
@@ -74,6 +74,8 @@ module Fluent::Plugin
74
74
  config_param :blocking_timeout, :time, default: 0.5
75
75
  desc 'Set a allow list of domains that can do CORS (Cross-Origin Resource Sharing)'
76
76
  config_param :cors_allow_origins, :array, default: nil
77
+ desc 'Tells browsers whether to expose the response to frontend when the credentials mode is "include".'
78
+ config_param :cors_allow_credentials, :bool, default: false
77
79
  desc 'Respond with empty gif image of 1x1 pixel.'
78
80
  config_param :respond_with_empty_img, :bool, default: false
79
81
  desc 'Respond status code with 204.'
@@ -112,6 +114,12 @@ module Fluent::Plugin
112
114
 
113
115
  super
114
116
 
117
+ if @cors_allow_credentials
118
+ if @cors_allow_origins.nil? || @cors_allow_origins.include?('*')
119
+ raise Fluent::ConfigError, "Cannot enable cors_allow_credentials without specific origins"
120
+ end
121
+ end
122
+
115
123
  m = if @parser_configs.first['@type'] == 'in_http'
116
124
  @parser_msgpack = parser_create(usage: 'parser_in_http_msgpack', type: 'msgpack')
117
125
  @parser_msgpack.time_key = nil
@@ -279,7 +287,10 @@ module Fluent::Plugin
279
287
  private
280
288
 
281
289
  def on_server_connect(conn)
282
- handler = Handler.new(conn, @km, method(:on_request), @body_size_limit, @format_name, log, @cors_allow_origins, @add_query_params)
290
+ handler = Handler.new(conn, @km, method(:on_request),
291
+ @body_size_limit, @format_name, log,
292
+ @cors_allow_origins, @cors_allow_credentials,
293
+ @add_query_params)
283
294
 
284
295
  conn.on(:data) do |data|
285
296
  handler.on_read(data)
@@ -303,8 +314,16 @@ module Fluent::Plugin
303
314
  @parser_json.parse(js) do |_time, record|
304
315
  return nil, record
305
316
  end
317
+ elsif ndjson = params['ndjson']
318
+ events = []
319
+ ndjson.split(/\r?\n/).each do |js|
320
+ @parser_json.parse(js) do |_time, record|
321
+ events.push(record)
322
+ end
323
+ end
324
+ return nil, events
306
325
  else
307
- raise "'json' or 'msgpack' parameter is required"
326
+ raise "'json', 'ndjson' or 'msgpack' parameter is required"
308
327
  end
309
328
  end
310
329
 
@@ -356,7 +375,8 @@ module Fluent::Plugin
356
375
  class Handler
357
376
  attr_reader :content_type
358
377
 
359
- def initialize(io, km, callback, body_size_limit, format_name, log, cors_allow_origins, add_query_params)
378
+ def initialize(io, km, callback, body_size_limit, format_name, log,
379
+ cors_allow_origins, cors_allow_credentials, add_query_params)
360
380
  @io = io
361
381
  @km = km
362
382
  @callback = callback
@@ -365,6 +385,7 @@ module Fluent::Plugin
365
385
  @format_name = format_name
366
386
  @log = log
367
387
  @cors_allow_origins = cors_allow_origins
388
+ @cors_allow_credentials = cors_allow_credentials
368
389
  @idle = 0
369
390
  @add_query_params = add_query_params
370
391
  @km.add(self)
@@ -407,7 +428,7 @@ module Fluent::Plugin
407
428
  @content_type = ""
408
429
  @content_encoding = ""
409
430
  headers.each_pair {|k,v|
410
- @env["HTTP_#{k.gsub('-','_').upcase}"] = v
431
+ @env["HTTP_#{k.tr('-','_').upcase}"] = v
411
432
  case k
412
433
  when /\AExpect\z/i
413
434
  expect = v
@@ -418,9 +439,9 @@ module Fluent::Plugin
418
439
  when /\AContent-Encoding\Z/i
419
440
  @content_encoding = v
420
441
  when /\AConnection\Z/i
421
- if v =~ /close/i
442
+ if /close/i.match?(v)
422
443
  @keep_alive = false
423
- elsif v =~ /Keep-alive/i
444
+ elsif /Keep-alive/i.match?(v)
424
445
  @keep_alive = true
425
446
  end
426
447
  when /\AOrigin\Z/i
@@ -491,6 +512,9 @@ module Fluent::Plugin
491
512
  send_response_and_close(RES_200_STATUS, header, "")
492
513
  elsif include_cors_allow_origin
493
514
  header["Access-Control-Allow-Origin"] = @origin
515
+ if @cors_allow_credentials
516
+ header["Access-Control-Allow-Credentials"] = true
517
+ end
494
518
  send_response_and_close(RES_200_STATUS, header, "")
495
519
  else
496
520
  send_response_and_close(RES_403_STATUS, {}, "")
@@ -542,15 +566,17 @@ module Fluent::Plugin
542
566
 
543
567
  if @format_name != 'default'
544
568
  params[EVENT_RECORD_PARAMETER] = @body
545
- elsif @content_type =~ /^application\/x-www-form-urlencoded/
569
+ elsif /^application\/x-www-form-urlencoded/.match?(@content_type)
546
570
  params.update WEBrick::HTTPUtils.parse_query(@body)
547
571
  elsif @content_type =~ /^multipart\/form-data; boundary=(.+)/
548
572
  boundary = WEBrick::HTTPUtils.dequote($1)
549
573
  params.update WEBrick::HTTPUtils.parse_form_data(@body, boundary)
550
- elsif @content_type =~ /^application\/json/
574
+ elsif /^application\/json/.match?(@content_type)
551
575
  params['json'] = @body
552
- elsif @content_type =~ /^application\/msgpack/
576
+ elsif /^application\/msgpack/.match?(@content_type)
553
577
  params['msgpack'] = @body
578
+ elsif /^application\/x-ndjson/.match?(@content_type)
579
+ params['ndjson'] = @body
554
580
  end
555
581
  path_info = uri.path
556
582
 
@@ -559,7 +585,7 @@ module Fluent::Plugin
559
585
  query_params = WEBrick::HTTPUtils.parse_query(uri.query)
560
586
 
561
587
  query_params.each_pair {|k,v|
562
- params["QUERY_#{k.gsub('-','_').upcase}"] = v
588
+ params["QUERY_#{k.tr('-','_').upcase}"] = v
563
589
  }
564
590
  end
565
591
 
@@ -576,6 +602,9 @@ module Fluent::Plugin
576
602
  header['Access-Control-Allow-Origin'] = '*'
577
603
  elsif include_cors_allow_origin
578
604
  header['Access-Control-Allow-Origin'] = @origin
605
+ if @cors_allow_credentials
606
+ header["Access-Control-Allow-Credentials"] = true
607
+ end
579
608
  end
580
609
  end
581
610
 
@@ -238,7 +238,7 @@ module Fluent::Plugin
238
238
  'buffer_queue_length' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil? && @buffer.is_a?(::Fluent::Plugin::Buffer); @buffer.queue.size },
239
239
  'buffer_timekeys' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil? && @buffer.is_a?(::Fluent::Plugin::Buffer); @buffer.timekeys },
240
240
  'buffer_total_queued_size' => ->(){ throw(:skip) unless instance_variable_defined?(:@buffer) && !@buffer.nil? && @buffer.is_a?(::Fluent::Plugin::Buffer); @buffer.stage_size + @buffer.queue_size },
241
- 'retry_count' => ->(){ instance_variable_defined?(:@num_errors) ? @num_errors : nil },
241
+ 'retry_count' => ->(){ respond_to?(:num_errors) ? num_errors : nil },
242
242
  }
243
243
 
244
244
  def all_plugins
@@ -335,7 +335,9 @@ module Fluent::Plugin
335
335
  }
336
336
 
337
337
  if pe.respond_to?(:statistics)
338
- obj.merge!(pe.statistics['output'] || {})
338
+ obj.merge!(pe.statistics.dig('output') || {})
339
+ obj.merge!(pe.statistics.dig('filter') || {})
340
+ obj.merge!(pe.statistics.dig('input') || {})
339
341
  end
340
342
 
341
343
  obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry] && pe.instance_variable_defined?(:@retry)
@@ -64,7 +64,7 @@ module Fluent::Plugin
64
64
  def configure(conf)
65
65
  super
66
66
  @sample_index = 0
67
- config = conf.elements.select{|e| e.name == 'storage' }.first
67
+ config = conf.elements.find{|e| e.name == 'storage' }
68
68
  @storage = storage_create(usage: 'suspend', conf: config, default_type: DEFAULT_STORAGE_TYPE)
69
69
  end
70
70
 
@@ -89,6 +89,8 @@ module Fluent::Plugin
89
89
  config_param :source_hostname_key, :string, default: nil
90
90
  desc 'Try to resolve hostname from IP addresses or not.'
91
91
  config_param :resolve_hostname, :bool, default: nil
92
+ desc 'Check the remote connection is still available by sending a keepalive packet if this value is true.'
93
+ config_param :send_keepalive_packet, :bool, default: false
92
94
  desc 'The field name of source address of sender.'
93
95
  config_param :source_address_key, :string, default: nil
94
96
  desc 'The field name of the severity.'
@@ -143,6 +145,11 @@ module Fluent::Plugin
143
145
  end
144
146
 
145
147
  @_event_loop_run_timeout = @blocking_timeout
148
+
149
+ protocol = @protocol_type || @transport_config.protocol
150
+ if @send_keepalive_packet && protocol == :udp
151
+ raise Fluent::ConfigError, "send_keepalive_packet is available for tcp/tls"
152
+ end
146
153
  end
147
154
 
148
155
  def multi_workers_ready?
@@ -173,7 +180,12 @@ module Fluent::Plugin
173
180
 
174
181
  delimiter = octet_count_frame ? " " : @delimiter
175
182
  delimiter_size = delimiter.size
176
- server_create_connection(tls ? :in_syslog_tls_server : :in_syslog_tcp_server, @port, bind: @bind, resolve_name: @resolve_hostname) do |conn|
183
+ server_create_connection(
184
+ tls ? :in_syslog_tls_server : :in_syslog_tcp_server, @port,
185
+ bind: @bind,
186
+ resolve_name: @resolve_hostname,
187
+ send_keepalive_packet: @send_keepalive_packet
188
+ ) do |conn|
177
189
  conn.data do |data|
178
190
  buffer = conn.buffer
179
191
  buffer << data
@@ -0,0 +1,204 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'fluent/plugin/input'
18
+
19
+ module Fluent::Plugin
20
+ class TailInput < Fluent::Plugin::Input
21
+ module GroupWatchParams
22
+ include Fluent::Configurable
23
+
24
+ DEFAULT_KEY = /.*/
25
+ DEFAULT_LIMIT = -1
26
+ REGEXP_JOIN = "_"
27
+
28
+ config_section :group, param_name: :group, required: false, multi: false do
29
+ desc 'Regex for extracting group\'s metadata'
30
+ config_param :pattern,
31
+ :regexp,
32
+ default: /^\/var\/log\/containers\/(?<podname>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\/[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace>[^_]+)_(?<container>.+)-(?<docker_id>[a-z0-9]{64})\.log$/
33
+
34
+ desc 'Period of time in which the group_line_limit is applied'
35
+ config_param :rate_period, :time, default: 5
36
+
37
+ config_section :rule, param_name: :rule, required: true, multi: true do
38
+ desc 'Key-value pairs for grouping'
39
+ config_param :match, :hash, value_type: :regexp, default: { namespace: [DEFAULT_KEY], podname: [DEFAULT_KEY] }
40
+ desc 'Maximum number of log lines allowed per group over a period of rate_period'
41
+ config_param :limit, :integer, default: DEFAULT_LIMIT
42
+ end
43
+ end
44
+ end
45
+
46
+ module GroupWatch
47
+ def self.included(mod)
48
+ mod.include GroupWatchParams
49
+ end
50
+
51
+ attr_reader :group_watchers, :default_group_key
52
+
53
+ def initialize
54
+ super
55
+ @group_watchers = {}
56
+ @group_keys = nil
57
+ @default_group_key = nil
58
+ end
59
+
60
+ def configure(conf)
61
+ super
62
+
63
+ unless @group.nil?
64
+ ## Ensuring correct time period syntax
65
+ @group.rule.each { |rule|
66
+ raise "Metadata Group Limit >= DEFAULT_LIMIT" unless rule.limit >= GroupWatchParams::DEFAULT_LIMIT
67
+ }
68
+
69
+ @group_keys = Regexp.compile(@group.pattern).named_captures.keys
70
+ @default_group_key = ([GroupWatchParams::DEFAULT_KEY] * @group_keys.length).join(GroupWatchParams::REGEXP_JOIN)
71
+
72
+ ## Ensures that "specific" rules (with larger number of `rule.match` keys)
73
+ ## have a higher priority against "generic" rules (with less number of `rule.match` keys).
74
+ ## This will be helpful when a file satisfies more than one rule.
75
+ @group.rule.sort_by! { |rule| -rule.match.length() }
76
+ construct_groupwatchers
77
+ @group_watchers[@default_group_key] ||= GroupWatcher.new(@group.rate_period, GroupWatchParams::DEFAULT_LIMIT)
78
+ end
79
+ end
80
+
81
+ def add_path_to_group_watcher(path)
82
+ return nil if @group.nil?
83
+ group_watcher = find_group_from_metadata(path)
84
+ group_watcher.add(path) unless group_watcher.include?(path)
85
+ group_watcher
86
+ end
87
+
88
+ def remove_path_from_group_watcher(path)
89
+ return if @group.nil?
90
+ group_watcher = find_group_from_metadata(path)
91
+ group_watcher.delete(path)
92
+ end
93
+
94
+ def construct_group_key(named_captures)
95
+ match_rule = []
96
+ @group_keys.each { |key|
97
+ match_rule.append(named_captures.fetch(key, GroupWatchParams::DEFAULT_KEY))
98
+ }
99
+ match_rule = match_rule.join(GroupWatchParams::REGEXP_JOIN)
100
+
101
+ match_rule
102
+ end
103
+
104
+ def construct_groupwatchers
105
+ @group.rule.each { |rule|
106
+ match_rule = construct_group_key(rule.match)
107
+ @group_watchers[match_rule] ||= GroupWatcher.new(@group.rate_period, rule.limit)
108
+ }
109
+ end
110
+
111
+ def find_group(metadata)
112
+ metadata_key = construct_group_key(metadata)
113
+ gw_key = @group_watchers.keys.find { |regexp| metadata_key.match?(regexp) && regexp != @default_group_key }
114
+ gw_key ||= @default_group_key
115
+
116
+ @group_watchers[gw_key]
117
+ end
118
+
119
+ def find_group_from_metadata(path)
120
+ begin
121
+ metadata = @group.pattern.match(path).named_captures
122
+ group_watcher = find_group(metadata)
123
+ rescue
124
+ log.warn "Cannot find group from metadata, Adding file in the default group"
125
+ group_watcher = @group_watchers[@default_group_key]
126
+ end
127
+
128
+ group_watcher
129
+ end
130
+ end
131
+
132
+ class GroupWatcher
133
+ attr_accessor :current_paths, :limit, :number_lines_read, :start_reading_time, :rate_period
134
+
135
+ FileCounter = Struct.new(
136
+ :number_lines_read,
137
+ :start_reading_time,
138
+ )
139
+
140
+ def initialize(rate_period = 60, limit = -1)
141
+ @current_paths = {}
142
+ @rate_period = rate_period
143
+ @limit = limit
144
+ end
145
+
146
+ def add(path)
147
+ @current_paths[path] = FileCounter.new(0, nil)
148
+ end
149
+
150
+ def include?(path)
151
+ @current_paths.key?(path)
152
+ end
153
+
154
+ def size
155
+ @current_paths.size
156
+ end
157
+
158
+ def delete(path)
159
+ @current_paths.delete(path)
160
+ end
161
+
162
+ def update_reading_time(path)
163
+ @current_paths[path].start_reading_time ||= Fluent::Clock.now
164
+ end
165
+
166
+ def update_lines_read(path, value)
167
+ @current_paths[path].number_lines_read += value
168
+ end
169
+
170
+ def reset_counter(path)
171
+ @current_paths[path].start_reading_time = nil
172
+ @current_paths[path].number_lines_read = 0
173
+ end
174
+
175
+ def time_spent_reading(path)
176
+ Fluent::Clock.now - @current_paths[path].start_reading_time
177
+ end
178
+
179
+ def limit_time_period_reached?(path)
180
+ time_spent_reading(path) < @rate_period
181
+ end
182
+
183
+ def limit_lines_reached?(path)
184
+ return true unless include?(path)
185
+ return true if @limit == 0
186
+
187
+ return false if @limit < 0
188
+ return false if @current_paths[path].number_lines_read < @limit / size
189
+
190
+ # update_reading_time(path)
191
+ if limit_time_period_reached?(path) # Exceeds limit
192
+ true
193
+ else # Does not exceed limit
194
+ reset_counter(path)
195
+ false
196
+ end
197
+ end
198
+
199
+ def to_s
200
+ super + " current_paths: #{@current_paths} rate_period: #{@rate_period} limit: #{@limit}"
201
+ end
202
+ end
203
+ end
204
+ end
@@ -53,10 +53,16 @@ module Fluent::Plugin
53
53
  }
54
54
  end
55
55
 
56
+ def unwatch_removed_targets(existing_targets)
57
+ @map.reject { |key, entry|
58
+ existing_targets.key?(key)
59
+ }.each_key { |key|
60
+ unwatch_key(key)
61
+ }
62
+ end
63
+
56
64
  def unwatch(target_info)
57
- if (entry = @map.delete(@follow_inodes ? target_info.ino : target_info.path))
58
- entry.update_pos(UNWATCHED_POSITION)
59
- end
65
+ unwatch_key(@follow_inodes ? target_info.ino : target_info.path)
60
66
  end
61
67
 
62
68
  def load(existing_targets = nil)
@@ -96,6 +102,7 @@ module Fluent::Plugin
96
102
  end
97
103
 
98
104
  entries = fetch_compacted_entries
105
+ @logger&.debug "Compacted entries: ", entries.keys
99
106
 
100
107
  @file_mutex.synchronize do
101
108
  if last_modified == @file.mtime && size == @file.size
@@ -117,17 +124,31 @@ module Fluent::Plugin
117
124
 
118
125
  private
119
126
 
127
+ def unwatch_key(key)
128
+ if (entry = @map.delete(key))
129
+ entry.update_pos(UNWATCHED_POSITION)
130
+ end
131
+ end
132
+
120
133
  def compact(existing_targets = nil)
121
134
  @file_mutex.synchronize do
122
- entries = fetch_compacted_entries(existing_targets).values.map(&:to_entry_fmt)
135
+ entries = fetch_compacted_entries
136
+ @logger&.debug "Compacted entries: ", entries.keys
137
+
138
+ if existing_targets
139
+ entries = remove_deleted_files_entries(entries, existing_targets)
140
+ @logger&.debug "Remove missing entries.",
141
+ existing_targets: existing_targets.keys,
142
+ entries_after_removing: entries.keys
143
+ end
123
144
 
124
145
  @file.pos = 0
125
146
  @file.truncate(0)
126
- @file.write(entries.join)
147
+ @file.write(entries.values.map(&:to_entry_fmt).join)
127
148
  end
128
149
  end
129
150
 
130
- def fetch_compacted_entries(existing_targets = nil)
151
+ def fetch_compacted_entries
131
152
  entries = {}
132
153
 
133
154
  @file.pos = 0
@@ -145,31 +166,24 @@ module Fluent::Plugin
145
166
  if pos == UNWATCHED_POSITION
146
167
  @logger.debug "Remove unwatched line from pos_file: #{line}" if @logger
147
168
  else
148
- if entries.include?(path)
149
- @logger.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if @logger
150
- end
151
-
152
169
  if @follow_inodes
170
+ @logger&.warn("#{path} (inode: #{ino}) already exists. use latest one: deleted #{entries[ino]}") if entries.include?(ino)
153
171
  entries[ino] = Entry.new(path, pos, ino, file_pos + path.bytesize + 1)
154
172
  else
173
+ @logger&.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if entries.include?(path)
155
174
  entries[path] = Entry.new(path, pos, ino, file_pos + path.bytesize + 1)
156
175
  end
157
176
  file_pos += line.size
158
177
  end
159
178
  end
160
179
 
161
- entries = remove_deleted_files_entries(entries, existing_targets) if @follow_inodes
162
180
  entries
163
181
  end
164
182
 
165
183
  def remove_deleted_files_entries(existent_entries, existing_targets)
166
- if existing_targets
167
- existent_entries.select { |path_or_ino|
168
- existing_targets.key?(path_or_ino)
169
- }
170
- else
171
- existent_entries
172
- end
184
+ existent_entries.select { |path_or_ino|
185
+ existing_targets.key?(path_or_ino)
186
+ }
173
187
  end
174
188
  end
175
189
 
@@ -250,20 +264,6 @@ module Fluent::Plugin
250
264
  end
251
265
  end
252
266
 
253
- TargetInfo = Struct.new(:path, :ino) do
254
- def ==(other)
255
- return false unless other.is_a?(TargetInfo)
256
- self.path == other.path
257
- end
258
-
259
- def hash
260
- self.path.hash
261
- end
262
-
263
- def eql?(other)
264
- return false unless other.is_a?(TargetInfo)
265
- self.path == other.path
266
- end
267
- end
267
+ TargetInfo = Struct.new(:path, :ino)
268
268
  end
269
269
  end