right_support 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +38 -14
- data/lib/right_support.rb +1 -1
- data/lib/right_support/crypto/signed_hash.rb +11 -2
- data/lib/right_support/db.rb +1 -3
- data/lib/right_support/db/cassandra_model.rb +299 -18
- data/lib/right_support/log.rb +4 -0
- data/lib/right_support/log/exception_logger.rb +86 -0
- data/lib/right_support/log/filter_logger.rb +75 -0
- data/lib/right_support/log/mixin.rb +104 -0
- data/lib/right_support/log/multiplexer.rb +93 -0
- data/lib/right_support/log/null_logger.rb +76 -0
- data/lib/right_support/net.rb +5 -3
- data/lib/right_support/net/balancing/health_check.rb +40 -23
- data/lib/right_support/net/request_balancer.rb +19 -32
- data/lib/right_support/rack.rb +2 -3
- data/lib/right_support/rack/custom_logger.rb +29 -8
- data/lib/right_support/rack/request_logger.rb +113 -0
- data/lib/right_support/ruby.rb +4 -3
- data/lib/right_support/ruby/easy_singleton.rb +24 -0
- data/lib/right_support/ruby/object_extensions.rb +18 -2
- data/lib/right_support/ruby/string_extensions.rb +120 -0
- data/lib/right_support/stats.rb +34 -0
- data/lib/right_support/stats/activity.rb +206 -0
- data/lib/right_support/stats/exceptions.rb +96 -0
- data/lib/right_support/stats/helpers.rb +438 -0
- data/lib/right_support/validation.rb +2 -4
- data/right_support.gemspec +3 -5
- metadata +18 -20
@@ -1,5 +1,22 @@
|
|
1
1
|
module RightSupport::Ruby
|
2
2
|
module ObjectExtensions
|
3
|
+
# Attempt to require one or more source files.
|
4
|
+
#
|
5
|
+
# This method is useful to conditionally define code depending on the availability
|
6
|
+
# of gems or standard-library source files.
|
7
|
+
#
|
8
|
+
# === Parameters
|
9
|
+
# Forwards all parameters transparently through to Kernel#require.
|
10
|
+
#
|
11
|
+
# === Return
|
12
|
+
# Returns true or false
|
13
|
+
def require_succeeds?(*args)
|
14
|
+
require(*args)
|
15
|
+
return true
|
16
|
+
rescue LoadError => e
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
|
3
20
|
# Attempt to require one or more source files; if the require succeeds (or
|
4
21
|
# if the files have already been successfully required), yield to the block.
|
5
22
|
#
|
@@ -7,8 +24,7 @@ module RightSupport::Ruby
|
|
7
24
|
# of gems or standard-library source files.
|
8
25
|
#
|
9
26
|
# === Parameters
|
10
|
-
#
|
11
|
-
# Kernel#require.
|
27
|
+
# Forwards all parameters transparently through to Kernel#require.
|
12
28
|
#
|
13
29
|
# === Block
|
14
30
|
# The block will be called if the require succeeds (if it does not raise LoadError).
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'rbconfig'
|
24
|
+
|
25
|
+
module RightSupport::Ruby
|
26
|
+
module StringExtensions
|
27
|
+
if require_succeeds?('active_support')
|
28
|
+
ACTIVE_SUPPORT_WORKALIKES = false
|
29
|
+
else
|
30
|
+
ACTIVE_SUPPORT_WORKALIKES = true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Convert to snake case.
|
34
|
+
#
|
35
|
+
# "FooBar".snake_case #=> "foo_bar"
|
36
|
+
# "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
|
37
|
+
# "CNN".snake_case #=> "cnn"
|
38
|
+
#
|
39
|
+
# @return [String] Receiver converted to snake case.
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def snake_case
|
43
|
+
return downcase if match(/\A[A-Z]+\z/)
|
44
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
45
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').
|
46
|
+
downcase
|
47
|
+
end
|
48
|
+
|
49
|
+
# Convert a constant name to a path, assuming a conventional structure.
|
50
|
+
#
|
51
|
+
# "FooBar::Baz".to_const_path # => "foo_bar/baz"
|
52
|
+
#
|
53
|
+
# @return [String] Path to the file containing the constant named by receiver
|
54
|
+
# (constantized string), assuming a conventional structure.
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def to_const_path
|
58
|
+
snake_case.gsub(/::/, "/")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convert constant name to constant
|
62
|
+
#
|
63
|
+
# "FooBar::Baz".to_const => FooBar::Baz
|
64
|
+
#
|
65
|
+
# @return [Constant] Constant corresponding to given name or nil if no
|
66
|
+
# constant with that name exists
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def to_const
|
70
|
+
names = split('::')
|
71
|
+
names.shift if names.empty? || names.first.empty?
|
72
|
+
|
73
|
+
constant = Object
|
74
|
+
names.each do |name|
|
75
|
+
# modified to return nil instead of raising an const_missing error
|
76
|
+
constant = constant && constant.const_defined?(name) ? constant.const_get(name) : nil
|
77
|
+
end
|
78
|
+
constant
|
79
|
+
end
|
80
|
+
|
81
|
+
# Reverse operation of snake case:
|
82
|
+
#
|
83
|
+
# "some_string/some_other_string" => "SomeString::SomeOtherString"
|
84
|
+
#
|
85
|
+
# @return [String] Camelized string
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
if !String.public_method_defined?(:camelize) && ACTIVE_SUPPORT_WORKALIKES
|
89
|
+
def camelize(first_letter = :upper)
|
90
|
+
case first_letter
|
91
|
+
when :upper then gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
92
|
+
when :lower then first + camelize(self)[1..-1]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add ability to output colored text to console
|
98
|
+
# e.g.: puts "Hello".red
|
99
|
+
def bold; colorize("\e[1m\e[29m"); end
|
100
|
+
def grey; colorize("\e[30m"); end
|
101
|
+
def red; colorize("\e[1m\e[31m"); end
|
102
|
+
def dark_red; colorize("\e[31m"); end
|
103
|
+
def green; colorize("\e[1m\e[32m"); end
|
104
|
+
def dark_green; colorize("\e[32m"); end
|
105
|
+
def yellow; colorize("\e[1m\e[33m"); end
|
106
|
+
def blue; colorize("\e[1m\e[34m"); end
|
107
|
+
def dark_blue; colorize("\e[34m"); end
|
108
|
+
def pur; colorize("\e[1m\e[35m"); end
|
109
|
+
def colorize(color_code)
|
110
|
+
# Doesn't work with the Windows prompt...
|
111
|
+
@windows ||= RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i
|
112
|
+
(@windows || !$stdout.isatty) ? to_s : "#{color_code}#{to_s}\e[0m"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class String
|
119
|
+
include RightSupport::Ruby::StringExtensions
|
120
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2012 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightSupport
|
24
|
+
#
|
25
|
+
# A namespace for statistics tracking functionality.
|
26
|
+
#
|
27
|
+
module Stats
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'right_support/stats/exceptions'
|
33
|
+
require 'right_support/stats/helpers'
|
34
|
+
require 'right_support/stats/activity'
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# Copyright (c) 2009-2012 RightScale Inc
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
module RightSupport::Stats
|
23
|
+
|
24
|
+
# Track statistics for a given kind of activity
|
25
|
+
class Activity
|
26
|
+
|
27
|
+
# Number of samples included when calculating average recent activity
|
28
|
+
# with the smoothing formula A = ((A * (RECENT_SIZE - 1)) + V) / RECENT_SIZE,
|
29
|
+
# where A is the current recent average and V is the new activity value
|
30
|
+
# As a rough guide, it takes approximately 2 * RECENT_SIZE activity values
|
31
|
+
# at value V for average A to reach 90% of the original difference between A and V
|
32
|
+
# For example, for A = 0, V = 1, RECENT_SIZE = 3 the progression for A is
|
33
|
+
# 0, 0.3, 0.5, 0.7, 0.8, 0.86, 0.91, 0.94, 0.96, 0.97, 0.98, 0.99, ...
|
34
|
+
RECENT_SIZE = 3
|
35
|
+
|
36
|
+
# Maximum string length for activity type
|
37
|
+
MAX_TYPE_SIZE = 60
|
38
|
+
|
39
|
+
# (Integer) Total activity count
|
40
|
+
attr_reader :total
|
41
|
+
|
42
|
+
# (Hash) Count of activity per type
|
43
|
+
attr_reader :count_per_type
|
44
|
+
|
45
|
+
# Initialize activity data
|
46
|
+
#
|
47
|
+
# === Parameters
|
48
|
+
# measure_rate(Boolean):: Whether to measure activity rate
|
49
|
+
def initialize(measure_rate = true)
|
50
|
+
@measure_rate = measure_rate
|
51
|
+
reset
|
52
|
+
end
|
53
|
+
|
54
|
+
# Reset statistics
|
55
|
+
#
|
56
|
+
# === Return
|
57
|
+
# true:: Always return true
|
58
|
+
def reset
|
59
|
+
@interval = 0.0
|
60
|
+
@last_start_time = Time.now
|
61
|
+
@avg_duration = nil
|
62
|
+
@total = 0
|
63
|
+
@count_per_type = {}
|
64
|
+
@last_type = nil
|
65
|
+
@last_id = nil
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
# Mark the start of an activity and update counts and average rate
|
70
|
+
# with weighting toward recent activity
|
71
|
+
# Ignore the update if its type contains "stats"
|
72
|
+
#
|
73
|
+
# === Parameters
|
74
|
+
# type(String|Symbol):: Type of activity, with anything that is not a symbol, true, or false
|
75
|
+
# automatically converted to a String and truncated to MAX_TYPE_SIZE characters,
|
76
|
+
# defaults to nil
|
77
|
+
# id(String):: Unique identifier associated with this activity
|
78
|
+
#
|
79
|
+
# === Return
|
80
|
+
# now(Time):: Update time
|
81
|
+
def update(type = nil, id = nil)
|
82
|
+
now = Time.now
|
83
|
+
if type.nil? || !(type =~ /stats/)
|
84
|
+
@interval = average(@interval, now - @last_start_time) if @measure_rate
|
85
|
+
@last_start_time = now
|
86
|
+
@total += 1
|
87
|
+
unless type.nil?
|
88
|
+
unless [Symbol, TrueClass, FalseClass].include?(type.class)
|
89
|
+
type = type.inspect unless type.is_a?(String)
|
90
|
+
type = type[0, MAX_TYPE_SIZE - 3] + "..." if type.size > (MAX_TYPE_SIZE - 3)
|
91
|
+
end
|
92
|
+
@count_per_type[type] = (@count_per_type[type] || 0) + 1
|
93
|
+
end
|
94
|
+
@last_type = type
|
95
|
+
@last_id = id
|
96
|
+
end
|
97
|
+
now
|
98
|
+
end
|
99
|
+
|
100
|
+
# Mark the finish of an activity and update the average duration
|
101
|
+
#
|
102
|
+
# === Parameters
|
103
|
+
# start_time(Time):: Time when activity started, defaults to last time update was called
|
104
|
+
# id(String):: Unique identifier associated with this activity
|
105
|
+
#
|
106
|
+
# === Return
|
107
|
+
# duration(Float):: Activity duration in seconds
|
108
|
+
def finish(start_time = nil, id = nil)
|
109
|
+
now = Time.now
|
110
|
+
start_time ||= @last_start_time
|
111
|
+
duration = now - start_time
|
112
|
+
@avg_duration = average(@avg_duration || 0.0, duration)
|
113
|
+
@last_id = 0 if id && id == @last_id
|
114
|
+
duration
|
115
|
+
end
|
116
|
+
|
117
|
+
# Convert average interval to average rate
|
118
|
+
#
|
119
|
+
# === Return
|
120
|
+
# (Float|nil):: Recent average rate, or nil if total is 0
|
121
|
+
def avg_rate
|
122
|
+
if @total > 0
|
123
|
+
if @interval == 0.0 then 0.0 else 1.0 / @interval end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Get average duration of activity
|
129
|
+
#
|
130
|
+
# === Return
|
131
|
+
# (Float|nil) Average duration in seconds of activity weighted toward recent activity, or nil if total is 0
|
132
|
+
def avg_duration
|
133
|
+
@avg_duration if @total > 0
|
134
|
+
end
|
135
|
+
|
136
|
+
# Get stats about last activity
|
137
|
+
#
|
138
|
+
# === Return
|
139
|
+
# (Hash|nil):: Information about last activity, or nil if the total is 0
|
140
|
+
# "elapsed"(Integer):: Seconds since last activity started
|
141
|
+
# "type"(String):: Type of activity if specified, otherwise omitted
|
142
|
+
# "active"(Boolean):: Whether activity still active
|
143
|
+
def last
|
144
|
+
if @total > 0
|
145
|
+
result = {"elapsed" => (Time.now - @last_start_time).to_i}
|
146
|
+
result["type"] = @last_type if @last_type
|
147
|
+
result["active"] = @last_id != 0 if !@last_id.nil?
|
148
|
+
result
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Convert count per type into percentage by type
|
153
|
+
#
|
154
|
+
# === Return
|
155
|
+
# (Hash|nil):: Converted counts, or nil if total is 0
|
156
|
+
# "total"(Integer):: Total activity count
|
157
|
+
# "percent"(Hash):: Percentage for each type of activity if tracking type, otherwise omitted
|
158
|
+
def percentage
|
159
|
+
if @total > 0
|
160
|
+
percent = {}
|
161
|
+
@count_per_type.each { |k, v| percent[k] = (v / @total.to_f) * 100.0 }
|
162
|
+
{"percent" => percent, "total" => @total}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Get stat summary including all aspects of activity that were measured except duration
|
167
|
+
#
|
168
|
+
# === Return
|
169
|
+
# (Hash|nil):: Information about activity, or nil if the total is 0
|
170
|
+
# "total"(Integer):: Total activity count
|
171
|
+
# "percent"(Hash):: Percentage for each type of activity if tracking type, otherwise omitted
|
172
|
+
# "last"(Hash):: Information about last activity
|
173
|
+
# "elapsed"(Integer):: Seconds since last activity started
|
174
|
+
# "type"(String):: Type of activity if tracking type, otherwise omitted
|
175
|
+
# "active"(Boolean):: Whether activity still active if tracking whether active, otherwise omitted
|
176
|
+
# "rate"(Float):: Recent average rate if measuring rate, otherwise omitted
|
177
|
+
def all
|
178
|
+
if @total > 0
|
179
|
+
result = if @count_per_type.empty?
|
180
|
+
{"total" => @total}
|
181
|
+
else
|
182
|
+
percentage
|
183
|
+
end
|
184
|
+
result.merge!("last" => last)
|
185
|
+
result.merge!("rate" => avg_rate) if @measure_rate
|
186
|
+
result
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
protected
|
191
|
+
|
192
|
+
# Calculate smoothed average with weighting toward recent activity
|
193
|
+
#
|
194
|
+
# === Parameters
|
195
|
+
# current(Float|Integer):: Current average value
|
196
|
+
# value(Float|Integer):: New value
|
197
|
+
#
|
198
|
+
# === Return
|
199
|
+
# (Float):: New average
|
200
|
+
def average(current, value)
|
201
|
+
((current * (RECENT_SIZE - 1)) + value) / RECENT_SIZE.to_f
|
202
|
+
end
|
203
|
+
|
204
|
+
end # Activity
|
205
|
+
|
206
|
+
end # RightScale::Stats
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Copyright (c) 2009-2012 RightScale Inc
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
module RightSupport::Stats
|
23
|
+
|
24
|
+
# Track statistics for exceptions
|
25
|
+
class Exceptions
|
26
|
+
|
27
|
+
include RightSupport::Log::Mixin
|
28
|
+
|
29
|
+
# Maximum number of recent exceptions to track per category
|
30
|
+
MAX_RECENT_EXCEPTIONS = 10
|
31
|
+
|
32
|
+
# (Hash) Exceptions raised per category with keys
|
33
|
+
# "total"(Integer):: Total exceptions for this category
|
34
|
+
# "recent"(Array):: Most recent as a hash of "count", "type", "message", "when", and "where"
|
35
|
+
attr_reader :stats
|
36
|
+
alias :all :stats
|
37
|
+
|
38
|
+
# Initialize exception data
|
39
|
+
#
|
40
|
+
# === Parameters
|
41
|
+
# server(Object):: Server where exceptions are originating, must be defined for callbacks
|
42
|
+
# callback(Proc):: Block with following parameters to be activated when an exception occurs
|
43
|
+
# exception(Exception):: Exception
|
44
|
+
# message(Packet):: Message being processed
|
45
|
+
# server(Server):: Server where exception occurred
|
46
|
+
def initialize(server = nil, callback = nil)
|
47
|
+
@server = server
|
48
|
+
@callback = callback
|
49
|
+
reset
|
50
|
+
end
|
51
|
+
|
52
|
+
# Reset statistics
|
53
|
+
#
|
54
|
+
# === Return
|
55
|
+
# true:: Always return true
|
56
|
+
def reset
|
57
|
+
@stats = nil
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
# Track exception statistics and optionally make callback to report exception
|
62
|
+
# Catch any exceptions since this function may be called from within an EM block
|
63
|
+
# and an exception here would then derail EM
|
64
|
+
#
|
65
|
+
# === Parameters
|
66
|
+
# category(String):: Exception category
|
67
|
+
# exception(Exception):: Exception
|
68
|
+
#
|
69
|
+
# === Return
|
70
|
+
# true:: Always return true
|
71
|
+
def track(category, exception, message = nil)
|
72
|
+
begin
|
73
|
+
@callback.call(exception, message, @server) if @server && @callback && message
|
74
|
+
@stats ||= {}
|
75
|
+
exceptions = (@stats[category] ||= {"total" => 0, "recent" => []})
|
76
|
+
exceptions["total"] += 1
|
77
|
+
recent = exceptions["recent"]
|
78
|
+
last = recent.last
|
79
|
+
if last && last["type"] == exception.class.name && last["message"] == exception.message && last["where"] == exception.backtrace.first
|
80
|
+
last["count"] += 1
|
81
|
+
last["when"] = Time.now.to_i
|
82
|
+
else
|
83
|
+
backtrace = exception.backtrace.first if exception.backtrace
|
84
|
+
recent.shift if recent.size >= MAX_RECENT_EXCEPTIONS
|
85
|
+
recent.push({"count" => 1, "when" => Time.now.to_i, "type" => exception.class.name,
|
86
|
+
"message" => exception.message, "where" => backtrace})
|
87
|
+
end
|
88
|
+
rescue Exception => e
|
89
|
+
logger.exception("Failed to track exception '#{exception}'", e, :trace) rescue nil
|
90
|
+
end
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
end # Exceptions
|
95
|
+
|
96
|
+
end # RightSupport::Stats
|