enhanced_errors 2.0.5 → 2.1.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/README.md +93 -108
- data/doc/images/enhance.png +0 -0
- data/doc/images/enhanced-error.png +0 -0
- data/doc/images/enhanced-spec.png +0 -0
- data/enhanced_errors.gemspec +4 -1
- data/examples/{division_by_zero_example.rb → demo_exception_enhancement.rb} +3 -1
- data/examples/demo_minitest.rb +22 -0
- data/examples/demo_rspec.rb +41 -0
- data/lib/enhanced/minitest_patch.rb +17 -0
- data/lib/enhanced_errors.rb +373 -219
- metadata +23 -6
- data/examples/demo_spec.rb +0 -32
- data/examples/example_spec.rb +0 -47
- data/lib/enhanced/integrations/rspec_error_failure_message.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03cd9eda304184a474e2cccf59199027d622d2a32f5b90e93f75a0d1dbed4530
|
4
|
+
data.tar.gz: 48d4974d2b53c00155a6768d22f814ce072fd0d407051fab955d72f156fa81cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03c169ed6955512bd4ce743d9343199f4a0f833a3d663be47f366fe8be0a46b8b749fc7f6e326f07a008e16f26cfbed790f561829068e61eecb6510ae95a3b20
|
7
|
+
data.tar.gz: 38ef1f2cb5daa334fa067cdbffd873046e7936688556d1393a5e10a7e7992eb677e47b1fd6c91a97f2d511b76a5e8acd3bbcd4118505a5b4180c944c4627bf00
|
data/README.md
CHANGED
@@ -2,45 +2,13 @@
|
|
2
2
|
|
3
3
|
## Overview
|
4
4
|
|
5
|
-
**EnhancedErrors** is a
|
5
|
+
**EnhancedErrors** is a lightweight Ruby gem that enhances exceptions by capturing variables and their values from the scope where the exception was raised.
|
6
6
|
|
7
7
|
**EnhancedErrors** leverages Ruby's built-in [TracePoint](https://ruby-doc.org/core-3.1.0/TracePoint.html) feature to provide detailed context for exceptions, making debugging easier without significant performance overhead.
|
8
8
|
|
9
|
-
|
10
|
-
<br>
|
11
|
-
|
12
|
-
#### Enhanced Exception In Code:
|
13
|
-
|
14
|
-
```ruby
|
15
|
-
|
16
|
-
require 'enhanced_errors'
|
17
|
-
require 'awesome_print' # Optional, for better output
|
18
|
-
|
19
|
-
# Enable capturing of variables at exception at raise-time. The .captured_variables method
|
20
|
-
# is added to all Exceptions and gets populated with in-scope variables and values on `raise`
|
21
|
-
|
22
|
-
EnhancedErrors.enhance_exceptions!
|
23
|
-
|
24
|
-
def foo
|
25
|
-
begin
|
26
|
-
myvar = 0
|
27
|
-
@myinstance = 10
|
28
|
-
foo = @myinstance / myvar
|
29
|
-
rescue => e
|
30
|
-
puts e.captured_variables
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
foo
|
35
|
-
```
|
9
|
+
EnhancedErrors captures exception context using either a test-framework integration (RSpec/Minitest) or a global enhancement for runtime exceptions.
|
36
10
|
|
37
|
-
|
38
|
-
|
39
|
-
<img src="./doc/images/enhanced-error.png" style="height: 215px; width: 429px;"></img>
|
40
|
-
<br>
|
41
|
-
|
42
|
-
|
43
|
-
#### Enhanced Exception In Specs:
|
11
|
+
### Enhanced Errors In RSpec:
|
44
12
|
|
45
13
|
```ruby
|
46
14
|
describe 'sees through' do
|
@@ -64,60 +32,84 @@ end
|
|
64
32
|
|
65
33
|
<img src="./doc/images/enhanced-spec.png" style="height: 369px; width: 712px;"></img>
|
66
34
|
|
67
|
-
# RSpec Setup
|
68
35
|
|
69
|
-
The simplest way to get started with EnhancedErrors is to use it for RSpec
|
70
|
-
exception capturing. To get variable output into RSpec, the approach below
|
71
|
-
enables capturing, but also gives nice output by formatting the failure message
|
72
|
-
with the variable capture.
|
73
36
|
|
74
|
-
The
|
75
|
-
|
76
|
-
|
77
|
-
|
37
|
+
The RSpec test-time only approach constrained only to test-time.
|
38
|
+
|
39
|
+
### RSpec Setup
|
40
|
+
|
41
|
+
Use EnhancedErrors with RSpec for test-specific exception capturing, ideal for CI and local testing without impacting production.
|
78
42
|
|
79
43
|
```ruby
|
80
44
|
|
81
45
|
RSpec.configure do |config|
|
82
|
-
|
83
|
-
# add these config changes to your RSpec config to get variable messages
|
84
|
-
config.before(:suite) do
|
85
|
-
RSpec::Core::Example.prepend(Enhanced::Integrations::RSpecErrorFailureMessage)
|
86
|
-
end
|
87
|
-
|
88
46
|
config.before(:example) do |_example|
|
89
47
|
EnhancedErrors.start_rspec_binding_capture
|
90
48
|
end
|
91
49
|
|
92
50
|
config.after(:example) do |example|
|
93
|
-
example.
|
51
|
+
EnhancedErrors.override_exception_message(example.exception, EnhancedErrors.stop_rspec_binding_capture)
|
94
52
|
end
|
95
|
-
|
96
53
|
end
|
54
|
+
```
|
55
|
+
|
56
|
+
<br>
|
57
|
+
|
58
|
+
|
59
|
+
## MiniTest Setup
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'enhanced_errors'
|
63
|
+
require 'enhanced/minitest_patch'
|
64
|
+
|
65
|
+
# Once the patch is loaded, it should just work!
|
97
66
|
|
98
67
|
```
|
99
68
|
|
100
|
-
|
69
|
+
<br>
|
70
|
+
|
71
|
+
### Enhanced Errors In Everyday Ruby Exceptions:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
|
75
|
+
require 'enhanced_errors'
|
76
|
+
require 'awesome_print' # Optional, for better output
|
77
|
+
|
78
|
+
# Enable capturing of variables at exception at raise-time. The .captured_variables method
|
79
|
+
# is added to all Exceptions and gets populated with in-scope variables and values on `raise`
|
80
|
+
|
81
|
+
EnhancedErrors.enhance_exceptions!
|
82
|
+
|
83
|
+
def foo
|
84
|
+
begin
|
85
|
+
myvar = 0
|
86
|
+
@myinstance = 10
|
87
|
+
foo = @myinstance / myvar
|
88
|
+
rescue => e
|
89
|
+
puts e.captured_variables
|
90
|
+
end
|
91
|
+
end
|
101
92
|
|
102
|
-
|
93
|
+
foo
|
94
|
+
```
|
103
95
|
|
104
|
-
If anyone wants to look into an integration implementation like RSpec, it would
|
105
|
-
be welcomed. With a more targeted approach like the RSpec one, exceptions could be captured and
|
106
|
-
modified only during test time, like the RSpec approach. This would be advantageous as
|
107
|
-
it wouldn't modify the exception message itself, but still makes the variable output available in
|
108
|
-
test messages.
|
109
96
|
|
110
|
-
|
97
|
+
### Enhancing .message
|
111
98
|
|
112
|
-
EnhancedErrors can
|
113
|
-
.message method
|
99
|
+
EnhancedErrors can append the captured variable description onto every Exception's
|
100
|
+
.message method with
|
101
|
+
```ruby
|
102
|
+
EnhancedErrors.enhance_exceptions(override_messages: true)
|
103
|
+
```
|
114
104
|
|
115
|
-
This
|
116
|
-
|
105
|
+
This captures unanticipated exceptions without modifying all your error handlers.
|
106
|
+
This approach can be used to get detailed logs when problems happen in something like a cron-job.
|
117
107
|
|
118
|
-
The
|
119
|
-
|
120
|
-
|
108
|
+
The tradeoff of this approach is that if you have expectations in your tests/specs around
|
109
|
+
exception messages, those may break. Also, if you are doing something like storing the errors
|
110
|
+
in a database, they could be *much* longer and that may pose an issue on field lengths.
|
111
|
+
Or if you are writing your logs to Datadog, New Relic, Splunk, etc, log messages for
|
112
|
+
errors will be longer, and you should consider what data/PII you are sharing.
|
121
113
|
|
122
114
|
Ideally, use exception.captured_variables instead.
|
123
115
|
|
@@ -126,6 +118,13 @@ EnhancedErrors.enhance_exceptions!(override_messages: true)
|
|
126
118
|
```
|
127
119
|
|
128
120
|
|
121
|
+
#### Output:
|
122
|
+
|
123
|
+
<img src="./doc/images/enhanced-error.png" style="height: 215px; width: 429px;"></img>
|
124
|
+
<br>
|
125
|
+
|
126
|
+
|
127
|
+
|
129
128
|
## Features
|
130
129
|
|
131
130
|
- **Pure Ruby**: No external dependencies, C extensions, or C API calls.
|
@@ -138,26 +137,13 @@ EnhancedErrors.enhance_exceptions!(override_messages: true)
|
|
138
137
|
- **No dependencies**: EnhancedErrors does not ___require___ any dependencies--it uses [awesome_print](https://github.com/awesome-print/awesome_print) for nicer output if it is installed and available.
|
139
138
|
- **Lightweight**: Minimal performance impact, as tracing is only active during exception raising.
|
140
139
|
|
141
|
-
EnhancedErrors
|
142
|
-
|
143
|
-
* **Catch Data-driven bugs**. For example, if, while processing a 10 gig file, you get an error, you can't just re-run the code with a debugger.
|
144
|
-
You also can't just print out all the data, because it's too big. You want to know what the data was the cause of the error.
|
145
|
-
Ideally, without long instrument-re-run-fix loops. If your logging didn't capture the data, normally, you'd be stuck.
|
146
|
-
|
147
|
-
* **Debug** a complex application erroring deep in the stack when you can't tell where the error originates.
|
148
|
-
|
149
|
-
* **Reduce MTTR** Reduce mean time to resolution.
|
150
|
-
|
151
|
-
* **Faster CI -> Fix loop**. When a bug happens in CI, usually there's a step where you first reproduce it locally.
|
152
|
-
EnhancedErrors can help you skip that step.
|
140
|
+
EnhancedErrors use-cases:
|
153
141
|
|
154
|
-
*
|
155
|
-
|
156
|
-
*
|
157
|
-
|
158
|
-
*
|
159
|
-
|
160
|
-
* **Cron jobs** and **daemons** - when it fails for unknown reasons at 4am, check the log and fix--it probably has what you need. Note that
|
142
|
+
* Catch data-driven bugs without needing re-runs or extensive logging.
|
143
|
+
* Debug deep-stack errors and reduce mean time to resolution (MTTR).
|
144
|
+
* Handle CI failures faster by skipping reproduction steps.
|
145
|
+
* Address elusive "Heisenbugs" by capturing error context preemptively.
|
146
|
+
* Debug cron jobs and daemons with rich, failure-specific logs.
|
161
147
|
|
162
148
|
## Installation
|
163
149
|
|
@@ -194,14 +180,15 @@ EnhancedErrors.enhance_exceptions!(override_messages: true)
|
|
194
180
|
|
195
181
|
```
|
196
182
|
|
197
|
-
|
198
|
-
It also overrides the .message to
|
183
|
+
This captures all exceptions and their surrounding context.
|
184
|
+
It also overrides the .message to display the variables.
|
199
185
|
|
200
186
|
If modifying your exception handlers is an option, it is better *not* to use
|
201
|
-
|
202
|
-
a string describing what was found
|
187
|
+
but instead just use the exception.captured_variables, which is
|
188
|
+
a string describing what was found.
|
203
189
|
|
204
|
-
Note
|
190
|
+
Note: a minimalistic approach is taken to generating the capture string.
|
191
|
+
If no qualifying variables were present, you won't see any message additions!
|
205
192
|
|
206
193
|
### Configuration Options
|
207
194
|
|
@@ -219,9 +206,6 @@ end
|
|
219
206
|
- `enabled`: Enables or disables the enhancement (default: `true`).
|
220
207
|
- `max_length`: Sets the maximum length of the captured_variables string (default: `2500`).
|
221
208
|
|
222
|
-
Currently, the first `raise` exception binding is presented.
|
223
|
-
This may be changed in the future to allow more binding data to be presented.
|
224
|
-
|
225
209
|
|
226
210
|
### Environment-Based Defaults
|
227
211
|
|
@@ -352,8 +336,8 @@ SystemStackError Psych::BadAlias
|
|
352
336
|
|
353
337
|
While this is close to "Things that don't descend from StandardError", it's not exactly that.
|
354
338
|
|
355
|
-
|
356
|
-
|
339
|
+
By default, many noisy instance variables are ignored in the default skip list.
|
340
|
+
If you want to see every instance variable, you'll need to clear out the skip list.
|
357
341
|
|
358
342
|
### Capture Levels
|
359
343
|
|
@@ -412,7 +396,6 @@ The captured data is available in .captured_variables, to provide context for de
|
|
412
396
|
* There are benchmarks around Tracepoint in the benchmark folder. Targeted tracepoints
|
413
397
|
seem to be very cheap--as in, you can hit them ten thousand+ times a second
|
414
398
|
without heavy overhead.
|
415
|
-
*
|
416
399
|
|
417
400
|
## Awesome Print
|
418
401
|
|
@@ -433,28 +416,30 @@ Why not use:
|
|
433
416
|
|
434
417
|
[binding_of_caller](https://github.com/banister/binding_of_caller) or [Pry](https://github.com/pry/pry) or [better_errors](https://github.com/BetterErrors/better_errors)?
|
435
418
|
|
436
|
-
First off, these gems are
|
419
|
+
First off, these gems are a-m-a-z-i-n-g!!! I use them every day--kudos to their creators and maintainers!
|
437
420
|
|
438
|
-
|
421
|
+
EnhancedErrors is intended as an every-day driver for __non-interactive__ variable inspection.
|
439
422
|
|
440
|
-
|
441
|
-
To make that work, it has to be able to safely be 'on'
|
442
|
-
a way I naturally will
|
423
|
+
I want extra details when I run into a problem I __didn't anticipate ahead of time__.
|
424
|
+
To make that work, it has to be able to safely be 'on' ahead of time, and gather data in
|
425
|
+
a way I naturally will retain without requiring extra preparation I obviously didn't know to do.
|
443
426
|
|
444
|
-
-
|
445
|
-
-
|
446
|
-
|
427
|
+
- EnhancedErrors won't interrupt CI, but it lets me know what happened _without_ reproduction steps
|
428
|
+
- EnhancedErrors could, theoretically, also be fine in production (if data security, redaction,
|
429
|
+
PII, access, and encryption concerns were all addressed.
|
430
|
+
Big list, but another option is to selectively enable targeted capture.
|
447
431
|
- Has decent performance characteristics
|
448
432
|
- **Only** becomes active in exception raise/rescue scenarios
|
449
433
|
|
450
434
|
This gem could have been implemented using binding_of_caller, or the gem it depends on, [debug_inspector](https://rubygems.org/gems/debug_inspector/versions/1.1.0?locale=en).
|
451
|
-
However, the recommendation is not to use those in production as they use C API extensions. This doesn't.
|
452
|
-
Ruby's TracePoint binding capture very narrowly with no other C API or dependencies, and only to target
|
435
|
+
However, the recommendation is not to use those in production as they use C API extensions. This doesn't.
|
436
|
+
EnhancedErrors selectively uses Ruby's TracePoint binding capture very narrowly with no other C API or dependencies, and only to target
|
437
|
+
Exceptions. It operates in a narrow scope--becoming active only when exceptions are raised.
|
453
438
|
|
454
439
|
|
455
440
|
## Performance Considerations
|
456
441
|
|
457
|
-
- **
|
442
|
+
- **Small Overhead**: Since TracePoint is only activated during exception raising and rescuing, the performance impact is negligible during normal operation. (Benchmark included)
|
458
443
|
|
459
444
|
- **TBD**: Memory considerations. This does capture data when an exception happens. EnhancedErrors hides under the bed when it sees **NoMemoryError**.
|
460
445
|
|
Binary file
|
Binary file
|
Binary file
|
data/enhanced_errors.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "enhanced_errors"
|
3
|
-
spec.version = "2.0
|
3
|
+
spec.version = "2.1.0"
|
4
4
|
spec.authors = ["Eric Beland"]
|
5
5
|
|
6
6
|
spec.summary = "Automatically enhance your errors with messages containing variable values from the moment they were raised."
|
@@ -21,7 +21,10 @@ Gem::Specification.new do |spec|
|
|
21
21
|
end
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
|
+
# For development on this gem
|
24
25
|
spec.add_development_dependency "awesome_print", "~> 1.0"
|
25
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
26
27
|
spec.add_development_dependency 'yard', '~> 0.9'
|
28
|
+
spec.add_development_dependency 'minitest'
|
29
|
+
|
27
30
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require './lib/enhanced_errors'
|
2
2
|
require 'awesome_print' # Optional, for better output
|
3
3
|
|
4
|
+
# Demonstrates enhancing exceptions in normal day to day Ruby usage
|
5
|
+
# from this folder: ruby demo_exception_enhancement.rb
|
6
|
+
|
4
7
|
EnhancedErrors.enhance_exceptions!(override_messages: true, capture_events: [:raise, :rescue])
|
5
8
|
|
6
9
|
def foo
|
@@ -24,7 +27,6 @@ rescue Exception => e
|
|
24
27
|
puts e.message
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
30
|
puts "\n--- Example with raise ---\n\n\n"
|
29
31
|
|
30
32
|
foo
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'enhanced_errors'
|
3
|
+
require 'enhanced/minitest_patch'
|
4
|
+
|
5
|
+
# You must install minitest and load it first to run this demo.
|
6
|
+
# EnhancedErrors does NOT ship with minitest as a dependency.
|
7
|
+
|
8
|
+
class MagicBallTest < Minitest::Test
|
9
|
+
def setup
|
10
|
+
@foo = 'bar'
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_boo_capture
|
14
|
+
bee = 'fee'
|
15
|
+
assert false
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_i_raise
|
19
|
+
zoo = 'zee'
|
20
|
+
raise "Crud"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# spec/enhanced_errors_spec.rb
|
2
|
+
|
3
|
+
# INSTRUCTIONS: Install rspec
|
4
|
+
# gem install rspec
|
5
|
+
# rspec examples/example_spec.rb
|
6
|
+
|
7
|
+
require 'rspec'
|
8
|
+
require_relative '../lib/enhanced_errors'
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
# -- Add to your RSPec config in your spec_helper.
|
13
|
+
config.before(:example) do |_example|
|
14
|
+
EnhancedErrors.start_rspec_binding_capture
|
15
|
+
end
|
16
|
+
|
17
|
+
config.after(:example) do |example|
|
18
|
+
EnhancedErrors.override_exception_message(example.exception, EnhancedErrors.stop_rspec_binding_capture)
|
19
|
+
end
|
20
|
+
# -- End EnhancedErrors config
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
RSpec.describe 'Neo' do
|
26
|
+
describe 'sees through' do
|
27
|
+
let(:the_matrix) { 'code rains, dramatically' }
|
28
|
+
|
29
|
+
before(:each) do
|
30
|
+
@spoon = 'there is no spoon'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'the matrix' do
|
34
|
+
#activate memoized item
|
35
|
+
the_matrix
|
36
|
+
stop = 'bullets'
|
37
|
+
raise 'No!'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Minitest
|
2
|
+
class << self
|
3
|
+
alias_method :original_run_one_method, :run_one_method
|
4
|
+
|
5
|
+
def run_one_method(klass, method_name)
|
6
|
+
EnhancedErrors.start_minitest_binding_capture
|
7
|
+
result = original_run_one_method(klass, method_name)
|
8
|
+
ensure
|
9
|
+
begin
|
10
|
+
binding_infos = EnhancedErrors.stop_minitest_binding_capture
|
11
|
+
EnhancedErrors.override_exception_message(result.failures.last, binding_infos) if result.failures.any?
|
12
|
+
rescue => e
|
13
|
+
puts "Ignored error during error enhancement: #{e}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|