eventmachine-tail 0.2.20100516235116 → 0.2.20100517011408
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 +62 -30
- data/lib/em/globwatcher.rb +118 -32
- data/samples/globwatch.rb +1 -1
- data/test/alltests.rb +2 -0
- data/test/test_glob.rb +1 -1
- metadata +4 -3
data/lib/em/filetail.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require "rubygems" if __FILE__ == $0
|
4
3
|
require "eventmachine"
|
5
4
|
require "logger"
|
6
5
|
|
@@ -9,7 +8,7 @@ EventMachine.epoll if EventMachine.epoll?
|
|
9
8
|
# Tail a file.
|
10
9
|
#
|
11
10
|
# Example
|
12
|
-
# class Tailer < EventMachine::
|
11
|
+
# class Tailer < EventMachine::FileTail
|
13
12
|
# def receive_data(data)
|
14
13
|
# puts "Got #{data.length} bytes"
|
15
14
|
# end
|
@@ -24,18 +23,26 @@ EventMachine.epoll if EventMachine.epoll?
|
|
24
23
|
# EM.run do
|
25
24
|
# Tailer.new("/var/log/messages")
|
26
25
|
# end
|
26
|
+
#
|
27
|
+
# See also: EventMachine::FileTail#receive_data
|
27
28
|
class EventMachine::FileTail
|
29
|
+
# Maximum size to read at a time from a single file.
|
28
30
|
CHUNKSIZE = 65536
|
29
|
-
MAXSLEEP = 2
|
30
31
|
|
32
|
+
#
|
33
|
+
#MAXSLEEP = 2
|
34
|
+
|
35
|
+
# The path of the file being tailed
|
31
36
|
attr_reader :path
|
32
37
|
|
33
38
|
# Tail a file
|
34
39
|
#
|
35
|
-
# path is a string file path
|
36
|
-
# startpos is an offset to start tailing the file at. If -1, start at end of
|
40
|
+
# * path is a string file path to tail
|
41
|
+
# * startpos is an offset to start tailing the file at. If -1, start at end of
|
37
42
|
# file.
|
38
43
|
#
|
44
|
+
# See also: EventMachine::file_tail
|
45
|
+
#
|
39
46
|
public
|
40
47
|
def initialize(path, startpos=-1)
|
41
48
|
@path = path
|
@@ -51,7 +58,7 @@ class EventMachine::FileTail
|
|
51
58
|
end
|
52
59
|
|
53
60
|
open
|
54
|
-
watch
|
61
|
+
watch { |what| notify(what) }
|
55
62
|
if (startpos == -1)
|
56
63
|
@file.sysseek(0, IO::SEEK_END)
|
57
64
|
else
|
@@ -60,11 +67,33 @@ class EventMachine::FileTail
|
|
60
67
|
end
|
61
68
|
end # def initialize
|
62
69
|
|
63
|
-
#
|
64
|
-
#
|
70
|
+
# This method is called when a tailed file has data read.
|
71
|
+
#
|
72
|
+
# * data - string data read from the file.
|
73
|
+
#
|
74
|
+
# If you want to read lines from your file, you should use BufferedTokenizer
|
75
|
+
# (which comes with EventMachine):
|
76
|
+
# class Tailer < EventMachine::FileTail
|
77
|
+
# def initialize(*args)
|
78
|
+
# super(*args)
|
79
|
+
# @buffer = BufferedTokenizer.new
|
80
|
+
# end
|
65
81
|
#
|
66
|
-
#
|
82
|
+
# def receive_data(data)
|
83
|
+
# @buffer.extract(data).each do |line|
|
84
|
+
# # do something with 'line'
|
85
|
+
# end
|
86
|
+
# end
|
67
87
|
public
|
88
|
+
def receive_data(data)
|
89
|
+
raise NotImplementedError.new("#{self.class.name}#receive_data is not "\
|
90
|
+
"implemented. Did you forget to implement this in your subclass or "\
|
91
|
+
"module?")
|
92
|
+
end # def receive_data
|
93
|
+
|
94
|
+
# notify is invoked when the file you are tailing has been modified or
|
95
|
+
# otherwise needs to be acted on.
|
96
|
+
private
|
68
97
|
def notify(status)
|
69
98
|
@logger.debug("#{status} on #{path}")
|
70
99
|
if status == :modified
|
@@ -75,11 +104,7 @@ class EventMachine::FileTail
|
|
75
104
|
end
|
76
105
|
end
|
77
106
|
|
78
|
-
|
79
|
-
def receive_data(data)
|
80
|
-
@logger.warn("Got #{data.length} bytes")
|
81
|
-
end
|
82
|
-
|
107
|
+
# Open (or reopen, if necessary) our file and schedule a read.
|
83
108
|
private
|
84
109
|
def open
|
85
110
|
@file.close if @file
|
@@ -95,11 +120,13 @@ class EventMachine::FileTail
|
|
95
120
|
schedule_next_read
|
96
121
|
end
|
97
122
|
|
123
|
+
# Watch our file.
|
98
124
|
private
|
99
|
-
def watch
|
100
|
-
EventMachine::watch_file(@path, EventMachine::FileTail::FileWatcher,
|
125
|
+
def watch(&block)
|
126
|
+
EventMachine::watch_file(@path, EventMachine::FileTail::FileWatcher, block)
|
101
127
|
end
|
102
128
|
|
129
|
+
# Schedule a read.
|
103
130
|
private
|
104
131
|
def schedule_next_read
|
105
132
|
EventMachine::add_timer(@naptime) do
|
@@ -107,6 +134,7 @@ class EventMachine::FileTail
|
|
107
134
|
end
|
108
135
|
end
|
109
136
|
|
137
|
+
# Read CHUNKSIZE from our file and pass it to .receive_data()
|
110
138
|
private
|
111
139
|
def read
|
112
140
|
begin
|
@@ -132,11 +160,12 @@ class EventMachine::FileTail
|
|
132
160
|
#@logger.info("EOF. Naptime: #{@naptime}")
|
133
161
|
#end
|
134
162
|
|
135
|
-
# TODO(sissel): schedule an fstat instead of doing it now
|
163
|
+
# TODO(sissel): should we schedule an fstat instead of doing it now?
|
136
164
|
fstat = File.stat(@path)
|
137
165
|
handle_fstat(fstat)
|
138
166
|
end # def eof
|
139
167
|
|
168
|
+
# Handle fstat changes appropriately.
|
140
169
|
private
|
141
170
|
def handle_fstat(fstat)
|
142
171
|
if (fstat.ino != @fstat.ino)
|
@@ -144,6 +173,7 @@ class EventMachine::FileTail
|
|
144
173
|
elsif (fstat.rdev != @fstat.rdev)
|
145
174
|
open # Reopen if the filesystem device changed
|
146
175
|
elsif (fstat.size < @fstat.size)
|
176
|
+
# Schedule a read if the file size has changed
|
147
177
|
@logger.info("File likely truncated... #{path}")
|
148
178
|
@file.sysseek(0, IO::SEEK_SET)
|
149
179
|
schedule_next_read
|
@@ -152,30 +182,32 @@ class EventMachine::FileTail
|
|
152
182
|
end # def eof
|
153
183
|
end # class EventMachine::FileTail
|
154
184
|
|
155
|
-
# Internal usage only
|
185
|
+
# Internal usage only. This class is used by EventMachine::FileTail
|
186
|
+
# to watch files you are tailing.
|
187
|
+
#
|
188
|
+
# See also: EventMachine::FileTail#watch
|
156
189
|
class EventMachine::FileTail::FileWatcher < EventMachine::FileWatch
|
157
|
-
def initialize(
|
158
|
-
@filewatch = filewatch
|
190
|
+
def initialize(block)
|
159
191
|
@logger = Logger.new(STDOUT)
|
160
192
|
@logger.level = ($DEBUG and Logger::DEBUG or Logger::WARN)
|
161
|
-
@
|
162
|
-
end
|
193
|
+
@callback = block
|
194
|
+
end # def initialize
|
163
195
|
|
164
196
|
def file_modified
|
165
|
-
@
|
166
|
-
end
|
197
|
+
@callback.call(:modified)
|
198
|
+
end # def file_modified
|
167
199
|
|
168
200
|
def file_moved
|
169
|
-
@
|
170
|
-
end
|
201
|
+
@callback.call(:moved)
|
202
|
+
end # def file_moved
|
171
203
|
|
172
204
|
def file_deleted
|
173
|
-
@
|
174
|
-
end
|
205
|
+
@callback.call(:deleted)
|
206
|
+
end # def file_deleted
|
175
207
|
|
176
208
|
def unbind
|
177
|
-
@
|
178
|
-
end
|
209
|
+
@callback.call(:unbind)
|
210
|
+
end # def unbind
|
179
211
|
end # class EventMachine::FileTail::FileWatch < EventMachine::FileWatch
|
180
212
|
|
181
213
|
# Add EventMachine::file_tail
|
data/lib/em/globwatcher.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "em/filetail"
|
4
4
|
require "eventmachine"
|
5
5
|
require "logger"
|
6
|
-
require "
|
6
|
+
require "set"
|
7
|
+
|
8
|
+
EventMachine.epoll if EventMachine.epoll?
|
7
9
|
|
8
10
|
# A file glob pattern watcher for EventMachine.
|
9
11
|
#
|
@@ -16,10 +18,22 @@ require "em/filetail"
|
|
16
18
|
# This class will allow you to get notified whenever a file
|
17
19
|
# is created or deleted that matches your glob.
|
18
20
|
#
|
21
|
+
# If you are subclassing, here are the methods you should implement:
|
22
|
+
# file_found(path)
|
23
|
+
# file_deleted(path)
|
24
|
+
#
|
25
|
+
# See alsoe
|
26
|
+
# * EventMachine::watch_glob
|
27
|
+
# * EventMachine::FileGlobWatch#file_found
|
28
|
+
# * EventMachine::FileGlobWatch#file_deleted
|
19
29
|
#
|
20
30
|
class EventMachine::FileGlobWatch
|
21
|
-
|
22
|
-
|
31
|
+
# Watch a glob
|
32
|
+
#
|
33
|
+
# * glob - a string path or glob, such as "/var/log/*.log"
|
34
|
+
# * interval - number of seconds between scanning the glob for changes
|
35
|
+
def initialize(glob, interval=60)
|
36
|
+
@glob = glob
|
23
37
|
@files = Set.new
|
24
38
|
@watches = Hash.new
|
25
39
|
@logger = Logger.new(STDOUT)
|
@@ -36,13 +50,39 @@ class EventMachine::FileGlobWatch
|
|
36
50
|
EM.add_periodic_timer(interval) do
|
37
51
|
find_files
|
38
52
|
end
|
39
|
-
end
|
53
|
+
end # EM.next_tick
|
40
54
|
end # def initialize
|
41
55
|
|
56
|
+
# This method is called when a new file is found
|
57
|
+
#
|
58
|
+
# * path - the string path of the file found
|
59
|
+
#
|
60
|
+
# You must implement this in your subclass or module for it
|
61
|
+
# to work with EventMachine::watch_glob
|
62
|
+
public
|
63
|
+
def file_found(path)
|
64
|
+
raise NotImplementedError.new("#{self.class.name}#file_found is not "\
|
65
|
+
"implemented. Did you forget to implement this in your subclass or "\
|
66
|
+
"module?")
|
67
|
+
end # def file_found
|
68
|
+
|
69
|
+
# This method is called when a file is deleted.
|
70
|
+
#
|
71
|
+
# * path - the string path of the file deleted
|
72
|
+
#
|
73
|
+
# You must implement this in your subclass or module for it
|
74
|
+
# to work with EventMachine::watch_glob
|
75
|
+
public
|
76
|
+
def file_deleted(path)
|
77
|
+
raise NotImplementedError.new("#{self.class.name}#file_found is not "\
|
78
|
+
"implemented. Did you forget to implement this in your subclass or "\
|
79
|
+
"module?")
|
80
|
+
end # def file_found
|
81
|
+
|
42
82
|
private
|
43
83
|
def find_files
|
44
|
-
@logger.info("Searching for files in #{@
|
45
|
-
list = Set.new(Dir.glob(@
|
84
|
+
@logger.info("Searching for files in #{@glob}")
|
85
|
+
list = Set.new(Dir.glob(@glob))
|
46
86
|
list.each do |path|
|
47
87
|
next if @files.include?(path)
|
48
88
|
add(path)
|
@@ -53,56 +93,82 @@ class EventMachine::FileGlobWatch
|
|
53
93
|
end
|
54
94
|
end # def find_files
|
55
95
|
|
56
|
-
|
96
|
+
# Remove a file from being watched and notify file_deleted()
|
97
|
+
private
|
57
98
|
def remove(path)
|
58
99
|
@files.delete(path)
|
59
100
|
@watches.delete(path)
|
60
|
-
|
61
|
-
end
|
101
|
+
file_deleted(path)
|
102
|
+
end # def remove
|
62
103
|
|
104
|
+
# Add a file to watch and notify file_found()
|
63
105
|
private
|
64
106
|
def add(path)
|
65
107
|
@files.add(path)
|
66
108
|
|
67
109
|
# If EventMachine::watch_file fails, that's ok, I guess.
|
68
110
|
# We'll still find the file 'missing' from the next glob attempt.
|
69
|
-
begin
|
111
|
+
#begin
|
70
112
|
# EM currently has a bug that only the first handler for a watch_file
|
71
113
|
# on each file gets events. This causes globtails to never get data
|
72
114
|
# since the glob is watching the file already.
|
73
115
|
# Until we fix that, let's skip file watching here.
|
74
|
-
#@watches[path] = EventMachine::watch_file(path,
|
75
|
-
|
76
|
-
|
77
|
-
|
116
|
+
#@watches[path] = EventMachine::watch_file(path, FileWatcher, self) do |path|
|
117
|
+
# remove(path)
|
118
|
+
#end
|
119
|
+
#rescue Errno::EACCES => e
|
120
|
+
#@logger.warn(e)
|
121
|
+
#end
|
78
122
|
file_found(path)
|
79
123
|
end # def watch
|
80
124
|
|
81
125
|
private
|
82
|
-
class
|
83
|
-
def initialize(globwatch)
|
126
|
+
class FileWatcher < EventMachine::FileWatch
|
127
|
+
def initialize(globwatch, &block)
|
84
128
|
@globwatch = globwatch
|
129
|
+
@block = block
|
85
130
|
end
|
86
131
|
|
87
132
|
def file_moved
|
88
133
|
stop_watching
|
89
|
-
|
134
|
+
block.call path
|
90
135
|
end
|
91
136
|
|
92
137
|
def file_deleted
|
93
|
-
|
138
|
+
block.call path
|
94
139
|
end
|
95
|
-
end # class
|
140
|
+
end # class EventMachine::FileGlobWatch::FileWatcher < EventMachine::FileWatch
|
96
141
|
end # class EventMachine::FileGlobWatch
|
97
142
|
|
143
|
+
# A glob tailer for EventMachine
|
144
|
+
#
|
145
|
+
# This class combines features of EventMachine::file_tail and
|
146
|
+
# EventMachine::watch_glob.
|
147
|
+
#
|
148
|
+
# You won't generally subclass this class (See EventMachine::FileGlobWatch)
|
149
|
+
#
|
150
|
+
# See also: EventMachine::glob_tail
|
151
|
+
#
|
98
152
|
class EventMachine::FileGlobWatchTail < EventMachine::FileGlobWatch
|
153
|
+
# Initialize a new file glob tail.
|
154
|
+
#
|
155
|
+
# path should be a glob or file path.
|
156
|
+
# handler should be a module or subclass of EventMachine::FileTail
|
157
|
+
# See also EventMachine::file_tail
|
158
|
+
# interval is how often (seconds) the glob path should be scanned
|
159
|
+
# exclude is an array of Regexp (or anything with .match) for
|
160
|
+
# excluding from things to tail
|
161
|
+
# The remainder of arguments are passed to EventMachine::file_tail as
|
162
|
+
# EventMachine::file_tail(path_found, handler, *args)
|
163
|
+
public
|
99
164
|
def initialize(path, handler=nil, interval=60, exclude=[], *args)
|
100
165
|
super(path, interval)
|
101
166
|
@handler = handler
|
102
167
|
@args = args
|
103
168
|
@exclude = exclude
|
104
|
-
end
|
169
|
+
end # def initialize
|
105
170
|
|
171
|
+
public
|
106
172
|
def file_found(path)
|
107
173
|
begin
|
108
174
|
@logger.info "#{self.class}: Trying #{path}"
|
@@ -120,25 +186,32 @@ class EventMachine::FileGlobWatchTail < EventMachine::FileGlobWatch
|
|
120
186
|
file_error(path, e)
|
121
187
|
rescue Errno::EISDIR => e
|
122
188
|
file_error(path, e)
|
123
|
-
end
|
124
|
-
end
|
189
|
+
end
|
190
|
+
end # def file_found
|
125
191
|
|
192
|
+
public
|
126
193
|
def file_excluded(path)
|
127
194
|
@logger.info "#{self.class}: Skipping path #{path} due to exclude rule"
|
128
|
-
end
|
195
|
+
end # def file_excluded
|
129
196
|
|
130
|
-
|
197
|
+
public
|
198
|
+
def file_deleted(path)
|
131
199
|
# Nothing to do
|
132
|
-
end
|
200
|
+
end # def file_deleted
|
133
201
|
|
202
|
+
public
|
134
203
|
def file_error(path, e)
|
135
204
|
$stderr.puts "#{e.class} while trying to tail #{path}"
|
136
205
|
# otherwise, drop the error by default
|
137
|
-
end
|
206
|
+
end # def file_error
|
138
207
|
end # class EventMachine::FileGlobWatchHandler
|
139
208
|
|
140
|
-
# Add EventMachine::glob_tail
|
141
209
|
module EventMachine
|
210
|
+
# Watch a glob and tail any files found.
|
211
|
+
#
|
212
|
+
# 'glob' should be a string path or glob, such as /var/log/*.log
|
213
|
+
# handler must be a module or subclass of EventMachine::FileGlobWatchTail
|
214
|
+
# See EventMachine::FileGlobWatchTail for the callback methods.
|
142
215
|
def self.glob_tail(glob, handler=nil, *args)
|
143
216
|
handler = EventMachine::FileGlobWatch if handler == nil
|
144
217
|
args.unshift(glob)
|
@@ -146,12 +219,25 @@ module EventMachine
|
|
146
219
|
c = klass.new(*args)
|
147
220
|
yield c if block_given?
|
148
221
|
return c
|
149
|
-
end
|
150
|
-
|
151
|
-
|
222
|
+
end
|
223
|
+
|
224
|
+
# Watch a glob for any files.
|
225
|
+
#
|
226
|
+
# * glob - a string path or glob, such as "/var/log/*.log"
|
227
|
+
# * handler - must be a module or a subclass of EventMachine::FileGlobWatch
|
228
|
+
#
|
229
|
+
# The remaining (optional) arguments are passed to your handler like this:
|
230
|
+
# If you call this:
|
231
|
+
# EventMachine.watch_glob("/var/log/*.log", YourHandler, 1, 2, 3, ...)
|
232
|
+
# This will be invoked when new matching files are found:
|
233
|
+
# YourHandler.new(path_found, 1, 2, 3, ...)
|
234
|
+
# ^ path_found is the new path found by the glob
|
235
|
+
#
|
236
|
+
# See EventMachine::FileGlobWatch for the callback methods.
|
237
|
+
def self.watch_glob(glob, handler=nil, *args)
|
152
238
|
# This code mostly styled on what EventMachine does in many of it's other
|
153
239
|
# methods.
|
154
|
-
args = [
|
240
|
+
args = [glob, *args]
|
155
241
|
klass = klass_from_handler(EventMachine::FileGlobWatch, handler, *args);
|
156
242
|
c = klass.new(*args)
|
157
243
|
yield c if block_given?
|
data/samples/globwatch.rb
CHANGED
data/test/alltests.rb
ADDED
data/test/test_glob.rb
CHANGED
@@ -25,7 +25,7 @@ class Watcher < EventMachine::FileGlobWatch
|
|
25
25
|
@testobj.finish if @data.length == 0
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def file_deleted(path)
|
29
29
|
@testobj.assert(@data.include?(path), "Expected #{path} in \n#{@data.join("\n")}")
|
30
30
|
@data.delete(path)
|
31
31
|
@testobj.finish if @data.length == 0
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 20100517011408
|
9
|
+
version: 0.2.20100517011408
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jordan Sissel
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-17 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- samples/globwatch.rb
|
47
47
|
- test/test_filetail.rb
|
48
48
|
- test/test_glob.rb
|
49
|
+
- test/alltests.rb
|
49
50
|
- test/testcase_helpers.rb
|
50
51
|
- bin/rtail
|
51
52
|
has_rdoc: true
|