logstash-input-file 4.1.16 → 4.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/LICENSE +199 -10
  4. data/docs/index.asciidoc +23 -7
  5. data/lib/filewatch/discoverer.rb +9 -8
  6. data/lib/filewatch/observing_base.rb +1 -12
  7. data/lib/filewatch/processor.rb +55 -0
  8. data/lib/filewatch/read_mode/handlers/base.rb +8 -6
  9. data/lib/filewatch/read_mode/handlers/read_file.rb +26 -8
  10. data/lib/filewatch/read_mode/handlers/read_zip_file.rb +63 -34
  11. data/lib/filewatch/read_mode/processor.rb +22 -36
  12. data/lib/filewatch/settings.rb +3 -2
  13. data/lib/filewatch/sincedb_collection.rb +23 -21
  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/delete.rb +2 -4
  17. data/lib/filewatch/tail_mode/processor.rb +47 -54
  18. data/lib/filewatch/watch.rb +12 -14
  19. data/lib/filewatch/watched_file.rb +25 -14
  20. data/lib/filewatch/watched_files_collection.rb +11 -74
  21. data/lib/jars/filewatch-1.0.1.jar +0 -0
  22. data/lib/logstash/inputs/delete_completed_file_handler.rb +5 -0
  23. data/lib/logstash/inputs/file.rb +32 -11
  24. data/logstash-input-file.gemspec +3 -2
  25. data/spec/filewatch/reading_spec.rb +60 -9
  26. data/spec/filewatch/rotate_spec.rb +2 -1
  27. data/spec/filewatch/settings_spec.rb +3 -0
  28. data/spec/filewatch/spec_helper.rb +13 -15
  29. data/spec/filewatch/tailing_spec.rb +14 -12
  30. data/spec/filewatch/watched_file_spec.rb +30 -0
  31. data/spec/filewatch/watched_files_collection_spec.rb +62 -8
  32. data/spec/helpers/spec_helper.rb +8 -0
  33. data/spec/inputs/file_read_spec.rb +154 -4
  34. data/spec/inputs/file_tail_spec.rb +3 -2
  35. metadata +21 -6
@@ -19,16 +19,21 @@ module FileWatch module ReadMode module Handlers
19
19
  watched_file.listener.eof
20
20
  watched_file.file_close
21
21
  key = watched_file.sincedb_key
22
- sincedb_collection.reading_completed(key)
23
- sincedb_collection.clear_watched_file(key)
22
+ if sincedb_collection.get(key)
23
+ sincedb_collection.reading_completed(key)
24
+ sincedb_collection.clear_watched_file(key)
25
+ end
24
26
  watched_file.listener.deleted
27
+ # NOTE: on top of un-watching we should also remove from the watched files collection
28
+ # if the file is getting deleted (on completion), that part currently resides in
29
+ # DeleteCompletedFileHandler - triggered above using `watched_file.listener.deleted`
25
30
  watched_file.unwatch
26
31
  end
27
32
  end
28
33
  end
29
34
 
30
35
  def controlled_read(watched_file, loop_control)
31
- logger.trace("reading...", "iterations" => loop_control.count, "amount" => loop_control.size, "filename" => watched_file.filename)
36
+ logger.trace? && logger.trace("reading...", :filename => watched_file.filename, :iterations => loop_control.count, :amount => loop_control.size)
32
37
  loop_control.count.times do
33
38
  break if quit?
34
39
  begin
@@ -40,22 +45,35 @@ module FileWatch module ReadMode module Handlers
40
45
  delta = line.bytesize + @settings.delimiter_byte_size
41
46
  sincedb_collection.increment(watched_file.sincedb_key, delta)
42
47
  end
43
- rescue EOFError
44
- logger.error("controlled_read: eof error reading file", "path" => watched_file.path, "error" => e.inspect, "backtrace" => e.backtrace.take(8))
48
+ rescue EOFError => e
49
+ log_error("controlled_read: eof error reading file", watched_file, e)
45
50
  loop_control.flag_read_error
46
51
  break
47
- rescue Errno::EWOULDBLOCK, Errno::EINTR
48
- logger.error("controlled_read: block or interrupt error reading file", "path" => watched_file.path, "error" => e.inspect, "backtrace" => e.backtrace.take(8))
52
+ rescue Errno::EWOULDBLOCK, Errno::EINTR => e
53
+ log_error("controlled_read: block or interrupt error reading file", watched_file, e)
49
54
  watched_file.listener.error
50
55
  loop_control.flag_read_error
51
56
  break
52
57
  rescue => e
53
- logger.error("controlled_read: general error reading file", "path" => watched_file.path, "error" => e.inspect, "backtrace" => e.backtrace.take(8))
58
+ log_error("controlled_read: general error reading file", watched_file, e)
54
59
  watched_file.listener.error
55
60
  loop_control.flag_read_error
56
61
  break
57
62
  end
58
63
  end
59
64
  end
65
+
66
+ def log_error(msg, watched_file, error)
67
+ details = { :path => watched_file.path,
68
+ :exception => error.class,
69
+ :message => error.message,
70
+ :backtrace => error.backtrace }
71
+ if logger.debug?
72
+ details[:file] = watched_file
73
+ else
74
+ details[:backtrace] = details[:backtrace].take(8) if details[:backtrace]
75
+ end
76
+ logger.error(msg, details)
77
+ end
60
78
  end
61
79
  end end end
@@ -1,13 +1,15 @@
1
1
  # encoding: utf-8
2
2
  require 'java'
3
- java_import java.io.InputStream
4
- java_import java.io.InputStreamReader
5
- java_import java.io.FileInputStream
6
- java_import java.io.BufferedReader
7
- java_import java.util.zip.GZIPInputStream
8
- java_import java.util.zip.ZipException
9
3
 
10
4
  module FileWatch module ReadMode module Handlers
5
+
6
+ java_import java.io.InputStream
7
+ java_import java.io.InputStreamReader
8
+ java_import java.io.FileInputStream
9
+ java_import java.io.BufferedReader
10
+ java_import java.util.zip.GZIPInputStream
11
+ java_import java.util.zip.ZipException
12
+
11
13
  class ReadZipFile < Base
12
14
  def handle_specifically(watched_file)
13
15
  add_or_update_sincedb_collection(watched_file) unless sincedb_collection.member?(watched_file.sincedb_key)
@@ -18,34 +20,40 @@ module FileWatch module ReadMode module Handlers
18
20
  # fast forward through the lines until we reach unseen content?
19
21
  # meaning that we can quit in the middle of a zip file
20
22
  key = watched_file.sincedb_key
21
- begin
22
- file_stream = FileInputStream.new(watched_file.path)
23
- gzip_stream = GZIPInputStream.new(file_stream)
24
- decoder = InputStreamReader.new(gzip_stream, "UTF-8")
25
- buffered = BufferedReader.new(decoder)
26
- while (line = buffered.readLine(false))
27
- watched_file.listener.accept(line)
28
- # can't quit, if we did then we would incorrectly write a 'completed' sincedb entry
29
- # what do we do about quit when we have just begun reading the zipped file (e.g. pipeline reloading)
30
- # should we track lines read in the sincedb and
31
- # fast forward through the lines until we reach unseen content?
32
- # meaning that we can quit in the middle of a zip file
33
- end
34
- watched_file.listener.eof
35
- rescue ZipException => e
36
- logger.error("Cannot decompress the gzip file at path: #{watched_file.path}")
37
- watched_file.listener.error
38
- else
39
- sincedb_collection.store_last_read(key, watched_file.last_stat_size)
40
- sincedb_collection.request_disk_flush
41
- watched_file.listener.deleted
23
+
24
+ if @settings.check_archive_validity && corrupted?(watched_file)
42
25
  watched_file.unwatch
43
- ensure
44
- # rescue each close individually so all close attempts are tried
45
- close_and_ignore_ioexception(buffered) unless buffered.nil?
46
- close_and_ignore_ioexception(decoder) unless decoder.nil?
47
- close_and_ignore_ioexception(gzip_stream) unless gzip_stream.nil?
48
- close_and_ignore_ioexception(file_stream) unless file_stream.nil?
26
+ else
27
+ begin
28
+ file_stream = FileInputStream.new(watched_file.path)
29
+ gzip_stream = GZIPInputStream.new(file_stream)
30
+ decoder = InputStreamReader.new(gzip_stream, "UTF-8")
31
+ buffered = BufferedReader.new(decoder)
32
+ while (line = buffered.readLine(false))
33
+ watched_file.listener.accept(line)
34
+ # can't quit, if we did then we would incorrectly write a 'completed' sincedb entry
35
+ # what do we do about quit when we have just begun reading the zipped file (e.g. pipeline reloading)
36
+ # should we track lines read in the sincedb and
37
+ # fast forward through the lines until we reach unseen content?
38
+ # meaning that we can quit in the middle of a zip file
39
+ end
40
+ watched_file.listener.eof
41
+ rescue ZipException => e
42
+ logger.error("Cannot decompress the gzip file at path: #{watched_file.path}", :exception => e.class,
43
+ :message => e.message, :backtrace => e.backtrace)
44
+ watched_file.listener.error
45
+ else
46
+ sincedb_collection.store_last_read(key, watched_file.last_stat_size)
47
+ sincedb_collection.request_disk_flush
48
+ watched_file.listener.deleted
49
+ watched_file.unwatch
50
+ ensure
51
+ # rescue each close individually so all close attempts are tried
52
+ close_and_ignore_ioexception(buffered) unless buffered.nil?
53
+ close_and_ignore_ioexception(decoder) unless decoder.nil?
54
+ close_and_ignore_ioexception(gzip_stream) unless gzip_stream.nil?
55
+ close_and_ignore_ioexception(file_stream) unless file_stream.nil?
56
+ end
49
57
  end
50
58
  sincedb_collection.clear_watched_file(key)
51
59
  end
@@ -56,7 +64,28 @@ module FileWatch module ReadMode module Handlers
56
64
  begin
57
65
  closeable.close
58
66
  rescue Exception => e # IOException can be thrown by any of the Java classes that implement the Closable interface.
59
- logger.warn("Ignoring an IOException when closing an instance of #{closeable.class.name}", "exception" => e)
67
+ logger.warn("Ignoring an IOException when closing an instance of #{closeable.class.name}",
68
+ :exception => e.class, :message => e.message, :backtrace => e.backtrace)
69
+ end
70
+ end
71
+
72
+ def corrupted?(watched_file)
73
+ begin
74
+ file_stream = FileInputStream.new(watched_file.path)
75
+ gzip_stream = GZIPInputStream.new(file_stream)
76
+ buffer = Java::byte[8192].new
77
+ start = Time.new
78
+ until gzip_stream.read(buffer) == -1
79
+ end
80
+ return false
81
+ rescue ZipException => e
82
+ duration = Time.now - start
83
+ logger.warn("Detected corrupted archive #{watched_file.path} file won't be processed", :message => e.message,
84
+ :duration => duration.round(3))
85
+ return true
86
+ ensure
87
+ close_and_ignore_ioexception(gzip_stream) unless gzip_stream.nil?
88
+ close_and_ignore_ioexception(file_stream) unless file_stream.nil?
60
89
  end
61
90
  end
62
91
  end
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
- require "logstash/util/loggable"
3
-
2
+ require 'filewatch/processor'
4
3
  require_relative "handlers/base"
5
4
  require_relative "handlers/read_file"
6
5
  require_relative "handlers/read_zip_file"
@@ -9,20 +8,7 @@ module FileWatch module ReadMode
9
8
  # Must handle
10
9
  # :read_file
11
10
  # :read_zip_file
12
- class Processor
13
- include LogStash::Util::Loggable
14
-
15
- attr_reader :watch, :deletable_filepaths
16
-
17
- def initialize(settings)
18
- @settings = settings
19
- @deletable_filepaths = []
20
- end
21
-
22
- def add_watch(watch)
23
- @watch = watch
24
- self
25
- end
11
+ class Processor < FileWatch::Processor
26
12
 
27
13
  def initialize_handlers(sincedb_collection, observer)
28
14
  # we deviate from the tail mode handler initialization here
@@ -48,24 +34,23 @@ module FileWatch module ReadMode
48
34
  private
49
35
 
50
36
  def process_watched(watched_files)
51
- logger.trace("Watched processing")
37
+ logger.trace(__method__.to_s)
52
38
  # Handles watched_files in the watched state.
53
39
  # for a slice of them:
54
40
  # move to the active state
55
41
  # should never have been active before
56
42
  # how much of the max active window is available
57
- to_take = @settings.max_active - watched_files.count{|wf| wf.active?}
43
+ to_take = @settings.max_active - watched_files.count { |wf| wf.active? }
58
44
  if to_take > 0
59
- watched_files.select {|wf| wf.watched?}.take(to_take).each do |watched_file|
60
- path = watched_file.path
45
+ watched_files.select(&:watched?).take(to_take).each do |watched_file|
61
46
  begin
62
- watched_file.restat
47
+ restat(watched_file)
63
48
  watched_file.activate
64
49
  rescue Errno::ENOENT
65
- common_deleted_reaction(watched_file, "Watched")
50
+ common_deleted_reaction(watched_file, __method__)
66
51
  next
67
52
  rescue => e
68
- common_error_reaction(path, e, "Watched")
53
+ common_error_reaction(watched_file, e, __method__)
69
54
  next
70
55
  end
71
56
  break if watch.quit?
@@ -74,7 +59,7 @@ module FileWatch module ReadMode
74
59
  now = Time.now.to_i
75
60
  if (now - watch.lastwarn_max_files) > MAX_FILES_WARN_INTERVAL
76
61
  waiting = watched_files.size - @settings.max_active
77
- logger.warn(@settings.max_warn_msg + ", files yet to open: #{waiting}")
62
+ logger.warn("#{@settings.max_warn_msg}, files yet to open: #{waiting}")
78
63
  watch.lastwarn_max_files = now
79
64
  end
80
65
  end
@@ -83,17 +68,18 @@ module FileWatch module ReadMode
83
68
  ## TODO add process_rotation_in_progress
84
69
 
85
70
  def process_active(watched_files)
86
- logger.trace("Active processing")
71
+ logger.trace(__method__.to_s)
87
72
  # Handles watched_files in the active state.
88
- watched_files.select {|wf| wf.active? }.each do |watched_file|
89
- path = watched_file.path
73
+ watched_files.each do |watched_file|
74
+ next unless watched_file.active?
75
+
90
76
  begin
91
- watched_file.restat
77
+ restat(watched_file)
92
78
  rescue Errno::ENOENT
93
- common_deleted_reaction(watched_file, "Active")
79
+ common_deleted_reaction(watched_file, __method__)
94
80
  next
95
81
  rescue => e
96
- common_error_reaction(path, e, "Active")
82
+ common_error_reaction(watched_file, e, __method__)
97
83
  next
98
84
  end
99
85
  break if watch.quit?
@@ -114,19 +100,19 @@ module FileWatch module ReadMode
114
100
  def common_detach_when_allread(watched_file)
115
101
  watched_file.unwatch
116
102
  watched_file.listener.reading_completed
117
- deletable_filepaths << watched_file.path
118
- logger.trace("Whole file read: #{watched_file.path}, removing from collection")
103
+ add_deletable_path watched_file.path
104
+ logger.trace? && logger.trace("whole file read, removing from collection", :path => watched_file.path)
119
105
  end
120
106
 
121
107
  def common_deleted_reaction(watched_file, action)
122
108
  # file has gone away or we can't read it anymore.
123
109
  watched_file.unwatch
124
- deletable_filepaths << watched_file.path
125
- logger.trace("#{action} - stat failed: #{watched_file.path}, removing from collection")
110
+ add_deletable_path watched_file.path
111
+ logger.trace? && logger.trace("#{action} - stat failed, removing from collection", :path => watched_file.path)
126
112
  end
127
113
 
128
- def common_error_reaction(path, error, action)
129
- logger.error("#{action} - other error #{path}: (#{error.message}, #{error.backtrace.take(8).inspect})")
114
+ def common_error_reaction(watched_file, error, action)
115
+ logger.error("#{action} - other error", error_details(error, watched_file))
130
116
  end
131
117
  end
132
118
  end end
@@ -6,9 +6,10 @@ 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
+ attr_reader :check_archive_validity
12
13
 
13
14
  def self.from_options(opts)
14
15
  new.add_options(opts)
@@ -40,7 +41,6 @@ module FileWatch
40
41
  @file_chunk_size = @opts[:file_chunk_size]
41
42
  @close_older = @opts[:close_older]
42
43
  @ignore_older = @opts[:ignore_older]
43
- @sincedb_write_interval = @opts[:sincedb_write_interval]
44
44
  @stat_interval = @opts[:stat_interval]
45
45
  @discover_interval = @opts[:discover_interval]
46
46
  @exclude = Array(@opts[:exclude])
@@ -52,6 +52,7 @@ module FileWatch
52
52
  @file_sort_by = @opts[:file_sort_by]
53
53
  @file_sort_direction = @opts[:file_sort_direction]
54
54
  @exit_after_read = @opts[:exit_after_read]
55
+ @check_archive_validity = @opts[:check_archive_validity]
55
56
  self
56
57
  end
57
58
 
@@ -56,12 +56,12 @@ module FileWatch
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.trace("open: error:", :path => 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
@@ -71,7 +71,8 @@ module FileWatch
71
71
  logger.trace("associate: unmatched")
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)
74
+ logger.trace? && logger.trace("associate: found sincedb record", :filename => watched_file.filename,
75
+ :sincedb_key => watched_file.sincedb_key, :sincedb_value => sincedb_value)
75
76
  if sincedb_value.watched_file.nil?
76
77
  # not associated
77
78
  if sincedb_value.path_in_sincedb.nil?
@@ -106,7 +107,8 @@ module FileWatch
106
107
  # after the original is deleted
107
108
  # are not yet in the delete phase, let this play out
108
109
  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)
110
+ logger.trace? && logger.trace("----------------- >> associate: the found sincedb_value has a watched_file - this is a rename",
111
+ :this_watched_file => watched_file.details, :existing_watched_file => existing_watched_file.details)
110
112
  watched_file.rotation_in_progress
111
113
  true
112
114
  end
@@ -149,8 +151,8 @@ module FileWatch
149
151
  end
150
152
 
151
153
  def watched_file_deleted(watched_file)
152
- return unless member?(watched_file.sincedb_key)
153
- get(watched_file.sincedb_key).unset_watched_file
154
+ value = @sincedb[watched_file.sincedb_key]
155
+ value.unset_watched_file if value
154
156
  end
155
157
 
156
158
  def store_last_read(key, pos)
@@ -178,24 +180,24 @@ module FileWatch
178
180
  get(key).watched_file.nil?
179
181
  end
180
182
 
181
- private
182
-
183
183
  def flush_at_interval
184
- now = Time.now.to_i
185
- delta = now - @sincedb_last_write
184
+ now = Time.now
185
+ delta = now.to_i - @sincedb_last_write
186
186
  if delta >= @settings.sincedb_write_interval
187
187
  logger.debug("writing sincedb (delta since last write = #{delta})")
188
188
  sincedb_write(now)
189
189
  end
190
190
  end
191
191
 
192
+ private
193
+
192
194
  def handle_association(sincedb_value, watched_file)
193
195
  watched_file.update_bytes_read(sincedb_value.position)
194
196
  sincedb_value.set_watched_file(watched_file)
195
197
  watched_file.initial_completed
196
198
  if watched_file.all_read?
197
199
  watched_file.ignore
198
- logger.trace("handle_association fully read, ignoring.....", "watched file" => watched_file.details, "sincedb value" => sincedb_value)
200
+ logger.trace? && logger.trace("handle_association fully read, ignoring.....", :watched_file => watched_file.details, :sincedb_value => sincedb_value)
199
201
  end
200
202
  end
201
203
 
@@ -208,33 +210,33 @@ module FileWatch
208
210
  end
209
211
  end
210
212
 
211
- def sincedb_write(time = Time.now.to_i)
212
- logger.trace("sincedb_write: to: #{path}")
213
+ def sincedb_write(time = Time.now)
214
+ logger.trace("sincedb_write: #{path} (time = #{time})")
213
215
  begin
214
- @write_method.call
216
+ @write_method.call(time)
215
217
  @serializer.expired_keys.each do |key|
216
218
  @sincedb[key].unset_watched_file
217
219
  delete(key)
218
- logger.trace("sincedb_write: cleaned", "key" => "'#{key}'")
220
+ logger.trace? && logger.trace("sincedb_write: cleaned", :key => key)
219
221
  end
220
- @sincedb_last_write = time
222
+ @sincedb_last_write = time.to_i
221
223
  @write_requested = false
222
224
  rescue Errno::EACCES
223
225
  # no file handles free perhaps
224
226
  # maybe it will work next time
225
- logger.trace("sincedb_write: error: #{path}: #{$!}")
227
+ logger.trace("sincedb_write: #{path} error: #{$!}")
226
228
  end
227
229
  end
228
230
 
229
- def atomic_write
231
+ def atomic_write(time)
230
232
  FileHelper.write_atomically(@full_path) do |io|
231
- @serializer.serialize(@sincedb, io)
233
+ @serializer.serialize(@sincedb, io, time.to_f)
232
234
  end
233
235
  end
234
236
 
235
- def non_atomic_write
237
+ def non_atomic_write(time)
236
238
  IO.open(IO.sysopen(@full_path, "w+")) do |io|
237
- @serializer.serialize(@sincedb, io)
239
+ @serializer.serialize(@sincedb, io, time.to_f)
238
240
  end
239
241
  end
240
242
  end
@@ -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