typhon 0.1.0 → 0.2.0
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/bin/typhon +22 -22
- data/lib/typhon.rb +68 -66
- data/lib/typhon/config.rb +32 -32
- data/lib/typhon/head.rb +21 -0
- data/lib/typhon/heads.rb +144 -136
- data/lib/typhon/log.rb +38 -38
- data/lib/typhon/ratelimit.rb +49 -0
- data/lib/typhon/stompclient.rb +37 -37
- metadata +8 -6
data/bin/typhon
CHANGED
@@ -10,15 +10,15 @@ pidfile = nil
|
|
10
10
|
opt = OptionParser.new
|
11
11
|
|
12
12
|
opt.on("--config [DIR]", "Directory for configuration file and heads") do |v|
|
13
|
-
|
13
|
+
configdir = v
|
14
14
|
end
|
15
15
|
|
16
16
|
opt.on("--daemonize", "-d", "Daemonize the process") do |v|
|
17
|
-
|
17
|
+
daemon = true
|
18
18
|
end
|
19
19
|
|
20
20
|
opt.on("--pid [PIDFILE]", "-p", "Write a pidfile") do |v|
|
21
|
-
|
21
|
+
pidfile = v
|
22
22
|
end
|
23
23
|
|
24
24
|
opt.parse!
|
@@ -26,13 +26,13 @@ opt.parse!
|
|
26
26
|
raise "The directory #{configdir} does not exist" unless File.directory?(configdir)
|
27
27
|
|
28
28
|
def stop_and_exit(daemon, pidfile, signal)
|
29
|
-
|
29
|
+
Typhon::Log.info("Exiting after signal #{signal}")
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
if daemon && pidfile
|
32
|
+
File.unlink(pidfile)
|
33
|
+
end
|
34
34
|
|
35
|
-
|
35
|
+
exit
|
36
36
|
end
|
37
37
|
|
38
38
|
Signal.trap('INT') { stop_and_exit(daemon, pidfile, :int) }
|
@@ -41,23 +41,23 @@ Signal.trap('TERM') { stop_and_exit(daemon, pidfile, :term) }
|
|
41
41
|
typhon = Typhon.new(configdir)
|
42
42
|
|
43
43
|
Typhon.files.each do |f|
|
44
|
-
|
44
|
+
Typhon::Log.info("Tailing log #{f}")
|
45
45
|
end
|
46
46
|
|
47
47
|
if daemon
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
Typhon::Log.info("Running in the background as pid #{Process.pid}")
|
59
|
-
typhon.tail
|
48
|
+
raise "Pidfile #{pidfile} exist" if pidfile && File.exist?(pidfile)
|
49
|
+
|
50
|
+
Typhon.daemonize do
|
51
|
+
if pidfile
|
52
|
+
begin
|
53
|
+
File.open(pidfile, 'w') {|f| f.write(Process.pid) }
|
54
|
+
rescue
|
55
|
+
end
|
60
56
|
end
|
61
|
-
|
57
|
+
|
58
|
+
Typhon::Log.info("Running in the background as pid #{Process.pid}")
|
62
59
|
typhon.tail
|
60
|
+
end
|
61
|
+
else
|
62
|
+
typhon.tail
|
63
63
|
end
|
data/lib/typhon.rb
CHANGED
@@ -1,82 +1,84 @@
|
|
1
1
|
class Typhon
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'yaml'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'eventmachine-tail'
|
6
|
+
require 'typhon/heads'
|
7
|
+
require 'typhon/log'
|
8
|
+
require 'typhon/config'
|
9
|
+
require 'typhon/stompclient'
|
10
|
+
require 'typhon/ratelimit'
|
11
|
+
require 'typhon/head'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def heads
|
15
|
+
Heads.heads
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
def files
|
19
|
+
Heads.heads.keys
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
def grow(options, &blk)
|
23
|
+
raise "Heads need a name" unless options[:name]
|
24
|
+
raise "Heads need files" unless options[:files]
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
Heads.register_head(options[:name], options[:files], blk)
|
27
|
+
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
def stomp=(stomp)
|
30
|
+
@stomp = stomp
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
def stomp
|
34
|
+
@stomp
|
35
|
+
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
37
|
+
def daemonize
|
38
|
+
fork do
|
39
|
+
Process.setsid
|
40
|
+
exit if fork
|
41
|
+
Dir.chdir('/tmp')
|
42
|
+
STDIN.reopen('/dev/null')
|
43
|
+
STDOUT.reopen('/dev/null', 'a')
|
44
|
+
STDERR.reopen('/dev/null', 'a')
|
45
|
+
|
46
|
+
yield
|
47
|
+
end
|
47
48
|
end
|
49
|
+
end
|
48
50
|
|
49
|
-
|
51
|
+
attr_reader :heads
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
+
def initialize(path="/etc/typhon")
|
54
|
+
@configdir = path
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
+
Config[:configdir] = path
|
57
|
+
Config.loadconfig
|
56
58
|
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
@heads = Heads.new
|
60
|
+
@stomp = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def tail
|
64
|
+
EM.run do
|
65
|
+
@heads.loadheads
|
66
|
+
|
67
|
+
if Config[:stomp]
|
68
|
+
Log.debug("Connecting to Stomp Server %s:%d" % [ Config[:stomp][:server], Config[:stomp][:port] ])
|
69
|
+
@stomp = EM.connect Config[:stomp][:server], Config[:stomp][:port], Typhon::StompClient, {:auto_reconnect => true, :timeout => 2}
|
70
|
+
Typhon.stomp = @stomp
|
71
|
+
end
|
72
|
+
|
73
|
+
EM.add_periodic_timer(10) do
|
74
|
+
@heads.loadheads
|
75
|
+
end
|
60
76
|
|
61
|
-
|
62
|
-
EM.
|
63
|
-
|
64
|
-
|
65
|
-
if Config[:stomp]
|
66
|
-
Log.debug("Connecting to Stomp Server %s:%d" % [ Config[:stomp][:server], Config[:stomp][:port] ])
|
67
|
-
@stomp = EM.connect Config[:stomp][:server], Config[:stomp][:port], Typhon::StompClient, {:auto_reconnect => true, :timeout => 2}
|
68
|
-
Typhon.stomp = @stomp
|
69
|
-
end
|
70
|
-
|
71
|
-
EM.add_periodic_timer(10) do
|
72
|
-
@heads.loadheads
|
73
|
-
end
|
74
|
-
|
75
|
-
if Config[:stat_log_frequency] > 0
|
76
|
-
EM.add_periodic_timer(Config[:stat_log_frequency]) do
|
77
|
-
@heads.log_stats
|
78
|
-
end
|
79
|
-
end
|
77
|
+
if Config[:stat_log_frequency] > 0
|
78
|
+
EM.add_periodic_timer(Config[:stat_log_frequency]) do
|
79
|
+
@heads.log_stats
|
80
80
|
end
|
81
|
+
end
|
81
82
|
end
|
83
|
+
end
|
82
84
|
end
|
data/lib/typhon/config.rb
CHANGED
@@ -1,48 +1,48 @@
|
|
1
1
|
class Typhon
|
2
|
-
|
3
|
-
|
2
|
+
class Config
|
3
|
+
include Enumerable
|
4
4
|
|
5
|
-
|
5
|
+
@settings = {:loglevel => :info, :stomp => false, :stat_log_frequency => 3600}
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
class << self
|
8
|
+
attr_reader :settings
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def []=(key,val)
|
11
|
+
@settings[key] = val
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def [](key)
|
15
|
+
@settings[key]
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def include?(key)
|
19
|
+
@settings.include?(key)
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
def each
|
23
|
+
@settings.each_pair do |k, v|
|
24
|
+
yield({k => v})
|
25
|
+
end
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
def loadconfig
|
29
|
+
raise "Set configdir" unless @settings.include?(:configdir)
|
30
30
|
|
31
|
-
|
31
|
+
file = File.join([@settings[:configdir], "typhon.yaml"])
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
raise "Cannot find file #{file}" unless File.exist?(file)
|
34
|
+
@settings.merge!(YAML.load_file(file))
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
def method_missing(k, *args, &block)
|
38
|
+
return @settings[k] if @settings.include?(k)
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
k = k.to_s.gsub("_", ".")
|
41
|
+
return @settings[k] if @settings.include?(k)
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
end
|
43
|
+
super
|
44
|
+
end
|
46
45
|
end
|
46
|
+
end
|
47
47
|
end
|
48
48
|
|
data/lib/typhon/head.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Typhon
|
2
|
+
# heads get loaded into this class on the 'call' method, provides convenient access
|
3
|
+
# to stomp, rate limiters etc
|
4
|
+
class Head
|
5
|
+
def define_singleton_method(*args, &block)
|
6
|
+
class << self
|
7
|
+
self
|
8
|
+
end.send(:define_method, *args, &block)
|
9
|
+
end unless method_defined? :define_singleton_method
|
10
|
+
|
11
|
+
def ratelimit(name, time=60)
|
12
|
+
@limiters ||= {}
|
13
|
+
|
14
|
+
@limiters[name] ||= RateLimit.new(time)
|
15
|
+
end
|
16
|
+
|
17
|
+
def stomp
|
18
|
+
Typhon.stomp
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/typhon/heads.rb
CHANGED
@@ -1,160 +1,168 @@
|
|
1
1
|
class Typhon
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
[files].flatten.each do |file|
|
12
|
-
@heads[file] ||= {}
|
13
|
-
|
14
|
-
raise "Already have a head called #{name} for file #{file}" if @heads[file].include?(name)
|
15
|
-
|
16
|
-
@heads[file][name] = head
|
17
|
-
|
18
|
-
Log.debug("Registered a new head: #{name} for file #{file}")
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def clear!
|
23
|
-
Log.debug("Clearing previously loaded heads")
|
24
|
-
@heads = {}
|
25
|
-
end
|
26
|
-
|
27
|
-
def heads
|
28
|
-
@heads || {}
|
29
|
-
end
|
30
|
-
|
31
|
-
def files
|
32
|
-
@heads.keys
|
33
|
-
end
|
34
|
-
end
|
2
|
+
class Heads
|
3
|
+
# these methods are here to help the DSL have somewhere to
|
4
|
+
# store instances of the heads and some utilities to manage
|
5
|
+
# them. This is effectively a global named scope that just
|
6
|
+
# holds blocks of codes
|
7
|
+
class << self
|
8
|
+
def register_head(name, files, head)
|
9
|
+
@heads ||= {}
|
35
10
|
|
36
|
-
|
37
|
-
|
38
|
-
@tails = {}
|
39
|
-
@linecount = 0
|
40
|
-
@starttime = Time.now
|
41
|
-
end
|
11
|
+
[files].flatten.each do |file|
|
12
|
+
@heads[file] ||= {}
|
42
13
|
|
43
|
-
|
44
|
-
uptime = seconds_to_human((Time.now - @starttime).to_i)
|
14
|
+
raise "Already have a head called #{name} for file #{file}" if @heads[file].include?(name)
|
45
15
|
|
46
|
-
|
47
|
-
|
16
|
+
headklass = Head.new
|
17
|
+
headklass.define_singleton_method(:call, head)
|
48
18
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
return unless Heads.heads.include?(file)
|
53
|
-
|
54
|
-
Heads.heads[file].each_pair do |name, head|
|
55
|
-
begin
|
56
|
-
head.call(file, pos, text)
|
57
|
-
@linecount += 1
|
58
|
-
rescue Exception => e
|
59
|
-
Log.error("Failed to handle line from #{file}##{pos} with head #{name}: #{e.class}: #{e}")
|
60
|
-
end
|
61
|
-
end
|
19
|
+
@heads[file][name] = headklass
|
20
|
+
|
21
|
+
Log.debug("Registered a new head: #{name} for file #{file}")
|
62
22
|
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
Log.debug("Clearing previously loaded heads")
|
27
|
+
@heads = {}
|
28
|
+
end
|
63
29
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
if File.exist?(triggerfile)
|
68
|
-
triggerage = File::Stat.new(triggerfile).mtime.to_f
|
69
|
-
else
|
70
|
-
triggerage = 0
|
71
|
-
end
|
30
|
+
def heads
|
31
|
+
@heads || {}
|
32
|
+
end
|
72
33
|
|
73
|
-
|
34
|
+
def files
|
35
|
+
@heads.keys
|
36
|
+
end
|
37
|
+
end
|
74
38
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
39
|
+
def initialize
|
40
|
+
@dir = File.join(Config.configdir, "heads")
|
41
|
+
@tails = {}
|
42
|
+
@linecount = 0
|
43
|
+
@starttime = Time.now
|
44
|
+
end
|
81
45
|
|
82
|
-
|
46
|
+
def log_stats
|
47
|
+
uptime = seconds_to_human((Time.now - @starttime).to_i)
|
83
48
|
|
84
|
-
|
85
|
-
|
49
|
+
Log.info("Up for #{uptime} read #{@linecount} lines")
|
50
|
+
end
|
86
51
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
Typhon.files.each do |file|
|
92
|
-
unless @tails.include?(file)
|
93
|
-
Log.debug("Starting a new tailer for #{file}")
|
94
|
-
@tails[file] = EventMachine::file_tail(file) do |ft, line|
|
95
|
-
self.feed(ft.path, ft.position, line)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# for all the tailers make sure there are files, else close the tailer
|
101
|
-
@tails.keys.each do |file|
|
102
|
-
unless Typhon.files.include?(file)
|
103
|
-
Log.debug("Closing tailer for #{file} there are no heads attached")
|
104
|
-
|
105
|
-
begin
|
106
|
-
@tails[file].close
|
107
|
-
rescue
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
52
|
+
# Handles a line of text from a log file by finding the
|
53
|
+
# heads associated with that file and calling them all
|
54
|
+
def feed(file, pos, text)
|
55
|
+
return unless Heads.heads.include?(file)
|
112
56
|
|
113
|
-
|
114
|
-
|
115
|
-
|
57
|
+
Heads.heads[file].each_pair do |name, head|
|
58
|
+
begin
|
59
|
+
head.call(file, pos, text)
|
60
|
+
@linecount += 1
|
116
61
|
rescue Exception => e
|
117
|
-
|
118
|
-
|
62
|
+
Log.error("Failed to handle line from #{file}##{pos} with head #{name}: #{e.class}: #{e}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Loads/Reload all the heads from disk, a trigger file is used that
|
68
|
+
# the user can touch the trigger and it will initiate a complete reload
|
69
|
+
def loadheads
|
70
|
+
if File.exist?(triggerfile)
|
71
|
+
triggerage = File::Stat.new(triggerfile).mtime.to_f
|
72
|
+
else
|
73
|
+
triggerage = 0
|
74
|
+
end
|
75
|
+
|
76
|
+
@loaded ||= 0
|
77
|
+
|
78
|
+
if (@loaded < triggerage) || @loaded == 0
|
79
|
+
Heads.clear!
|
80
|
+
headfiles.each do |head|
|
81
|
+
loadhead(head)
|
119
82
|
end
|
83
|
+
end
|
84
|
+
|
85
|
+
starttails
|
86
|
+
|
87
|
+
@loaded = Time.now.to_f
|
88
|
+
end
|
120
89
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
90
|
+
# Start EM tailers for each known file. If a file has become orphaned
|
91
|
+
# by all its heads being removed then close the tail
|
92
|
+
def starttails
|
93
|
+
# for all the files that have interested heads start tailers
|
94
|
+
Typhon.files.each do |file|
|
95
|
+
unless @tails.include?(file)
|
96
|
+
Log.debug("Starting a new tailer for #{file}")
|
97
|
+
@tails[file] = EventMachine::file_tail(file) do |ft, line|
|
98
|
+
self.feed(ft.path, ft.position, line)
|
99
|
+
end
|
129
100
|
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# for all the tailers make sure there are files, else close the tailer
|
104
|
+
@tails.keys.each do |file|
|
105
|
+
unless Typhon.files.include?(file)
|
106
|
+
Log.debug("Closing tailer for #{file} there are no heads attached")
|
130
107
|
|
131
|
-
|
132
|
-
|
108
|
+
begin
|
109
|
+
@tails[file].close
|
110
|
+
rescue
|
111
|
+
end
|
133
112
|
end
|
113
|
+
end
|
114
|
+
end
|
134
115
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
elsif minutes > 0
|
153
|
-
return sprintf("%d minutes %02d seconds", minutes, seconds)
|
154
|
-
else
|
155
|
-
return sprintf("%02d seconds", seconds)
|
156
|
-
end
|
116
|
+
def loadhead(head)
|
117
|
+
Log.debug("Loading head #{head}")
|
118
|
+
load head
|
119
|
+
rescue Exception => e
|
120
|
+
Log.error "Failed to load #{head}: #{e.class}: #{e}"
|
121
|
+
Log.error e.backtrace
|
122
|
+
|
123
|
+
if STDOUT.tty?
|
124
|
+
puts "Failed to load #{head}: #{e.class}: #{e}"
|
125
|
+
p e.backtrace
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def headfiles
|
130
|
+
if File.directory?(@dir)
|
131
|
+
Dir.entries(@dir).grep(/head.rb$/).map do |f|
|
132
|
+
File.join([@dir, f])
|
157
133
|
end
|
134
|
+
else
|
135
|
+
raise "#{@dir} is not a directory"
|
136
|
+
end
|
137
|
+
end
|
158
138
|
|
139
|
+
def triggerfile
|
140
|
+
File.join([@dir, "reload.txt"])
|
159
141
|
end
|
142
|
+
|
143
|
+
# borrowed from ohai, thanks Adam.
|
144
|
+
def seconds_to_human(seconds)
|
145
|
+
days = seconds.to_i / 86400
|
146
|
+
seconds -= 86400 * days
|
147
|
+
|
148
|
+
hours = seconds.to_i / 3600
|
149
|
+
seconds -= 3600 * hours
|
150
|
+
|
151
|
+
minutes = seconds.to_i / 60
|
152
|
+
seconds -= 60 * minutes
|
153
|
+
|
154
|
+
if days > 1
|
155
|
+
return sprintf("%d days %02d hours %02d minutes %02d seconds", days, hours, minutes, seconds)
|
156
|
+
elsif days == 1
|
157
|
+
return sprintf("%d day %02d hours %02d minutes %02d seconds", days, hours, minutes, seconds)
|
158
|
+
elsif hours > 0
|
159
|
+
return sprintf("%d hours %02d minutes %02d seconds", hours, minutes, seconds)
|
160
|
+
elsif minutes > 0
|
161
|
+
return sprintf("%d minutes %02d seconds", minutes, seconds)
|
162
|
+
else
|
163
|
+
return sprintf("%02d seconds", seconds)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
160
168
|
end
|
data/lib/typhon/log.rb
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
class Typhon
|
2
|
-
|
3
|
-
|
2
|
+
class Log
|
3
|
+
require 'syslog'
|
4
4
|
|
5
|
-
|
5
|
+
include Syslog::Constants
|
6
6
|
|
7
|
-
|
7
|
+
@configured = false
|
8
8
|
|
9
|
-
|
9
|
+
@known_levels = [:debug, :info, :warn, :error, :fatal]
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
class << self
|
12
|
+
def log(msg, severity=:debug)
|
13
|
+
configure unless @configured
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
if @known_levels.index(severity) >= @known_levels.index(@active_level)
|
16
|
+
Syslog.send(valid_levels[severity.to_sym], "#{from} #{msg}")
|
17
|
+
end
|
18
|
+
rescue Exception => e
|
19
|
+
STDERR.puts("Failed to log: #{e.class}: #{e}: original log message: #{severity}: #{msg}")
|
20
|
+
STDERR.puts(e.backtrace.join("\n\t"))
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def configure
|
24
|
+
Syslog.close if Syslog.opened?
|
25
|
+
Syslog.open(File.basename($0))
|
26
26
|
|
27
|
-
|
27
|
+
@active_level = Config[:loglevel]
|
28
28
|
|
29
|
-
|
29
|
+
raise "Unknown log level #{@active_level} specified" unless valid_levels.include?(@active_level)
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
@configured = true
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
# figures out the filename that called us
|
35
|
+
def from
|
36
|
+
from = File.basename(caller[4])
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
def valid_levels
|
40
|
+
{:info => :info,
|
41
|
+
:warn => :warning,
|
42
|
+
:debug => :debug,
|
43
|
+
:fatal => :crit,
|
44
|
+
:error => :err}
|
45
|
+
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
def method_missing(level, *args, &block)
|
48
|
+
super unless [:info, :warn, :debug, :fatal, :error].include?(level)
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
end
|
50
|
+
log(args[0], level)
|
51
|
+
end
|
53
52
|
end
|
53
|
+
end
|
54
54
|
end
|
55
55
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Typhon
|
2
|
+
class RateLimit
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
attr_reader :timespan
|
6
|
+
|
7
|
+
# timespan - how many seconds you want to track events for.
|
8
|
+
# events older than this will drop off the queue
|
9
|
+
def initialize(timespan)
|
10
|
+
@timespan = timespan
|
11
|
+
@events = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# Record that an event happened
|
15
|
+
#
|
16
|
+
# Just pass in any text, unique text will be tracked individually
|
17
|
+
def record(event)
|
18
|
+
key = Digest::MD5.hexdigest(event)
|
19
|
+
|
20
|
+
event = {:k => key,
|
21
|
+
:t => Time.now.to_f}
|
22
|
+
|
23
|
+
@events << event
|
24
|
+
|
25
|
+
domaint
|
26
|
+
end
|
27
|
+
|
28
|
+
# How many times have the event been seen in the last interval
|
29
|
+
def rate(event)
|
30
|
+
key = Digest::MD5.hexdigest(event)
|
31
|
+
|
32
|
+
@events.select{|e| e[:k] == key}.size
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
# Deletes events older than the defined timespan
|
37
|
+
def domaint
|
38
|
+
@events.each_with_index do |event, idx|
|
39
|
+
# events are stored in time order delete them till we find ont thats
|
40
|
+
# newer then we can stop looking
|
41
|
+
if event[:t].to_f < (Time.now.to_f - @timespan.to_f)
|
42
|
+
@events.delete_at(idx)
|
43
|
+
else
|
44
|
+
return
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/typhon/stompclient.rb
CHANGED
@@ -1,49 +1,49 @@
|
|
1
1
|
class Typhon
|
2
|
-
|
3
|
-
|
2
|
+
class StompClient < EM::Connection
|
3
|
+
include EM::Protocols::Stomp
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
def initialize(params={})
|
6
|
+
@connected = false
|
7
|
+
@options = {:auto_reconnect => true, :timeout => 2, :max_queue_size => 500}
|
8
|
+
@queue = EM::Queue.new
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
def connection_completed
|
12
|
+
connect :login => Config[:stomp][:user], :passcode => Config[:stomp][:pass]
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
Log.debug("Authenticated to %s:%d" % [ Config[:stomp][:server], Config[:stomp][:port] ])
|
15
|
+
@connected = true
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def unbind
|
19
|
+
Log.error("Connection to %s:%d failed" % [ Config[:stomp][:server], Config[:stomp][:port] ])
|
20
|
+
@connected = false
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
EM.add_timer(@options[:timeout]) do
|
23
|
+
Log.debug("Connecting to Stomp Server %s:%d" % [ Config[:stomp][:server], Config[:stomp][:port] ])
|
24
|
+
reconnect Config[:stomp][:server], Config[:stomp][:port]
|
25
|
+
end
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def connected?
|
29
|
+
(@connected && !error?)
|
30
|
+
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
end
|
32
|
+
def publish(topic, message, param={})
|
33
|
+
if connected?
|
34
|
+
send(topic, message, param)
|
35
|
+
|
36
|
+
until @queue.empty? do
|
37
|
+
@queue.pop do |msg|
|
38
|
+
send(msg[:topic], msg[:message], msg[:param])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
if @queue.size < @options[:max_queue_size]
|
43
|
+
@queue.push({:topic => topic, :message => message, :param => param})
|
46
44
|
end
|
45
|
+
end
|
47
46
|
end
|
47
|
+
end
|
48
48
|
end
|
49
49
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typhon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- R.I.Pienaar
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2012-03-04 00:00:00 +00:00
|
19
19
|
default_executable: typhon
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -30,9 +30,11 @@ extra_rdoc_files: []
|
|
30
30
|
files:
|
31
31
|
- bin/typhon
|
32
32
|
- lib/typhon/config.rb
|
33
|
-
- lib/typhon/stompclient.rb
|
34
|
-
- lib/typhon/heads.rb
|
35
33
|
- lib/typhon/log.rb
|
34
|
+
- lib/typhon/heads.rb
|
35
|
+
- lib/typhon/ratelimit.rb
|
36
|
+
- lib/typhon/head.rb
|
37
|
+
- lib/typhon/stompclient.rb
|
36
38
|
- lib/typhon.rb
|
37
39
|
has_rdoc: true
|
38
40
|
homepage: https://github.com/ripienaar/typhon/
|