cirrocumulus 0.4.6 → 0.5.2
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/Gemfile +1 -1
- data/LICENSE.txt +502 -0
- data/VERSION +1 -1
- data/cirrocumulus.gemspec +14 -15
- data/lib/cirrocumulus/agent.rb +97 -37
- data/lib/cirrocumulus/agent_wrapper.rb +68 -0
- data/lib/cirrocumulus/engine.rb +142 -63
- data/lib/cirrocumulus/ontology.rb +7 -2
- data/lib/cirrocumulus/rule_engine.rb +2 -259
- data/lib/cirrocumulus/rules/engine.rb +314 -0
- data/lib/cirrocumulus/rules/run_queue.rb +108 -0
- metadata +100 -156
- data/Gemfile.lock +0 -33
- data/lib/cirrocumulus/master_agent.rb +0 -205
- data/lib/test.rb +0 -56
@@ -11,6 +11,7 @@ module Ontology
|
|
11
11
|
@saga_idx = 0
|
12
12
|
end
|
13
13
|
|
14
|
+
# Restores saved state. Called once at initialization
|
14
15
|
def restore_state()
|
15
16
|
puts "call to dummy Ontology::Base.restore_state()"
|
16
17
|
end
|
@@ -18,8 +19,12 @@ module Ontology
|
|
18
19
|
def tick()
|
19
20
|
@sagas.each do |saga|
|
20
21
|
next if saga.is_finished?
|
21
|
-
|
22
|
-
|
22
|
+
begin
|
23
|
+
saga.timeout -= 1 if saga.timeout > 0
|
24
|
+
saga.handle(nil) if saga.timeout == 0
|
25
|
+
rescue Exception => e
|
26
|
+
Log4r::Logger['agent'].warn "Got exception while ticking saga: %s\n%s" % [e.to_s, e.backtrace.to_s]
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
handle_tick()
|
@@ -1,259 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
def self.rule(name, facts, &block)
|
4
|
-
current_ruleset << {:name => name, :facts => facts, :body => block}
|
5
|
-
end
|
6
|
-
|
7
|
-
def dump_kb()
|
8
|
-
log "Dumping current knowledge base:\n"
|
9
|
-
@facts.each_with_index do |fact,i|
|
10
|
-
log "%d) %s" % [i, fact.inspect]
|
11
|
-
end
|
12
|
-
|
13
|
-
log "Empty" if @facts.empty?
|
14
|
-
end
|
15
|
-
|
16
|
-
def dump_ruleset()
|
17
|
-
log "Dumping current ruleset:\n"
|
18
|
-
self.class.current_ruleset.each_with_index do |rule,i|
|
19
|
-
log "%d) %s" % [i, rule[:name]]
|
20
|
-
end
|
21
|
-
|
22
|
-
log "Empty ruleset" if self.class.current_ruleset.empty?
|
23
|
-
end
|
24
|
-
|
25
|
-
def assert(fact, silent = false)
|
26
|
-
@facts = [] if @facts.nil?
|
27
|
-
return if @facts.include? fact
|
28
|
-
|
29
|
-
log "assert: #{fact.inspect}"
|
30
|
-
|
31
|
-
@facts << fact
|
32
|
-
process() if !silent
|
33
|
-
end
|
34
|
-
|
35
|
-
def retract(fact, silent = false)
|
36
|
-
@facts = [] if @facts.nil?
|
37
|
-
if @facts.delete(fact)
|
38
|
-
log "retract: #{fact.inspect}"
|
39
|
-
process() if !silent
|
40
|
-
else
|
41
|
-
#puts "fact #{fact.inspect} not found"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def replace(pattern, values)
|
46
|
-
log "replace: #{pattern.inspect} => #{values.inspect}"
|
47
|
-
|
48
|
-
data = match(pattern)
|
49
|
-
data.each do |match_data|
|
50
|
-
old_fact = pattern.clone
|
51
|
-
new_fact = pattern.clone
|
52
|
-
pattern.each_with_index do |item,i|
|
53
|
-
if match_data.include? item
|
54
|
-
old_fact[i] = match_data[item]
|
55
|
-
new_fact[i] = values.is_a?(Hash) ? values[item] : values
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
retract(old_fact, true)
|
60
|
-
assert(new_fact)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def query(fact)
|
65
|
-
@facts = [] if @facts.nil?
|
66
|
-
return @facts.include? fact
|
67
|
-
end
|
68
|
-
|
69
|
-
def match(pattern)
|
70
|
-
res = []
|
71
|
-
match_pattern(pattern).each do |fact|
|
72
|
-
res << bind_parameters(pattern, fact, {})
|
73
|
-
end
|
74
|
-
|
75
|
-
res
|
76
|
-
end
|
77
|
-
|
78
|
-
def execute()
|
79
|
-
process()
|
80
|
-
end
|
81
|
-
|
82
|
-
protected
|
83
|
-
|
84
|
-
@@loaded_rules = {}
|
85
|
-
|
86
|
-
def self.current_ruleset()
|
87
|
-
return @@loaded_rules[self.name] ||= []
|
88
|
-
end
|
89
|
-
|
90
|
-
def log(msg)
|
91
|
-
Log4r::Logger['kb'].info(msg)
|
92
|
-
rescue
|
93
|
-
puts "[INFO] %s" % msg
|
94
|
-
end
|
95
|
-
|
96
|
-
def debug(msg)
|
97
|
-
Log4r::Logger['kb'].debug(msg)
|
98
|
-
rescue
|
99
|
-
puts "[DEBUG] %s" % msg
|
100
|
-
end
|
101
|
-
|
102
|
-
def matches?(rule)
|
103
|
-
debug "Processing rule '#{rule[:name]}' (#{rule[:facts].size} condition(s))"
|
104
|
-
|
105
|
-
pattern_candidates = []
|
106
|
-
rule[:facts].each do |pattern|
|
107
|
-
pattern_candidates << match_pattern(pattern)
|
108
|
-
end
|
109
|
-
|
110
|
-
return nil if !pattern_candidates.all? {|c| c.size > 0}
|
111
|
-
|
112
|
-
debug "Rule '#{rule[:name]}' has candidates for each pattern"
|
113
|
-
|
114
|
-
match_parameters(rule, pattern_candidates)
|
115
|
-
end
|
116
|
-
|
117
|
-
def match_pattern(pattern)
|
118
|
-
debug "Attempting to match pattern #{pattern.inspect}"
|
119
|
-
fact_matches = true
|
120
|
-
candidates = []
|
121
|
-
|
122
|
-
@facts.each do |fact|
|
123
|
-
next if fact.size != pattern.size
|
124
|
-
fact_matches = true
|
125
|
-
|
126
|
-
pattern.each_with_index do |el,i|
|
127
|
-
if el.is_a?(Symbol) && el.to_s.upcase == el.to_s # parameter
|
128
|
-
else
|
129
|
-
fact_matches = false if el != fact[i]
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
candidates << fact if fact_matches
|
134
|
-
end
|
135
|
-
|
136
|
-
debug "Found #{candidates.size} candidate(s)"
|
137
|
-
candidates
|
138
|
-
end
|
139
|
-
|
140
|
-
def match_parameters(rule, candidates)
|
141
|
-
debug "Attempting to match parameters"
|
142
|
-
|
143
|
-
result = []
|
144
|
-
attempt = []
|
145
|
-
while (attempt = generate_combination(rule, candidates, attempt)) != [] do
|
146
|
-
bindings = test_combination(rule, candidates, attempt)
|
147
|
-
if bindings
|
148
|
-
debug "Matched! %s" % bindings.inspect
|
149
|
-
result << bindings
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
result
|
154
|
-
end
|
155
|
-
|
156
|
-
def test_combination(rule, candidates, attempt)
|
157
|
-
facts = []
|
158
|
-
attempt.each_with_index {|a,i| facts << candidates[i][a]}
|
159
|
-
debug "Testing combination #{attempt.inspect}: %s" % facts.inspect
|
160
|
-
|
161
|
-
binded_params = {}
|
162
|
-
pattern_params = {}
|
163
|
-
facts.each_with_index do |fact,i|
|
164
|
-
pattern_params = bind_parameters(rule[:facts][i], fact, binded_params)
|
165
|
-
if pattern_params.nil? # failure, parameters mismatch
|
166
|
-
return nil
|
167
|
-
else
|
168
|
-
binded_params.merge!(pattern_params)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
binded_params
|
173
|
-
end
|
174
|
-
|
175
|
-
def bind_parameters(pattern, fact, current_bindings)
|
176
|
-
result = {}
|
177
|
-
|
178
|
-
pattern.each_with_index do |p,i|
|
179
|
-
if p.is_a?(Symbol) && p.to_s.upcase == p.to_s
|
180
|
-
return nil if current_bindings.has_key?(p) && current_bindings[p] != fact[i]
|
181
|
-
result[p] = fact[i]
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
result
|
186
|
-
end
|
187
|
-
|
188
|
-
def generate_combination(rule, candidates, attempt)
|
189
|
-
next_attempt = []
|
190
|
-
|
191
|
-
if attempt == []
|
192
|
-
rule[:facts].each {|pattern| next_attempt << 0}
|
193
|
-
else
|
194
|
-
next_attempt = increment_attempt(attempt, rule[:facts].size - 1, candidates.map {|c| c.size})
|
195
|
-
end
|
196
|
-
|
197
|
-
next_attempt
|
198
|
-
end
|
199
|
-
|
200
|
-
def increment_attempt(attempt, idx, limits)
|
201
|
-
return [] if idx < 0
|
202
|
-
|
203
|
-
if attempt[idx] < limits[idx] - 1
|
204
|
-
attempt[idx] += 1
|
205
|
-
else
|
206
|
-
i = idx
|
207
|
-
while i < limits.size do
|
208
|
-
attempt[i] = 0
|
209
|
-
i += 1
|
210
|
-
end
|
211
|
-
|
212
|
-
return increment_attempt(attempt, idx-1, limits)
|
213
|
-
end
|
214
|
-
|
215
|
-
attempt
|
216
|
-
end
|
217
|
-
|
218
|
-
def pattern_matches?(fact, pattern, current_params = {})
|
219
|
-
return nil if fact.size != pattern.size
|
220
|
-
|
221
|
-
puts "DEBUG: testing pattern %s against fact %s" % [pattern.inspect, fact.inspect]
|
222
|
-
puts "DEBUG: current parameters binding is %s" % current_params.inspect
|
223
|
-
|
224
|
-
binded_params = {}
|
225
|
-
|
226
|
-
pattern.each_with_index do |el,i|
|
227
|
-
if el.is_a?(Symbol) && el.to_s.upcase == el.to_s
|
228
|
-
puts "DEBUG: need to bind parameter %s" % el.to_s
|
229
|
-
if current_params && current_params.has_key?(el)
|
230
|
-
current_value = current_params[el]
|
231
|
-
return nil if fact[i] != current_value
|
232
|
-
else
|
233
|
-
binded_params[el] = fact[i]
|
234
|
-
end
|
235
|
-
else
|
236
|
-
return nil if el != fact[i]
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
puts "DEBUG: match! binding parameters: %s" % binded_params.inspect
|
241
|
-
binded_params
|
242
|
-
end
|
243
|
-
|
244
|
-
def execute_rule(rule, params)
|
245
|
-
debug "executing rule '#{rule[:name]}'"
|
246
|
-
rule[:body].call(self, params)
|
247
|
-
end
|
248
|
-
|
249
|
-
def process()
|
250
|
-
self.class.current_ruleset.each do |rule|
|
251
|
-
binded_params = matches?(rule)
|
252
|
-
next if binded_params.nil?
|
253
|
-
binded_params.each {|params| execute_rule(rule, params)}
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
end
|
258
|
-
|
259
|
-
end
|
1
|
+
require_relative 'rules/run_queue.rb'
|
2
|
+
require_relative 'rules/engine.rb'
|
@@ -0,0 +1,314 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module RuleEngine
|
4
|
+
class RuleDescription
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :conditions
|
7
|
+
attr_reader :options
|
8
|
+
attr_reader :code
|
9
|
+
|
10
|
+
def initialize(name, conditions, options, code)
|
11
|
+
@name = name
|
12
|
+
@conditions = conditions
|
13
|
+
@options = options
|
14
|
+
@code = code
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Base
|
20
|
+
def self.rule(name, facts, options = {}, &block)
|
21
|
+
current_ruleset << RuleDescription.new(name, facts, options, block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@queue = RunQueue.new(self)
|
26
|
+
@facts = []
|
27
|
+
@fact_times = {}
|
28
|
+
@mutex = Mutex.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump_kb()
|
32
|
+
log "Dumping current knowledge base:\n"
|
33
|
+
|
34
|
+
@facts.each_with_index do |fact,i|
|
35
|
+
log "%d) %s (at %s)" % [i, fact.inspect, @fact_times[fact]]
|
36
|
+
end
|
37
|
+
|
38
|
+
log "Empty" if @facts.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def dump_ruleset()
|
42
|
+
log "Dumping current ruleset:\n"
|
43
|
+
self.class.current_ruleset.each_with_index do |rule,i|
|
44
|
+
log "%d) %s" % [i, rule[:name]]
|
45
|
+
end
|
46
|
+
|
47
|
+
log "Empty ruleset" if self.class.current_ruleset.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
def assert(fact, silent = false)
|
51
|
+
@mutex.synchronize do
|
52
|
+
assert_nonblocking(fact, silent)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def retract(fact, silent = false)
|
57
|
+
@mutex.synchronize do
|
58
|
+
retract_nonblocking(fact, silent)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def replace(pattern, values)
|
63
|
+
@mutex.synchronize do
|
64
|
+
log "replace #{pattern.inspect} for #{values.inspect}"
|
65
|
+
|
66
|
+
data = match(pattern)
|
67
|
+
data.each do |match_data|
|
68
|
+
old_fact = pattern.clone
|
69
|
+
new_fact = pattern.clone
|
70
|
+
pattern.each_with_index do |item,i|
|
71
|
+
if match_data.include? item
|
72
|
+
old_fact[i] = match_data[item]
|
73
|
+
new_fact[i] = values.is_a?(Hash) ? values[item] : values
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
retract_nonblocking(old_fact, true)
|
78
|
+
assert_nonblocking(new_fact)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def query(fact)
|
84
|
+
@facts = [] if @facts.nil?
|
85
|
+
return @facts.include? fact
|
86
|
+
end
|
87
|
+
|
88
|
+
def match(pattern)
|
89
|
+
res = []
|
90
|
+
match_pattern(pattern).each do |fact|
|
91
|
+
res << bind_parameters(pattern, fact, {})
|
92
|
+
end
|
93
|
+
|
94
|
+
res
|
95
|
+
end
|
96
|
+
|
97
|
+
def start()
|
98
|
+
@worker_thread = Thread.new do
|
99
|
+
while true do
|
100
|
+
@queue.run_queued_rules()
|
101
|
+
sleep 0.1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def execute()
|
107
|
+
process()
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
@@loaded_rules = {}
|
113
|
+
|
114
|
+
def self.current_ruleset()
|
115
|
+
return @@loaded_rules[self.name] ||= []
|
116
|
+
end
|
117
|
+
|
118
|
+
def assert_nonblocking(fact, silent = false)
|
119
|
+
return if @facts.include? fact
|
120
|
+
|
121
|
+
time = DateTime.now
|
122
|
+
log "assert: %s" % fact.inspect
|
123
|
+
|
124
|
+
@facts << fact
|
125
|
+
@fact_times[fact] = time
|
126
|
+
|
127
|
+
process() if !silent
|
128
|
+
end
|
129
|
+
|
130
|
+
def retract_nonblocking(fact, silent = false)
|
131
|
+
if @facts.delete(fact)
|
132
|
+
@fact_times.delete(fact)
|
133
|
+
log "retract: #{fact.inspect}"
|
134
|
+
process() if !silent
|
135
|
+
else
|
136
|
+
#puts "fact #{fact.inspect} not found"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def matches?(rule)
|
141
|
+
trace "Processing rule '#{rule.name}' (#{rule.conditions.size} condition(s)):"
|
142
|
+
|
143
|
+
pattern_candidates = []
|
144
|
+
rule.conditions.each do |pattern|
|
145
|
+
pattern_candidates << match_pattern(pattern)
|
146
|
+
end
|
147
|
+
|
148
|
+
return nil if !pattern_candidates.all? {|c| c.size > 0}
|
149
|
+
|
150
|
+
trace "Result: rule '#{rule.name}' has candidates for each pattern"
|
151
|
+
|
152
|
+
match_parameters(rule, pattern_candidates)
|
153
|
+
end
|
154
|
+
|
155
|
+
def match_pattern(pattern)
|
156
|
+
trace "=> attempting to match pattern #{pattern.inspect}"
|
157
|
+
fact_matches = true
|
158
|
+
candidates = []
|
159
|
+
|
160
|
+
@facts.each do |fact|
|
161
|
+
next if fact.size != pattern.size
|
162
|
+
fact_matches = true
|
163
|
+
|
164
|
+
pattern.each_with_index do |el,i|
|
165
|
+
if el.is_a?(Symbol) && el.to_s.upcase == el.to_s # parameter
|
166
|
+
else
|
167
|
+
fact_matches = false if el != fact[i]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
candidates << fact if fact_matches
|
172
|
+
end
|
173
|
+
|
174
|
+
trace "=> found #{candidates.size} candidate(s)" if candidates.size > 0
|
175
|
+
candidates
|
176
|
+
end
|
177
|
+
|
178
|
+
def match_parameters(rule, candidates)
|
179
|
+
trace "Attempting to match parameters:"
|
180
|
+
|
181
|
+
result = []
|
182
|
+
attempt = []
|
183
|
+
while (attempt = generate_combination(rule, candidates, attempt)) != [] do
|
184
|
+
bindings = test_combination(rule, candidates, attempt)
|
185
|
+
if bindings
|
186
|
+
trace "Found: %s" % bindings.inspect
|
187
|
+
result << bindings
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
result
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_combination(rule, candidates, attempt)
|
195
|
+
facts = []
|
196
|
+
attempt.each_with_index {|a,i| facts << candidates[i][a]}
|
197
|
+
trace "=> testing combination #{attempt.inspect}: %s" % facts.inspect
|
198
|
+
|
199
|
+
binded_params = {}
|
200
|
+
pattern_params = {}
|
201
|
+
facts.each_with_index do |fact,i|
|
202
|
+
pattern_params = bind_parameters(rule.conditions[i], fact, binded_params)
|
203
|
+
if pattern_params.nil? # failure, parameters mismatch
|
204
|
+
return nil
|
205
|
+
else
|
206
|
+
binded_params.merge!(pattern_params)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
binded_params
|
211
|
+
end
|
212
|
+
|
213
|
+
def bind_parameters(pattern, fact, current_bindings)
|
214
|
+
result = {}
|
215
|
+
|
216
|
+
pattern.each_with_index do |p,i|
|
217
|
+
if p.is_a?(Symbol) && p.to_s.upcase == p.to_s
|
218
|
+
return nil if current_bindings.has_key?(p) && current_bindings[p] != fact[i]
|
219
|
+
result[p] = fact[i]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
result
|
224
|
+
end
|
225
|
+
|
226
|
+
def generate_combination(rule, candidates, attempt)
|
227
|
+
next_attempt = []
|
228
|
+
|
229
|
+
if attempt == []
|
230
|
+
rule.conditions.each {|pattern| next_attempt << 0}
|
231
|
+
else
|
232
|
+
next_attempt = increment_attempt(attempt, rule.conditions.size - 1, candidates.map {|c| c.size})
|
233
|
+
end
|
234
|
+
|
235
|
+
next_attempt
|
236
|
+
end
|
237
|
+
|
238
|
+
def increment_attempt(attempt, idx, limits)
|
239
|
+
return [] if idx < 0
|
240
|
+
|
241
|
+
if attempt[idx] < limits[idx] - 1
|
242
|
+
attempt[idx] += 1
|
243
|
+
else
|
244
|
+
i = idx
|
245
|
+
while i < limits.size do
|
246
|
+
attempt[i] = 0
|
247
|
+
i += 1
|
248
|
+
end
|
249
|
+
|
250
|
+
return increment_attempt(attempt, idx-1, limits)
|
251
|
+
end
|
252
|
+
|
253
|
+
attempt
|
254
|
+
end
|
255
|
+
|
256
|
+
def pattern_matches?(fact, pattern, current_params = {})
|
257
|
+
return nil if fact.size != pattern.size
|
258
|
+
|
259
|
+
puts "DEBUG: testing pattern %s against fact %s" % [pattern.inspect, fact.inspect]
|
260
|
+
puts "DEBUG: current parameters binding is %s" % current_params.inspect
|
261
|
+
|
262
|
+
binded_params = {}
|
263
|
+
|
264
|
+
pattern.each_with_index do |el,i|
|
265
|
+
if el.is_a?(Symbol) && el.to_s.upcase == el.to_s
|
266
|
+
puts "DEBUG: need to bind parameter %s" % el.to_s
|
267
|
+
if current_params && current_params.has_key?(el)
|
268
|
+
current_value = current_params[el]
|
269
|
+
return nil if fact[i] != current_value
|
270
|
+
else
|
271
|
+
binded_params[el] = fact[i]
|
272
|
+
end
|
273
|
+
else
|
274
|
+
return nil if el != fact[i]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
puts "DEBUG: match! binding parameters: %s" % binded_params.inspect
|
279
|
+
binded_params
|
280
|
+
end
|
281
|
+
|
282
|
+
def execute_rule(rule, params)
|
283
|
+
@queue.enqueue(rule, params)
|
284
|
+
end
|
285
|
+
|
286
|
+
def process()
|
287
|
+
self.class.current_ruleset.each do |rule|
|
288
|
+
binded_params = matches?(rule)
|
289
|
+
next if binded_params.nil?
|
290
|
+
binded_params.each {|params| execute_rule(rule, params)}
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def log(msg)
|
295
|
+
Log4r::Logger['kb'].info(msg)
|
296
|
+
rescue
|
297
|
+
puts "[INFO] %s" % msg
|
298
|
+
end
|
299
|
+
|
300
|
+
def debug(msg)
|
301
|
+
Log4r::Logger['kb'].debug(msg)
|
302
|
+
rescue
|
303
|
+
puts "[DEBUG] %s" % msg
|
304
|
+
end
|
305
|
+
|
306
|
+
def trace(msg)
|
307
|
+
return
|
308
|
+
Log4r::Logger['kb_trace'].debug(msg)
|
309
|
+
rescue
|
310
|
+
puts "[TRACE] %s" % msg
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|