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
@@ -36,10 +36,16 @@ module Fluent::Plugin
36
36
  desc "The field name of the client's address."
37
37
  config_param :source_address_key, :string, default: nil
38
38
 
39
+ # Setting default to nil for backward compatibility
40
+ desc "The max bytes of message."
41
+ config_param :message_length_limit, :size, default: nil
42
+
39
43
  config_param :blocking_timeout, :time, default: 0.5
40
44
 
41
45
  desc 'The payload is read up to this character.'
42
46
  config_param :delimiter, :string, default: "\n" # syslog family add "\n" to each message and this seems only way to split messages in tcp stream
47
+ desc 'Check the remote connection is still available by sending a keepalive packet if this value is true.'
48
+ config_param :send_keepalive_packet, :bool, default: false
43
49
 
44
50
  # in_forward like host/network restriction
45
51
  config_section :security, required: false, multi: false do
@@ -100,8 +106,9 @@ module Fluent::Plugin
100
106
 
101
107
  log.info "listening tcp socket", bind: @bind, port: @port
102
108
  del_size = @delimiter.length
109
+ discard_till_next_delimiter = false
103
110
  if @_extract_enabled && @_extract_tag_key
104
- server_create(:in_tcp_server_single_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key) do |data, conn|
111
+ server_create(:in_tcp_server_single_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |data, conn|
105
112
  unless check_client(conn)
106
113
  conn.close
107
114
  next
@@ -114,6 +121,16 @@ module Fluent::Plugin
114
121
  msg = buf[pos...i]
115
122
  pos = i + del_size
116
123
 
124
+ if discard_till_next_delimiter
125
+ discard_till_next_delimiter = false
126
+ next
127
+ end
128
+
129
+ if !@message_length_limit.nil? && @message_length_limit < msg.bytesize
130
+ log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize, head: msg[...32]
131
+ next
132
+ end
133
+
117
134
  @parser.parse(msg) do |time, record|
118
135
  unless time && record
119
136
  log.warn "pattern not matched", message: msg
@@ -129,9 +146,18 @@ module Fluent::Plugin
129
146
  end
130
147
  end
131
148
  buf.slice!(0, pos) if pos > 0
149
+ # If the buffer size exceeds the limit here, it means that the next message will definitely exceed the limit.
150
+ # So we should clear the buffer here. Otherwise, it will keep storing useless data until the next delimiter comes.
151
+ if !@message_length_limit.nil? && @message_length_limit < buf.bytesize
152
+ log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32]
153
+ buf.clear
154
+ # We should discard the subsequent data until the next delimiter comes.
155
+ discard_till_next_delimiter = true
156
+ next
157
+ end
132
158
  end
133
159
  else
134
- server_create(:in_tcp_server_batch_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key) do |data, conn|
160
+ server_create(:in_tcp_server_batch_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |data, conn|
135
161
  unless check_client(conn)
136
162
  conn.close
137
163
  next
@@ -145,6 +171,16 @@ module Fluent::Plugin
145
171
  msg = buf[pos...i]
146
172
  pos = i + del_size
147
173
 
174
+ if discard_till_next_delimiter
175
+ discard_till_next_delimiter = false
176
+ next
177
+ end
178
+
179
+ if !@message_length_limit.nil? && @message_length_limit < msg.bytesize
180
+ log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize, head: msg[...32]
181
+ next
182
+ end
183
+
148
184
  @parser.parse(msg) do |time, record|
149
185
  unless time && record
150
186
  log.warn "pattern not matched", message: msg
@@ -159,6 +195,15 @@ module Fluent::Plugin
159
195
  end
160
196
  router.emit_stream(@tag, es)
161
197
  buf.slice!(0, pos) if pos > 0
198
+ # If the buffer size exceeds the limit here, it means that the next message will definitely exceed the limit.
199
+ # So we should clear the buffer here. Otherwise, it will keep storing useless data until the next delimiter comes.
200
+ if !@message_length_limit.nil? && @message_length_limit < buf.bytesize
201
+ log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32]
202
+ buf.clear
203
+ # We should discard the subsequent data until the next delimiter comes.
204
+ discard_till_next_delimiter = true
205
+ next
206
+ end
162
207
  end
163
208
  end
164
209
  end
@@ -27,7 +27,45 @@ module Fluent
27
27
  include PluginLoggerMixin
28
28
  include PluginHelper::Mixin
29
29
 
30
- helpers_internal :event_emitter
30
+ helpers_internal :event_emitter, :metrics
31
+
32
+ def initialize
33
+ super
34
+ @emit_records_metrics = nil
35
+ @emit_size_metrics = nil
36
+ @counter_mutex = Mutex.new
37
+ @enable_size_metrics = false
38
+ end
39
+
40
+ def emit_records
41
+ @emit_records_metrics.get
42
+ end
43
+
44
+ def emit_size
45
+ @emit_size_metrics.get
46
+ end
47
+
48
+ def configure(conf)
49
+ super
50
+
51
+ @emit_records_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "emit_records", help_text: "Number of count emit records")
52
+ @emit_size_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "emit_size", help_text: "Total size of emit events")
53
+ @enable_size_metrics = !!system_config.enable_size_metrics
54
+ end
55
+
56
+ def statistics
57
+ stats = {
58
+ 'emit_records' => @emit_records_metrics.get,
59
+ 'emit_size' => @emit_size_metrics.get,
60
+ }
61
+
62
+ { 'input' => stats }
63
+ end
64
+
65
+ def metric_callback(es)
66
+ @emit_records_metrics.add(es.size)
67
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
68
+ end
31
69
 
32
70
  def multi_workers_ready?
33
71
  false
@@ -0,0 +1,119 @@
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 'socket'
18
+
19
+ require 'fluent/plugin/base'
20
+
21
+ require 'fluent/log'
22
+ require 'fluent/unique_id'
23
+ require 'fluent/plugin_id'
24
+
25
+ module Fluent
26
+ module Plugin
27
+ class Metrics < Base
28
+ include PluginId
29
+ include PluginLoggerMixin
30
+ include UniqueId::Mixin
31
+
32
+ DEFAULT_TYPE = 'local'
33
+
34
+ configured_in :metrics
35
+
36
+ config_param :default_labels, :hash, default: {agent: "Fluentd", hostname: "#{Socket.gethostname}"}
37
+ config_param :labels, :hash, default: {}
38
+
39
+ attr_reader :use_gauge_metric
40
+ attr_reader :has_methods_for_gauge, :has_methods_for_counter
41
+
42
+ def initialize
43
+ super
44
+
45
+ @has_methods_for_counter = false
46
+ @has_methods_for_gauge = false
47
+ @use_gauge_metric = false
48
+ end
49
+
50
+ def configure(conf)
51
+ super
52
+
53
+ if use_gauge_metric
54
+ @has_methods_for_gauge = has_methods_for_gauge?
55
+ else
56
+ @has_methods_for_counter = has_methods_for_counter?
57
+ end
58
+ end
59
+
60
+ # Some metrics should be counted by gauge.
61
+ # ref: https://prometheus.io/docs/concepts/metric_types/#gauge
62
+ def use_gauge_metric=(use_gauge_metric=false)
63
+ @use_gauge_metric = use_gauge_metric
64
+ end
65
+
66
+ def create(namespace:, subsystem:,name:,help_text:,labels: {})
67
+ # This API is for cmetrics type.
68
+ end
69
+
70
+ def get
71
+ raise NotImplementedError, "Implement this method in child class"
72
+ end
73
+
74
+ def inc
75
+ raise NotImplementedError, "Implement this method in child class"
76
+ end
77
+
78
+ def dec
79
+ raise NotImplementedError, "Implement this method in child class"
80
+ end
81
+
82
+ def add(value)
83
+ raise NotImplementedError, "Implement this method in child class"
84
+ end
85
+
86
+ def sub(value)
87
+ raise NotImplementedError, "Implement this method in child class"
88
+ end
89
+
90
+ def set(value)
91
+ raise NotImplementedError, "Implement this method in child class"
92
+ end
93
+
94
+ private
95
+
96
+ def has_methods_for_counter?
97
+ implemented_methods = self.class.instance_methods(false)
98
+
99
+ if [:get, :inc, :add].all? {|e| implemented_methods.include?(e)} &&
100
+ [:set].all?{|e| self.class.method_defined?(e)}
101
+ true
102
+ else
103
+ raise "BUG: metrics plugin on counter mode MUST implement `get`, `inc`, `add` methods. And aliased `set` methods should be aliased from another method"
104
+ end
105
+ end
106
+
107
+ def has_methods_for_gauge?
108
+ implemented_methods = self.class.instance_methods(false)
109
+
110
+ if [:get, :inc, :add].all? {|e| implemented_methods.include?(e)} &&
111
+ [:set, :dec, :sub].all?{|e| self.class.method_defined?(e)}
112
+ true
113
+ else
114
+ raise "BUG: metrics plugin on gauge mode MUST implement `get`, `inc`, and `add` methods. And `dec`, `sub`, and `set` methods should be aliased from other methods"
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,96 @@
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'
18
+ require 'fluent/plugin/metrics'
19
+
20
+ module Fluent
21
+ module Plugin
22
+ class LocalMetrics < Metrics
23
+ Fluent::Plugin.register_metrics('local', self)
24
+
25
+ def initialize
26
+ super
27
+ @store = 0
28
+ @monitor = Monitor.new
29
+ end
30
+
31
+ def configure(conf)
32
+ super
33
+
34
+ if use_gauge_metric
35
+ class << self
36
+ alias_method :dec, :dec_gauge
37
+ alias_method :set, :set_gauge
38
+ alias_method :sub, :sub_gauge
39
+ end
40
+ else
41
+ class << self
42
+ alias_method :set, :set_counter
43
+ end
44
+ end
45
+ end
46
+
47
+ def multi_workers_ready?
48
+ true
49
+ end
50
+
51
+ def get
52
+ @monitor.synchronize do
53
+ @store
54
+ end
55
+ end
56
+
57
+ def inc
58
+ @monitor.synchronize do
59
+ @store += 1
60
+ end
61
+ end
62
+
63
+ def dec_gauge
64
+ @monitor.synchronize do
65
+ @store -= 1
66
+ end
67
+ end
68
+
69
+ def add(value)
70
+ @monitor.synchronize do
71
+ @store += value
72
+ end
73
+ end
74
+
75
+ def sub_gauge(value)
76
+ @monitor.synchronize do
77
+ @store -= value
78
+ end
79
+ end
80
+
81
+ def set_counter(value)
82
+ return if @store > value
83
+
84
+ @monitor.synchronize do
85
+ @store = value
86
+ end
87
+ end
88
+
89
+ def set_gauge(value)
90
+ @monitor.synchronize do
91
+ @store = value
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -27,6 +27,7 @@ module Fluent
27
27
  include PluginHelper::Mixin # for event_emitter
28
28
 
29
29
  helpers :event_emitter # to get router from agent, which will be supplied to child plugins
30
+ helpers_internal :metrics
30
31
 
31
32
  config_section :store, param_name: :stores, multi: true, required: true do
32
33
  config_argument :arg, :string, default: ''
@@ -46,11 +47,40 @@ module Fluent
46
47
 
47
48
  @counter_mutex = Mutex.new
48
49
  # TODO: well organized counters
49
- @num_errors = 0
50
- @emit_count = 0
51
- @emit_records = 0
50
+ @num_errors_metrics = nil
51
+ @emit_count_metrics = nil
52
+ @emit_records_metrics = nil
53
+ @emit_size_metrics = nil
52
54
  # @write_count = 0
53
55
  # @rollback_count = 0
56
+ @enable_size_metrics = false
57
+ end
58
+
59
+ def num_errors
60
+ @num_errors_metrics.get
61
+ end
62
+
63
+ def emit_count
64
+ @emit_count_metrics.get
65
+ end
66
+
67
+ def emit_size
68
+ @emit_size_metrics.get
69
+ end
70
+
71
+ def emit_records
72
+ @emit_records_metrics.get
73
+ end
74
+
75
+ def statistics
76
+ stats = {
77
+ 'num_errors' => @num_errors_metrics.get,
78
+ 'emit_records' => @emit_records_metrics.get,
79
+ 'emit_count' => @emit_count_metrics.get,
80
+ 'emit_size' => @emit_size_metrics.get,
81
+ }
82
+
83
+ { 'multi_output' => stats }
54
84
  end
55
85
 
56
86
  def multi_output?
@@ -60,6 +90,12 @@ module Fluent
60
90
  def configure(conf)
61
91
  super
62
92
 
93
+ @num_errors_metrics = metrics_create(namespace: "fluentd", subsystem: "multi_output", name: "num_errors", help_text: "Number of count num errors")
94
+ @emit_count_metrics = metrics_create(namespace: "fluentd", subsystem: "multi_output", name: "emit_records", help_text: "Number of count emits")
95
+ @emit_records_metrics = metrics_create(namespace: "fluentd", subsystem: "multi_output", name: "emit_records", help_text: "Number of emit records")
96
+ @emit_size_metrics = metrics_create(namespace: "fluentd", subsystem: "multi_output", name: "emit_size", help_text: "Total size of emit events")
97
+ @enable_size_metrics = !!system_config.enable_size_metrics
98
+
63
99
  @stores.each do |store|
64
100
  store_conf = store.corresponding_config_element
65
101
  type = store_conf['@type']
@@ -143,12 +179,13 @@ module Fluent
143
179
  end
144
180
 
145
181
  def emit_sync(tag, es)
146
- @counter_mutex.synchronize{ @emit_count += 1 }
182
+ @emit_count_metrics.inc
147
183
  begin
148
184
  process(tag, es)
149
- @counter_mutex.synchronize{ @emit_records += es.size }
185
+ @emit_records_metrics.add(es.size)
186
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
150
187
  rescue
151
- @counter_mutex.synchronize{ @num_errors += 1 }
188
+ @num_errors_metrics.inc
152
189
  raise
153
190
  end
154
191
  end
@@ -46,7 +46,7 @@ module Fluent::Plugin
46
46
  @ignore_errors << (store.arg.include?('ignore_error'))
47
47
  @ignore_if_prev_successes << (store.arg.include?('ignore_if_prev_success'))
48
48
  }
49
- if @ignore_errors.uniq.size == 1 && @ignore_errors.include?(true) && @ignore_if_prev_successes.include?(false)
49
+ if @ignore_errors.uniq.size == 1 && @ignore_errors.include?(true) && !@ignore_if_prev_successes.include?(true)
50
50
  log.warn "ignore_errors are specified in all <store>, but ignore_if_prev_success is not specified. Is this intended?"
51
51
  end
52
52
  end
@@ -163,7 +163,7 @@ module Fluent::Plugin
163
163
  0
164
164
  elsif (@child_respawn == 'inf') || (@child_respawn == '-1')
165
165
  -1
166
- elsif @child_respawn =~ /^\d+$/
166
+ elsif /^\d+$/.match?(@child_respawn)
167
167
  @child_respawn.to_i
168
168
  else
169
169
  raise ConfigError, "child_respawn option argument invalid: none(or 0), inf(or -1) or positive number"
@@ -187,7 +187,7 @@ module Fluent::Plugin
187
187
  @rr = 0
188
188
 
189
189
  exit_callback = ->(status){
190
- c = @children.select{|child| child.pid == status.pid }.first
190
+ c = @children.find{|child| child.pid == status.pid }
191
191
  if c
192
192
  unless self.stopped?
193
193
  log.warn "child process exits with error code", code: status.to_i, status: status.exitstatus, signal: status.termsig
@@ -181,6 +181,13 @@ module Fluent::Plugin
181
181
  @dir_perm = system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION
182
182
  @file_perm = system_config.file_permission || Fluent::DEFAULT_FILE_PERMISSION
183
183
  @need_lock = system_config.workers > 1
184
+
185
+ # https://github.com/fluent/fluentd/issues/3569
186
+ @need_ruby_on_macos_workaround = false
187
+ if @append && Fluent.macos?
188
+ condition = Gem::Dependency.new('', [">= 2.7.0", "< 3.1.0"])
189
+ @need_ruby_on_macos_workaround = true if condition.match?('', RUBY_VERSION)
190
+ end
184
191
  end
185
192
 
186
193
  def multi_workers_ready?
@@ -210,7 +217,13 @@ module Fluent::Plugin
210
217
  end
211
218
 
212
219
  if @append
213
- writer.call(path, chunk)
220
+ if @need_lock
221
+ acquire_worker_lock(path) do
222
+ writer.call(path, chunk)
223
+ end
224
+ else
225
+ writer.call(path, chunk)
226
+ end
214
227
  else
215
228
  find_filepath_available(path, with_lock: @need_lock) do |actual_path|
216
229
  writer.call(actual_path, chunk)
@@ -223,7 +236,12 @@ module Fluent::Plugin
223
236
 
224
237
  def write_without_compression(path, chunk)
225
238
  File.open(path, "ab", @file_perm) do |f|
226
- chunk.write_to(f)
239
+ if @need_ruby_on_macos_workaround
240
+ content = chunk.read()
241
+ f.puts content
242
+ else
243
+ chunk.write_to(f)
244
+ end
227
245
  end
228
246
  end
229
247
 
@@ -59,7 +59,13 @@ module Fluent::Plugin
59
59
  @ack_waitings = new_list
60
60
  end
61
61
 
62
- readable_sockets, _, _ = IO.select(sockets, nil, nil, select_interval)
62
+ begin
63
+ readable_sockets, _, _ = IO.select(sockets, nil, nil, select_interval)
64
+ rescue IOError
65
+ @log.info "connection closed while waiting for readable sockets"
66
+ readable_sockets = nil
67
+ end
68
+
63
69
  if readable_sockets
64
70
  readable_sockets.each do |sock|
65
71
  results << read_ack_from_sock(sock)
@@ -109,13 +115,22 @@ module Fluent::Plugin
109
115
  raw_data = sock.instance_of?(Fluent::PluginHelper::Socket::WrappedSocket::TLS) ? sock.readpartial(@read_length) : sock.recv(@read_length)
110
116
  rescue Errno::ECONNRESET, EOFError # ECONNRESET for #recv, #EOFError for #readpartial
111
117
  raw_data = ''
118
+ rescue IOError
119
+ @log.info "socket closed while receiving ack response"
120
+ return nil, Result::FAILED
112
121
  end
113
122
 
114
123
  info = find(sock)
115
124
 
116
- # When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
117
- # If this happens we assume the data wasn't delivered and retry it.
118
- if raw_data.empty?
125
+ if info.nil?
126
+ # The info can be deleted by another thread during `sock.recv()` and `find()`.
127
+ # This is OK since another thread has completed to process the ack, so we can skip this.
128
+ # Note: exclusion mechanism about `collect_response()` may need to be considered.
129
+ @log.debug "could not find the ack info. this ack may be processed by another thread."
130
+ return nil, Result::FAILED
131
+ elsif raw_data.empty?
132
+ # When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
133
+ # If this happens we assume the data wasn't delivered and retry it.
119
134
  @log.warn 'destination node closed the connection. regard it as unavailable.', host: info.node.host, port: info.node.port
120
135
  # info.node.disable!
121
136
  return info, Result::FAILED
@@ -50,6 +50,7 @@ module Fluent::Plugin
50
50
  def checkin(sock)
51
51
  @mutex.synchronize do
52
52
  if (s = @inflight_sockets.delete(sock))
53
+ s.timeout = timeout
53
54
  @available_sockets[s.key] << s
54
55
  else
55
56
  @log.debug("there is no socket #{sock}")
@@ -122,6 +123,7 @@ module Fluent::Plugin
122
123
  t = Time.now
123
124
  if (s = @available_sockets[key].find { |sock| !expired_socket?(sock, time: t) })
124
125
  @inflight_sockets[s.sock] = @available_sockets[key].delete(s)
126
+ s.timeout = timeout
125
127
  s
126
128
  else
127
129
  nil
@@ -167,6 +167,8 @@ module Fluent::Plugin
167
167
  @usock = nil
168
168
  @keep_alive_watcher_interval = 5 # TODO
169
169
  @suspend_flush = false
170
+ @healthy_nodes_count_metrics = nil
171
+ @registered_nodes_count_metrics = nil
170
172
  end
171
173
 
172
174
  def configure(conf)
@@ -265,6 +267,9 @@ module Fluent::Plugin
265
267
  end
266
268
 
267
269
  raise Fluent::ConfigError, "ack_response_timeout must be a positive integer" if @ack_response_timeout < 1
270
+ @healthy_nodes_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "healthy_nodes_count", help_text: "Number of count healthy nodes", prefer_gauge: true)
271
+ @registered_nodes_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "registered_nodes_count", help_text: "Number of count registered nodes", prefer_gauge: true)
272
+
268
273
  end
269
274
 
270
275
  def multi_workers_ready?
@@ -418,18 +423,21 @@ module Fluent::Plugin
418
423
  def statistics
419
424
  stats = super
420
425
  services = service_discovery_services
421
- healthy_nodes_count = 0
422
- registed_nodes_count = services.size
426
+ @healthy_nodes_count_metrics.set(0)
427
+ @registered_nodes_count_metrics.set(services.size)
423
428
  services.each do |s|
424
429
  if s.available?
425
- healthy_nodes_count += 1
430
+ @healthy_nodes_count_metrics.inc
426
431
  end
427
432
  end
428
433
 
429
- stats.merge(
430
- 'healthy_nodes_count' => healthy_nodes_count,
431
- 'registered_nodes_count' => registed_nodes_count,
432
- )
434
+ stats = {
435
+ 'output' => stats["output"].merge({
436
+ 'healthy_nodes_count' => @healthy_nodes_count_metrics.get,
437
+ 'registered_nodes_count' => @registered_nodes_count_metrics.get,
438
+ })
439
+ }
440
+ stats
433
441
  end
434
442
 
435
443
  # MessagePack FixArray length is 3
@@ -513,8 +521,8 @@ module Fluent::Plugin
513
521
  when AckHandler::Result::SUCCESS
514
522
  commit_write(chunk_id)
515
523
  when AckHandler::Result::FAILED
516
- node.disable!
517
- rollback_write(chunk_id, update_retry: false)
524
+ node&.disable!
525
+ rollback_write(chunk_id, update_retry: false) if chunk_id
518
526
  when AckHandler::Result::CHUNKID_UNMATCHED
519
527
  rollback_write(chunk_id, update_retry: false)
520
528
  else