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.
Files changed (3) hide show
  1. data/lib/em/filetail.rb +162 -0
  2. data/lib/em/globwatcher.rb +87 -0
  3. metadata +66 -0
@@ -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
+