logstash-input-file 4.1.17 → 4.2.3

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/lib/filewatch/discoverer.rb +9 -8
  4. data/lib/filewatch/observing_base.rb +1 -12
  5. data/lib/filewatch/processor.rb +55 -0
  6. data/lib/filewatch/read_mode/handlers/base.rb +12 -11
  7. data/lib/filewatch/read_mode/handlers/read_file.rb +26 -8
  8. data/lib/filewatch/read_mode/handlers/read_zip_file.rb +8 -6
  9. data/lib/filewatch/read_mode/processor.rb +22 -36
  10. data/lib/filewatch/settings.rb +1 -2
  11. data/lib/filewatch/sincedb_collection.rb +39 -40
  12. data/lib/filewatch/sincedb_record_serializer.rb +5 -11
  13. data/lib/filewatch/stat/generic.rb +8 -13
  14. data/lib/filewatch/stat/windows_path.rb +7 -9
  15. data/lib/filewatch/tail_mode/handlers/base.rb +32 -23
  16. data/lib/filewatch/tail_mode/handlers/delete.rb +2 -4
  17. data/lib/filewatch/tail_mode/handlers/shrink.rb +2 -3
  18. data/lib/filewatch/tail_mode/handlers/unignore.rb +4 -4
  19. data/lib/filewatch/tail_mode/processor.rb +47 -54
  20. data/lib/filewatch/watch.rb +12 -14
  21. data/lib/filewatch/watched_file.rb +25 -14
  22. data/lib/filewatch/watched_files_collection.rb +11 -74
  23. data/lib/jars/filewatch-1.0.1.jar +0 -0
  24. data/lib/logstash/inputs/delete_completed_file_handler.rb +5 -0
  25. data/lib/logstash/inputs/file.rb +28 -13
  26. data/lib/logstash/inputs/file_listener.rb +3 -14
  27. data/logstash-input-file.gemspec +2 -1
  28. data/spec/filewatch/reading_spec.rb +60 -9
  29. data/spec/filewatch/settings_spec.rb +3 -0
  30. data/spec/filewatch/sincedb_record_serializer_spec.rb +6 -2
  31. data/spec/filewatch/spec_helper.rb +12 -14
  32. data/spec/filewatch/tailing_spec.rb +14 -12
  33. data/spec/filewatch/watched_file_spec.rb +30 -0
  34. data/spec/filewatch/watched_files_collection_spec.rb +62 -8
  35. data/spec/helpers/spec_helper.rb +1 -0
  36. data/spec/inputs/file_read_spec.rb +119 -0
  37. metadata +17 -2
@@ -35,5 +35,35 @@ module FileWatch
35
35
  expect(watched_file.recent_states).to eq([:watched, :active, :watched, :closed, :watched, :active, :unwatched, :active])
36
36
  end
37
37
  end
38
+
39
+ context 'restat' do
40
+
41
+ let(:directory) { Stud::Temporary.directory }
42
+ let(:file_path) { ::File.join(directory, "restat.file.txt") }
43
+ let(:pathname) { Pathname.new(file_path) }
44
+
45
+ before { FileUtils.touch file_path, :mtime => Time.now - 300 }
46
+
47
+ it 'reports false value when no changes' do
48
+ file = WatchedFile.new(pathname, PathStatClass.new(pathname), Settings.new)
49
+ mtime = file.modified_at
50
+ expect( file.modified_at_changed? ).to be false
51
+ expect( file.restat! ).to be_falsy
52
+ expect( file.modified_at_changed? ).to be false
53
+ expect( file.modified_at ).to eql mtime
54
+ expect( file.modified_at(true) ).to eql mtime
55
+ end
56
+
57
+ it 'reports truthy when changes detected' do
58
+ file = WatchedFile.new(pathname, PathStatClass.new(pathname), Settings.new)
59
+ mtime = file.modified_at
60
+ expect( file.modified_at_changed? ).to be false
61
+ FileUtils.touch file_path
62
+ expect( file.restat! ).to be_truthy
63
+ expect( file.modified_at_changed? ).to be true
64
+ expect( file.modified_at ).to eql mtime # until updated
65
+ expect( file.modified_at(true) ).to be > mtime
66
+ end
67
+ end
38
68
  end
39
69
  end
@@ -4,15 +4,18 @@ require_relative 'spec_helper'
4
4
  module FileWatch
5
5
  describe WatchedFilesCollection do
6
6
  let(:time) { Time.now }
7
- let(:filepath1){"/var/log/z.log"}
8
- let(:filepath2){"/var/log/m.log"}
9
- let(:filepath3){"/var/log/a.log"}
10
- let(:stat1) { double("stat1", :size => 98, :modified_at => time - 30, :identifier => nil, :inode => 234567, :inode_struct => InodeStruct.new("234567", 3, 2)) }
11
- let(:stat2) { double("stat2", :size => 99, :modified_at => time - 20, :identifier => nil, :inode => 234568, :inode_struct => InodeStruct.new("234568", 3, 2)) }
12
- let(:stat3) { double("stat3", :size => 100, :modified_at => time, :identifier => nil, :inode => 234569, :inode_struct => InodeStruct.new("234569", 3, 2)) }
7
+ let(:filepath1) { "/var/log/z.log" }
8
+ let(:filepath2) { "/var/log/m.log" }
9
+ let(:filepath3) { "/var/log/a.log" }
10
+ let(:filepath4) { "/var/log/b.log" }
11
+ let(:stat1) { double("stat1", :size => 98, :modified_at => time - 30, :inode => 234567, :inode_struct => InodeStruct.new("234567", 3, 2)) }
12
+ let(:stat2) { double("stat2", :size => 99, :modified_at => time - 20, :inode => 234568, :inode_struct => InodeStruct.new("234568", 3, 2)) }
13
+ let(:stat3) { double("stat3", :size => 100, :modified_at => time, :inode => 234569, :inode_struct => InodeStruct.new("234569", 3, 2)) }
14
+ let(:stat4) { double("stat4", :size => 99, :modified_at => time, :inode => 234570, :inode_struct => InodeStruct.new("234570", 3, 2)) }
13
15
  let(:wf1) { WatchedFile.new(filepath1, stat1, Settings.new) }
14
16
  let(:wf2) { WatchedFile.new(filepath2, stat2, Settings.new) }
15
17
  let(:wf3) { WatchedFile.new(filepath3, stat3, Settings.new) }
18
+ let(:wf4) { WatchedFile.new(filepath4, stat4, Settings.new) }
16
19
 
17
20
  context "sort by last_modified in ascending order" do
18
21
  let(:sort_by) { "last_modified" }
@@ -20,12 +23,29 @@ module FileWatch
20
23
 
21
24
  it "sorts earliest modified first" do
22
25
  collection = described_class.new(Settings.from_options(:file_sort_by => sort_by, :file_sort_direction => sort_direction))
26
+ expect(collection.empty?).to be true
23
27
  collection.add(wf2)
28
+ expect(collection.empty?).to be false
24
29
  expect(collection.values).to eq([wf2])
25
30
  collection.add(wf3)
26
31
  expect(collection.values).to eq([wf2, wf3])
27
32
  collection.add(wf1)
28
33
  expect(collection.values).to eq([wf1, wf2, wf3])
34
+ expect(collection.keys.size).to eq 3
35
+ end
36
+
37
+ it "sorts by path when mtime is same" do
38
+ collection = described_class.new(Settings.from_options(:file_sort_by => sort_by, :file_sort_direction => sort_direction))
39
+ expect(collection.size).to eq 0
40
+ collection.add(wf2)
41
+ collection.add(wf4)
42
+ collection.add(wf1)
43
+ expect(collection.size).to eq 3
44
+ expect(collection.values).to eq([wf1, wf2, wf4])
45
+ collection.add(wf3)
46
+ expect(collection.size).to eq 4
47
+ expect(collection.values).to eq([wf1, wf2, wf3, wf4])
48
+ expect(collection.keys.size).to eq 4
29
49
  end
30
50
  end
31
51
 
@@ -74,7 +94,7 @@ module FileWatch
74
94
  end
75
95
  end
76
96
 
77
- context "when delete called" do
97
+ context "remove_paths" do
78
98
  let(:sort_by) { "path" }
79
99
  let(:sort_direction) { "desc" }
80
100
 
@@ -85,9 +105,43 @@ module FileWatch
85
105
  collection.add(wf3)
86
106
  expect(collection.keys).to eq([filepath1, filepath2, filepath3])
87
107
 
88
- collection.delete([filepath2,filepath3])
108
+ ret = collection.remove_paths([filepath2, filepath3])
109
+ expect(ret).to eq 2
89
110
  expect(collection.keys).to eq([filepath1])
111
+ expect(collection.values.size).to eq 1
112
+
113
+ ret = collection.remove_paths([filepath2])
114
+ expect(ret).to eq 0
115
+ end
116
+ end
117
+
118
+ context "update" do
119
+ let(:sort_by) { "last_modified" }
120
+ let(:sort_direction) { "asc" }
121
+
122
+ let(:re_stat1) { double("restat1", :size => 99, :modified_at => time, :inode => 234567, :inode_struct => InodeStruct.new("234567", 3, 2)) }
123
+ let(:re_stat2) { double("restat2", :size => 99, :modified_at => time, :inode => 234568, :inode_struct => InodeStruct.new("234568", 3, 2)) }
124
+
125
+ it "updates entry with changed mtime" do
126
+ collection = described_class.new(Settings.from_options(:file_sort_by => sort_by, :file_sort_direction => sort_direction))
127
+ collection.add(wf1)
128
+ collection.add(wf2)
129
+ collection.add(wf3)
130
+ expect(collection.files).to eq([wf1, wf2, wf3])
131
+
132
+ wf2.send(:set_stat, re_stat2)
133
+ expect( wf2.modified_at_changed? ).to be_truthy
134
+
135
+ collection.update wf2
136
+ expect(collection.files).to eq([wf1, wf3, wf2])
137
+
138
+ wf1.send(:set_stat, re_stat1)
139
+ expect( wf1.modified_at_changed? ).to be_truthy
140
+ collection.update wf1
141
+ expect(collection.files).to eq([wf3, wf2, wf1])
90
142
 
143
+ collection.add(wf4)
144
+ expect(collection.files).to eq([wf3, wf4, wf2, wf1])
91
145
  end
92
146
  end
93
147
 
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require "logstash/devutils/rspec/spec_helper"
4
+ require "rspec/wait"
4
5
  require "rspec_sequencing"
5
6
 
6
7
  module FileInput
@@ -247,4 +247,123 @@ describe LogStash::Inputs::File do
247
247
  end
248
248
  end
249
249
  end
250
+
251
+ let(:temp_directory) { Stud::Temporary.directory }
252
+ let(:interval) { 0.1 }
253
+ let(:options) do
254
+ {
255
+ 'mode' => "read",
256
+ 'path' => "#{temp_directory}/*",
257
+ 'stat_interval' => interval,
258
+ 'discover_interval' => interval,
259
+ 'sincedb_path' => "#{temp_directory}/.sincedb",
260
+ 'sincedb_write_interval' => interval
261
+ }
262
+ end
263
+
264
+ let(:queue) { Queue.new }
265
+ let(:plugin) { LogStash::Inputs::File.new(options) }
266
+
267
+ describe 'delete on complete' do
268
+
269
+ let(:options) do
270
+ super.merge({ 'file_completed_action' => "delete", 'exit_after_read' => false })
271
+ end
272
+
273
+ let(:sample_file) { File.join(temp_directory, "sample.log") }
274
+
275
+ before do
276
+ plugin.register
277
+ @run_thread = Thread.new(plugin) do |plugin|
278
+ Thread.current.abort_on_exception = true
279
+ plugin.run queue
280
+ end
281
+
282
+ File.open(sample_file, 'w') { |fd| fd.write("sample-content\n") }
283
+
284
+ wait_for_start_processing(@run_thread)
285
+ end
286
+
287
+ after { plugin.stop }
288
+
289
+ it 'processes a file' do
290
+ wait_for_file_removal(sample_file) # watched discovery
291
+
292
+ expect( plugin.queue.size ).to eql 1
293
+ event = plugin.queue.pop
294
+ expect( event.get('message') ).to eql 'sample-content'
295
+ end
296
+
297
+ it 'removes watched file from collection' do
298
+ wait_for_file_removal(sample_file) # watched discovery
299
+ sleep(0.25) # give CI some space to execute the removal
300
+ # TODO shouldn't be necessary once WatchedFileCollection does proper locking
301
+ watched_files = plugin.watcher.watch.watched_files_collection
302
+ expect( watched_files ).to be_empty
303
+ end
304
+ end
305
+
306
+ describe 'sincedb cleanup' do
307
+
308
+ let(:options) do
309
+ super.merge(
310
+ 'sincedb_path' => sincedb_path,
311
+ 'sincedb_clean_after' => '1.0 seconds',
312
+ 'sincedb_write_interval' => 0.25,
313
+ 'stat_interval' => 0.1,
314
+ )
315
+ end
316
+
317
+ let(:sincedb_path) { "#{temp_directory}/.sincedb" }
318
+
319
+ let(:sample_file) { File.join(temp_directory, "sample.txt") }
320
+
321
+ before do
322
+ plugin.register
323
+ @run_thread = Thread.new(plugin) do |plugin|
324
+ Thread.current.abort_on_exception = true
325
+ plugin.run queue
326
+ end
327
+
328
+ File.open(sample_file, 'w') { |fd| fd.write("line1\nline2\n") }
329
+
330
+ wait_for_start_processing(@run_thread)
331
+ end
332
+
333
+ after { plugin.stop }
334
+
335
+ it 'cleans up sincedb entry' do
336
+ wait_for_file_removal(sample_file) # watched discovery
337
+
338
+ sincedb_content = File.read(sincedb_path).strip
339
+ expect( sincedb_content ).to_not be_empty
340
+
341
+ Stud.try(3.times) do
342
+ sleep(1.5) # > sincedb_clean_after
343
+
344
+ sincedb_content = File.read(sincedb_path).strip
345
+ expect( sincedb_content ).to be_empty
346
+ end
347
+ end
348
+
349
+ end
350
+
351
+ private
352
+
353
+ def wait_for_start_processing(run_thread, timeout: 1.0)
354
+ begin
355
+ Timeout.timeout(timeout) do
356
+ sleep(0.01) while run_thread.status != 'sleep'
357
+ sleep(timeout) unless plugin.queue
358
+ end
359
+ rescue Timeout::Error
360
+ raise "plugin did not start processing (timeout: #{timeout})" unless plugin.queue
361
+ else
362
+ raise "plugin did not start processing" unless plugin.queue
363
+ end
364
+ end
365
+
366
+ def wait_for_file_removal(path, timeout: 3 * interval)
367
+ wait(timeout).for { File.exist?(path) }.to be_falsey
368
+ end
250
369
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-file
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.17
4
+ version: 4.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-03 00:00:00.000000000 Z
11
+ date: 2020-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +58,20 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.0'
67
+ name: concurrent-ruby
68
+ prerelease: false
69
+ type: :runtime
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
61
75
  - !ruby/object:Gem::Dependency
62
76
  requirement: !ruby/object:Gem::Requirement
63
77
  requirements:
@@ -178,6 +192,7 @@ files:
178
192
  - lib/filewatch/observing_base.rb
179
193
  - lib/filewatch/observing_read.rb
180
194
  - lib/filewatch/observing_tail.rb
195
+ - lib/filewatch/processor.rb
181
196
  - lib/filewatch/read_mode/handlers/base.rb
182
197
  - lib/filewatch/read_mode/handlers/read_file.rb
183
198
  - lib/filewatch/read_mode/handlers/read_zip_file.rb