escalate 0.2.0.pre.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile.lock +1 -2
- data/README.md +84 -6
- data/escalate.gemspec +0 -2
- data/lib/escalate.rb +42 -32
- data/lib/escalate/version.rb +1 -1
- metadata +5 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9844b8aaae39fe3b2e23417e7b39e1ec59ff8896e5a6f8be4e3737c3ccf8ce02
|
4
|
+
data.tar.gz: 7e03db14d0da2ec0e6458cd03a9da89cf949fce7d2c0a34bad65b69b7ede2409
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8eab0d9486440921db00178fa7560779976ed9e132377ca1dacea9960a9847f6eed01f964945f88719ef26583789b6bf1dad83f7dbcaef8c3625e83e8825c15
|
7
|
+
data.tar.gz: fa039e7c5c2c3e0c48e50840ba0831eb0f7a87d2902690b3740304f2b36b8aca91df8233c885c6e60ef9bd26fd0b4929604040082adefbdcbf0cbb14287a42ca
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [0.
|
7
|
+
## [0.3.0] - 2021-03-05
|
8
|
+
### Added
|
9
|
+
- Added `rescue_and_escalate`.
|
10
|
+
|
11
|
+
## [0.2.0] - 2021-03-02
|
8
12
|
### Added
|
9
13
|
- Added support for `on_escalate(log_first: false)`. The `escalate` gem will log first before
|
10
14
|
escalating if either of these is true: there are no `on_escalate` blocks registered, or
|
@@ -16,5 +20,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
16
20
|
- Added `escalate` method for escalating rescued exceptions with default behavior of logging to `STDERR`
|
17
21
|
- Added `Escalate.on_escalate` for registering escalation callbacks like `Honeybadger` or `Sentry`
|
18
22
|
|
23
|
+
[0.3.0]: https://github.com/Invoca/escalate/compare/v0.2.0...v0.3.0
|
19
24
|
[0.2.0]: https://github.com/Invoca/escalate/compare/v0.1.0...v0.2.0
|
20
25
|
[0.1.0]: https://github.com/Invoca/escalate/releases/tag/v0.1.0
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -34,6 +34,7 @@ module SomeGem
|
|
34
34
|
end
|
35
35
|
```
|
36
36
|
|
37
|
+
#### Using .escalate
|
37
38
|
This will expose the `Escalate#escalate` method within your gem to be used instead
|
38
39
|
of using `logger.error`.
|
39
40
|
|
@@ -46,20 +47,97 @@ module SomeGem
|
|
46
47
|
end
|
47
48
|
|
48
49
|
class SomeClass
|
49
|
-
def something_dangerous
|
50
|
-
# ...
|
51
|
-
rescue =>
|
52
|
-
SomeGem.escalate(
|
50
|
+
def something_dangerous(key)
|
51
|
+
# code here...
|
52
|
+
rescue => exception
|
53
|
+
SomeGem.escalate(exception,
|
54
|
+
"Exception raised in SomeGem::SomeClass#something_dangerous",
|
55
|
+
context: { key: key })
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
56
59
|
```
|
60
|
+
The following arguments are supported by `.escalate`:
|
61
|
+
|
62
|
+
| argument | default | type | description |
|
63
|
+
|------------|---|----|-----|
|
64
|
+
| `exception` | | `Exception` | The exception to escalate. |
|
65
|
+
| `location_message` | | `String` | A message providing information about where and why this exception is being escalated. |
|
66
|
+
| `context:` | `{}` | `Hash` | An optional hash of context. This will be logged with the exception. |
|
57
67
|
|
58
68
|
When `SomeGem.escalate` above is triggered, it will use the logger returned by `SomeGem.logger` or
|
59
69
|
default to a `STDERR` logger and do the following:
|
60
70
|
|
61
|
-
1. Log an error containing the exception and
|
62
|
-
2. Trigger any `
|
71
|
+
1. [optional] Log an error containing the exception, location_message, and context hash
|
72
|
+
2. Trigger any `on_escalate_callbacks` configured on the `Escalate` gem
|
73
|
+
|
74
|
+
Step (1) is optional. It will happen if either of these is true:
|
75
|
+
- by default if no `on_escalate_callbacks` have been registered; or
|
76
|
+
- if any of the `on_escalate_callbacks` was registered with `on_escalate(log_first: true)`.
|
77
|
+
|
78
|
+
#### Using .rescue_and_escalate
|
79
|
+
The above pattern of `rescue` with `escalate` is very common, so a single method is provided to do both.
|
80
|
+
This is equivalent to the code above:
|
81
|
+
```
|
82
|
+
class SomeClass
|
83
|
+
def something_dangerous(key)
|
84
|
+
SomeGem.rescue_and_escalate("Exception raised in SomeGem::SomeClass#something_dangerous",
|
85
|
+
context: { key: key }) do
|
86
|
+
# code here...
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
The following arguments are supported by `.rescue_and_escalate`:
|
92
|
+
|
93
|
+
| argument | default | type | description |
|
94
|
+
|-------------|---|----|-----|
|
95
|
+
| `location_message` | _required_ | `String` | A message providing information about where and why this exception is being escalated. |
|
96
|
+
| `context:` | `{}` | `Hash` | An optional hash of context. This will be logged with the exception. |
|
97
|
+
| `exceptions:` | `StandardError` | `Class` or `Array(Class)` | The `Class` or `Array(Class)` to rescue. `Class` must be `Exception` or a sub-class. |
|
98
|
+
| `pass_through_exceptions:` | `[SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException]` | `Class` or `Array(Class)` | The `Class` or `Array(Class)` to pass through without rescuing. `Class` must be `Exception`, or a sub-class. These take precedence over `exceptions:`.|
|
99
|
+
|
100
|
+
----------------------------
|
101
|
+
|
102
|
+
### Registering an Escalate Callback
|
103
|
+
|
104
|
+
If you are using an error reporting service, you can register an `on_escalate` callback to escalate exceptions.
|
105
|
+
You have the option to handle logging yourself, or to let `escalate` log first, before yielding to callbacks.
|
106
|
+
|
107
|
+
The following arguments are supported by `.on_escalate`:
|
108
|
+
|
109
|
+
| argument | default | type | description |
|
110
|
+
|-------------|---|----|-----|
|
111
|
+
| `name:` | `block.source_location` | `String` or `Array` | Globally unique name for this callback. |
|
112
|
+
| `log_first:` | `true` | `Boolean` | Whether `escalate` should log the error first. `false` means the block will take care of this. |
|
113
|
+
| `&block` | _required_ | `Proc` | The callback block to yield to from `escalate`. |
|
114
|
+
|
115
|
+
#### Leave the Logging to the Escalate Gem
|
116
|
+
Here is an example that uses the default `log_first: true` so that logging is handled by the `Escalate` gem first:
|
117
|
+
```
|
118
|
+
Escalate.on_escalate do |exception, location_message, **context|
|
119
|
+
# send exception, location_message, **context to the error reporting service here
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
#### Callback Uniqueness
|
124
|
+
Each callback may be named with the `name:` keyword argument.
|
125
|
+
If a callback with the same name has been registered before, it will be overwritten with the new one.
|
126
|
+
```
|
127
|
+
Escalate.on_escalate(name: 'abc gem') do |exception, location_message, **context|
|
128
|
+
# send exception, location_message, **context to the error reporting service here
|
129
|
+
end
|
130
|
+
```
|
131
|
+
If not given, the `name:` defaults to the `.source_location` property of the passed-in block.
|
132
|
+
|
133
|
+
#### Handle the Logging in the `on_escalate` Callback
|
134
|
+
Here is an example that handles logging itself with `log_first: false`:
|
135
|
+
```
|
136
|
+
Escalate.on_escalate(log_first: false) do |exception, location_message, **context|
|
137
|
+
# log here first
|
138
|
+
# send exception, location_message, **context to the error reporting service here
|
139
|
+
end
|
140
|
+
```
|
63
141
|
## Development
|
64
142
|
|
65
143
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/escalate.gemspec
CHANGED
data/lib/escalate.rb
CHANGED
@@ -9,7 +9,16 @@ require_relative "escalate/mixin"
|
|
9
9
|
module Escalate
|
10
10
|
class Error < StandardError; end
|
11
11
|
|
12
|
+
LOG_FIRST_INSTANCE_VARIABLE = :@_escalate_log_first
|
13
|
+
|
14
|
+
DEFAULT_RESCUE_EXCEPTIONS = [StandardError].freeze
|
15
|
+
DEFAULT_PASS_THROUGH_EXCEPTIONS = [SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException].freeze
|
16
|
+
|
17
|
+
@on_escalate_callbacks = {}
|
18
|
+
|
12
19
|
class << self
|
20
|
+
attr_reader :on_escalate_callbacks
|
21
|
+
|
13
22
|
# Logs and escalated an exception
|
14
23
|
#
|
15
24
|
# @param [Exception] exception
|
@@ -23,23 +32,24 @@ module Escalate
|
|
23
32
|
#
|
24
33
|
# @param [Hash] context
|
25
34
|
# Any additional context to be tied to the escalation
|
26
|
-
def escalate(exception, location_message, logger,
|
35
|
+
def escalate(exception, location_message, logger, context: {})
|
27
36
|
ensure_failsafe("Exception rescued while escalating #{exception.inspect}") do
|
28
|
-
if
|
37
|
+
if on_escalate_callbacks.none? || on_escalate_callbacks.values.any? { |block| block.instance_variable_get(LOG_FIRST_INSTANCE_VARIABLE) }
|
38
|
+
logger_allows_added_context?(logger) or context_string = " (#{context.inspect})"
|
29
39
|
error_message = <<~EOS
|
30
|
-
[Escalate] #{location_message}
|
40
|
+
[Escalate] #{location_message}#{context_string}
|
31
41
|
#{exception.class.name}: #{exception.message}
|
32
42
|
#{exception.backtrace.join("\n")}
|
33
43
|
EOS
|
34
44
|
|
35
|
-
if
|
36
|
-
logger.error(error_message, **context)
|
37
|
-
else
|
45
|
+
if context_string
|
38
46
|
logger.error(error_message)
|
47
|
+
else
|
48
|
+
logger.error(error_message, **context)
|
39
49
|
end
|
40
50
|
end
|
41
51
|
|
42
|
-
|
52
|
+
on_escalate_callbacks.values.each do |block|
|
43
53
|
ensure_failsafe("Exception rescued while escalating #{exception.inspect} to #{block.inspect}") do
|
44
54
|
block.call(exception, location_message, **context)
|
45
55
|
end
|
@@ -65,8 +75,21 @@ module Escalate
|
|
65
75
|
|
66
76
|
attr_accessor :escalate_logger_block
|
67
77
|
|
68
|
-
def escalate(exception, location_message,
|
69
|
-
Escalate.escalate(exception, location_message, escalate_logger,
|
78
|
+
def escalate(exception, location_message, context: {})
|
79
|
+
Escalate.escalate(exception, location_message, escalate_logger, context: context)
|
80
|
+
end
|
81
|
+
|
82
|
+
def rescue_and_escalate(location_message, context: {},
|
83
|
+
exceptions: DEFAULT_RESCUE_EXCEPTIONS,
|
84
|
+
pass_through_exceptions: DEFAULT_PASS_THROUGH_EXCEPTIONS,
|
85
|
+
&block)
|
86
|
+
|
87
|
+
yield
|
88
|
+
|
89
|
+
rescue *Array(pass_through_exceptions)
|
90
|
+
raise
|
91
|
+
rescue *Array(exceptions) => exception
|
92
|
+
escalate(exception, location_message, context: context)
|
70
93
|
end
|
71
94
|
|
72
95
|
private
|
@@ -81,22 +104,21 @@ module Escalate
|
|
81
104
|
end
|
82
105
|
end
|
83
106
|
|
84
|
-
# Registers an escalation callback to be executed when `escalate`
|
85
|
-
# is invoked.
|
107
|
+
# Registers an escalation callback to be executed when `escalate` is invoked.
|
86
108
|
#
|
87
109
|
# @param [boolean] log_first: true
|
88
110
|
# whether escalate should log first before escalating, or leave the logging to the escalate block
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
111
|
+
# @param [string | Array] name:
|
112
|
+
# unique name for this callback block
|
113
|
+
# any previously-registered block with the same name will be discarded
|
114
|
+
# if not provided, name defaults to `block.source_location`
|
115
|
+
def on_escalate(log_first: true, name: nil, &block)
|
116
|
+
block.instance_variable_set(LOG_FIRST_INSTANCE_VARIABLE, log_first)
|
117
|
+
on_escalate_callbacks[name || block.source_location] = block
|
95
118
|
end
|
96
119
|
|
97
|
-
def
|
98
|
-
|
99
|
-
on_escalate_no_log_first_blocks.clear
|
120
|
+
def clear_on_escalate_callbacks
|
121
|
+
on_escalate_callbacks.clear
|
100
122
|
end
|
101
123
|
|
102
124
|
private
|
@@ -111,17 +133,5 @@ module Escalate
|
|
111
133
|
defined?(ContextualLogger::LoggerMixin) &&
|
112
134
|
logger.is_a?(ContextualLogger::LoggerMixin)
|
113
135
|
end
|
114
|
-
|
115
|
-
def on_escalate_blocks
|
116
|
-
@on_escalate_blocks ||= Set.new
|
117
|
-
end
|
118
|
-
|
119
|
-
def on_escalate_no_log_first_blocks
|
120
|
-
@on_escalate_no_log_first_blocks ||= Set.new
|
121
|
-
end
|
122
|
-
|
123
|
-
def all_on_escalate_blocks
|
124
|
-
on_escalate_blocks + on_escalate_no_log_first_blocks
|
125
|
-
end
|
126
136
|
end
|
127
137
|
end
|
data/lib/escalate/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: escalate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Invoca Development
|
@@ -9,22 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
13
|
-
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: activesupport
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - ">="
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '0'
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - ">="
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: '0'
|
12
|
+
date: 2021-03-05 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
28
14
|
description: A simple and lightweight gem to escalate rescued exceptions.
|
29
15
|
email:
|
30
16
|
- development@invoca.com
|
@@ -67,9 +53,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
53
|
version: 2.4.0
|
68
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
55
|
requirements:
|
70
|
-
- - "
|
56
|
+
- - ">="
|
71
57
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
58
|
+
version: '0'
|
73
59
|
requirements: []
|
74
60
|
rubygems_version: 3.1.2
|
75
61
|
signing_key:
|