logstash-input-file 4.1.18 → 4.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +1 -1
  4. data/lib/filewatch/discoverer.rb +9 -8
  5. data/lib/filewatch/observing_base.rb +1 -12
  6. data/lib/filewatch/processor.rb +55 -0
  7. data/lib/filewatch/read_mode/handlers/base.rb +12 -11
  8. data/lib/filewatch/read_mode/handlers/read_file.rb +23 -8
  9. data/lib/filewatch/read_mode/handlers/read_zip_file.rb +8 -6
  10. data/lib/filewatch/read_mode/processor.rb +22 -36
  11. data/lib/filewatch/settings.rb +1 -2
  12. data/lib/filewatch/sincedb_collection.rb +40 -41
  13. data/lib/filewatch/sincedb_record_serializer.rb +5 -11
  14. data/lib/filewatch/stat/generic.rb +8 -13
  15. data/lib/filewatch/stat/windows_path.rb +7 -9
  16. data/lib/filewatch/tail_mode/handlers/base.rb +32 -23
  17. data/lib/filewatch/tail_mode/handlers/delete.rb +2 -4
  18. data/lib/filewatch/tail_mode/handlers/shrink.rb +2 -3
  19. data/lib/filewatch/tail_mode/handlers/unignore.rb +4 -4
  20. data/lib/filewatch/tail_mode/processor.rb +47 -54
  21. data/lib/filewatch/watch.rb +12 -14
  22. data/lib/filewatch/watched_file.rb +25 -14
  23. data/lib/filewatch/watched_files_collection.rb +11 -78
  24. data/lib/jars/filewatch-1.0.1.jar +0 -0
  25. data/lib/logstash/inputs/file.rb +4 -3
  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 +63 -12
  29. data/spec/filewatch/rotate_spec.rb +4 -4
  30. data/spec/filewatch/settings_spec.rb +3 -0
  31. data/spec/filewatch/sincedb_record_serializer_spec.rb +6 -2
  32. data/spec/filewatch/spec_helper.rb +12 -14
  33. data/spec/filewatch/tailing_spec.rb +24 -22
  34. data/spec/filewatch/watched_file_spec.rb +30 -0
  35. data/spec/filewatch/watched_files_collection_spec.rb +62 -8
  36. data/spec/inputs/file_read_spec.rb +58 -14
  37. metadata +17 -2
@@ -6,7 +6,7 @@ module FileWatch
6
6
  attr_reader :max_active, :max_warn_msg, :lastwarn_max_files
7
7
  attr_reader :sincedb_write_interval, :stat_interval, :discover_interval
8
8
  attr_reader :exclude, :start_new_files_at, :file_chunk_count, :file_chunk_size
9
- attr_reader :sincedb_path, :sincedb_write_interval, :sincedb_expiry_duration
9
+ attr_reader :sincedb_path, :sincedb_expiry_duration
10
10
  attr_reader :file_sort_by, :file_sort_direction
11
11
  attr_reader :exit_after_read
12
12
  attr_reader :check_archive_validity
@@ -41,7 +41,6 @@ module FileWatch
41
41
  @file_chunk_size = @opts[:file_chunk_size]
42
42
  @close_older = @opts[:close_older]
43
43
  @ignore_older = @opts[:ignore_older]
44
- @sincedb_write_interval = @opts[:sincedb_write_interval]
45
44
  @stat_interval = @opts[:stat_interval]
46
45
  @discover_interval = @opts[:discover_interval]
47
46
  @exclude = Array(@opts[:exclude])
@@ -47,55 +47,53 @@ module FileWatch
47
47
  @time_sdb_opened = Time.now.to_f
48
48
  begin
49
49
  path.open do |file|
50
- logger.trace("open: reading from #{path}")
50
+ logger.debug("open: reading from #{path}")
51
51
  @serializer.deserialize(file) do |key, value|
52
- logger.trace("open: importing ... '#{key}' => '#{value}'")
52
+ logger.trace? && logger.trace("open: importing #{key.inspect} => #{value.inspect}")
53
53
  set_key_value(key, value)
54
54
  end
55
55
  end
56
56
  logger.trace("open: count of keys read: #{@sincedb.keys.size}")
57
57
  rescue => e
58
58
  #No existing sincedb to load
59
- logger.trace("open: error: #{path}: #{e.inspect}")
59
+ logger.debug("open: error opening #{path}", :exception => e.class, :message => e.message)
60
60
  end
61
61
  end
62
62
 
63
63
  def associate(watched_file)
64
- logger.trace("associate: finding", "inode" => watched_file.sincedb_key.inode, "path" => watched_file.path)
64
+ logger.trace? && logger.trace("associate: finding", :path => watched_file.path, :inode => watched_file.sincedb_key.inode)
65
65
  sincedb_value = find(watched_file)
66
66
  if sincedb_value.nil?
67
67
  # sincedb has no record of this inode
68
68
  # and due to the window handling of many files
69
69
  # this file may not be opened in this session.
70
70
  # a new value will be added when the file is opened
71
- logger.trace("associate: unmatched")
71
+ logger.trace("associate: unmatched", :filename => watched_file.filename)
72
72
  return true
73
73
  end
74
- logger.trace("associate: found sincedb record", "filename" => watched_file.filename, "sincedb key" => watched_file.sincedb_key,"sincedb_value" => sincedb_value)
75
- if sincedb_value.watched_file.nil?
76
- # not associated
74
+ logger.trace? && logger.trace("associate: found sincedb record", :filename => watched_file.filename,
75
+ :sincedb_key => watched_file.sincedb_key, :sincedb_value => sincedb_value)
76
+ if sincedb_value.watched_file.nil? # not associated
77
77
  if sincedb_value.path_in_sincedb.nil?
78
78
  handle_association(sincedb_value, watched_file)
79
- logger.trace("associate: inode matched but no path in sincedb")
79
+ logger.trace? && logger.trace("associate: inode matched but no path in sincedb", :filename => watched_file.filename)
80
80
  return true
81
81
  end
82
82
  if sincedb_value.path_in_sincedb == watched_file.path
83
- # the path on disk is the same as discovered path
84
- # and the inode is the same.
83
+ # the path on disk is the same as discovered path and the inode is the same.
85
84
  handle_association(sincedb_value, watched_file)
86
- logger.trace("associate: inode and path matched")
85
+ logger.trace? && logger.trace("associate: inode and path matched", :filename => watched_file.filename)
87
86
  return true
88
87
  end
89
- # the path on disk is different from discovered unassociated path
90
- # but they have the same key (inode)
88
+ # the path on disk is different from discovered unassociated path but they have the same key (inode)
91
89
  # treat as a new file, a new value will be added when the file is opened
92
90
  sincedb_value.clear_watched_file
93
91
  delete(watched_file.sincedb_key)
94
- logger.trace("associate: matched but allocated to another")
92
+ logger.trace? && logger.trace("associate: matched but allocated to another", :filename => watched_file.filename)
95
93
  return true
96
94
  end
97
95
  if sincedb_value.watched_file.equal?(watched_file) # pointer equals
98
- logger.trace("associate: already associated")
96
+ logger.trace? && logger.trace("associate: already associated", :filename => watched_file.filename)
99
97
  return true
100
98
  end
101
99
  # sincedb_value.watched_file is not this discovered watched_file but they have the same key (inode)
@@ -106,7 +104,8 @@ module FileWatch
106
104
  # after the original is deleted
107
105
  # are not yet in the delete phase, let this play out
108
106
  existing_watched_file = sincedb_value.watched_file
109
- logger.trace("----------------- >> associate: the found sincedb_value has a watched_file - this is a rename", "this watched_file details" => watched_file.details, "other watched_file details" => existing_watched_file.details)
107
+ logger.trace? && logger.trace("associate: found sincedb_value has a watched_file - this is a rename",
108
+ :this_watched_file => watched_file.details, :existing_watched_file => existing_watched_file.details)
110
109
  watched_file.rotation_in_progress
111
110
  true
112
111
  end
@@ -149,8 +148,8 @@ module FileWatch
149
148
  end
150
149
 
151
150
  def watched_file_deleted(watched_file)
152
- return unless member?(watched_file.sincedb_key)
153
- get(watched_file.sincedb_key).unset_watched_file
151
+ value = @sincedb[watched_file.sincedb_key]
152
+ value.unset_watched_file if value
154
153
  end
155
154
 
156
155
  def store_last_read(key, pos)
@@ -178,63 +177,63 @@ module FileWatch
178
177
  get(key).watched_file.nil?
179
178
  end
180
179
 
181
- private
182
-
183
180
  def flush_at_interval
184
- now = Time.now.to_i
185
- delta = now - @sincedb_last_write
181
+ now = Time.now
182
+ delta = now.to_i - @sincedb_last_write
186
183
  if delta >= @settings.sincedb_write_interval
187
184
  logger.debug("writing sincedb (delta since last write = #{delta})")
188
185
  sincedb_write(now)
189
186
  end
190
187
  end
191
188
 
189
+ private
190
+
192
191
  def handle_association(sincedb_value, watched_file)
193
192
  watched_file.update_bytes_read(sincedb_value.position)
194
193
  sincedb_value.set_watched_file(watched_file)
195
194
  watched_file.initial_completed
196
195
  if watched_file.all_read?
197
196
  watched_file.ignore
198
- logger.trace("handle_association fully read, ignoring.....", "watched file" => watched_file.details, "sincedb value" => sincedb_value)
197
+ logger.trace? && logger.trace("handle_association fully read, ignoring", :watched_file => watched_file.details, :sincedb_value => sincedb_value)
199
198
  end
200
199
  end
201
200
 
202
201
  def set_key_value(key, value)
203
202
  if @time_sdb_opened < value.last_changed_at_expires(@settings.sincedb_expiry_duration)
204
- logger.trace("open: setting #{key.inspect} to #{value.inspect}")
205
203
  set(key, value)
206
204
  else
207
- logger.trace("open: record has expired, skipping: #{key.inspect} #{value.inspect}")
205
+ logger.debug("set_key_value: record has expired, skipping: #{key.inspect} => #{value.inspect}")
208
206
  end
209
207
  end
210
208
 
211
- def sincedb_write(time = Time.now.to_i)
212
- logger.trace("sincedb_write: to: #{path}")
209
+ def sincedb_write(time = Time.now)
210
+ logger.trace? && logger.trace("sincedb_write: #{path} (time = #{time})")
213
211
  begin
214
- @write_method.call
215
- @serializer.expired_keys.each do |key|
212
+ expired_keys = @write_method.call(time)
213
+ expired_keys.each do |key|
216
214
  @sincedb[key].unset_watched_file
217
215
  delete(key)
218
- logger.trace("sincedb_write: cleaned", "key" => "'#{key}'")
216
+ logger.trace? && logger.trace("sincedb_write: cleaned", :key => key)
219
217
  end
220
- @sincedb_last_write = time
218
+ @sincedb_last_write = time.to_i
221
219
  @write_requested = false
222
- rescue Errno::EACCES
223
- # no file handles free perhaps
224
- # maybe it will work next time
225
- logger.trace("sincedb_write: error: #{path}: #{$!}")
220
+ rescue Errno::EACCES => e
221
+ # no file handles free perhaps - maybe it will work next time
222
+ logger.debug("sincedb_write: #{path} error:", :exception => e.class, :message => e.message)
226
223
  end
227
224
  end
228
225
 
229
- def atomic_write
226
+ # @return expired keys
227
+ def atomic_write(time)
230
228
  FileHelper.write_atomically(@full_path) do |io|
231
- @serializer.serialize(@sincedb, io)
229
+ @serializer.serialize(@sincedb, io, time.to_f)
232
230
  end
233
231
  end
234
232
 
235
- def non_atomic_write
236
- IO.open(IO.sysopen(@full_path, "w+")) do |io|
237
- @serializer.serialize(@sincedb, io)
233
+ # @return expired keys
234
+ def non_atomic_write(time)
235
+ File.open(@full_path, "w+") do |io|
236
+ @serializer.serialize(@sincedb, io, time.to_f)
238
237
  end
239
238
  end
240
239
  end
@@ -3,30 +3,25 @@
3
3
  module FileWatch
4
4
  class SincedbRecordSerializer
5
5
 
6
- attr_reader :expired_keys
7
-
8
6
  def self.days_to_seconds(days)
9
7
  (24 * 3600) * days.to_f
10
8
  end
11
9
 
12
10
  def initialize(sincedb_value_expiry)
13
11
  @sincedb_value_expiry = sincedb_value_expiry
14
- @expired_keys = []
15
- end
16
-
17
- def update_sincedb_value_expiry_from_days(days)
18
- @sincedb_value_expiry = SincedbRecordSerializer.days_to_seconds(days)
19
12
  end
20
13
 
14
+ # @return Array expired keys (ones that were not written to the file)
21
15
  def serialize(db, io, as_of = Time.now.to_f)
22
- @expired_keys.clear
16
+ expired_keys = []
23
17
  db.each do |key, value|
24
18
  if as_of > value.last_changed_at_expires(@sincedb_value_expiry)
25
- @expired_keys << key
19
+ expired_keys << key
26
20
  next
27
21
  end
28
22
  io.write(serialize_record(key, value))
29
23
  end
24
+ expired_keys
30
25
  end
31
26
 
32
27
  def deserialize(io)
@@ -36,8 +31,7 @@ module FileWatch
36
31
  end
37
32
 
38
33
  def serialize_record(k, v)
39
- # effectively InodeStruct#to_s SincedbValue#to_s
40
- "#{k} #{v}\n"
34
+ "#{k} #{v}\n" # effectively InodeStruct#to_s SincedbValue#to_s
41
35
  end
42
36
 
43
37
  def deserialize_record(record)
@@ -3,24 +3,19 @@
3
3
  module FileWatch module Stat
4
4
  class Generic
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
10
- @identifier = nil
9
+ @source = source # Pathname
11
10
  restat
12
11
  end
13
12
 
14
- def add_identifier(identifier) self; end
15
-
16
13
  def restat
17
- @inner_stat = @source.stat
18
- @inode = @inner_stat.ino.to_s
19
- @modified_at = @inner_stat.mtime.to_f
20
- @size = @inner_stat.size
21
- @dev_major = @inner_stat.dev_major
22
- @dev_minor = @inner_stat.dev_minor
23
- @inode_struct = InodeStruct.new(@inode, @dev_major, @dev_minor)
14
+ stat = @source.stat
15
+ @inode = stat.ino.to_s
16
+ @modified_at = stat.mtime.to_f
17
+ @size = stat.size
18
+ @inode_struct = InodeStruct.new(@inode, stat.dev_major, stat.dev_minor)
24
19
  end
25
20
 
26
21
  def windows?
@@ -28,7 +23,7 @@ module FileWatch module Stat
28
23
  end
29
24
 
30
25
  def inspect
31
- "<Generic size='#{@size}', modified_at='#{@modified_at}', inode='#{@inode}', inode_struct='#{@inode_struct}'>"
26
+ "<#{self.class.name} size=#{@size}, modified_at=#{@modified_at}, inode='#{@inode}', inode_struct=#{@inode_struct}>"
32
27
  end
33
28
  end
34
29
  end end
@@ -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
@@ -18,7 +18,7 @@ module FileWatch module TailMode module Handlers
18
18
  end
19
19
 
20
20
  def handle(watched_file)
21
- logger.trace("handling: #{watched_file.filename}")
21
+ logger.trace? && logger.trace("handling:", :path => watched_file.path)
22
22
  unless watched_file.has_listener?
23
23
  watched_file.set_listener(@observer)
24
24
  end
@@ -37,7 +37,7 @@ module FileWatch module TailMode module Handlers
37
37
 
38
38
  def controlled_read(watched_file, loop_control)
39
39
  changed = false
40
- logger.trace("reading...", "iterations" => loop_control.count, "amount" => loop_control.size, "filename" => watched_file.filename)
40
+ logger.trace? && logger.trace(__method__.to_s, :iterations => loop_control.count, :amount => loop_control.size, :filename => watched_file.filename)
41
41
  # from a real config (has 102 file inputs)
42
42
  # -- This cfg creates a file input for every log file to create a dedicated file pointer and read all file simultaneously
43
43
  # -- If we put all log files in one file input glob we will have indexing delay, because Logstash waits until the first file becomes EOF
@@ -48,7 +48,7 @@ module FileWatch module TailMode module Handlers
48
48
  loop_control.count.times do
49
49
  break if quit?
50
50
  begin
51
- logger.debug("read_to_eof: get chunk")
51
+ logger.debug? && logger.debug("#{__method__} get chunk")
52
52
  result = watched_file.read_extract_lines(loop_control.size) # expect BufferExtractResult
53
53
  logger.trace(result.warning, result.additional) unless result.warning.empty?
54
54
  changed = true
@@ -57,40 +57,42 @@ module FileWatch module TailMode module Handlers
57
57
  # sincedb position is now independent from the watched_file bytes_read
58
58
  sincedb_collection.increment(watched_file.sincedb_key, line.bytesize + @settings.delimiter_byte_size)
59
59
  end
60
- rescue EOFError
60
+ rescue EOFError => e
61
61
  # it only makes sense to signal EOF in "read" mode not "tail"
62
+ logger.debug(__method__.to_s, exception_details(watched_file.path, e, false))
62
63
  loop_control.flag_read_error
63
64
  break
64
- rescue Errno::EWOULDBLOCK, Errno::EINTR
65
+ rescue Errno::EWOULDBLOCK, Errno::EINTR => e
66
+ logger.debug(__method__.to_s, exception_details(watched_file.path, e, false))
65
67
  watched_file.listener.error
66
68
  loop_control.flag_read_error
67
69
  break
68
70
  rescue => e
69
- logger.error("read_to_eof: general error reading #{watched_file.path}", "error" => e.inspect, "backtrace" => e.backtrace.take(4))
71
+ logger.error("#{__method__} general error reading", exception_details(watched_file.path, e))
70
72
  watched_file.listener.error
71
73
  loop_control.flag_read_error
72
74
  break
73
75
  end
74
76
  end
75
- logger.debug("read_to_eof: exit due to quit") if quit?
77
+ logger.debug("#{__method__} stopped loop due quit") if quit?
76
78
  sincedb_collection.request_disk_flush if changed
77
79
  end
78
80
 
79
81
  def open_file(watched_file)
80
82
  return true if watched_file.file_open?
81
- logger.trace("opening #{watched_file.filename}")
83
+ logger.trace? && logger.trace("open_file", :filename => watched_file.filename)
82
84
  begin
83
85
  watched_file.open
84
- rescue
86
+ rescue => e
85
87
  # don't emit this message too often. if a file that we can't
86
88
  # read is changing a lot, we'll try to open it more often, and spam the logs.
87
89
  now = Time.now.to_i
88
- logger.trace("open_file OPEN_WARN_INTERVAL is '#{OPEN_WARN_INTERVAL}'")
90
+ logger.trace? && logger.trace("open_file OPEN_WARN_INTERVAL is '#{OPEN_WARN_INTERVAL}'")
89
91
  if watched_file.last_open_warning_at.nil? || now - watched_file.last_open_warning_at > OPEN_WARN_INTERVAL
90
- logger.warn("failed to open #{watched_file.path}: #{$!.inspect}, #{$!.backtrace.take(3)}")
92
+ logger.warn("failed to open file", exception_details(watched_file.path, e))
91
93
  watched_file.last_open_warning_at = now
92
94
  else
93
- logger.trace("suppressed warning for `failed to open` #{watched_file.path}: #{$!.inspect}")
95
+ logger.debug("open_file suppressed warning `failed to open file`", exception_details(watched_file.path, e, false))
94
96
  end
95
97
  watched_file.watch # set it back to watch so we can try it again
96
98
  else
@@ -108,26 +110,22 @@ module FileWatch module TailMode module Handlers
108
110
  update_existing_sincedb_collection_value(watched_file, sincedb_value)
109
111
  watched_file.initial_completed
110
112
  else
111
- msg = "add_or_update_sincedb_collection: found sincedb record"
112
- logger.trace(msg,
113
- "sincedb key" => watched_file.sincedb_key,
114
- "sincedb value" => sincedb_value
115
- )
113
+ logger.trace? && logger.trace("add_or_update_sincedb_collection: found sincedb record",
114
+ :sincedb_key => watched_file.sincedb_key, :sincedb_value => sincedb_value)
116
115
  # detected a rotation, Discoverer can't handle this because this watched file is not a new discovery.
117
116
  # we must handle it here, by transferring state and have the sincedb value track this watched file
118
117
  # rotate_as_file and rotate_from will switch the sincedb key to the inode that the path is now pointing to
119
118
  # and pickup the sincedb_value from before.
120
- msg = "add_or_update_sincedb_collection: the found sincedb_value has a watched_file - this is a rename, switching inode to this watched file"
121
- logger.trace(msg)
119
+ logger.debug("add_or_update_sincedb_collection: the found sincedb_value has a watched_file - this is a rename, switching inode to this watched file")
122
120
  existing_watched_file = sincedb_value.watched_file
123
121
  if existing_watched_file.nil?
124
122
  sincedb_value.set_watched_file(watched_file)
125
- logger.trace("add_or_update_sincedb_collection: switching as new file")
123
+ logger.trace? && logger.trace("add_or_update_sincedb_collection: switching as new file")
126
124
  watched_file.rotate_as_file
127
125
  watched_file.update_bytes_read(sincedb_value.position)
128
126
  else
129
127
  sincedb_value.set_watched_file(watched_file)
130
- logger.trace("add_or_update_sincedb_collection: switching from...", "watched_file details" => watched_file.details)
128
+ logger.trace? && logger.trace("add_or_update_sincedb_collection: switching from:", :watched_file => watched_file.details)
131
129
  watched_file.rotate_from(existing_watched_file)
132
130
  end
133
131
  end
@@ -135,13 +133,15 @@ module FileWatch module TailMode module Handlers
135
133
  end
136
134
 
137
135
  def update_existing_sincedb_collection_value(watched_file, sincedb_value)
138
- logger.trace("update_existing_sincedb_collection_value: #{watched_file.filename}, last value #{sincedb_value.position}, cur size #{watched_file.last_stat_size}")
136
+ logger.trace? && logger.trace("update_existing_sincedb_collection_value", :position => sincedb_value.position,
137
+ :filename => watched_file.filename, :last_stat_size => watched_file.last_stat_size)
139
138
  update_existing_specifically(watched_file, sincedb_value)
140
139
  end
141
140
 
142
141
  def add_new_value_sincedb_collection(watched_file)
143
142
  sincedb_value = get_new_value_specifically(watched_file)
144
- logger.trace("add_new_value_sincedb_collection", "position" => sincedb_value.position, "watched_file details" => watched_file.details)
143
+ logger.trace? && logger.trace("add_new_value_sincedb_collection", :position => sincedb_value.position,
144
+ :watched_file => watched_file.details)
145
145
  sincedb_collection.set(watched_file.sincedb_key, sincedb_value)
146
146
  sincedb_value
147
147
  end
@@ -153,5 +153,14 @@ module FileWatch module TailMode module Handlers
153
153
  watched_file.update_bytes_read(position)
154
154
  value
155
155
  end
156
+
157
+ private
158
+
159
+ def exception_details(path, e, trace = true)
160
+ details = { :path => path, :exception => e.class, :message => e.message }
161
+ details[:backtrace] = e.backtrace if trace && logger.debug?
162
+ details
163
+ end
164
+
156
165
  end
157
166
  end end end