Class: Amigo::RateLimitedErrorHandler
- Inherits:
-
Object
- Object
- Amigo::RateLimitedErrorHandler
- Defined in:
- lib/amigo/rate_limited_error_handler.rb
Instance Attribute Summary collapse
-
#sample_rate ⇒ Object
readonly
After the first error with a fingerprint is seen, how many future errors with the same fingerprint should we sample, until the fingerprint expires
ttl
after the first error? Use 1 to called the wrapped handler on all errors with the same fingerprint, and 0 to never call the wrapped handler on those errors until ttl has elapsed. -
#ttl ⇒ Object
readonly
How long does the fingerprint live for an error? For example, with a sample rate of 0 and a ttl of 2 minutes, the rate will be at most one of the same error every 2 minutes; the error is always sent when the key is set; then no events are sent until the key expires.
-
#wrapped ⇒ Object
readonly
The error handler that will be called to report the error.
Instance Method Summary collapse
- #call(ex, context) ⇒ Object
-
#fingerprint(ex) ⇒ Object
Fingerprint an exception.
-
#initialize(wrapped, sample_rate: 0.1, ttl: 120) ⇒ RateLimitedErrorHandler
constructor
A new instance of RateLimitedErrorHandler.
Constructor Details
#initialize(wrapped, sample_rate: 0.1, ttl: 120) ⇒ RateLimitedErrorHandler
Returns a new instance of RateLimitedErrorHandler.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/amigo/rate_limited_error_handler.rb', line 60 def initialize(wrapped, sample_rate: 0.1, ttl: 120) @mutex = Mutex.new @wrapped = wrapped @sample_rate = sample_rate @inverse_sample_rate = 1 - @sample_rate @ttl = ttl # Key is fingerprint, value is when to expire @store = {} # Add some fast-paths to handle 0 and 1 sample rates. @call = if sample_rate == 1 ->(*a) { @wrapped.call(*a) } elsif sample_rate.zero? self.method(:call_zero) else self.method(:call_sampled) end end |
Instance Attribute Details
#sample_rate ⇒ Object (readonly)
After the first error with a fingerprint is seen, how many future errors with the same fingerprint should we sample, until the fingerprint expires ttl
after the first error? Use 1 to called the wrapped handler on all errors with the same fingerprint, and 0 to never call the wrapped handler on those errors until ttl has elapsed.
46 47 48 |
# File 'lib/amigo/rate_limited_error_handler.rb', line 46 def sample_rate @sample_rate end |
#ttl ⇒ Object (readonly)
How long does the fingerprint live for an error? For example, with a sample rate of 0 and a ttl of 2 minutes, the rate will be at most one of the same error every 2 minutes; the error is always sent when the key is set; then no events are sent until the key expires.
Note that, unlike Redis TTL, the ttl is set only when the error is first seen (and then after it’s seen once the fingerprint expires); this means that, if an error is seen once a minute, with a TTL of 2 minutes, even with a sample rate of 0, an error is recorded every 2 minutes, rather than just once and never again.
58 59 60 |
# File 'lib/amigo/rate_limited_error_handler.rb', line 58 def ttl @ttl end |
#wrapped ⇒ Object (readonly)
The error handler that will be called to report the error.
39 40 41 |
# File 'lib/amigo/rate_limited_error_handler.rb', line 39 def wrapped @wrapped end |
Instance Method Details
#call(ex, context) ⇒ Object
78 79 80 |
# File 'lib/amigo/rate_limited_error_handler.rb', line 78 def call(ex, context) @call[ex, context] end |
#fingerprint(ex) ⇒ Object
Fingerprint an exception.
-
No two exceptions with the same class can be the same.
-
If an exception has no backtrace (it was manually constructed), the identity of the exception instance (object_id) is the fingerprint.
-
If an exception has a backtrace, the md5 of the backtrace is the fingerprint.
111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/amigo/rate_limited_error_handler.rb', line 111 def fingerprint(ex) md5 = Digest::MD5.new md5.update ex.class.to_s if ex.backtrace.nil? md5.update ex.object_id.to_s else ex.backtrace.each { |line| md5.update(line) } end md5.update(self.fingerprint(ex.cause)) if ex.cause return md5.hexdigest end |