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
@@ -47,6 +47,8 @@ module Fluent
47
47
  @match_cache = MatchCache.new
48
48
  @default_collector = default_collector
49
49
  @emit_error_handler = emit_error_handler
50
+ @metric_callbacks = {}
51
+ @caller_plugin_id = nil
50
52
  end
51
53
 
52
54
  attr_accessor :default_collector
@@ -83,6 +85,22 @@ module Fluent
83
85
  @match_rules << Rule.new(pattern, collector)
84
86
  end
85
87
 
88
+ def add_metric_callbacks(caller_plugin_id, callback)
89
+ @metric_callbacks[caller_plugin_id] = callback
90
+ end
91
+
92
+ def caller_plugin_id=(caller_plugin_id)
93
+ @caller_plugin_id = caller_plugin_id
94
+ end
95
+
96
+ def find_callback
97
+ if @caller_plugin_id
98
+ @metric_callbacks[@caller_plugin_id]
99
+ else
100
+ nil
101
+ end
102
+ end
103
+
86
104
  def emit(tag, time, record)
87
105
  unless record.nil?
88
106
  emit_stream(tag, OneEventStream.new(time, record))
@@ -95,6 +113,11 @@ module Fluent
95
113
 
96
114
  def emit_stream(tag, es)
97
115
  match(tag).emit_events(tag, es)
116
+ if callback = find_callback
117
+ callback.call(es)
118
+ end
119
+ rescue Pipeline::OutputError => e
120
+ @emit_error_handler.handle_emits_error(tag, e.processed_es, e.internal_error)
98
121
  rescue => e
99
122
  @emit_error_handler.handle_emits_error(tag, es, e)
100
123
  end
@@ -140,6 +163,17 @@ module Fluent
140
163
  private
141
164
 
142
165
  class Pipeline
166
+
167
+ class OutputError < StandardError
168
+ attr_reader :internal_error
169
+ attr_reader :processed_es
170
+
171
+ def initialize(internal_error, processed_es)
172
+ @internal_error = internal_error
173
+ @processed_es = processed_es
174
+ end
175
+ end
176
+
143
177
  def initialize
144
178
  @filters = []
145
179
  @output = nil
@@ -157,7 +191,12 @@ module Fluent
157
191
 
158
192
  def emit_events(tag, es)
159
193
  processed = @optimizer.filter_stream(tag, es)
160
- @output.emit_events(tag, processed)
194
+
195
+ begin
196
+ @output.emit_events(tag, processed)
197
+ rescue => e
198
+ raise OutputError.new(e, processed)
199
+ end
161
200
  end
162
201
 
163
202
  class FilterOptimizer
@@ -175,7 +214,11 @@ module Fluent
175
214
  if optimizable?
176
215
  optimized_filter_stream(tag, es)
177
216
  else
178
- @filters.reduce(es) { |acc, filter| filter.filter_stream(tag, acc) }
217
+ @filters.reduce(es) { |acc, filter|
218
+ filtered_es = filter.filter_stream(tag, acc)
219
+ filter.measure_metrics(filtered_es)
220
+ filtered_es
221
+ }
179
222
  end
180
223
  end
181
224
 
@@ -193,6 +236,7 @@ module Fluent
193
236
  begin
194
237
  filtered_time, filtered_record = filter.filter_with_time(tag, filtered_time, filtered_record)
195
238
  throw :break_loop unless filtered_record && filtered_time
239
+ filter.measure_metrics(OneEventStream.new(time, record))
196
240
  rescue => e
197
241
  filter.router.emit_error_event(tag, filtered_time, filtered_record, e)
198
242
  end
@@ -200,6 +244,7 @@ module Fluent
200
244
  begin
201
245
  filtered_record = filter.filter(tag, filtered_time, filtered_record)
202
246
  throw :break_loop unless filtered_record
247
+ filter.measure_metrics(OneEventStream.new(time, record))
203
248
  rescue => e
204
249
  filter.router.emit_error_event(tag, filtered_time, filtered_record, e)
205
250
  end
@@ -0,0 +1,137 @@
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
+ unless Fluent.windows?
18
+ Fluent::FileWrapper = File
19
+ else
20
+ require 'fluent/win32api'
21
+
22
+ module Fluent
23
+ module FileWrapper
24
+ def self.open(path, mode='r')
25
+ io = WindowsFile.new(path, mode).io
26
+ if block_given?
27
+ v = yield io
28
+ io.close
29
+ v
30
+ else
31
+ io
32
+ end
33
+ end
34
+
35
+ def self.stat(path)
36
+ f = WindowsFile.new(path)
37
+ s = f.stat
38
+ f.close
39
+ s
40
+ end
41
+ end
42
+
43
+ class WindowsFile
44
+ include File::Constants
45
+
46
+ attr_reader :io
47
+
48
+ INVALID_HANDLE_VALUE = -1
49
+
50
+ def initialize(path, mode_enc='r')
51
+ @path = path
52
+ mode, enc = mode_enc.split(":", 2)
53
+ @io = File.open(path, mode2flags(mode))
54
+ @io.set_encoding(enc) if enc
55
+ @file_handle = Win32API._get_osfhandle(@io.to_i)
56
+ @io.instance_variable_set(:@file_index, self.ino)
57
+ def @io.ino
58
+ @file_index
59
+ end
60
+ end
61
+
62
+ def close
63
+ @io.close
64
+ @file_handle = INVALID_HANDLE_VALUE
65
+ end
66
+
67
+ # To keep backward compatibility, we continue to use GetFileInformationByHandle()
68
+ # to get file id.
69
+ # Note that Ruby's File.stat uses GetFileInformationByHandleEx() with FileIdInfo
70
+ # and returned value is different with above one, former one is 64 bit while
71
+ # later one is 128bit.
72
+ def ino
73
+ by_handle_file_information = '\0'*(4+8+8+8+4+4+4+4+4+4) #72bytes
74
+
75
+ unless Win32API.GetFileInformationByHandle(@file_handle, by_handle_file_information)
76
+ return 0
77
+ end
78
+
79
+ by_handle_file_information.unpack("I11Q1")[11] # fileindex
80
+ end
81
+
82
+ def stat
83
+ raise Errno::ENOENT if delete_pending
84
+ s = File.stat(@path)
85
+ s.instance_variable_set :@ino, self.ino
86
+ def s.ino; @ino; end
87
+ s
88
+ end
89
+
90
+ private
91
+
92
+ def mode2flags(mode)
93
+ # Always inject File::Constants::SHARE_DELETE
94
+ # https://github.com/fluent/fluentd/pull/3585#issuecomment-1101502617
95
+ # To enable SHARE_DELETE, BINARY is also required.
96
+ # https://bugs.ruby-lang.org/issues/11218
97
+ # https://github.com/ruby/ruby/blob/d6684f063bc53e3cab025bd39526eca3b480b5e7/win32/win32.c#L6332-L6345
98
+ flags = BINARY | SHARE_DELETE
99
+ case mode.delete("b")
100
+ when "r"
101
+ flags |= RDONLY
102
+ when "r+"
103
+ flags |= RDWR
104
+ when "w"
105
+ flags |= WRONLY | CREAT | TRUNC
106
+ when "w+"
107
+ flags |= RDWR | CREAT | TRUNC
108
+ when "a"
109
+ flags |= WRONLY | CREAT | APPEND
110
+ when "a+"
111
+ flags |= RDWR | CREAT | APPEND
112
+ else
113
+ raise Errno::EINVAL.new("Unsupported mode by Fluent::FileWrapper: #{mode}")
114
+ end
115
+ end
116
+
117
+ # DeletePending is a Windows-specific file state that roughly means
118
+ # "this file is queued for deletion, so close any open handlers"
119
+ #
120
+ # This flag can be retrieved via GetFileInformationByHandleEx().
121
+ #
122
+ # https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfileinformationbyhandleex
123
+ #
124
+ def delete_pending
125
+ file_standard_info = 0x01
126
+ bufsize = 1024
127
+ buf = '\0' * bufsize
128
+
129
+ unless Win32API.GetFileInformationByHandleEx(@file_handle, file_standard_info, buf, bufsize)
130
+ return false
131
+ end
132
+
133
+ return buf.unpack("QQICC")[3] != 0
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,66 @@
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 'console/terminal/logger'
18
+
19
+ module Fluent
20
+ class Log
21
+ # Async gem which is used by http_server helper switched logger mechanism to
22
+ # Console gem which isn't complatible with Ruby's standard Logger (since
23
+ # v1.17). This class adapts it to Fluentd's logger mechanism.
24
+ class ConsoleAdapter < Console::Terminal::Logger
25
+ def self.wrap(logger)
26
+ _, level = Console::Logger::LEVELS.find { |key, value|
27
+ if logger.level <= 0
28
+ key == :debug
29
+ else
30
+ value == logger.level - 1
31
+ end
32
+ }
33
+ Console::Logger.new(ConsoleAdapter.new(logger), level: level)
34
+ end
35
+
36
+ def initialize(logger)
37
+ @logger = logger
38
+ # When `verbose` is `true`, following items will be added as a prefix or
39
+ # suffix of the subject:
40
+ # * Severity
41
+ # * Object ID
42
+ # * PID
43
+ # * Time
44
+ # Severity and Time are added by Fluentd::Log too so they are redundant.
45
+ # PID is the worker's PID so it's also redundant.
46
+ # Object ID will be too verbose in usual cases.
47
+ # So set it as `false` here to suppress redundant items.
48
+ super(StringIO.new, verbose: false)
49
+ end
50
+
51
+ def call(subject = nil, *arguments, name: nil, severity: 'info', **options, &block)
52
+ if LEVEL_TEXT.include?(severity.to_s)
53
+ level = severity
54
+ else
55
+ @logger.warn("Unknown severity: #{severity}")
56
+ level = 'warn'
57
+ end
58
+
59
+ @io.seek(0)
60
+ @io.truncate(0)
61
+ super
62
+ @logger.send(level, @io.string.chomp)
63
+ end
64
+ end
65
+ end
66
+ end
data/lib/fluent/log.rb CHANGED
@@ -51,6 +51,8 @@ module Fluent
51
51
  LOG_TYPES = [LOG_TYPE_SUPERVISOR, LOG_TYPE_WORKER0, LOG_TYPE_DEFAULT].freeze
52
52
  LOG_ROTATE_AGE = %w(daily weekly monthly)
53
53
 
54
+ IGNORE_SAME_LOG_MAX_CACHE_SIZE = 1000 # If need, make this an option of system config.
55
+
54
56
  def self.str_to_level(log_level_str)
55
57
  case log_level_str.downcase
56
58
  when "trace" then LEVEL_TRACE
@@ -67,8 +69,29 @@ module Fluent
67
69
  LEVEL_TEXT.map{|t| "#{LOG_EVENT_TAG_PREFIX}.#{t}" }
68
70
  end
69
71
 
72
+ # Create a unique path for each process.
73
+ #
74
+ # >>> per_process_path("C:/tmp/test.log", :worker, 1)
75
+ # C:/tmp/test-1.log
76
+ # >>> per_process_path("C:/tmp/test.log", :supervisor, 0)
77
+ # C:/tmp/test-supervisor-0.log
78
+ def self.per_process_path(path, process_type, worker_id)
79
+ path = Pathname(path)
80
+ ext = path.extname
81
+
82
+ if process_type == :supervisor
83
+ suffix = "-#{process_type}-0#{ext}" # "-0" for backword compatibility.
84
+ else
85
+ suffix = "-#{worker_id}#{ext}"
86
+ end
87
+ return path.sub_ext(suffix).to_s
88
+ end
89
+
70
90
  def initialize(logger, opts={})
71
- # overwrites logger.level= so that config reloading resets level of Fluentd::Log
91
+ # When ServerEngine changes the logger.level, the Fluentd logger level should also change.
92
+ # So overwrites logger.level= below.
93
+ # However, currently Fluentd doesn't use the ServerEngine's reloading feature,
94
+ # so maybe we don't need this overwriting anymore.
72
95
  orig_logger_level_setter = logger.class.public_instance_method(:level=).bind(logger)
73
96
  me = self
74
97
  # The original ruby logger sets the number as each log level like below.
@@ -92,6 +115,7 @@ module Fluent
92
115
  # So if serverengine's logger level is changed, fluentd's log level will be changed to that + 1.
93
116
  logger.define_singleton_method(:level=) {|level| orig_logger_level_setter.call(level); me.level = self.level + 1 }
94
117
 
118
+ @path = opts[:path]
95
119
  @logger = logger
96
120
  @out = logger.instance_variable_get(:@logdev)
97
121
  @level = logger.level + 1
@@ -102,7 +126,8 @@ module Fluent
102
126
  @time_format = nil
103
127
  @formatter = nil
104
128
 
105
- self.format = :text
129
+ self.format = opts.fetch(:format, :text)
130
+ self.time_format = opts[:time_format] if opts.key?(:time_format)
106
131
  enable_color out.tty?
107
132
  # TODO: This variable name is unclear so we should change to better name.
108
133
  @threads_exclude_events = []
@@ -154,8 +179,12 @@ module Fluent
154
179
 
155
180
  attr_reader :format
156
181
  attr_reader :time_format
157
- attr_accessor :log_event_enabled, :ignore_repeated_log_interval, :ignore_same_log_interval
182
+ attr_accessor :log_event_enabled, :ignore_repeated_log_interval, :ignore_same_log_interval, :suppress_repeated_stacktrace
158
183
  attr_accessor :out
184
+ # Strictly speaking, we should also change @logger.level when the setter of @level is called.
185
+ # Currently, we don't need to do it, since Fluentd::Log doesn't use ServerEngine::DaemonLogger.level.
186
+ # Since We overwrites logger.level= so that @logger.level is applied to @level,
187
+ # we need to find a good way to do this, otherwise we will end up in an endless loop.
159
188
  attr_accessor :level
160
189
  attr_accessor :optional_header, :optional_attrs
161
190
 
@@ -202,9 +231,12 @@ module Fluent
202
231
  @time_formatter = Strftime.new(@time_format) rescue nil
203
232
  end
204
233
 
234
+ def stdout?
235
+ @out == $stdout
236
+ end
237
+
205
238
  def reopen!
206
- # do nothing in @logger.reopen! because it's already reopened in Supervisor.load_config
207
- @logger.reopen! if @logger
239
+ @out.reopen(@path, "a") if @path && @path != "-"
208
240
  nil
209
241
  end
210
242
 
@@ -447,6 +479,13 @@ module Fluent
447
479
  false
448
480
  end
449
481
  else
482
+ if cached_log.size >= IGNORE_SAME_LOG_MAX_CACHE_SIZE
483
+ cached_log.reject! do |_, cached_time|
484
+ (time - cached_time) > @ignore_same_log_interval
485
+ end
486
+ end
487
+ # If the size is still over, we have no choice but to clear it.
488
+ cached_log.clear if cached_log.size >= IGNORE_SAME_LOG_MAX_CACHE_SIZE
450
489
  cached_log[message] = time
451
490
  false
452
491
  end
data/lib/fluent/match.rb CHANGED
@@ -115,7 +115,7 @@ module Fluent
115
115
  stack.last << regex.pop
116
116
  regex.push ''
117
117
 
118
- elsif c =~ /[a-zA-Z0-9_]/
118
+ elsif /[a-zA-Z0-9_]/.match?(c)
119
119
  regex.last << c
120
120
 
121
121
  else
@@ -100,7 +100,12 @@ module Fluent
100
100
  end
101
101
 
102
102
  def self.thread_local_msgpack_unpacker
103
- Thread.current[:local_msgpack_unpacker] ||= MessagePackFactory.engine_factory.unpacker
103
+ unpacker = Thread.current[:local_msgpack_unpacker]
104
+ if unpacker.nil?
105
+ return Thread.current[:local_msgpack_unpacker] = MessagePackFactory.engine_factory.unpacker
106
+ end
107
+ unpacker.reset
108
+ unpacker
104
109
  end
105
110
  end
106
111
  end
@@ -4,14 +4,13 @@ module Fluent
4
4
  class OjOptions
5
5
  OPTIONS = {
6
6
  'bigdecimal_load': :symbol,
7
- 'max_nesting': :integer,
8
7
  'mode': :symbol,
9
8
  'use_to_json': :bool
10
9
  }
11
10
 
12
11
  ALLOWED_VALUES = {
13
12
  'bigdecimal_load': %i[bigdecimal float auto],
14
- 'mode': %i[strict null compat json rails object custom]
13
+ 'mode': %i[strict null compat json rails custom]
15
14
  }
16
15
 
17
16
  DEFAULTS = {
@@ -23,37 +23,78 @@ require 'fluent/plugin_helper'
23
23
  module Fluent
24
24
  module Plugin
25
25
  class BareOutput < Base
26
+ include PluginHelper::Mixin # for metrics
27
+
26
28
  # DO NOT USE THIS plugin for normal output plugin. Use Output instead.
27
29
  # This output plugin base class is only for meta-output plugins
28
30
  # which cannot be implemented on MultiOutput.
29
31
  # E.g,: forest, config-expander
30
32
 
33
+ helpers_internal :metrics
34
+
31
35
  include PluginId
32
36
  include PluginLoggerMixin
33
37
  include PluginHelper::Mixin
34
38
 
35
- attr_reader :num_errors, :emit_count, :emit_records
36
-
37
39
  def process(tag, es)
38
40
  raise NotImplementedError, "BUG: output plugins MUST implement this method"
39
41
  end
40
42
 
43
+ def num_errors
44
+ @num_errors_metrics.get
45
+ end
46
+
47
+ def emit_count
48
+ @emit_count_metrics.get
49
+ end
50
+
51
+ def emit_size
52
+ @emit_size_metrics.get
53
+ end
54
+
55
+ def emit_records
56
+ @emit_records_metrics.get
57
+ end
58
+
41
59
  def initialize
42
60
  super
43
61
  @counter_mutex = Mutex.new
44
62
  # TODO: well organized counters
45
- @num_errors = 0
46
- @emit_count = 0
47
- @emit_records = 0
63
+ @num_errors_metrics = nil
64
+ @emit_count_metrics = nil
65
+ @emit_records_metrics = nil
66
+ @emit_size_metrics = nil
67
+ end
68
+
69
+ def configure(conf)
70
+ super
71
+
72
+ @num_errors_metrics = metrics_create(namespace: "fluentd", subsystem: "bare_output", name: "num_errors", help_text: "Number of count num errors")
73
+ @emit_count_metrics = metrics_create(namespace: "fluentd", subsystem: "bare_output", name: "emit_count", help_text: "Number of count emits")
74
+ @emit_records_metrics = metrics_create(namespace: "fluentd", subsystem: "bare_output", name: "emit_records", help_text: "Number of emit records")
75
+ @emit_size_metrics = metrics_create(namespace: "fluentd", subsystem: "bare_output", name: "emit_size", help_text: "Total size of emit events")
76
+ @enable_size_metrics = !!system_config.enable_size_metrics
77
+ end
78
+
79
+ def statistics
80
+ stats = {
81
+ 'num_errors' => @num_errors_metrics.get,
82
+ 'emit_records' => @emit_records_metrics.get,
83
+ 'emit_count' => @emit_count_metrics.get,
84
+ 'emit_size' => @emit_size_metrics.get,
85
+ }
86
+
87
+ { 'bare_output' => stats }
48
88
  end
49
89
 
50
90
  def emit_sync(tag, es)
51
- @counter_mutex.synchronize{ @emit_count += 1 }
91
+ @emit_count_metrics.inc
52
92
  begin
53
93
  process(tag, es)
54
- @counter_mutex.synchronize{ @emit_records += es.size }
94
+ @emit_records_metrics.add(es.size)
95
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
55
96
  rescue
56
- @counter_mutex.synchronize{ @num_errors += 1 }
97
+ @num_errors_metrics.inc
57
98
  raise
58
99
  end
59
100
  end
@@ -31,6 +31,7 @@ module Fluent
31
31
  def initialize
32
32
  @log = nil
33
33
  super
34
+ @fluentd_lock_dir = ENV['FLUENTD_LOCK_DIR']
34
35
  @_state = State.new(false, false, false, false, false, false, false, false, false)
35
36
  @_context_router = nil
36
37
  @_fluentd_worker_id = nil
@@ -52,14 +53,12 @@ module Fluent
52
53
  end
53
54
 
54
55
  def configure(conf)
55
- if Fluent::Engine.supervisor_mode || (conf.respond_to?(:for_this_worker?) && conf.for_this_worker?)
56
- workers = if conf.target_worker_ids && !conf.target_worker_ids.empty?
57
- conf.target_worker_ids.size
58
- else
59
- 1
60
- end
61
- system_config_override(workers: workers)
56
+ raise ArgumentError, "BUG: type of conf must be Fluent::Config::Element, but #{conf.class} is passed." unless conf.is_a?(Fluent::Config::Element)
57
+
58
+ if conf.for_this_worker? || (Fluent::Engine.supervisor_mode && !conf.for_every_workers?)
59
+ system_config_override(workers: conf.target_worker_ids.size)
62
60
  end
61
+
63
62
  super(conf, system_config.strict_config_value)
64
63
  @_state ||= State.new(false, false, false, false, false, false, false, false, false)
65
64
  @_state.configure = true
@@ -70,10 +69,28 @@ module Fluent
70
69
  true
71
70
  end
72
71
 
72
+ def get_lock_path(name)
73
+ name = name.gsub(/[^a-zA-Z0-9]/, "_")
74
+ File.join(@fluentd_lock_dir, "fluentd-#{name}.lock")
75
+ end
76
+
77
+ def acquire_worker_lock(name)
78
+ if @fluentd_lock_dir.nil?
79
+ raise InvalidLockDirectory, "can't acquire lock because FLUENTD_LOCK_DIR isn't set"
80
+ end
81
+ lock_path = get_lock_path(name)
82
+ File.open(lock_path, "w") do |f|
83
+ f.flock(File::LOCK_EX)
84
+ yield
85
+ end
86
+ # Update access time to prevent tmpwatch from deleting a lock file.
87
+ FileUtils.touch(lock_path);
88
+ end
89
+
73
90
  def string_safe_encoding(str)
74
91
  unless str.valid_encoding?
75
- log.info "invalid byte sequence is replaced in `#{str}`" if self.respond_to?(:log)
76
92
  str = str.scrub('?')
93
+ log.info "invalid byte sequence is replaced in `#{str}`" if self.respond_to?(:log)
77
94
  end
78
95
  yield str
79
96
  end
@@ -173,7 +190,7 @@ module Fluent
173
190
  # Thread::Backtrace::Location#path returns base filename or absolute path.
174
191
  # #absolute_path returns absolute_path always.
175
192
  # https://bugs.ruby-lang.org/issues/12159
176
- if location.absolute_path =~ /\/test_[^\/]+\.rb$/ # location.path =~ /test_.+\.rb$/
193
+ if /\/test_[^\/]+\.rb$/.match?(location.absolute_path) # location.path =~ /test_.+\.rb$/
177
194
  return true
178
195
  end
179
196
  end
@@ -39,8 +39,8 @@ module Fluent
39
39
  config_set_default :chunk_limit_size, DEFAULT_CHUNK_LIMIT_SIZE
40
40
  config_set_default :total_limit_size, DEFAULT_TOTAL_LIMIT_SIZE
41
41
 
42
- config_param :file_permission, :string, default: nil # '0644'
43
- config_param :dir_permission, :string, default: nil # '0755'
42
+ config_param :file_permission, :string, default: nil # '0644' (Fluent::DEFAULT_FILE_PERMISSION)
43
+ config_param :dir_permission, :string, default: nil # '0755' (Fluent::DEFAULT_DIR_PERMISSION)
44
44
 
45
45
  def initialize
46
46
  super
@@ -139,13 +139,20 @@ module Fluent
139
139
  def resume
140
140
  stage = {}
141
141
  queue = []
142
+ exist_broken_file = false
142
143
 
143
144
  patterns = [@path]
144
145
  patterns.unshift @additional_resume_path if @additional_resume_path
145
146
  Dir.glob(escaped_patterns(patterns)) do |path|
146
147
  next unless File.file?(path)
147
148
 
148
- log.debug { "restoring buffer file: path = #{path}" }
149
+ if owner.respond_to?(:buffer_config) && owner.buffer_config&.flush_at_shutdown
150
+ # When `flush_at_shutdown` is `true`, the remaining chunk files during resuming are possibly broken
151
+ # since there may be a power failure or similar failure.
152
+ log.warn { "restoring buffer file: path = #{path}" }
153
+ else
154
+ log.debug { "restoring buffer file: path = #{path}" }
155
+ end
149
156
 
150
157
  m = new_metadata() # this metadata will be overwritten by resuming .meta file content
151
158
  # so it should not added into @metadata_list for now
@@ -158,6 +165,7 @@ module Fluent
158
165
  begin
159
166
  chunk = Fluent::Plugin::Buffer::FileChunk.new(m, path, mode, compress: @compress) # file chunk resumes contents of metadata
160
167
  rescue Fluent::Plugin::Buffer::FileChunk::FileChunkError => e
168
+ exist_broken_file = true
161
169
  handle_broken_files(path, mode, e)
162
170
  next
163
171
  end
@@ -182,6 +190,15 @@ module Fluent
182
190
 
183
191
  queue.sort_by!{ |chunk| chunk.modified_at }
184
192
 
193
+ # If one of the files is corrupted, other files may also be corrupted and be undetected.
194
+ # The time priods of each chunk are helpful to check the data.
195
+ if exist_broken_file
196
+ log.info "Since a broken chunk file was found, it is possible that other files remaining at the time of resuming were also broken. Here is the list of the files."
197
+ (stage.values + queue).each { |chunk|
198
+ log.info " #{chunk.path}:", :created_at => chunk.created_at, :modified_at => chunk.modified_at
199
+ }
200
+ end
201
+
185
202
  return stage, queue
186
203
  end
187
204
 
@@ -195,8 +212,20 @@ module Fluent
195
212
  end
196
213
 
197
214
  def handle_broken_files(path, mode, e)
198
- log.error "found broken chunk file during resume. Deleted corresponding files:", :path => path, :mode => mode, :err_msg => e.message
199
- # After support 'backup_dir' feature, these files are moved to backup_dir instead of unlink.
215
+ log.error "found broken chunk file during resume.", :path => path, :mode => mode, :err_msg => e.message
216
+ unique_id = Fluent::Plugin::Buffer::FileChunk.unique_id_from_path(path)
217
+ backup(unique_id) { |f|
218
+ File.open(path, 'rb') { |chunk|
219
+ chunk.set_encoding(Encoding::ASCII_8BIT)
220
+ chunk.sync = true
221
+ chunk.binmode
222
+ IO.copy_stream(chunk, f)
223
+ }
224
+ }
225
+ rescue => error
226
+ log.error "backup failed. Delete corresponding files.", :err_msg => error.message
227
+ ensure
228
+ log.warn "disable_chunk_backup is true. #{dump_unique_id_hex(unique_id)} chunk is thrown away." if @disable_chunk_backup
200
229
  File.unlink(path, path + '.meta') rescue nil
201
230
  end
202
231