eventmachine-tail 0.0.1 → 0.6.1

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.
data/bin/emtail ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "eventmachine"
4
+ require "eventmachine-tail"
5
+ require "optparse"
6
+
7
+ class Reader < EventMachine::FileTail
8
+ def initialize(path, startpos=-1, with_filenames=true)
9
+ super(path, startpos)
10
+ @buffer = BufferedTokenizer.new
11
+ @with_filenames = with_filenames
12
+ end
13
+
14
+ def receive_data(data)
15
+ @buffer.extract(data).each do |line|
16
+ if @with_filenames # global flag, see the '-n' option
17
+ puts "#{path}: #{line}"
18
+ else
19
+ puts line
20
+ end # if @with_filenames
21
+ end # buffer extract
22
+ end # def receive_data
23
+ end # class Reader
24
+
25
+ def pattern_to_regexp(pattern)
26
+ pattern.gsub!(".", "\\.") # fix literal .
27
+ pattern.gsub!("*", ".+") # * becomes .+
28
+ pattern.gsub!("?", ".") # ? becomes .
29
+ return Regexp.new(pattern)
30
+ end # def pattern_to_regexp
31
+
32
+ def main(args)
33
+ with_filenames = true
34
+ globcheck_interval = 5
35
+ exclude_patterns = []
36
+
37
+ opts = OptionParser.new do |opts|
38
+ opts.banner = "Usage: #{$0} [options] <path_or_glob> [path_or_glob2] [...]"
39
+
40
+ opts.on("-n", "--no-filename",
41
+ "Supress prefixing of output with file names") do |x|
42
+ with_filenames = false
43
+ end # -n
44
+
45
+ opts.on("-i SECONDS", "--check-interval SECONDS",
46
+ "How frequently, in seconds, to check the glob patterns" \
47
+ "for new files") do |x|
48
+ globcheck_interval = x.to_f
49
+ end # -i SECONDS
50
+
51
+ opts.on("-x EXCLUDE", "--exclude EXCLUDE",
52
+ "A pattern to ignore. Wildcard/globs accepted." \
53
+ " Can be specified multiple times") do |pattern|
54
+ exclude_patterns << pattern_to_regexp(pattern)
55
+ end
56
+ end # OptionParser
57
+
58
+ opts.parse!(args)
59
+
60
+ if args.length == 0
61
+ puts opts.banner
62
+ return 1
63
+ end
64
+
65
+ EventMachine.run do
66
+ Signal.trap("INT") do
67
+ EventMachine.schedule do
68
+ $stderr.puts "Got SIGINT"
69
+ exit 128 + (Signal.list["INT"])
70
+ end
71
+ end
72
+
73
+ args.each do |path|
74
+ EventMachine::FileGlobWatchTail.new(path, Reader,
75
+ interval = globcheck_interval,
76
+ exclude = exclude_patterns,
77
+ start_pos = -1,
78
+ with_filenames = with_filenames)
79
+ end # args.each
80
+ end # EventMachine.run
81
+ end # def main
82
+
83
+ exit(main(ARGV))
data/bin/rtail ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "eventmachine"
4
+ require "eventmachine-tail"
5
+ require "optparse"
6
+
7
+ class Reader < EventMachine::FileTail
8
+ def initialize(path, startpos=-1, with_filenames=true)
9
+ super(path, startpos)
10
+ @buffer = BufferedTokenizer.new
11
+ @with_filenames = with_filenames
12
+ end
13
+
14
+ def receive_data(data)
15
+ @buffer.extract(data).each do |line|
16
+ if @with_filenames # global flag, see the '-n' option
17
+ puts "#{path}: #{line}"
18
+ else
19
+ puts line
20
+ end # if @with_filenames
21
+ end # buffer extract
22
+ end # def receive_data
23
+ end # class Reader
24
+
25
+ def pattern_to_regexp(pattern)
26
+ pattern.gsub!(".", "\\.") # fix literal .
27
+ pattern.gsub!("*", ".+") # * becomes .+
28
+ pattern.gsub!("?", ".") # ? becomes .
29
+ return Regexp.new(pattern)
30
+ end # def pattern_to_regexp
31
+
32
+ def main(args)
33
+ with_filenames = true
34
+ globcheck_interval = 5
35
+ exclude_patterns = []
36
+
37
+ opts = OptionParser.new do |opts|
38
+ opts.banner = "Usage: #{$0} [options] <path_or_glob> [path_or_glob2] [...]"
39
+
40
+ opts.on("-n", "--no-filename",
41
+ "Supress prefixing of output with file names") do |x|
42
+ with_filenames = false
43
+ end # -n
44
+
45
+ opts.on("-i SECONDS", "--check-interval SECONDS",
46
+ "How frequently, in seconds, to check the glob patterns" \
47
+ "for new files") do |x|
48
+ globcheck_interval = x.to_f
49
+ end # -i SECONDS
50
+
51
+ opts.on("-x EXCLUDE", "--exclude EXCLUDE",
52
+ "A pattern to ignore. Wildcard/globs accepted." \
53
+ " Can be specified multiple times") do |pattern|
54
+ exclude_patterns << pattern_to_regexp(pattern)
55
+ end
56
+ end # OptionParser
57
+
58
+ opts.parse!(args)
59
+
60
+ if args.length == 0
61
+ puts opts.banner
62
+ return 1
63
+ end
64
+
65
+ EventMachine.run do
66
+ Signal.trap("INT") do
67
+ EventMachine.schedule do
68
+ $stderr.puts "Got SIGINT"
69
+ exit 128 + (Signal.list["INT"])
70
+ end
71
+ end
72
+
73
+ args.each do |path|
74
+ EventMachine::FileGlobWatchTail.new(path, Reader,
75
+ interval = globcheck_interval,
76
+ exclude = exclude_patterns,
77
+ start_pos = -1,
78
+ with_filenames = with_filenames)
79
+ end # args.each
80
+ end # EventMachine.run
81
+ end # def main
82
+
83
+ exit(main(ARGV))
data/lib/em/filetail.rb CHANGED
@@ -1,162 +1,447 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rubygems" if __FILE__ == $0
4
3
  require "eventmachine"
5
4
  require "logger"
6
5
 
7
6
  EventMachine.epoll if EventMachine.epoll?
7
+ EventMachine.kqueue = true if EventMachine.kqueue?
8
8
 
9
+ # Tail a file.
10
+ #
11
+ # Example
12
+ # class Tailer < EventMachine::FileTail
13
+ # def receive_data(data)
14
+ # puts "Got #{data.length} bytes"
15
+ # end
16
+ #
17
+ # # Optional
18
+ # def eof
19
+ # puts "Got EOF!"
20
+ # # If you want to stop
21
+ # stop
22
+ # end
23
+ # end
24
+ #
25
+ # # Now add it to EM
26
+ # EM.run do
27
+ # EM.file_tail("/var/log/messages", Tailer)
28
+ # end
29
+ #
30
+ # # Or this way:
31
+ # EM.run do
32
+ # Tailer.new("/var/log/messages")
33
+ # end
34
+ #
35
+ # See also: EventMachine::FileTail#receive_data
9
36
  class EventMachine::FileTail
37
+ # Maximum size to read at a time from a single file.
10
38
  CHUNKSIZE = 65536
11
- MAXSLEEP = 2
12
39
 
40
+ #MAXSLEEP = 2
41
+
42
+ FORCE_ENCODING = !! (defined? Encoding)
43
+
44
+ # The path of the file being tailed
13
45
  attr_reader :path
14
46
 
47
+ # The current file read position
48
+ attr_reader :position
49
+
50
+ # Check interval when checking symlinks for changes. This is only useful
51
+ # when you are actually tailing symlinks.
52
+ attr_accessor :symlink_check_interval
53
+
54
+ # Check interval for looking for a file if we are tailing it and it has
55
+ # gone missing.
56
+ attr_accessor :missing_file_check_interval
57
+
58
+ # Tail a file
59
+ #
60
+ # * path is a string file path to tail
61
+ # * startpos is an offset to start tailing the file at. If -1, start at end of
62
+ # file.
63
+ #
64
+ # If you want debug messages, run ruby with '-d' or set $DEBUG
65
+ #
66
+ # See also: EventMachine::file_tail
67
+ #
15
68
  public
16
- def initialize(path, startpos=0)
69
+ def initialize(path, startpos=-1, &block)
17
70
  @path = path
18
- @logger = Logger.new(STDOUT)
71
+ @logger = Logger.new(STDERR)
72
+ @logger.level = ($DEBUG and Logger::DEBUG or Logger::WARN)
73
+ @logger.debug("Tailing #{path} starting at position #{startpos}")
19
74
 
20
- #@need_scheduling = true
21
- open
75
+ @file = nil
76
+ @want_eof_handling = false
77
+ @want_read = false
78
+ @want_reopen = false
79
+ @reopen_on_eof = false
80
+ @symlink_timer = nil
81
+ @symlink_target = nil
82
+ @symlink_stat = nil
22
83
 
23
- @fstat = File.stat(@path)
24
- @file.seek(0, IO::SEEK_END)
25
- watch
84
+ @symlink_check_interval = 1
85
+ @missing_file_check_interval = 1
86
+
87
+ read_file_metadata
88
+
89
+ if @filestat.directory?
90
+ on_exception Errno::EISDIR.new(@path)
91
+ end
92
+
93
+ if block_given?
94
+ @handler = block
95
+ @buffer = BufferedTokenizer.new
96
+ end
97
+
98
+ EventMachine::next_tick do
99
+ open
100
+ if (startpos == -1)
101
+ @position = @file.sysseek(0, IO::SEEK_END)
102
+ # TODO(sissel): if we don't have inotify or kqueue, should we
103
+ # schedule a next read, here?
104
+ # Is there a race condition between setting the file position and
105
+ # watching given the two together are not atomic?
106
+ else
107
+ @position = @file.sysseek(startpos, IO::SEEK_SET)
108
+ schedule_next_read
109
+ end
110
+ watch
111
+ end # EventMachine::next_tick
26
112
  end # def initialize
27
113
 
114
+ # This method is called when a tailed file has data read.
115
+ #
116
+ # * data - string data read from the file.
117
+ #
118
+ # If you want to read lines from your file, you should use BufferedTokenizer
119
+ # (which comes with EventMachine):
120
+ # class Tailer < EventMachine::FileTail
121
+ # def initialize(*args)
122
+ # super(*args)
123
+ # @buffer = BufferedTokenizer.new
124
+ # end
125
+ #
126
+ # def receive_data(data)
127
+ # @buffer.extract(data).each do |line|
128
+ # # do something with 'line'
129
+ # end
130
+ # end
131
+ public
132
+ def receive_data(data)
133
+ if @handler # FileTail.new called with a block
134
+ @buffer.extract(data).each do |line|
135
+ @handler.call(self, line)
136
+ end
137
+ else
138
+ on_exception NotImplementedError.new("#{self.class.name}#receive_data is not "\
139
+ "implemented. Did you forget to implement this in your subclass or "\
140
+ "module?")
141
+ end
142
+ end # def receive_data
143
+
144
+ def on_exception(exception)
145
+ @logger.error("Exception raised. Using default handler in #{self.class.name}")
146
+ raise exception
147
+ end
148
+
149
+ # This method is called when a tailed file reaches EOF.
150
+ #
151
+ # If you want to stop reading this file, call close(), otherwise
152
+ # this eof is handled as normal tailing does. The default
153
+ # EOF handler is to do nothing.
28
154
  public
155
+ def eof
156
+ @logger.debug { 'EOF' }
157
+ # do nothing, subclassers should implement this.
158
+ end # def eof
159
+
160
+ # notify is invoked by EM::watch_file when the file you are tailing has been
161
+ # modified or otherwise needs to be acted on.
162
+ private
29
163
  def notify(status)
30
- @logger.debug("#{status} on #{path}")
164
+ @logger.debug { "notify: #{status} on #{path}" }
31
165
  if status == :modified
32
166
  schedule_next_read
33
167
  elsif status == :moved
34
- # TODO(sissel): read to EOF, then reopen.
35
- open
168
+ # read to EOF, then reopen.
169
+ @reopen_on_eof = true
170
+ schedule_next_read
171
+ elsif status == :unbind
172
+ # :unbind is called after the :deleted handler
173
+ # :deleted happens on FreeBSD's newsyslog instead of :moved
174
+ # clean up @watch since its reference is wiped in EM's file_deleted callback
175
+ @watch = nil
36
176
  end
37
- end
177
+ end # def notify
38
178
 
179
+ # Open (or reopen, if necessary) our file and schedule a read.
39
180
  private
40
181
  def open
182
+ return if @closed
41
183
  @file.close if @file
184
+ return unless File.exists?(@path)
42
185
  begin
186
+ @logger.debug "Opening file #{@path}"
43
187
  @file = File.open(@path, "r")
44
- rescue Errno::ENOENT
45
- # no file found
46
- raise
188
+ rescue Errno::ENOENT => e
189
+ @logger.info("File not found: '#{@path}' (#{e})")
190
+ on_exception(e)
47
191
  end
48
192
 
49
- @naptime = 0;
50
- @pos = 0
193
+ @naptime = 0
194
+ @logger.debug { 'EOF' }
195
+ @position = 0
51
196
  schedule_next_read
52
- end
197
+ end # def open
53
198
 
199
+ # Close this filetail
200
+ public
201
+ def close
202
+ @closed = true
203
+ EM.schedule do
204
+ @file.close if @file
205
+ end
206
+ end # def close
207
+
208
+ # Watch our file.
54
209
  private
55
210
  def watch
56
- EventMachine::watch_file(@path, FileWatcher, self)
57
- end
211
+ @watch.stop_watching if @watch
212
+ @symlink_timer.cancel if @symlink_timer
213
+ return unless File.exists?(@path)
214
+
215
+ @logger.debug "Starting watch on #{@path}"
216
+ callback = proc { |what| notify(what) }
217
+ @watch = EventMachine::watch_file(@path, EventMachine::FileTail::FileWatcher, callback)
218
+ watch_symlink if @symlink_target
219
+ end # def watch
220
+
221
+ # Watch a symlink
222
+ # EM doesn't currently support watching symlinks alone (inotify follows
223
+ # symlinks by default), so let's periodically stat the symlink.
224
+ private
225
+ def watch_symlink(&block)
226
+ @symlink_timer.cancel if @symlink_timer
227
+
228
+ @logger.debug "Launching timer to check for symlink changes since EM can't right now: #{@path}"
229
+ @symlink_timer = EM::PeriodicTimer.new(@symlink_check_interval) do
230
+ begin
231
+ @logger.debug("Checking #{@path}")
232
+ read_file_metadata do |filestat, linkstat, linktarget|
233
+ handle_fstat(filestat, linkstat, linktarget)
234
+ end
235
+ rescue Errno::ENOENT
236
+ # The file disappeared. Wait for it to reappear.
237
+ # This can happen if it was deleted or moved during log rotation.
238
+ @logger.debug "File not found, waiting for it to reappear. (#{@path})"
239
+ end # begin/rescue ENOENT
240
+ end # EM::PeriodicTimer
241
+ end # def watch_symlink
58
242
 
59
243
  private
60
244
  def schedule_next_read
61
- EventMachine::add_timer(@naptime) do
62
- read
63
- end
64
- end
245
+ if !@want_read
246
+ @want_read = true
247
+ EventMachine::add_timer(@naptime) do
248
+ @want_read = false
249
+ read
250
+ end
251
+ end # if !@want_read
252
+ end # def schedule_next_read
65
253
 
254
+ # Read CHUNKSIZE from our file and pass it to .receive_data()
66
255
  private
67
256
  def read
257
+ return if @closed
258
+ @logger.debug "#{self}: Reading..."
68
259
  begin
69
260
  data = @file.sysread(CHUNKSIZE)
261
+ data.force_encoding(@file.external_encoding) if FORCE_ENCODING
262
+
70
263
  # Won't get here if sysread throws EOF
71
- @pos += data.length
264
+ @position += data.length
72
265
  @naptime = 0
266
+
267
+ # Subclasses should implement receive_data
73
268
  receive_data(data)
74
269
  schedule_next_read
75
270
  rescue EOFError
76
- eof
271
+ schedule_eof
77
272
  end
78
- end
273
+ end # def read
79
274
 
275
+ # Do EOF handling on next EM iteration
80
276
  private
81
- def eof
82
- # TODO(sissel): This will be necessary if we can't use inotify or kqueue to
83
- # get notified of file changes
84
- #if @need_scheduling
85
- #@naptime = 0.100 if @naptime == 0
86
- #@naptime *= 2
87
- #@naptime = MAXSLEEP if @naptime > MAXSLEEP
88
- #@logger.info("EOF. Naptime: #{@naptime}")
89
- #end
90
-
91
- # TODO(sissel): schedule an fstat instead of doing it now.
92
- fstat = File.stat(@path)
93
- handle_fstat(fstat)
94
- end # def eof
277
+ def schedule_eof
278
+ if !@want_eof_handling
279
+ eof # Call our own eof event
280
+ @want_eof_handling = true
281
+ EventMachine::next_tick do
282
+ handle_eof
283
+ end # EventMachine::next_tick
284
+ end # if !@want_eof_handling
285
+ end # def schedule_eof
286
+
287
+ private
288
+ def schedule_reopen
289
+ if !@want_reopen
290
+ EventMachine::next_tick do
291
+ @want_reopen = false
292
+ open
293
+ watch
294
+ end
295
+ end # if !@want_reopen
296
+ end # def schedule_reopen
297
+
298
+ private
299
+ def handle_eof
300
+ @want_eof_handling = false
301
+
302
+ if @reopen_on_eof
303
+ @reopen_on_eof = false
304
+ schedule_reopen
305
+ end
306
+
307
+ # EOF actions:
308
+ # - Check if the file inode/device is changed
309
+ # - If symlink, check if the symlink has changed
310
+ # - Otherwise, do nothing
311
+ begin
312
+ read_file_metadata do |filestat, linkstat, linktarget|
313
+ handle_fstat(filestat, linkstat, linktarget)
314
+ end
315
+ rescue Errno::ENOENT
316
+ # The file disappeared. Wait for it to reappear.
317
+ # This can happen if it was deleted or moved during log rotation.
318
+ timer = EM::PeriodicTimer.new(@missing_file_check_interval) do
319
+ begin
320
+ read_file_metadata do |filestat, linkstat, linktarget|
321
+ handle_fstat(filestat, linkstat, linktarget)
322
+ end
323
+ timer.cancel
324
+ rescue Errno::ENOENT
325
+ # The file disappeared. Wait for it to reappear.
326
+ # This can happen if it was deleted or moved during log rotation.
327
+ @logger.debug "File not found, waiting for it to reappear. (#{@path})"
328
+ end # begin/rescue ENOENT
329
+ end # EM::PeriodicTimer
330
+ end # begin/rescue ENOENT
331
+ end # def handle_eof
332
+
333
+ private
334
+ def read_file_metadata(&block)
335
+ begin
336
+ filestat = File.stat(@path)
337
+ rescue => e
338
+ @logger.debug("File stat on '#{@path}' failed")
339
+ on_exception e
340
+ end
341
+ symlink_stat = nil
342
+ symlink_target = nil
343
+
344
+ if File.symlink?(@path)
345
+ symlink_stat = File.lstat(@path) rescue nil
346
+ symlink_target = File.readlink(@path) rescue nil
347
+ end
348
+
349
+ if block_given?
350
+ yield filestat, symlink_stat, symlink_target
351
+ end
95
352
 
96
- def handle_fstat(fstat)
97
- if (fstat.ino != @fstat.ino)
98
- open # Reopen if the inode has changed
99
- elsif (fstat.rdev != @fstat.rdev)
100
- open # Reopen if the filesystem device changed
101
- elsif (fstat.size < @fstat.size)
353
+ @filestat = filestat
354
+ @symlink_stat = symlink_stat
355
+ @symlink_target = symlink_target
356
+ end # def read_file_metadata
357
+
358
+ # Handle fstat changes appropriately.
359
+ private
360
+ def handle_fstat(filestat, symlinkstat, symlinktarget)
361
+ # If the symlink target changes, the filestat.ino is very likely to have
362
+ # changed since that is the stat on the resolved file (that the link points
363
+ # to). However, we'll check explicitly for the symlink target changing
364
+ # for better debuggability.
365
+ if symlinktarget
366
+ if symlinkstat.ino != @symlink_stat.ino
367
+ @logger.debug "Inode or device changed on symlink. Reopening..."
368
+ @reopen_on_eof = true
369
+ schedule_next_read
370
+ elsif symlinktarget != @symlink_target
371
+ @logger.debug "Symlink target changed. Reopening..."
372
+ @reopen_on_eof = true
373
+ schedule_next_read
374
+ end
375
+ elsif (filestat.ino != @filestat.ino or filestat.rdev != @filestat.rdev)
376
+ @logger.debug "Inode or device changed. Reopening..."
377
+ @logger.debug filestat
378
+ @reopen_on_eof = true
379
+ schedule_next_read
380
+ elsif (filestat.size < @filestat.size)
381
+ # If the file size shrank, assume truncation and seek to the beginning.
102
382
  @logger.info("File likely truncated... #{path}")
103
- @file.seek(0, IO::SEEK_SET)
383
+ @position = @file.sysseek(0, IO::SEEK_SET)
104
384
  schedule_next_read
105
385
  end
106
- @fstat = fstat
107
- end # def eof
386
+ end # def handle_fstat
387
+
388
+ def to_s
389
+ return "#{self.class.name}(#{@path}) @ pos:#{@position}"
390
+ end # def to_s
108
391
  end # class EventMachine::FileTail
109
392
 
110
- # Internal usage only
393
+ # Internal usage only. This class is used by EventMachine::FileTail
394
+ # to watch files you are tailing.
395
+ #
396
+ # See also: EventMachine::FileTail#watch
111
397
  class EventMachine::FileTail::FileWatcher < EventMachine::FileWatch
112
- def initialize(filestream)
113
- @filestream = filestream
114
- @logger = Logger.new(STDOUT)
115
- end
398
+ def initialize(block)
399
+ @logger = Logger.new(STDERR)
400
+ @logger.level = ($DEBUG and Logger::DEBUG or Logger::WARN)
401
+ @callback = block
402
+ end # def initialize
116
403
 
117
404
  def file_modified
118
- @filestream.notify :modified
119
- end
405
+ @callback.call(:modified)
406
+ end # def file_modified
120
407
 
121
408
  def file_moved
122
- @filestream.notify :moved
123
- end
409
+ @callback.call(:moved)
410
+ end # def file_moved
124
411
 
125
412
  def file_deleted
126
- @filestream.notify :deleted
127
- end
413
+ @callback.call(:deleted)
414
+ end # def file_deleted
128
415
 
129
416
  def unbind
130
- @filestream.notify :unbind
131
- end
417
+ @callback.call(:unbind)
418
+ end # def unbind
132
419
  end # class EventMachine::FileTail::FileWatch < EventMachine::FileWatch
133
420
 
134
421
  # Add EventMachine::file_tail
135
422
  module EventMachine
136
- def self.file_tail(path, handler=nil, *args)
137
- args.unshift(path)
423
+ # Tail a file.
424
+ #
425
+ # path is the path to the file to tail.
426
+ # handler should be a module implementing 'receive_data' or
427
+ # must be a subclasses of EventMachine::FileTail
428
+ #
429
+ # For example:
430
+ # EM::file_tail("/var/log/messages", MyHandler)
431
+ #
432
+ # If a block is given, and the handler is not specified or does
433
+ # not implement EventMachine::FileTail#receive_data, then it
434
+ # will be called as such:
435
+ # EM::file_tail(...) do |filetail, line|
436
+ # # filetail is the FileTail instance watching the file
437
+ # # line is the line read from the file
438
+ # end
439
+ def self.file_tail(path, handler=nil, *args, &block)
440
+ # This code mostly styled on what EventMachine does in many of it's other
441
+ # methods.
442
+ args = [path, *args]
138
443
  klass = klass_from_handler(EventMachine::FileTail, handler, *args);
139
- c = klass.new(*args)
140
- yield c if block_given?
444
+ c = klass.new(*args, &block)
141
445
  return c
142
446
  end # def self.file_tail
143
447
  end # module EventMachine
144
-
145
- if __FILE__ == $0
146
- class Reader < EventMachine::FileTail
147
- def initialize(*args)
148
- super(*args)
149
- @buffer = BufferedTokenizer.new
150
- end
151
-
152
- def receive_data(data)
153
- @buffer.extract(data).each do |line|
154
- ap [path, line]
155
- end
156
- end
157
- end
158
-
159
- EventMachine::run do
160
- fw = EventMachine::filestream("/var/log/user.log", Reader)
161
- end
162
- end # if __FILE__ == $0