rec 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rec/alert.rb +115 -0
- data/lib/rec/correlator.rb +121 -0
- data/lib/rec/mock-alert.rb +25 -0
- data/lib/rec/rule.rb +85 -0
- data/lib/rec/state.rb +102 -0
- data/lib/rec.rb +4 -0
- data/lib/string.rb +11 -0
- metadata +74 -0
data/lib/rec/alert.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/smtp'
|
3
|
+
require 'xmpp4r'
|
4
|
+
|
5
|
+
module REC
|
6
|
+
module Alert
|
7
|
+
|
8
|
+
# Provides the capability to send alerts
|
9
|
+
# --mail--
|
10
|
+
# The simplest approach is to use the native +mail+ program (no credentials required)
|
11
|
+
# Alert.mail(alert)
|
12
|
+
#
|
13
|
+
# --email and jabber--
|
14
|
+
# You can also send emails and instant messages via servers, but you'll need to provide
|
15
|
+
# credentials to do that.
|
16
|
+
# - Alert.smtp_credentials(user, password, domain, server, port)
|
17
|
+
# - Alert.jabber_credentials(user, password, server)
|
18
|
+
#
|
19
|
+
# Then you can send messages:
|
20
|
+
# - Alert.email(alert)
|
21
|
+
# - Alert.jabber(alert)
|
22
|
+
# or send messages to another recipient, with another subject
|
23
|
+
# - Alert.email(alert, you@example.com, "Serious problem")
|
24
|
+
# - Alert.jabber(alert, boss@example.com)
|
25
|
+
#
|
26
|
+
# --Sleeping--
|
27
|
+
# If you want to avoid being sent instant messages during sleeping hours, you can
|
28
|
+
# specify a range of working hours during which urgent alerts may be sent by jabber
|
29
|
+
# and outside those working hours the alert will be sent by email instead
|
30
|
+
# Alert.workHours(9,18) # IMs only between 9am and 6pm
|
31
|
+
# Alert.urgent(alert) # sent as instant message if during work hours, else by email
|
32
|
+
# Alert.jabber(alert) # sent as instant message regardless of the time
|
33
|
+
# Alert.normal(alert) # sent by email
|
34
|
+
|
35
|
+
|
36
|
+
def self.default_subject(subject)
|
37
|
+
@@defaultSubject = subject
|
38
|
+
end
|
39
|
+
@@defaultSubject = "Alert"
|
40
|
+
|
41
|
+
#load("/home/rec/alert.conf") can contain something like this:
|
42
|
+
# Alert.email_credentials("rec@gmail.com", "tricky", "mydomain.com")
|
43
|
+
# Alert.jabber_credentials("rec@gmail.com", "tricky")
|
44
|
+
#so the rules script need not contain passwords.
|
45
|
+
#/home/rec/alert.conf should be readable only by the otherwise unprivileged user (sec)
|
46
|
+
# running the script
|
47
|
+
|
48
|
+
# provides the credentials needed for sending email
|
49
|
+
def self.smtp_credentials(user, password, domain, server="smtp.gmail.com", port=587)
|
50
|
+
@@emailUser = user
|
51
|
+
@@emailPassword = password
|
52
|
+
@@emailDomain = domain
|
53
|
+
@@emailServer = server
|
54
|
+
@@emailPort = port
|
55
|
+
end
|
56
|
+
|
57
|
+
# provides the credentials needed for sending instant messages
|
58
|
+
def self.jabber_credentials(user, password, server="talk.google.com")
|
59
|
+
@@jabberUser = user
|
60
|
+
@@jabberPassword = password
|
61
|
+
@@jabberServer = server
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.emailTo=(address)
|
65
|
+
@@emailTo = address
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.jabberTo=(address)
|
69
|
+
@@jabberTo = address
|
70
|
+
end
|
71
|
+
|
72
|
+
# Send the alert via local mailer
|
73
|
+
def self.mail(alert, recipient=@@emailTo, subject=@@defaultSubject)
|
74
|
+
`echo "#{alert}" | mail -s #{subject.gsub(/\s/,'\ ')} #{recipient} 1<&2`
|
75
|
+
end
|
76
|
+
|
77
|
+
# Send the alert via an email server
|
78
|
+
def self.email(alert, recipient=@@emailTo, subject=@@defaultSubject)
|
79
|
+
smtp = Net::SMTP.new(@@emailServer, @@emailPort)
|
80
|
+
smtp.enable_starttls()
|
81
|
+
smtp.start(@@emailDomain, @@emailUsername, @@emailPassword, :plain)
|
82
|
+
smtp.send_message(alert, @@emailUsername, recipient)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Send the alert via google talk (or any other XMPP service)
|
86
|
+
def self.jabber(alert, recipient=@@jabberTo, subject=@@defaultSubject)
|
87
|
+
client = Jabber::Client::new(Jabber::JID.new(@@JabberUser))
|
88
|
+
client.connect(@@jabberServer)
|
89
|
+
client.auth(@@jabberPassword)
|
90
|
+
message = Jabber::Message::new(recipient, alert).set_type(:normal).set_id('1').set_subject(subject)
|
91
|
+
client.send(message)
|
92
|
+
end
|
93
|
+
|
94
|
+
# define the working hours during which instant messages are allowed
|
95
|
+
# Note that Alert.work_hours(7,21) means "7am-9pm" as you would think, so from 07:00 to 20:59
|
96
|
+
def self.work_hours(start, finish)
|
97
|
+
@@workHours = start..finish
|
98
|
+
end
|
99
|
+
@@workHours = 7...21
|
100
|
+
|
101
|
+
# Send an instant message during work hours, else send an email
|
102
|
+
def self.urgent(alert)
|
103
|
+
if @@workhours.include?(Time.now.hour)
|
104
|
+
self.jabber(alert)
|
105
|
+
else
|
106
|
+
self.email(alert)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.normal(alert)
|
111
|
+
self.email(alert)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'rec/rule'
|
2
|
+
|
3
|
+
module REC
|
4
|
+
class Correlator
|
5
|
+
|
6
|
+
@@eventsIn = 0
|
7
|
+
@@eventsMissed = 0
|
8
|
+
|
9
|
+
def self.start(opts={})
|
10
|
+
$debug = opts[:debug] || false
|
11
|
+
self.new().start()
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize()
|
15
|
+
@time = @startupTime = Time.now()
|
16
|
+
@year = @startupTime.year
|
17
|
+
@running = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def start()
|
21
|
+
Signal.trap("INT") { finish() }
|
22
|
+
Signal.trap("TERM") { finish() }
|
23
|
+
Signal.trap("USR1") {
|
24
|
+
stats()
|
25
|
+
run()
|
26
|
+
}
|
27
|
+
$stderr.puts("srec is starting...")
|
28
|
+
begin
|
29
|
+
$miss = IO.open(3, "a") # for missed events
|
30
|
+
rescue
|
31
|
+
$miss = nil
|
32
|
+
end
|
33
|
+
@running = true
|
34
|
+
run()
|
35
|
+
end
|
36
|
+
|
37
|
+
def run()
|
38
|
+
while @running and !$stdin.eof? do
|
39
|
+
logLine = gets()
|
40
|
+
next if logLine.nil?
|
41
|
+
logLine.strip!()
|
42
|
+
next if logLine.empty?
|
43
|
+
@@eventsIn += 1
|
44
|
+
@time, message = parse(logLine)
|
45
|
+
$stderr.puts("< "+message) if $debug
|
46
|
+
State.expire_states(@time) # remove expired states before we check the rules
|
47
|
+
eventMatched = false
|
48
|
+
Rule.each { |rule|
|
49
|
+
title = rule.check(message)
|
50
|
+
eventMatched = true unless title.nil? # empty match is still a match
|
51
|
+
next if title.nil?
|
52
|
+
break if title.empty? # match without a message means 'swallow this event'
|
53
|
+
state = State[title] || rule.create_state(title, @time)
|
54
|
+
rule.react(state, @time, logLine)
|
55
|
+
$stderr.puts("breaking after rule #{rule.rid}") unless (!$debug or rule.continue())
|
56
|
+
break unless rule.continue()
|
57
|
+
}
|
58
|
+
if !eventMatched
|
59
|
+
@@eventsMissed += 1
|
60
|
+
$miss.puts("* "+logLine) unless $miss.nil?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
finish() if $stdin.eof?
|
64
|
+
end
|
65
|
+
|
66
|
+
def finish()
|
67
|
+
@running = false
|
68
|
+
$miss.close() unless $miss.nil? or $miss.closed?
|
69
|
+
# NOTE: some states may have something useful to say, or maybe we could store them
|
70
|
+
stats()
|
71
|
+
$stderr.puts("srec is finished.")
|
72
|
+
exit 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse(logLine)
|
76
|
+
if logLine =~ /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d+)\s(\d\d)\:(\d\d)\:(\d\d)/
|
77
|
+
# Apr 22 16:40:18 aqua Firewall[205]: Skype is listening from 0.0.0.0:51304 proto=6
|
78
|
+
time = Time.local(@year, $1, $2, $3, $4, $5, 0)
|
79
|
+
message = $'
|
80
|
+
elsif logLine =~ /^\[\w+\]\s[Sun|Mon|Tue|Wed|Thu|Fri|Sat]\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d+)\s(\d\d)\:(\d\d)\:(\d\d)\s(\d{4})/
|
81
|
+
# [err] Fri Dec 30 23:58:56 2011 - scan error: 451 SCAN Engine error 2 ...
|
82
|
+
time = Time.local($6, $1, $2, $3, $4, $5, 0)
|
83
|
+
message = $'
|
84
|
+
elseif logLine =~ /^(\d{4})\-(\d\d)\-(\d\d)\s(\d\d)\:(\d\d)\:(\d\d)\.(\d+)\s(\w+)/
|
85
|
+
# 2012-04-22 08:43:22.099 EST - Module: PlistFile ...
|
86
|
+
if $7 == "UTC" or $7 == "GMT"
|
87
|
+
time = Time.utc($1, $2, $3, $4, $5, $6, $7)
|
88
|
+
else
|
89
|
+
time = Time.local($1, $2, $3, $4, $5, $6, $7)
|
90
|
+
end
|
91
|
+
message = $'
|
92
|
+
else
|
93
|
+
time = @time # time stands still
|
94
|
+
message = logLine
|
95
|
+
end
|
96
|
+
[time, message]
|
97
|
+
end
|
98
|
+
|
99
|
+
def stats()
|
100
|
+
# report statistics to stderr
|
101
|
+
checked, matched, created, reacted, rules = Rule.stats()
|
102
|
+
statesCount, eventsOut = State.stats()
|
103
|
+
$stderr.puts("-"*40)
|
104
|
+
$stderr.puts("srec has been running for %.1fs" % [Time.now - @startupTime])
|
105
|
+
$stderr.puts("events: in %-8d out %-8d missed %-8d" % [@@eventsIn, eventsOut, @@eventsMissed])
|
106
|
+
$stderr.puts("states: active %-8d created %-8d " % [statesCount, created.values.reduce(:+)])
|
107
|
+
$stderr.puts("rules: checked %-8d matched %-8d reacted %-8d" % [checked.values.reduce(:+),matched.values.reduce(:+), reacted.values.reduce(:+)])
|
108
|
+
$stderr.puts("Rule ID checked matched freq % reacted ")
|
109
|
+
#checked.keys.sort { |a,b| matched[b] <=> matched[a] }.each {|rid| # descending by matches
|
110
|
+
rules.collect { |rule| rule.rid }.each { |rid|
|
111
|
+
if checked[rid] > 0
|
112
|
+
freq = "%5.2f" % [matched[rid].to_f() / checked[rid].to_f() * 100]
|
113
|
+
else
|
114
|
+
freq = "never"
|
115
|
+
end
|
116
|
+
$stderr.puts("%-8d %-8d %-8d %-8s %-8d" % [rid, checked[rid], matched[rid], freq, reacted[rid]])
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module REC
|
2
|
+
# mock the Alert class for testing purposes
|
3
|
+
class Alert
|
4
|
+
|
5
|
+
@@emailsSent = []
|
6
|
+
@@jabbersSent = []
|
7
|
+
|
8
|
+
def self.email(alert, recipient=@@emailTo, subject=@@defaultSubject)
|
9
|
+
@@emailsSent << [alert, recipient, subject]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.jabber(alert, recipient=@@jabberTo, subject=@@defaultSubject)
|
13
|
+
@@jabbersSent << [alert, recipient, subject]
|
14
|
+
end
|
15
|
+
|
16
|
+
def emailsSent()
|
17
|
+
@@emailsSent
|
18
|
+
end
|
19
|
+
|
20
|
+
def jabbersSent()
|
21
|
+
@@jabberSent
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/rec/rule.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rec/state'
|
2
|
+
|
3
|
+
module REC
|
4
|
+
class Rule
|
5
|
+
|
6
|
+
@@rules = []
|
7
|
+
@@index = {} # index of rules to allow lookup of messages etc.
|
8
|
+
@@checked = {} # number of times each rule was checked
|
9
|
+
@@matched = {} # number of times each rule was matched
|
10
|
+
@@created = {} # number of states created by each rule
|
11
|
+
@@reacted = {} # number of times #react was called on each rule
|
12
|
+
|
13
|
+
def self.each(&block)
|
14
|
+
@@rules.each(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.<<(rule)
|
18
|
+
@@rules << rule
|
19
|
+
@@index[rule.rid] = rule
|
20
|
+
@@checked[rule.rid] = 0
|
21
|
+
@@matched[rule.rid] = 0
|
22
|
+
@@created[rule.rid] = 0
|
23
|
+
@@reacted[rule.rid] = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.[](rid)
|
27
|
+
@@index[rid]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.stats()
|
31
|
+
[@@checked, @@matched, @@created, @@reacted, @@rules]
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader(:rid, :pattern, :message, :lifespan, :alert, :params, :action)
|
35
|
+
|
36
|
+
def initialize(rid, params={}, &action)
|
37
|
+
@rid = rid
|
38
|
+
@pattern = params[:pattern] || raise("No pattern specified for rule #{@ruleId}")
|
39
|
+
@message = params[:message] || "" # no message means no state created - ie. ignore event
|
40
|
+
@lifespan = params[:lifespan] || 0
|
41
|
+
@alert = params[:alert] || @message
|
42
|
+
@allstates = params[:allstates] || []
|
43
|
+
@anystates = params[:anystates] || []
|
44
|
+
@notstates = params[:notstates] || []
|
45
|
+
@details = params[:details] || []
|
46
|
+
@params = params
|
47
|
+
@action = action
|
48
|
+
@matches = nil
|
49
|
+
Rule << self
|
50
|
+
end
|
51
|
+
|
52
|
+
def check(logMessage)
|
53
|
+
@@checked[@rid] += 1
|
54
|
+
matchData = @pattern.match(logMessage) || return
|
55
|
+
@matches = Hash[@details.zip(matchData.to_a()[1..-1])] # map matched values to detail keys
|
56
|
+
# if any notstates are specified, make sure none are present
|
57
|
+
@notstates.each { |str| return if State[str.sprinth(@matches)] }
|
58
|
+
# if any allstates are specified, they all need to be present
|
59
|
+
@allstates.each { |str| return unless State[str.sprinth(@matches)] }
|
60
|
+
# if anystates are specified, we must find one that matches
|
61
|
+
return unless @anystates.empty? or @anystates.detect {|str| State[str.sprinth(@matches)] }
|
62
|
+
title = @message.sprinth(@matches)
|
63
|
+
@@matched[@rid] += 1
|
64
|
+
return(title)
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_state(title, time)
|
68
|
+
@@created[@rid] += 1
|
69
|
+
$stderr.puts("+ Creating new state #{title}") if $debug
|
70
|
+
State.new(title, time, @lifespan, @params)
|
71
|
+
end
|
72
|
+
|
73
|
+
def react(state, time, logLine)
|
74
|
+
@@reacted[@rid] += 1
|
75
|
+
state.update(time, @rid, @matches, @alert, logLine)
|
76
|
+
$stderr.puts("~ Rule #{@rid}, state = #{state.inspect()}") if $debug
|
77
|
+
@action.call(state) if @action
|
78
|
+
end
|
79
|
+
|
80
|
+
def continue()
|
81
|
+
@params[:continue]
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
data/lib/rec/state.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module REC
|
4
|
+
class State
|
5
|
+
|
6
|
+
@@timeouts = []
|
7
|
+
@@states = {}
|
8
|
+
@@eventsOut = 0
|
9
|
+
|
10
|
+
Struct.new("Timeout", :expiry, :key)
|
11
|
+
|
12
|
+
def self.[](key)
|
13
|
+
@@states[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.timeout_at(time, title)
|
17
|
+
tnew = Struct::Timeout.new(time, title)
|
18
|
+
n = @@timeouts.find_index { |to|
|
19
|
+
to.expiry > time
|
20
|
+
}
|
21
|
+
if n.nil?
|
22
|
+
@@timeouts = @@timeouts << tnew
|
23
|
+
else
|
24
|
+
@@timeouts[n..n] = [tnew, @@timeouts[n]]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.expire_states(time)
|
29
|
+
timeout = @@timeouts.first()
|
30
|
+
while @@timeouts.length > 0 and timeout.expiry < time do
|
31
|
+
state = State[timeout.key]
|
32
|
+
if state.nil?
|
33
|
+
@@timeouts.shift
|
34
|
+
timeout = @@timeouts.first()
|
35
|
+
next
|
36
|
+
end
|
37
|
+
#$stderr.puts("Releasing state #{state.title} with count of #{state.count}")
|
38
|
+
@@states.delete(@@timeouts.shift().key)
|
39
|
+
timeout = @@timeouts.first()
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.stats()
|
44
|
+
statesCount = @@states.keys.length
|
45
|
+
[statesCount, @@eventsOut]
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader(:rid, :title, :lifespan, :alert, :params, :count, :created, :updated, :dur, :details)
|
49
|
+
|
50
|
+
def initialize(title, time, lifespan, params={})
|
51
|
+
@title = title
|
52
|
+
@lifespan = lifespan.to_f
|
53
|
+
@params = params
|
54
|
+
@count = 0
|
55
|
+
@created = time
|
56
|
+
@updated = time
|
57
|
+
@dur = 0
|
58
|
+
@rid = 0
|
59
|
+
@alert = ""
|
60
|
+
@logs = [] # array of remembered logLines
|
61
|
+
@details = {} # hash of remembered details
|
62
|
+
@@states[title] = self
|
63
|
+
State.timeout_at(time + @lifespan, @title)
|
64
|
+
end
|
65
|
+
|
66
|
+
def update(time, rid, matches, alert, logLine=nil)
|
67
|
+
@count = @count.succ
|
68
|
+
@updated = time
|
69
|
+
@dur = @updated - @created
|
70
|
+
@rid = rid
|
71
|
+
@details.merge!(matches)
|
72
|
+
@alert = alert
|
73
|
+
@logs << logLine if @params[:capture]
|
74
|
+
end
|
75
|
+
|
76
|
+
def release()
|
77
|
+
@@states.delete(@title)
|
78
|
+
end
|
79
|
+
|
80
|
+
def stats()
|
81
|
+
@details.merge({"count"=>@count, "dur"=>@dur, "created"=>@created, "updated"=>@updated})
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_alert()
|
85
|
+
message = @alert.sprinth(stats())
|
86
|
+
event = "%s %s" % [@created.iso8601, message] + @logs.join("\n")
|
87
|
+
print("> ") if $debug
|
88
|
+
puts(event)
|
89
|
+
@@eventsOut = @@eventsOut + 1
|
90
|
+
event
|
91
|
+
end
|
92
|
+
|
93
|
+
def alert_first_only()
|
94
|
+
generate_alert() if @count == 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def method_missing(symbol, *args)
|
98
|
+
@params[symbol]
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
data/lib/rec.rb
ADDED
data/lib/string.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class String
|
2
|
+
# interpolate hash values into a formatted string
|
3
|
+
# The string should have a form like "Stats uid %uid$-5d belongs to %userid$s"
|
4
|
+
# '%uid$-5d' is replaced by '%-5d' and '%userid$s' becomes '%s'
|
5
|
+
# and the relevant values are pulled from the hash into an array
|
6
|
+
# and interpolated using normal sprintf features
|
7
|
+
def sprinth(hash={})
|
8
|
+
raise ArgumentError.new("sprinth argument must be a Hash") unless hash.is_a?(Hash)
|
9
|
+
self.gsub(/\%\w+\$/,"%") % self.scan(/\%\w+\$/).collect { |token| hash[token[1..-2]] }
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Richard Kernahan
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-09-06 00:00:00 +10:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: "\tSifts through your log files in real time, using stateful intelligence to determine\n\
|
22
|
+
\twhat is really important. REC can alert you (by email or IM) or it can simply condense\n\
|
23
|
+
\ta large log file into a much shorter and more meaningful log.\n\
|
24
|
+
\tREC is inspired by Risto Vaarandi's brilliant *sec* (simple-evcorr.sourceforge.net)\n\
|
25
|
+
\tbut is original code and any defects are entirely mine.\n\
|
26
|
+
\tWhile event correlation is inherently complex, REC attempts to make common tasks easy\n\
|
27
|
+
\twhile preserving plenty of power and flexibility for ambitious tasks.\n"
|
28
|
+
email: rec@finalstep.com.au
|
29
|
+
executables: []
|
30
|
+
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files: []
|
34
|
+
|
35
|
+
files:
|
36
|
+
- lib/rec.rb
|
37
|
+
- lib/rec/rule.rb
|
38
|
+
- lib/rec/state.rb
|
39
|
+
- lib/rec/correlator.rb
|
40
|
+
- lib/rec/alert.rb
|
41
|
+
- lib/rec/mock-alert.rb
|
42
|
+
- lib/string.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://rubygems.org/gems/rec
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: rec
|
69
|
+
rubygems_version: 1.3.6
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Ruby event correlation
|
73
|
+
test_files: []
|
74
|
+
|