eventmachine-tail 0.1.2801 → 0.1.20100505022629

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/lib/em/filetail.rb CHANGED
@@ -6,26 +6,62 @@ require "logger"
6
6
 
7
7
  EventMachine.epoll if EventMachine.epoll?
8
8
 
9
+ # Tail a file.
10
+ #
11
+ # Example
12
+ # class Tailer < EventMachine::Tail
13
+ # def receive_data(data)
14
+ # puts "Got #{data.length} bytes"
15
+ # end
16
+ # end
17
+ #
18
+ # # Now add it to EM
19
+ # EM.run do
20
+ # EM.file_tail("/var/log/messages", Tailer)
21
+ # end
22
+ #
23
+ # # Or this way:
24
+ # EM.run do
25
+ # Tailer.new("/var/log/messages")
26
+ # end
9
27
  class EventMachine::FileTail
10
28
  CHUNKSIZE = 65536
11
29
  MAXSLEEP = 2
12
30
 
13
31
  attr_reader :path
14
-
32
+
33
+ # Tail a file
34
+ #
35
+ # path is a string file path
36
+ # startpos is an offset to start tailing the file at. If -1, start at end of
37
+ # file.
38
+ #
15
39
  public
16
- def initialize(path, startpos=0)
40
+ def initialize(path, startpos=-1)
17
41
  @path = path
18
42
  @logger = Logger.new(STDOUT)
19
43
  @logger.level = Logger::WARN
20
44
 
21
- #@need_scheduling = true
22
- open
23
-
24
45
  @fstat = File.stat(@path)
25
- @file.sysseek(0, IO::SEEK_END)
46
+
47
+ if @fstat.directory?
48
+ raise Errno::EISDIR.new(@path)
49
+ end
50
+
51
+ open
26
52
  watch
53
+ if (startpos == -1)
54
+ @file.sysseek(0, IO::SEEK_END)
55
+ else
56
+ @file.sysseek(startpos, IO::SEEK_SET)
57
+ schedule_next_read
58
+ end
27
59
  end # def initialize
28
60
 
61
+ # notify is invoked by EventMachine when the file you are tailing
62
+ # has been modified or otherwise needs to be acted on.
63
+ #
64
+ # You won't normally call this method.
29
65
  public
30
66
  def notify(status)
31
67
  @logger.debug("#{status} on #{path}")
@@ -136,6 +172,12 @@ end # class EventMachine::FileTail::FileWatch < EventMachine::FileWatch
136
172
 
137
173
  # Add EventMachine::file_tail
138
174
  module EventMachine
175
+
176
+ # Tail a file.
177
+ #
178
+ # path is the path to the file to tail.
179
+ # handler should be a module implementing 'receive_data' or
180
+ # must be a subclasses of EventMachine::FileTail
139
181
  def self.file_tail(path, handler=nil, *args)
140
182
  args.unshift(path)
141
183
  klass = klass_from_handler(EventMachine::FileTail, handler, *args);
@@ -5,14 +5,33 @@ require "eventmachine"
5
5
  require "logger"
6
6
  require "em/filetail"
7
7
 
8
+ # A file glob pattern watcher for EventMachine.
9
+ #
10
+ # If you are unfamiliar with globs, see Wikipedia:
11
+ # http://en.wikipedia.org/wiki/Glob_(programming)
12
+ #
13
+ # Any glob supported by Dir#glob will work with
14
+ # this class.
15
+ #
16
+ # This class will allow you to get notified whenever a file
17
+ # is created or deleted that matches your glob.
18
+ #
19
+ #
8
20
  class EventMachine::FileGlobWatch
9
- def initialize(pathglob, handler, interval=60)
21
+ def initialize(pathglob, interval=60)
10
22
  @pathglob = pathglob
11
- @handler = handler
23
+ #@handler = handler
12
24
  @files = Set.new
25
+ @watches = Hash.new
13
26
  @logger = Logger.new(STDOUT)
14
- @logger.level = Logger::WARN
27
+ @logger.level = Logger::INFO
15
28
 
29
+ # We periodically check here because it is easier than writing our own glob
30
+ # parser (so we can smartly watch globs like /foo/*/bar*/*.log)
31
+ #
32
+ # Reasons to fix this -
33
+ # This will likely perform badly on globs that result in a large number of
34
+ # files.
16
35
  EM.next_tick do
17
36
  find_files
18
37
  EM.add_periodic_timer(interval) do
@@ -21,44 +40,91 @@ class EventMachine::FileGlobWatch
21
40
  end
22
41
  end # def initialize
23
42
 
43
+ private
24
44
  def find_files
45
+ @logger.info("Searching for files in #{@pathglob}")
25
46
  list = Set.new(Dir.glob(@pathglob))
26
47
  list.each do |path|
27
48
  next if @files.include?(path)
28
- watch(path)
49
+ add(path)
29
50
  end
30
51
 
31
52
  (@files - list).each do |missing|
32
- @files.delete(missing)
33
- @handler.file_removed(missing)
53
+ remove(missing)
34
54
  end
35
55
  end # def find_files
36
56
 
37
- def watch(path)
57
+ public
58
+ def remove(path)
59
+ @files.delete(path)
60
+ @watches.delete(path)
61
+ file_removed(path)
62
+ end
63
+
64
+ private
65
+ def add(path)
38
66
  @logger.info "Watching #{path}"
39
67
  @files.add(path)
40
- @handler.file_found(path)
68
+
69
+ # If EventMachine::watch_file fails, that's ok, I guess.
70
+ # We'll still find the file 'missing' from the next glob attempt.
71
+ begin
72
+ @watches[path] = EventMachine::watch_file(path, GlobFileWatch, self)
73
+ rescue Errno::EACCES => e
74
+ @logger.warn(e)
75
+ end
76
+ file_found(path)
41
77
  end # def watch
78
+
79
+ private
80
+ class GlobFileWatch < EventMachine::FileWatch
81
+ def initialize(globwatch)
82
+ @globwatch = globwatch
83
+ end
84
+
85
+ def file_moved
86
+ stop_watching
87
+ @globwatch.remove(path)
88
+ end
89
+
90
+ def file_deleted
91
+ @globwatch.remove(path)
92
+ end
93
+ end # class GlobFileWatch < EventMachine::FileWatch
42
94
  end # class EventMachine::FileGlobWatch
43
95
 
44
- class EventMachine::FileGlobWatchTail
45
- def initialize(handler=nil, *args)
96
+ class EventMachine::FileGlobWatchTail < EventMachine::FileGlobWatch
97
+ def initialize(path, handler=nil, *args)
98
+ super(path, *args)
46
99
  @handler = handler
47
100
  @args = args
48
101
  end
49
102
 
50
103
  def file_found(path)
51
- EventMachine::file_tail(path, @handler, *@args)
104
+ begin
105
+ EventMachine::file_tail(path, @handler, *@args)
106
+ rescue Errno::EACCES => e
107
+ file_error(path, e)
108
+ rescue Errno::EISDIR => e
109
+ file_error(path, e)
110
+ end
52
111
  end
53
112
 
54
113
  def file_removed(path)
55
114
  # Nothing to do
56
115
  end
116
+
117
+ def file_error(path, e)
118
+ $stderr.puts "#{e.class} while trying to tail #{path}"
119
+ # Ignore by default
120
+ end
57
121
  end # class EventMachine::FileGlobWatchHandler
58
122
 
123
+ # Add EventMachine::glob_tail
59
124
  module EventMachine
60
125
  def self.glob_tail(glob, handler=nil, *args)
61
- handler = EventMachine::FileGlobHandler if handler == nil
126
+ handler = EventMachine::FileGlobWatch if handler == nil
127
+ args.unshift(glob)
62
128
  klass = klass_from_handler(EventMachine::FileGlobWatchTail, handler, *args)
63
129
  c = klass.new(*args)
64
130
  yield c if block_given?
data/samples/glob-tail.rb CHANGED
@@ -38,9 +38,11 @@ def main(args)
38
38
  end
39
39
 
40
40
  EventMachine.run do
41
- handler = EventMachine::FileGlobWatchTail.new(Reader)
41
+ #handler = EventMachine::FileGlobWatchTail.new(Reader)
42
42
  args.each do |path|
43
- EventMachine::FileGlobWatch.new(path, handler)
43
+ EventMachine::FileGlobWatchTail.new(path, Reader)
44
+ #EventMachine::FileGlobWatch.new(path, handler)
45
+ #EventMachine::file_tail(path, handler)
44
46
  end
45
47
  end
46
48
  end # def main
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "eventmachine"
5
+ require "eventmachine-tail"
6
+
7
+ class Watcher < EventMachine::FileGlobWatch
8
+ def initialize(pathglob, interval=5)
9
+ super(pathglob, interval)
10
+ end
11
+
12
+ def file_removed(path)
13
+ puts "Removed: #{path}"
14
+ end
15
+
16
+ def file_found(path)
17
+ puts "Found: #{path}"
18
+ end
19
+ end # class Watcher
20
+
21
+ EM.run do
22
+ Watcher.new("/var/log/*")
23
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
5
+ require 'eventmachine-tail'
6
+ require 'tempfile'
7
+ require 'test/unit'
8
+ require 'timeout'
9
+
10
+
11
+ # Generate some data
12
+ DATA = (1..10).collect { |i| rand.to_s }
13
+ SLEEPMAX = 2
14
+
15
+ class Reader < EventMachine::FileTail
16
+ def initialize(path, startpos=-1, testobj=nil)
17
+ super(path, startpos)
18
+ @data = DATA.clone
19
+ @buffer = BufferedTokenizer.new
20
+ @testobj = testobj
21
+ @lineno = 0
22
+ end # def initialize
23
+
24
+ def receive_data(data)
25
+ @buffer.extract(data).each do |line|
26
+ @lineno += 1
27
+ expected = @data.shift
28
+ @testobj.assert_equal(expected, line,
29
+ "Expected '#{expected}' on line #{@lineno}, but got '#{line}'")
30
+ if @data.length == 0
31
+ EM.stop_event_loop
32
+ end
33
+ end # @buffer.extract
34
+ end # def receive_data
35
+ end # class Reader
36
+
37
+ class TestFileTail < Test::Unit::TestCase
38
+ def abort_after_timeout(seconds)
39
+ EM::Timer.new(seconds) do
40
+ EM.stop_event_loop
41
+ flunk("Timeout (#{seconds} seconds) while running tests. Failing.")
42
+ end
43
+ end
44
+
45
+ # This test should run slow. We are trying to ensure that
46
+ # our file_tail correctly reads data slowly fed into the file
47
+ # as 'tail -f' would.
48
+ def test_filetail
49
+ tmp = Tempfile.new("testfiletail")
50
+ data = DATA.clone
51
+ EM.run do
52
+ abort_after_timeout(DATA.length * SLEEPMAX + 10)
53
+
54
+ EM::file_tail(tmp.path, Reader, -1, self)
55
+ timer = EM::PeriodicTimer.new(0.2) do
56
+ tmp.puts data.shift
57
+ tmp.flush
58
+ sleep(rand * SLEEPMAX)
59
+ timer.cancel if data.length == 0
60
+ end
61
+ end # EM.run
62
+ end # def test_filetail
63
+
64
+ def test_filetail_with_seek
65
+ tmp = Tempfile.new("testfiletail")
66
+ data = DATA.clone
67
+ data.each { |i| tmp.puts i }
68
+ tmp.flush
69
+ EM.run do
70
+ abort_after_timeout(2)
71
+
72
+ # Set startpos of 0 (beginning of file)
73
+ EM::file_tail(tmp.path, Reader, 0, self)
74
+ end # EM.run
75
+ end # def test_filetail
76
+ end # class TestFileTail
77
+
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventmachine-tail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2801
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 20100505022629
9
+ version: 0.1.20100505022629
5
10
  platform: ruby
6
11
  authors:
7
12
  - Jordan Sissel
@@ -9,20 +14,22 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-04-14 00:00:00 -07:00
17
+ date: 2010-05-05 00:00:00 -07:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
21
  name: eventmachine
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
20
24
  requirements:
21
25
  - - ">="
22
26
  - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
23
29
  version: "0"
24
- version:
25
- description: Add file 'tail' implemented with EventMachine
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ description: Add file 'tail' implemented with EventMachine. Also includes a 'glob watch' class for watching a directory pattern for new matches, like /var/log/*.log
26
33
  email: jls@semicomplete.com
27
34
  executables: []
28
35
 
@@ -36,6 +43,8 @@ files:
36
43
  - lib/em/globwatcher.rb
37
44
  - samples/tail.rb
38
45
  - samples/glob-tail.rb
46
+ - samples/globwatch.rb
47
+ - test/test_filetail.rb
39
48
  has_rdoc: true
40
49
  homepage: http://code.google.com/p/semicomplete/wiki/EventMachineTail
41
50
  licenses: []
@@ -50,18 +59,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
59
  requirements:
51
60
  - - ">="
52
61
  - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
53
64
  version: "0"
54
- version:
55
65
  required_rubygems_version: !ruby/object:Gem::Requirement
56
66
  requirements:
57
67
  - - ">="
58
68
  - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
59
71
  version: "0"
60
- version:
61
72
  requirements: []
62
73
 
63
74
  rubyforge_project:
64
- rubygems_version: 1.3.5
75
+ rubygems_version: 1.3.6
65
76
  signing_key:
66
77
  specification_version: 3
67
78
  summary: eventmachine tail - a file tail implementation