eventmachine-tail 0.0.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/lib/em/filetail.rb +162 -0
- data/lib/em/globwatcher.rb +87 -0
- metadata +66 -0
data/lib/em/filetail.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems" if __FILE__ == $0
|
4
|
+
require "eventmachine"
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
EventMachine.epoll if EventMachine.epoll?
|
8
|
+
|
9
|
+
class EventMachine::FileTail
|
10
|
+
CHUNKSIZE = 65536
|
11
|
+
MAXSLEEP = 2
|
12
|
+
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
public
|
16
|
+
def initialize(path, startpos=0)
|
17
|
+
@path = path
|
18
|
+
@logger = Logger.new(STDOUT)
|
19
|
+
|
20
|
+
#@need_scheduling = true
|
21
|
+
open
|
22
|
+
|
23
|
+
@fstat = File.stat(@path)
|
24
|
+
@file.seek(0, IO::SEEK_END)
|
25
|
+
watch
|
26
|
+
end # def initialize
|
27
|
+
|
28
|
+
public
|
29
|
+
def notify(status)
|
30
|
+
@logger.debug("#{status} on #{path}")
|
31
|
+
if status == :modified
|
32
|
+
schedule_next_read
|
33
|
+
elsif status == :moved
|
34
|
+
# TODO(sissel): read to EOF, then reopen.
|
35
|
+
open
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def open
|
41
|
+
@file.close if @file
|
42
|
+
begin
|
43
|
+
@file = File.open(@path, "r")
|
44
|
+
rescue Errno::ENOENT
|
45
|
+
# no file found
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
|
49
|
+
@naptime = 0;
|
50
|
+
@pos = 0
|
51
|
+
schedule_next_read
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def watch
|
56
|
+
EventMachine::watch_file(@path, FileWatcher, self)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def schedule_next_read
|
61
|
+
EventMachine::add_timer(@naptime) do
|
62
|
+
read
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def read
|
68
|
+
begin
|
69
|
+
data = @file.sysread(CHUNKSIZE)
|
70
|
+
# Won't get here if sysread throws EOF
|
71
|
+
@pos += data.length
|
72
|
+
@naptime = 0
|
73
|
+
receive_data(data)
|
74
|
+
schedule_next_read
|
75
|
+
rescue EOFError
|
76
|
+
eof
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
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
|
95
|
+
|
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)
|
102
|
+
@logger.info("File likely truncated... #{path}")
|
103
|
+
@file.seek(0, IO::SEEK_SET)
|
104
|
+
schedule_next_read
|
105
|
+
end
|
106
|
+
@fstat = fstat
|
107
|
+
end # def eof
|
108
|
+
end # class EventMachine::FileTail
|
109
|
+
|
110
|
+
# Internal usage only
|
111
|
+
class EventMachine::FileTail::FileWatcher < EventMachine::FileWatch
|
112
|
+
def initialize(filestream)
|
113
|
+
@filestream = filestream
|
114
|
+
@logger = Logger.new(STDOUT)
|
115
|
+
end
|
116
|
+
|
117
|
+
def file_modified
|
118
|
+
@filestream.notify :modified
|
119
|
+
end
|
120
|
+
|
121
|
+
def file_moved
|
122
|
+
@filestream.notify :moved
|
123
|
+
end
|
124
|
+
|
125
|
+
def file_deleted
|
126
|
+
@filestream.notify :deleted
|
127
|
+
end
|
128
|
+
|
129
|
+
def unbind
|
130
|
+
@filestream.notify :unbind
|
131
|
+
end
|
132
|
+
end # class EventMachine::FileTail::FileWatch < EventMachine::FileWatch
|
133
|
+
|
134
|
+
# Add EventMachine::file_tail
|
135
|
+
module EventMachine
|
136
|
+
def self.file_tail(path, handler=nil, *args)
|
137
|
+
args.unshift(path)
|
138
|
+
klass = klass_from_handler(EventMachine::FileTail, handler, *args);
|
139
|
+
c = klass.new(*args)
|
140
|
+
yield c if block_given?
|
141
|
+
return c
|
142
|
+
end # def self.file_tail
|
143
|
+
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
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems" if __FILE__ == $0
|
4
|
+
require "set"
|
5
|
+
require "eventmachine"
|
6
|
+
require "ap"
|
7
|
+
require "logger"
|
8
|
+
|
9
|
+
require "filetail"
|
10
|
+
|
11
|
+
class EventMachine::FileGlobWatch
|
12
|
+
def initialize(pathglob, handler, interval=60)
|
13
|
+
@pathglob = pathglob
|
14
|
+
@handler = handler
|
15
|
+
@files = Set.new
|
16
|
+
|
17
|
+
EM.next_tick do
|
18
|
+
find_files
|
19
|
+
EM.add_periodic_timer(interval) do
|
20
|
+
find_files
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end # def initialize
|
24
|
+
|
25
|
+
def find_files
|
26
|
+
list = Set.new(Dir.glob(@pathglob))
|
27
|
+
list.each do |path|
|
28
|
+
next if @files.include?(path)
|
29
|
+
watch(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
(@files - list).each do |missing|
|
33
|
+
@files.delete(missing)
|
34
|
+
@handler.file_removed(missing)
|
35
|
+
end
|
36
|
+
end # def find_files
|
37
|
+
|
38
|
+
def watch(path)
|
39
|
+
puts "Watching #{path}"
|
40
|
+
@files.add(path)
|
41
|
+
@handler.file_found(path)
|
42
|
+
end # def watch
|
43
|
+
end # class EventMachine::FileGlobWatch
|
44
|
+
|
45
|
+
class EventMachine::FileGlobWatchHandler
|
46
|
+
LOGGER = Logger.new(STDOUT)
|
47
|
+
def initialize(handler=nil)
|
48
|
+
@handler = handler
|
49
|
+
end
|
50
|
+
|
51
|
+
def file_found(path)
|
52
|
+
EventMachine::file_tail(path, @handler)
|
53
|
+
end
|
54
|
+
|
55
|
+
def file_removed(path)
|
56
|
+
# Nothing to do
|
57
|
+
end
|
58
|
+
end # class EventMachine::FileGlobWatchHandler
|
59
|
+
|
60
|
+
module EventMachine
|
61
|
+
def self.glob_tail(glob, handler=nil, *args)
|
62
|
+
handler = EventMachine::FileGlobHandler if handler == nil
|
63
|
+
klass = klass_from_handler(EventMachine::FileGlobWatchHandler, handler, *args)
|
64
|
+
c = klass.new(*args)
|
65
|
+
yield c if block_given?
|
66
|
+
return c
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if __FILE__ == $0
|
71
|
+
class Reader < EventMachine::FileTail
|
72
|
+
def initialize(*args)
|
73
|
+
super(*args)
|
74
|
+
@buffer = BufferedTokenizer.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def receive_data(data)
|
78
|
+
@buffer.extract(data).each do |line|
|
79
|
+
ap [path, line]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
EventMachine.run do
|
85
|
+
EventMachine::FileGlobWatch.new("/var/log/*.log", EventMachine::FileGlobWatchHandler.new(Reader))
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eventmachine-tail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordan Sissel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-04-11 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Add file 'tail' implemented with EventMachine
|
26
|
+
email: jls@semicomplete.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/em/filetail.rb
|
35
|
+
- lib/em/globwatcher.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://code.google.com/p/semicomplete/wiki/EventMachine-Tail
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: eventmachine tail - a file tail implementation
|
65
|
+
test_files: []
|
66
|
+
|