escalate 0.2.0.pre.1 → 0.3.0
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.
- 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:
|