fluentd 1.12.4-x86-mingw32 → 1.13.0-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/windows-test.yaml +13 -2
- data/CHANGELOG.md +42 -0
- data/CONTRIBUTING.md +2 -2
- data/MAINTAINERS.md +1 -1
- data/README.md +1 -1
- data/example/counter.conf +1 -1
- data/lib/fluent/command/cat.rb +19 -3
- data/lib/fluent/command/fluentd.rb +1 -2
- data/lib/fluent/log.rb +1 -0
- data/lib/fluent/plugin/file_wrapper.rb +13 -4
- data/lib/fluent/plugin/in_http.rb +10 -0
- data/lib/fluent/plugin/in_tail.rb +119 -39
- data/lib/fluent/plugin/out_forward.rb +13 -30
- data/lib/fluent/plugin_helper/service_discovery.rb +39 -1
- data/lib/fluent/plugin_helper/service_discovery/manager.rb +11 -5
- data/lib/fluent/supervisor.rb +15 -0
- data/lib/fluent/system_config.rb +14 -0
- data/lib/fluent/version.rb +1 -1
- data/test/command/test_cat.rb +96 -0
- data/test/config/test_system_config.rb +46 -0
- data/test/plugin/in_tail/test_io_handler.rb +4 -4
- data/test/plugin/test_file_wrapper.rb +11 -1
- data/test/plugin/test_in_http.rb +15 -0
- data/test/plugin/test_in_tail.rb +223 -1
- data/test/plugin/test_out_forward.rb +11 -6
- data/test/plugin_helper/test_service_discovery.rb +74 -14
- data/test/test_config.rb +2 -1
- data/test/test_supervisor.rb +35 -0
- metadata +4 -2
@@ -227,31 +227,14 @@ module Fluent::Plugin
|
|
227
227
|
socket_cache: socket_cache,
|
228
228
|
)
|
229
229
|
|
230
|
-
|
231
|
-
|
232
|
-
# rewrite for using server as sd_static
|
233
|
-
conf.elements(name: 'server').each do |s|
|
234
|
-
s.name = 'service'
|
235
|
-
end
|
236
|
-
|
237
|
-
unless conf.elements(name: 'service').empty?
|
238
|
-
# To copy `services` element only
|
239
|
-
new_elem = Fluent::Config::Element.new('static_service_discovery', {}, {}, conf.elements(name: 'service'))
|
240
|
-
configs << { type: :static, conf: new_elem }
|
241
|
-
end
|
242
|
-
|
243
|
-
conf.elements(name: 'service_discovery').each_with_index do |c, i|
|
244
|
-
configs << { type: @service_discovery[i][:@type], conf: c }
|
245
|
-
end
|
246
|
-
|
247
|
-
service_discovery_create_manager(
|
230
|
+
service_discovery_configure(
|
248
231
|
:out_forward_service_discovery_watcher,
|
249
|
-
|
232
|
+
static_default_service_directive: 'server',
|
250
233
|
load_balancer: LoadBalancer.new(log),
|
251
234
|
custom_build_method: method(:build_node),
|
252
235
|
)
|
253
236
|
|
254
|
-
|
237
|
+
service_discovery_services.each do |server|
|
255
238
|
# it's only for test
|
256
239
|
@nodes << server
|
257
240
|
unless @heartbeat_type == :none
|
@@ -273,7 +256,7 @@ module Fluent::Plugin
|
|
273
256
|
end
|
274
257
|
end
|
275
258
|
|
276
|
-
if
|
259
|
+
if service_discovery_services.empty?
|
277
260
|
raise Fluent::ConfigError, "forward output plugin requires at least one node is required. Add <server> or <service_discovery>"
|
278
261
|
end
|
279
262
|
|
@@ -306,7 +289,7 @@ module Fluent::Plugin
|
|
306
289
|
|
307
290
|
unless @heartbeat_type == :none
|
308
291
|
if @heartbeat_type == :udp
|
309
|
-
@usock = socket_create_udp(
|
292
|
+
@usock = socket_create_udp(service_discovery_services.first.host, service_discovery_services.first.port, nonblock: true)
|
310
293
|
server_create_udp(:out_forward_heartbeat_receiver, 0, socket: @usock, max_bytes: @read_length, &method(:on_udp_heatbeat_response_recv))
|
311
294
|
end
|
312
295
|
timer_execute(:out_forward_heartbeat_request, @heartbeat_interval, &method(:on_heartbeat_timer))
|
@@ -318,7 +301,7 @@ module Fluent::Plugin
|
|
318
301
|
end
|
319
302
|
|
320
303
|
if @verify_connection_at_startup
|
321
|
-
|
304
|
+
service_discovery_services.each do |node|
|
322
305
|
begin
|
323
306
|
node.verify_connection
|
324
307
|
rescue StandardError => e
|
@@ -374,7 +357,7 @@ module Fluent::Plugin
|
|
374
357
|
return if chunk.empty?
|
375
358
|
tag = chunk.metadata.tag
|
376
359
|
|
377
|
-
|
360
|
+
service_discovery_select_service { |node| node.send_data(tag, chunk) }
|
378
361
|
end
|
379
362
|
|
380
363
|
def try_write(chunk)
|
@@ -384,7 +367,7 @@ module Fluent::Plugin
|
|
384
367
|
return
|
385
368
|
end
|
386
369
|
tag = chunk.metadata.tag
|
387
|
-
|
370
|
+
service_discovery_select_service { |node| node.send_data(tag, chunk) }
|
388
371
|
last_ack if @require_ack_response && @suspend_flush
|
389
372
|
end
|
390
373
|
|
@@ -434,7 +417,7 @@ module Fluent::Plugin
|
|
434
417
|
|
435
418
|
def statistics
|
436
419
|
stats = super
|
437
|
-
services =
|
420
|
+
services = service_discovery_services
|
438
421
|
healthy_nodes_count = 0
|
439
422
|
registed_nodes_count = services.size
|
440
423
|
services.each do |s|
|
@@ -471,7 +454,7 @@ module Fluent::Plugin
|
|
471
454
|
|
472
455
|
def on_heartbeat_timer
|
473
456
|
need_rebuild = false
|
474
|
-
|
457
|
+
service_discovery_services.each do |n|
|
475
458
|
begin
|
476
459
|
log.trace "sending heartbeat", host: n.host, port: n.port, heartbeat_type: @heartbeat_type
|
477
460
|
n.usock = @usock if @usock
|
@@ -486,16 +469,16 @@ module Fluent::Plugin
|
|
486
469
|
end
|
487
470
|
|
488
471
|
if need_rebuild
|
489
|
-
|
472
|
+
service_discovery_rebalance
|
490
473
|
end
|
491
474
|
end
|
492
475
|
|
493
476
|
def on_udp_heatbeat_response_recv(data, sock)
|
494
477
|
sockaddr = Socket.pack_sockaddr_in(sock.remote_port, sock.remote_host)
|
495
|
-
if node =
|
478
|
+
if node = service_discovery_services.find { |n| n.sockaddr == sockaddr }
|
496
479
|
# log.trace "heartbeat arrived", name: node.name, host: node.host, port: node.port
|
497
480
|
if node.heartbeat
|
498
|
-
|
481
|
+
service_discovery_rebalance
|
499
482
|
end
|
500
483
|
else
|
501
484
|
log.warn("Unknown heartbeat response received from #{sock.remote_host}:#{sock.remote_port}. It may service out")
|
@@ -22,10 +22,19 @@ module Fluent
|
|
22
22
|
module ServiceDiscovery
|
23
23
|
include Fluent::PluginHelper::Timer
|
24
24
|
|
25
|
+
# For the compatibility with older versions without `param_name: :service_discovery_configs`
|
26
|
+
attr_reader :service_discovery
|
27
|
+
|
25
28
|
def self.included(mod)
|
26
29
|
mod.include ServiceDiscoveryParams
|
27
30
|
end
|
28
31
|
|
32
|
+
def configure(conf)
|
33
|
+
super
|
34
|
+
# For the compatibility with older versions without `param_name: :service_discovery_configs`
|
35
|
+
@service_discovery = @service_discovery_configs
|
36
|
+
end
|
37
|
+
|
29
38
|
def start
|
30
39
|
unless @discovery_manager
|
31
40
|
log.warn('There is no discovery_manager. skip start them')
|
@@ -52,6 +61,35 @@ module Fluent
|
|
52
61
|
|
53
62
|
private
|
54
63
|
|
64
|
+
# @param title [Symbol] the thread name. this value should be unique.
|
65
|
+
# @param static_default_service_directive [String] the directive name of each service when "static" service discovery is enabled in default
|
66
|
+
# @param load_balancer [Object] object which has two methods #rebalance and #select_service
|
67
|
+
# @param custom_build_method [Proc]
|
68
|
+
def service_discovery_configure(title, static_default_service_directive: nil, load_balancer: nil, custom_build_method: nil, interval: 3)
|
69
|
+
configs = @service_discovery_configs.map(&:corresponding_config_element)
|
70
|
+
if static_default_service_directive
|
71
|
+
configs.prepend Fluent::Config::Element.new(
|
72
|
+
'service_discovery',
|
73
|
+
'',
|
74
|
+
{'@type' => 'static'},
|
75
|
+
@config.elements(name: static_default_service_directive.to_s).map{|e| Fluent::Config::Element.new('service', e.arg, e.dup, e.elements, e.unused) }
|
76
|
+
)
|
77
|
+
end
|
78
|
+
service_discovery_create_manager(title, configurations: configs, load_balancer: load_balancer, custom_build_method: custom_build_method, interval: interval)
|
79
|
+
end
|
80
|
+
|
81
|
+
def service_discovery_select_service(&block)
|
82
|
+
@discovery_manager.select_service(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def service_discovery_services
|
86
|
+
@discovery_manager.services
|
87
|
+
end
|
88
|
+
|
89
|
+
def service_discovery_rebalance
|
90
|
+
@discovery_manager.rebalance
|
91
|
+
end
|
92
|
+
|
55
93
|
# @param title [Symbol] the thread name. this value should be unique.
|
56
94
|
# @param configurations [Hash] hash which must has discivery_service type and its configuration like `{ type: :static, conf: <Fluent::Config::Element> }`
|
57
95
|
# @param load_balancer [Object] object which has two methods #rebalance and #select_service
|
@@ -78,7 +116,7 @@ module Fluent
|
|
78
116
|
module ServiceDiscoveryParams
|
79
117
|
include Fluent::Configurable
|
80
118
|
|
81
|
-
config_section :service_discovery do
|
119
|
+
config_section :service_discovery, multi: true, param_name: :service_discovery_configs do
|
82
120
|
config_param :@type, :string
|
83
121
|
end
|
84
122
|
end
|
@@ -32,17 +32,23 @@ module Fluent
|
|
32
32
|
@static_config = true
|
33
33
|
end
|
34
34
|
|
35
|
-
def configure(
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
def configure(configs, parent: nil)
|
36
|
+
configs.each do |config|
|
37
|
+
type, conf = if config.has_key?(:conf) # for compatibility with initial API
|
38
|
+
[config[:type], config[:conf]]
|
39
|
+
else
|
40
|
+
[config['@type'], config]
|
41
|
+
end
|
42
|
+
|
43
|
+
sd = Fluent::Plugin.new_sd(type, parent: parent)
|
44
|
+
sd.configure(conf)
|
39
45
|
|
40
46
|
sd.services.each do |s|
|
41
47
|
@services[s.discovery_id] = build_service(s)
|
42
48
|
end
|
43
49
|
@discoveries << sd
|
44
50
|
|
45
|
-
if @static_config &&
|
51
|
+
if @static_config && type.to_sym != :static
|
46
52
|
@static_config = false
|
47
53
|
end
|
48
54
|
end
|
data/lib/fluent/supervisor.rb
CHANGED
@@ -518,6 +518,8 @@ module Fluent
|
|
518
518
|
dl_opts = {}
|
519
519
|
# subtract 1 to match serverengine daemon logger side logging severity.
|
520
520
|
dl_opts[:log_level] = @level - 1
|
521
|
+
dl_opts[:log_rotate_age] = @log_rotate_age if @log_rotate_age
|
522
|
+
dl_opts[:log_rotate_size] = @log_rotate_size if @log_rotate_size
|
521
523
|
logger = ServerEngine::DaemonLogger.new(@logdev, dl_opts)
|
522
524
|
$log = Fluent::Log.new(logger, @opts)
|
523
525
|
$log.enable_color(false) if @path
|
@@ -606,6 +608,19 @@ module Fluent
|
|
606
608
|
|
607
609
|
@cl_opt = opt
|
608
610
|
@conf = nil
|
611
|
+
# parse configuration immediately to initialize logger in early stage
|
612
|
+
if @config_path and File.exist?(@config_path)
|
613
|
+
@conf = Fluent::Config.build(config_path: @config_path,
|
614
|
+
encoding: @conf_encoding ? @conf_encoding : 'utf-8',
|
615
|
+
additional_config: @inline_config ? @inline_config : nil,
|
616
|
+
use_v1_config: !!@use_v1_config)
|
617
|
+
@system_config = build_system_config(@conf)
|
618
|
+
if @system_config.log
|
619
|
+
@log_rotate_age ||= @system_config.log.rotate_age
|
620
|
+
@log_rotate_size ||= @system_config.log.rotate_size
|
621
|
+
end
|
622
|
+
@conf = nil
|
623
|
+
end
|
609
624
|
|
610
625
|
log_opts = {suppress_repeated_stacktrace: opt[:suppress_repeated_stacktrace], ignore_repeated_log_interval: opt[:ignore_repeated_log_interval],
|
611
626
|
ignore_same_log_interval: opt[:ignore_same_log_interval]}
|
data/lib/fluent/system_config.rb
CHANGED
@@ -55,6 +55,20 @@ module Fluent
|
|
55
55
|
config_section :log, required: false, init: true, multi: false do
|
56
56
|
config_param :format, :enum, list: [:text, :json], default: :text
|
57
57
|
config_param :time_format, :string, default: '%Y-%m-%d %H:%M:%S %z'
|
58
|
+
config_param :rotate_age, default: nil do |v|
|
59
|
+
if Fluent::Log::LOG_ROTATE_AGE.include?(v)
|
60
|
+
v.to_sym
|
61
|
+
else
|
62
|
+
begin
|
63
|
+
Integer(v)
|
64
|
+
rescue ArgumentError => e
|
65
|
+
raise Fluent::ConfigError, e.message
|
66
|
+
else
|
67
|
+
v.to_i
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
config_param :rotate_size, :size, default: nil
|
58
72
|
end
|
59
73
|
|
60
74
|
config_section :counter_server, multi: false do
|
data/lib/fluent/version.rb
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
require 'test-unit'
|
4
|
+
require 'open3'
|
5
|
+
require 'fluent/plugin/output'
|
6
|
+
require 'fluent/plugin/in_forward'
|
7
|
+
require 'fluent/plugin/out_secondary_file'
|
8
|
+
require 'fluent/test/driver/output'
|
9
|
+
require 'fluent/test/driver/input'
|
10
|
+
|
11
|
+
class TestFluentCat < ::Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
Fluent::Test.setup
|
14
|
+
FileUtils.mkdir_p(TMP_DIR)
|
15
|
+
@record = { 'key' => 'value' }
|
16
|
+
@time = event_time
|
17
|
+
@es = Fluent::OneEventStream.new(@time, @record)
|
18
|
+
@primary = create_primary
|
19
|
+
metadata = @primary.buffer.new_metadata
|
20
|
+
@chunk = create_chunk(@primary, metadata, @es)
|
21
|
+
end
|
22
|
+
|
23
|
+
def teardown
|
24
|
+
FileUtils.rm_rf(TMP_DIR)
|
25
|
+
end
|
26
|
+
|
27
|
+
TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/../tmp/command/fluent_cat#{ENV['TEST_ENV_NUMBER']}")
|
28
|
+
FLUENT_CAT_COMMAND = File.expand_path(File.dirname(__FILE__) + "/../../bin/fluent-cat")
|
29
|
+
|
30
|
+
PORT = unused_port
|
31
|
+
CONFIG = %[
|
32
|
+
port #{PORT}
|
33
|
+
bind 127.0.0.1
|
34
|
+
]
|
35
|
+
|
36
|
+
SECONDARY_CONFIG = %[
|
37
|
+
directory #{TMP_DIR}
|
38
|
+
]
|
39
|
+
|
40
|
+
class DummyOutput < Fluent::Plugin::Output
|
41
|
+
def write(chunk); end
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_driver(conf=CONFIG)
|
45
|
+
Fluent::Test::Driver::Input.new(Fluent::Plugin::ForwardInput).configure(conf)
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_primary(buffer_cofig = config_element('buffer'))
|
49
|
+
DummyOutput.new.configure(config_element('ROOT','',{}, [buffer_cofig]))
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_secondary_driver(conf=SECONDARY_CONFIG)
|
53
|
+
c = Fluent::Test::Driver::Output.new(Fluent::Plugin::SecondaryFileOutput)
|
54
|
+
c.instance.acts_as_secondary(@primary)
|
55
|
+
c.configure(conf)
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_chunk(primary, metadata, es)
|
59
|
+
primary.buffer.generate_chunk(metadata).tap do |c|
|
60
|
+
c.concat(es.to_msgpack_stream, es.size)
|
61
|
+
c.commit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
sub_test_case "json" do
|
66
|
+
def test_cat_json
|
67
|
+
d = create_driver
|
68
|
+
d.run(expect_records: 1) do
|
69
|
+
Open3.pipeline_w("ruby #{FLUENT_CAT_COMMAND} --port #{PORT} json") do |stdin|
|
70
|
+
stdin.puts('{"key":"value"}')
|
71
|
+
stdin.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
event = d.events.first
|
75
|
+
assert_equal([1, "json", @record],
|
76
|
+
[d.events.size, event.first, event.last])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
sub_test_case "msgpack" do
|
81
|
+
def test_cat_secondary_file
|
82
|
+
d = create_secondary_driver
|
83
|
+
path = d.instance.write(@chunk)
|
84
|
+
d = create_driver
|
85
|
+
d.run(expect_records: 1) do
|
86
|
+
Open3.pipeline_w("ruby #{FLUENT_CAT_COMMAND} --port #{PORT} --format msgpack secondary") do |stdin|
|
87
|
+
stdin.write(File.read(path))
|
88
|
+
stdin.close
|
89
|
+
end
|
90
|
+
end
|
91
|
+
event = d.events.first
|
92
|
+
assert_equal([1, "secondary", @record],
|
93
|
+
[d.events.size, event.first, event.last])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -143,5 +143,51 @@ module Fluent::Config
|
|
143
143
|
sc.overwrite_variables(**s.for_system_config)
|
144
144
|
assert_equal(level, sc.log_level)
|
145
145
|
end
|
146
|
+
|
147
|
+
sub_test_case "log rotation" do
|
148
|
+
data('daily' => "daily",
|
149
|
+
'weekly' => 'weekly',
|
150
|
+
'monthly' => 'monthly')
|
151
|
+
test "symbols for rotate_age" do |age|
|
152
|
+
conf = parse_text(<<-EOS)
|
153
|
+
<system>
|
154
|
+
<log>
|
155
|
+
rotate_age #{age}
|
156
|
+
</log>
|
157
|
+
</system>
|
158
|
+
EOS
|
159
|
+
sc = Fluent::SystemConfig.new(conf)
|
160
|
+
assert_equal(age.to_sym, sc.log.rotate_age)
|
161
|
+
end
|
162
|
+
|
163
|
+
test "numeric number for rotate age" do
|
164
|
+
conf = parse_text(<<-EOS)
|
165
|
+
<system>
|
166
|
+
<log>
|
167
|
+
rotate_age 3
|
168
|
+
</log>
|
169
|
+
</system>
|
170
|
+
EOS
|
171
|
+
s = FakeSupervisor.new
|
172
|
+
sc = Fluent::SystemConfig.new(conf)
|
173
|
+
assert_equal(3, sc.log.rotate_age)
|
174
|
+
end
|
175
|
+
|
176
|
+
data(h: ['100', 100],
|
177
|
+
k: ['1k', 1024],
|
178
|
+
m: ['1m', 1024 * 1024],
|
179
|
+
g: ['1g', 1024 * 1024 * 1024])
|
180
|
+
test "numeric and SI prefix for rotate_size" do |(label, size)|
|
181
|
+
conf = parse_text(<<-EOS)
|
182
|
+
<system>
|
183
|
+
<log>
|
184
|
+
rotate_size #{label}
|
185
|
+
</log>
|
186
|
+
</system>
|
187
|
+
EOS
|
188
|
+
sc = Fluent::SystemConfig.new(conf)
|
189
|
+
assert_equal(size, sc.log.rotate_size)
|
190
|
+
end
|
191
|
+
end
|
146
192
|
end
|
147
193
|
end
|
@@ -30,7 +30,7 @@ class IntailIOHandlerTest < Test::Unit::TestCase
|
|
30
30
|
end
|
31
31
|
|
32
32
|
returned_lines = ''
|
33
|
-
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 100, log: $log, open_on_every_update: false) do |lines, _watcher|
|
33
|
+
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 100, read_bytes_limit_per_second: -1, log: $log, open_on_every_update: false) do |lines, _watcher|
|
34
34
|
returned_lines << lines.join
|
35
35
|
true
|
36
36
|
end
|
@@ -62,7 +62,7 @@ class IntailIOHandlerTest < Test::Unit::TestCase
|
|
62
62
|
end
|
63
63
|
|
64
64
|
returned_lines = ''
|
65
|
-
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 100, log: $log, open_on_every_update: true) do |lines, _watcher|
|
65
|
+
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 100, read_bytes_limit_per_second: -1, log: $log, open_on_every_update: true) do |lines, _watcher|
|
66
66
|
returned_lines << lines.join
|
67
67
|
true
|
68
68
|
end
|
@@ -93,7 +93,7 @@ class IntailIOHandlerTest < Test::Unit::TestCase
|
|
93
93
|
end
|
94
94
|
|
95
95
|
returned_lines = []
|
96
|
-
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 5, log: $log, open_on_every_update: false) do |lines, _watcher|
|
96
|
+
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 5, read_bytes_limit_per_second: -1, log: $log, open_on_every_update: false) do |lines, _watcher|
|
97
97
|
returned_lines << lines.dup
|
98
98
|
true
|
99
99
|
end
|
@@ -119,7 +119,7 @@ class IntailIOHandlerTest < Test::Unit::TestCase
|
|
119
119
|
end
|
120
120
|
|
121
121
|
returned_lines = []
|
122
|
-
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 5, log: $log, open_on_every_update: false) do |lines, _watcher|
|
122
|
+
r = Fluent::Plugin::TailInput::TailWatcher::IOHandler.new(watcher, path: @file.path, read_lines_limit: 5, read_bytes_limit_per_second: -1, log: $log, open_on_every_update: false) do |lines, _watcher|
|
123
123
|
returned_lines << lines.dup
|
124
124
|
true
|
125
125
|
end
|