eventmachine-tail 0.1.2801 → 0.1.20100505022629

Sign up to get free protection for your applications and to get access to all the features.
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