logstash-input-file 4.1.18 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,22 +3,20 @@
3
3
  module FileWatch module Stat
4
4
  class WindowsPath
5
5
 
6
- attr_reader :identifier, :inode, :modified_at, :size, :inode_struct
6
+ attr_reader :inode, :modified_at, :size, :inode_struct
7
7
 
8
8
  def initialize(source)
9
- @source = source
9
+ @source = source # Pathname
10
10
  @inode = Winhelper.identifier_from_path(@source.to_path)
11
- @dev_major = 0
12
- @dev_minor = 0
13
11
  # in windows the dev hi and low are in the identifier
14
- @inode_struct = InodeStruct.new(@inode, @dev_major, @dev_minor)
12
+ @inode_struct = InodeStruct.new(@inode, 0, 0)
15
13
  restat
16
14
  end
17
15
 
18
16
  def restat
19
- @inner_stat = @source.stat
20
- @modified_at = @inner_stat.mtime.to_f
21
- @size = @inner_stat.size
17
+ stat = @source.stat
18
+ @modified_at = stat.mtime.to_f
19
+ @size = stat.size
22
20
  end
23
21
 
24
22
  def windows?
@@ -26,7 +24,7 @@ module FileWatch module Stat
26
24
  end
27
25
 
28
26
  def inspect
29
- "<WindowsPath size='#{@size}', modified_at='#{@modified_at}', inode='#{@inode}', inode_struct='#{@inode_struct}'>"
27
+ "<#{self.class.name} size=#{@size}, modified_at=#{@modified_at}, inode=#{@inode}, inode_struct=#{@inode_struct}>"
30
28
  end
31
29
  end
32
30
  end end
@@ -7,11 +7,9 @@ module FileWatch module TailMode module Handlers
7
7
  # TODO consider trying to find the renamed file - it will have the same inode.
8
8
  # Needs a rotate scheme rename hint from user e.g. "<name>-YYYY-MM-DD-N.<ext>" or "<name>.<ext>.N"
9
9
  # send the found content to the same listener (stream identity)
10
- logger.trace("info",
11
- "watched_file details" => watched_file.details,
12
- "path" => watched_file.path)
10
+ logger.trace("delete", :path => watched_file.path, :watched_file => watched_file.details)
13
11
  if watched_file.bytes_unread > 0
14
- logger.warn(DATA_LOSS_WARNING, "unread_bytes" => watched_file.bytes_unread, "path" => watched_file.path)
12
+ logger.warn(DATA_LOSS_WARNING, :path => watched_file.path, :unread_bytes => watched_file.bytes_unread)
15
13
  end
16
14
  watched_file.listener.deleted
17
15
  # no need to worry about data in the buffer
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
- require "logstash/util/loggable"
2
+ require 'filewatch/processor'
3
3
  require_relative "handlers/base"
4
4
  require_relative "handlers/create_initial"
5
5
  require_relative "handlers/create"
@@ -18,20 +18,7 @@ module FileWatch module TailMode
18
18
  # :delete - file can't be read
19
19
  # :timeout - file is closable
20
20
  # :unignore - file was ignored, but have now received new content
21
- class Processor
22
- include LogStash::Util::Loggable
23
-
24
- attr_reader :watch, :deletable_filepaths
25
-
26
- def initialize(settings)
27
- @settings = settings
28
- @deletable_filepaths = []
29
- end
30
-
31
- def add_watch(watch)
32
- @watch = watch
33
- self
34
- end
21
+ class Processor < FileWatch::Processor
35
22
 
36
23
  def initialize_handlers(sincedb_collection, observer)
37
24
  @sincedb_collection = sincedb_collection
@@ -91,11 +78,12 @@ module FileWatch module TailMode
91
78
  private
92
79
 
93
80
  def process_closed(watched_files)
94
- # logger.trace("Closed processing")
81
+ logger.trace(__method__.to_s)
95
82
  # Handles watched_files in the closed state.
96
83
  # if its size changed it is put into the watched state
97
- watched_files.select {|wf| wf.closed? }.each do |watched_file|
98
- common_restat_with_delay(watched_file, "Closed") do
84
+ watched_files.each do |watched_file|
85
+ next unless watched_file.closed?
86
+ common_restat_with_delay(watched_file, __method__) do
99
87
  # it won't do this if rotation is detected
100
88
  if watched_file.size_changed?
101
89
  # if the closed file changed, move it to the watched state
@@ -108,13 +96,14 @@ module FileWatch module TailMode
108
96
  end
109
97
 
110
98
  def process_ignored(watched_files)
111
- # logger.trace("Ignored processing")
99
+ logger.trace(__method__.to_s)
112
100
  # Handles watched_files in the ignored state.
113
101
  # if its size changed:
114
102
  # put it in the watched state
115
103
  # invoke unignore
116
- watched_files.select {|wf| wf.ignored? }.each do |watched_file|
117
- common_restat_with_delay(watched_file, "Ignored") do
104
+ watched_files.each do |watched_file|
105
+ next unless watched_file.ignored?
106
+ common_restat_with_delay(watched_file, __method__) do
118
107
  # it won't do this if rotation is detected
119
108
  if watched_file.size_changed?
120
109
  watched_file.watch
@@ -128,11 +117,12 @@ module FileWatch module TailMode
128
117
  def process_delayed_delete(watched_files)
129
118
  # defer the delete to one loop later to ensure that the stat really really can't find a renamed file
130
119
  # because a `stat` can be called right in the middle of the rotation rename cascade
131
- logger.trace("Delayed Delete processing")
132
- watched_files.select {|wf| wf.delayed_delete?}.each do |watched_file|
133
- logger.trace(">>> Delayed Delete", "path" => watched_file.filename)
134
- common_restat_without_delay(watched_file, ">>> Delayed Delete") do
135
- logger.trace(">>> Delayed Delete: file at path found again", "watched_file" => watched_file.details)
120
+ logger.trace(__method__.to_s)
121
+ watched_files.each do |watched_file|
122
+ next unless watched_file.delayed_delete?
123
+ logger.trace(">>> Delayed Delete", :path => watched_file.path)
124
+ common_restat_without_delay(watched_file, __method__) do
125
+ logger.trace(">>> Delayed Delete: file at path found again", :watched_file => watched_file.details)
136
126
  watched_file.file_at_path_found_again
137
127
  end
138
128
  end
@@ -140,33 +130,35 @@ module FileWatch module TailMode
140
130
 
141
131
  def process_restat_for_watched_and_active(watched_files)
142
132
  # do restat on all watched and active states once now. closed and ignored have been handled already
143
- logger.trace("Watched + Active restat processing")
144
- watched_files.select {|wf| wf.watched? || wf.active?}.each do |watched_file|
145
- common_restat_with_delay(watched_file, "Watched")
133
+ logger.trace(__method__.to_s)
134
+ watched_files.each do |watched_file|
135
+ next if !watched_file.watched? && !watched_file.active?
136
+ common_restat_with_delay(watched_file, __method__)
146
137
  end
147
138
  end
148
139
 
149
140
  def process_rotation_in_progress(watched_files)
150
- logger.trace("Rotation In Progress processing")
151
- watched_files.select {|wf| wf.rotation_in_progress?}.each do |watched_file|
141
+ logger.trace(__method__.to_s)
142
+ watched_files.each do |watched_file|
143
+ next unless watched_file.rotation_in_progress?
152
144
  if !watched_file.all_read?
153
145
  if watched_file.file_open?
154
146
  # rotated file but original opened file is not fully read
155
147
  # we need to keep reading the open file, if we close it we lose it because the path is now pointing at a different file.
156
- logger.trace(">>> Rotation In Progress - inode change detected and original content is not fully read, reading all", "watched_file details" => watched_file.details)
148
+ logger.trace(">>> Rotation In Progress - inode change detected and original content is not fully read, reading all", :watched_file => watched_file.details)
157
149
  # need to fully read open file while we can
158
150
  watched_file.set_maximum_read_loop
159
151
  grow(watched_file)
160
152
  watched_file.set_standard_read_loop
161
153
  else
162
- logger.warn(">>> Rotation In Progress - inode change detected and original content is not fully read, file is closed and path points to new content", "watched_file details" => watched_file.details)
154
+ logger.warn(">>> Rotation In Progress - inode change detected and original content is not fully read, file is closed and path points to new content", :watched_file => watched_file.details)
163
155
  end
164
156
  end
165
157
  current_key = watched_file.sincedb_key
166
158
  sdb_value = @sincedb_collection.get(current_key)
167
159
  potential_key = watched_file.stat_sincedb_key
168
160
  potential_sdb_value = @sincedb_collection.get(potential_key)
169
- logger.trace(">>> Rotation In Progress", "watched_file" => watched_file.details, "found_sdb_value" => sdb_value, "potential_key" => potential_key, "potential_sdb_value" => potential_sdb_value)
161
+ logger.trace(">>> Rotation In Progress", :watched_file => watched_file.details, :found_sdb_value => sdb_value, :potential_key => potential_key, :potential_sdb_value => potential_sdb_value)
170
162
  if potential_sdb_value.nil?
171
163
  logger.trace("---------- >>>> Rotation In Progress: rotating as existing file")
172
164
  watched_file.rotate_as_file
@@ -189,13 +181,13 @@ module FileWatch module TailMode
189
181
  sdb_value.clear_watched_file unless sdb_value.nil?
190
182
  potential_sdb_value.set_watched_file(watched_file)
191
183
  else
192
- logger.trace("---------- >>>> Rotation In Progress: rotating from...", "this watched_file details" => watched_file.details, "other watched_file details" => other_watched_file.details)
184
+ logger.trace("---------- >>>> Rotation In Progress: rotating from...", :this_watched_file => watched_file.details, :other_watched_file => other_watched_file.details)
193
185
  watched_file.rotate_from(other_watched_file)
194
186
  sdb_value.clear_watched_file unless sdb_value.nil?
195
187
  potential_sdb_value.set_watched_file(watched_file)
196
188
  end
197
189
  end
198
- logger.trace("---------- >>>> Rotation In Progress: after handling rotation", "this watched_file details" => watched_file.details, "sincedb_value" => (potential_sdb_value || sdb_value))
190
+ logger.trace("---------- >>>> Rotation In Progress: after handling rotation", :this_watched_file => watched_file.details, :sincedb_value => (potential_sdb_value || sdb_value))
199
191
  end
200
192
  end
201
193
 
@@ -206,11 +198,11 @@ module FileWatch module TailMode
206
198
  # and we allow the block to open the file and create a sincedb collection record if needed
207
199
  # some have never been active and some have
208
200
  # those that were active before but are watched now were closed under constraint
209
- logger.trace("Watched processing")
201
+ logger.trace(__method__.to_s)
210
202
  # how much of the max active window is available
211
- to_take = @settings.max_active - watched_files.count{|wf| wf.active?}
203
+ to_take = @settings.max_active - watched_files.count(&:active?)
212
204
  if to_take > 0
213
- watched_files.select {|wf| wf.watched?}.take(to_take).each do |watched_file|
205
+ watched_files.select(&:watched?).take(to_take).each do |watched_file|
214
206
  watched_file.activate
215
207
  if watched_file.initial?
216
208
  create_initial(watched_file)
@@ -223,36 +215,37 @@ module FileWatch module TailMode
223
215
  now = Time.now.to_i
224
216
  if (now - watch.lastwarn_max_files) > MAX_FILES_WARN_INTERVAL
225
217
  waiting = watched_files.size - @settings.max_active
226
- logger.warn(@settings.max_warn_msg + ", files yet to open: #{waiting}")
218
+ logger.warn("#{@settings.max_warn_msg}, files yet to open: #{waiting}")
227
219
  watch.lastwarn_max_files = now
228
220
  end
229
221
  end
230
222
  end
231
223
 
232
224
  def process_active(watched_files)
233
- # logger.trace("Active processing")
225
+ logger.trace(__method__.to_s)
234
226
  # Handles watched_files in the active state.
235
227
  # files have been opened at this point
236
- watched_files.select {|wf| wf.active? }.each do |watched_file|
228
+ watched_files.each do |watched_file|
229
+ next unless watched_file.active?
237
230
  break if watch.quit?
238
231
  path = watched_file.filename
239
232
  if watched_file.grown?
240
- logger.trace("Active - file grew: #{path}: new size is #{watched_file.last_stat_size}, bytes read #{watched_file.bytes_read}")
233
+ logger.trace("#{__method__} file grew: new size is #{watched_file.last_stat_size}, bytes read #{watched_file.bytes_read}", :path => path)
241
234
  grow(watched_file)
242
235
  elsif watched_file.shrunk?
243
236
  if watched_file.bytes_unread > 0
244
- logger.warn("Active - shrunk: DATA LOSS!! truncate detected with #{watched_file.bytes_unread} unread bytes: #{path}")
237
+ logger.warn("potential data loss, file truncate detected with #{watched_file.bytes_unread} unread bytes", :path => path)
245
238
  end
246
239
  # we don't update the size here, its updated when we actually read
247
- logger.trace("Active - file shrunk #{path}: new size is #{watched_file.last_stat_size}, old size #{watched_file.bytes_read}")
240
+ logger.trace("#{__method__} file shrunk: new size is #{watched_file.last_stat_size}, old size #{watched_file.bytes_read}", :path => path)
248
241
  shrink(watched_file)
249
242
  else
250
243
  # same size, do nothing
251
- logger.trace("Active - no change", "watched_file" => watched_file.details)
244
+ logger.trace("#{__method__} no change", :path => path)
252
245
  end
253
246
  # can any active files be closed to make way for waiting files?
254
247
  if watched_file.file_closable?
255
- logger.trace("Watch each: active: file expired: #{path}")
248
+ logger.trace("#{__method__} file expired", :path => path)
256
249
  timeout(watched_file)
257
250
  watched_file.close
258
251
  end
@@ -270,28 +263,28 @@ module FileWatch module TailMode
270
263
  def common_restat(watched_file, action, delay, &block)
271
264
  all_ok = true
272
265
  begin
273
- watched_file.restat
266
+ restat(watched_file)
274
267
  if watched_file.rotation_in_progress?
275
- logger.trace("-------------------- >>>>> restat - rotation_detected", "watched_file details" => watched_file.details, "new sincedb key" => watched_file.stat_sincedb_key)
268
+ logger.trace("-------------------- >>>>> restat - rotation_detected", :watched_file => watched_file.details, :new_sincedb_key => watched_file.stat_sincedb_key)
276
269
  # don't yield to closed and ignore processing
277
270
  else
278
271
  yield if block_given?
279
272
  end
280
273
  rescue Errno::ENOENT
281
274
  if delay
282
- logger.trace("#{action} - delaying the stat fail on: #{watched_file.filename}")
275
+ logger.trace("#{action} - delaying the stat fail on", :filename => watched_file.filename)
283
276
  watched_file.delay_delete
284
277
  else
285
278
  # file has gone away or we can't read it anymore.
286
- logger.trace("#{action} - after a delay, really can't find this file: #{watched_file.filename}")
279
+ logger.trace("#{action} - after a delay, really can't find this file", :path => watched_file.path)
287
280
  watched_file.unwatch
288
- logger.trace("#{action} - removing from collection: #{watched_file.filename}")
281
+ logger.trace("#{action} - removing from collection", :filename => watched_file.filename)
289
282
  delete(watched_file)
290
- deletable_filepaths << watched_file.path
283
+ add_deletable_path watched_file.path
291
284
  all_ok = false
292
285
  end
293
286
  rescue => e
294
- logger.error("#{action} - other error #{watched_file.path}: (#{e.message}, #{e.backtrace.take(8).inspect})")
287
+ logger.error("#{action} - other error", error_details(e, watched_file))
295
288
  all_ok = false
296
289
  end
297
290
  all_ok
@@ -1,26 +1,25 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/util/loggable"
3
+ require "concurrent/atomic/atomic_boolean"
3
4
 
4
5
  module FileWatch
5
6
  class Watch
6
7
  include LogStash::Util::Loggable
7
8
 
8
9
  attr_accessor :lastwarn_max_files
9
- attr_reader :discoverer, :watched_files_collection
10
+ attr_reader :discoverer, :processor, :watched_files_collection
10
11
 
11
- def initialize(discoverer, watched_files_collection, settings)
12
+ def initialize(discoverer, processor, settings)
13
+ @discoverer = discoverer
14
+ @watched_files_collection = discoverer.watched_files_collection
12
15
  @settings = settings
16
+
13
17
  # we need to be threadsafe about the quit mutation
14
18
  @quit = Concurrent::AtomicBoolean.new(false)
15
19
  @lastwarn_max_files = 0
16
- @discoverer = discoverer
17
- @watched_files_collection = watched_files_collection
18
- end
19
20
 
20
- def add_processor(processor)
21
21
  @processor = processor
22
22
  @processor.add_watch(self)
23
- self
24
23
  end
25
24
 
26
25
  def watch(path)
@@ -67,20 +66,16 @@ module FileWatch
67
66
  watched_files = @watched_files_collection.values
68
67
  @processor.process_all_states(watched_files)
69
68
  ensure
70
- @watched_files_collection.remove_paths(@processor.deletable_filepaths)
71
- @processor.deletable_filepaths.clear
69
+ @watched_files_collection.remove_paths(@processor.clear_deletable_paths)
72
70
  end
73
- end # def each
71
+ end
74
72
 
75
73
  def quit
76
74
  @quit.make_true
77
75
  end
78
76
 
79
77
  def quit?
80
- if @settings.exit_after_read
81
- @exit = @watched_files_collection.empty?
82
- end
83
- @quit.true? || @exit
78
+ @quit.true? || (@settings.exit_after_read && @watched_files_collection.empty?)
84
79
  end
85
80
 
86
81
  private
@@ -6,7 +6,7 @@ module FileWatch
6
6
  IO_BASED_STAT = 1
7
7
 
8
8
  attr_reader :bytes_read, :state, :file, :buffer, :recent_states, :bytes_unread
9
- attr_reader :path, :accessed_at, :modified_at, :pathname, :filename
9
+ attr_reader :path, :accessed_at, :pathname, :filename
10
10
  attr_reader :listener, :read_loop_count, :read_chunk_size, :stat
11
11
  attr_reader :loop_count_type, :loop_count_mode
12
12
  attr_accessor :last_open_warning_at
@@ -16,7 +16,7 @@ module FileWatch
16
16
  def initialize(pathname, stat, settings)
17
17
  @settings = settings
18
18
  @pathname = Pathname.new(pathname) # given arg pathname might be a string or a Pathname object
19
- @path = @pathname.to_path
19
+ @path = @pathname.to_path.freeze
20
20
  @filename = @pathname.basename.to_s
21
21
  full_state_reset(stat)
22
22
  watch
@@ -24,10 +24,6 @@ module FileWatch
24
24
  set_accessed_at
25
25
  end
26
26
 
27
- def no_restat_reset
28
- full_state_reset(@stat)
29
- end
30
-
31
27
  def full_state_reset(this_stat = nil)
32
28
  if this_stat.nil?
33
29
  begin
@@ -75,6 +71,7 @@ module FileWatch
75
71
  @size = @stat.size
76
72
  @sdb_key_v1 = @stat.inode_struct
77
73
  end
74
+ private :set_stat
78
75
 
79
76
  def rotate_as_file(bytes_read = 0)
80
77
  # rotation, when a sincedb record exists for new inode, but no watched file to rotate from
@@ -100,19 +97,33 @@ module FileWatch
100
97
  stat_sincedb_key != sincedb_key
101
98
  end
102
99
 
103
- def restat
100
+ # @return true if the file was modified since last stat
101
+ def restat!
102
+ modified_at # to always be able to detect changes
104
103
  @stat.restat
105
104
  if rotation_detected?
106
105
  # switch to new state now
107
106
  rotation_in_progress
107
+ return true
108
108
  else
109
109
  @size = @stat.size
110
110
  update_bytes_unread
111
+ modified_at_changed?
112
+ end
113
+ end
114
+
115
+ def modified_at(update = false)
116
+ if update || @modified_at.nil?
117
+ @modified_at = @stat.modified_at
118
+ else
119
+ @modified_at
111
120
  end
112
121
  end
113
122
 
114
- def modified_at
115
- @stat.modified_at
123
+ # @return whether modified_at changed since it was last read
124
+ # @see #restat!
125
+ def modified_at_changed?
126
+ modified_at != @stat.modified_at
116
127
  end
117
128
 
118
129
  def position_for_new_sincedb_value
@@ -405,14 +416,14 @@ module FileWatch
405
416
  end
406
417
 
407
418
  def details
408
- detail = "@filename='#{filename}', @state='#{state}', @recent_states='#{@recent_states.inspect}', "
409
- detail.concat("@bytes_read='#{@bytes_read}', @bytes_unread='#{@bytes_unread}', current_size='#{current_size}', ")
410
- detail.concat("last_stat_size='#{last_stat_size}', file_open?='#{file_open?}', @initial=#{@initial}")
411
- "<FileWatch::WatchedFile: #{detail}, @sincedb_key='#{sincedb_key}'>"
419
+ detail = "@filename='#{@filename}', @state=#{@state.inspect}, @recent_states=#{@recent_states.inspect}, "
420
+ detail.concat("@bytes_read=#{@bytes_read}, @bytes_unread=#{@bytes_unread}, current_size=#{current_size}, ")
421
+ detail.concat("last_stat_size=#{last_stat_size}, file_open?=#{file_open?}, @initial=#{@initial}")
422
+ "<FileWatch::WatchedFile: #{detail}, sincedb_key='#{sincedb_key}'>"
412
423
  end
413
424
 
414
425
  def inspect
415
- "\"<FileWatch::WatchedFile: @filename='#{filename}', @state='#{state}', @sincedb_key='#{sincedb_key}, size=#{@size}>\""
426
+ "<FileWatch::WatchedFile: @filename='#{@filename}', @state=#{@state.inspect}, current_size=#{current_size}, sincedb_key='#{sincedb_key}'>"
416
427
  end
417
428
 
418
429
  def to_s
@@ -1,89 +1,22 @@
1
1
  # encoding: utf-8
2
- module FileWatch
3
- class WatchedFilesCollection
4
-
5
- def initialize(settings)
6
- @sort_by = settings.file_sort_by # "last_modified" | "path"
7
- @sort_direction = settings.file_sort_direction # "asc" | "desc"
8
- @sort_method = method("#{@sort_by}_#{@sort_direction}".to_sym)
9
- @files = Concurrent::Array.new
10
- @pointers = Concurrent::Hash.new
11
- end
12
2
 
13
- def add(watched_file)
14
- @files << watched_file
15
- @sort_method.call
16
- end
3
+ require 'java'
17
4
 
18
- def remove_paths(paths)
19
- removed_files = Array(paths).map do |path|
20
- index = @pointers.delete(path)
21
- if index
22
- watched_file = @files.delete_at(index)
23
- refresh_pointers
24
- watched_file
25
- end
26
- end
27
- @sort_method.call
28
- removed_files
29
- end
5
+ module FileWatch
6
+ # @see `org.logstash.filewatch.WatchedFilesCollection`
7
+ class WatchedFilesCollection
30
8
 
9
+ # Closes all managed watched files.
10
+ # @see FileWatch::WatchedFile#file_close
31
11
  def close_all
32
- @files.each(&:file_close)
33
- end
34
-
35
- def empty?
36
- @files.empty?
37
- end
38
-
39
- def keys
40
- @pointers.keys
12
+ each_file(&:file_close) # synchronized
41
13
  end
42
14
 
43
- def values
44
- @files
45
- end
46
-
47
- def watched_file_by_path(path)
48
- index = @pointers[path]
49
- return nil unless index
50
- @files[index]
51
- end
52
-
53
- private
54
-
55
- def last_modified_asc
56
- @files.sort! do |left, right|
57
- left.modified_at <=> right.modified_at
58
- end
59
- refresh_pointers
60
- end
15
+ # @return [Enumerable<String>] managed path keys (snapshot)
16
+ alias keys paths
61
17
 
62
- def last_modified_desc
63
- @files.sort! do |left, right|
64
- right.modified_at <=> left.modified_at
65
- end
66
- refresh_pointers
67
- end
68
-
69
- def path_asc
70
- @files.sort! do |left, right|
71
- left.path <=> right.path
72
- end
73
- refresh_pointers
74
- end
18
+ # @return [Enumerable<WatchedFile>] managed files (snapshot)
19
+ alias values files
75
20
 
76
- def path_desc
77
- @files.sort! do |left, right|
78
- right.path <=> left.path
79
- end
80
- refresh_pointers
81
- end
82
-
83
- def refresh_pointers
84
- @files.each_with_index do |watched_file, index|
85
- @pointers[watched_file.path] = index
86
- end
87
- end
88
21
  end
89
22
  end