enhanced_errors 2.0.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +95 -96
- 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 +370 -227
- metadata +23 -5
- data/examples/demo_spec.rb +0 -32
- data/examples/example_spec.rb +0 -47
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>
|
9
|
+
EnhancedErrors captures exception context using either a test-framework integration (RSpec/Minitest) or a global enhancement for runtime exceptions.
|
11
10
|
|
12
|
-
|
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
|
-
```
|
36
|
-
|
37
|
-
##### Output:
|
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,17 +32,13 @@ 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
|
|
@@ -84,26 +48,68 @@ RSpec.configure do |config|
|
|
84
48
|
end
|
85
49
|
|
86
50
|
config.after(:example) do |example|
|
87
|
-
example.
|
88
|
-
EnhancedErrors.override_exception_message(example.exception, example.metadata[:expect_binding])
|
51
|
+
EnhancedErrors.override_exception_message(example.exception, EnhancedErrors.stop_rspec_binding_capture)
|
89
52
|
end
|
90
53
|
end
|
91
54
|
```
|
92
55
|
|
93
|
-
|
56
|
+
<br>
|
94
57
|
|
95
58
|
|
96
|
-
##
|
59
|
+
## MiniTest Setup
|
97
60
|
|
98
|
-
|
99
|
-
|
61
|
+
```ruby
|
62
|
+
require 'enhanced_errors'
|
63
|
+
require 'enhanced/minitest_patch'
|
64
|
+
|
65
|
+
# Once the patch is loaded, it should just work!
|
66
|
+
|
67
|
+
```
|
100
68
|
|
101
|
-
|
102
|
-
|
69
|
+
<br>
|
70
|
+
|
71
|
+
### Enhanced Errors In Everyday Ruby Exceptions:
|
103
72
|
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
92
|
+
|
93
|
+
foo
|
94
|
+
```
|
95
|
+
|
96
|
+
|
97
|
+
### Enhancing .message
|
98
|
+
|
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
|
+
```
|
104
|
+
|
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.
|
107
|
+
|
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.
|
107
113
|
|
108
114
|
Ideally, use exception.captured_variables instead.
|
109
115
|
|
@@ -112,6 +118,13 @@ EnhancedErrors.enhance_exceptions!(override_messages: true)
|
|
112
118
|
```
|
113
119
|
|
114
120
|
|
121
|
+
#### Output:
|
122
|
+
|
123
|
+
<img src="./doc/images/enhanced-error.png" style="height: 215px; width: 429px;"></img>
|
124
|
+
<br>
|
125
|
+
|
126
|
+
|
127
|
+
|
115
128
|
## Features
|
116
129
|
|
117
130
|
- **Pure Ruby**: No external dependencies, C extensions, or C API calls.
|
@@ -124,26 +137,13 @@ EnhancedErrors.enhance_exceptions!(override_messages: true)
|
|
124
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.
|
125
138
|
- **Lightweight**: Minimal performance impact, as tracing is only active during exception raising.
|
126
139
|
|
127
|
-
EnhancedErrors
|
128
|
-
|
129
|
-
* **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.
|
130
|
-
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.
|
131
|
-
Ideally, without long instrument-re-run-fix loops. If your logging didn't capture the data, normally, you'd be stuck.
|
132
|
-
|
133
|
-
* **Debug** a complex application erroring deep in the stack when you can't tell where the error originates.
|
134
|
-
|
135
|
-
* **Reduce MTTR** Reduce mean time to resolution.
|
136
|
-
|
137
|
-
* **Faster CI -> Fix loop**. When a bug happens in CI, usually there's a step where you first reproduce it locally.
|
138
|
-
EnhancedErrors can help you skip that step.
|
140
|
+
EnhancedErrors use-cases:
|
139
141
|
|
140
|
-
*
|
141
|
-
|
142
|
-
*
|
143
|
-
|
144
|
-
*
|
145
|
-
|
146
|
-
* **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.
|
147
147
|
|
148
148
|
## Installation
|
149
149
|
|
@@ -180,14 +180,15 @@ EnhancedErrors.enhance_exceptions!(override_messages: true)
|
|
180
180
|
|
181
181
|
```
|
182
182
|
|
183
|
-
|
184
|
-
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.
|
185
185
|
|
186
186
|
If modifying your exception handlers is an option, it is better *not* to use
|
187
|
-
|
188
|
-
a string describing what was found
|
187
|
+
but instead just use the exception.captured_variables, which is
|
188
|
+
a string describing what was found.
|
189
189
|
|
190
|
-
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!
|
191
192
|
|
192
193
|
### Configuration Options
|
193
194
|
|
@@ -205,9 +206,6 @@ end
|
|
205
206
|
- `enabled`: Enables or disables the enhancement (default: `true`).
|
206
207
|
- `max_length`: Sets the maximum length of the captured_variables string (default: `2500`).
|
207
208
|
|
208
|
-
Currently, the first `raise` exception binding is presented.
|
209
|
-
This may be changed in the future to allow more binding data to be presented.
|
210
|
-
|
211
209
|
|
212
210
|
### Environment-Based Defaults
|
213
211
|
|
@@ -338,8 +336,8 @@ SystemStackError Psych::BadAlias
|
|
338
336
|
|
339
337
|
While this is close to "Things that don't descend from StandardError", it's not exactly that.
|
340
338
|
|
341
|
-
|
342
|
-
|
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.
|
343
341
|
|
344
342
|
### Capture Levels
|
345
343
|
|
@@ -398,7 +396,6 @@ The captured data is available in .captured_variables, to provide context for de
|
|
398
396
|
* There are benchmarks around Tracepoint in the benchmark folder. Targeted tracepoints
|
399
397
|
seem to be very cheap--as in, you can hit them ten thousand+ times a second
|
400
398
|
without heavy overhead.
|
401
|
-
*
|
402
399
|
|
403
400
|
## Awesome Print
|
404
401
|
|
@@ -419,28 +416,30 @@ Why not use:
|
|
419
416
|
|
420
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)?
|
421
418
|
|
422
|
-
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!
|
423
420
|
|
424
|
-
|
421
|
+
EnhancedErrors is intended as an every-day driver for __non-interactive__ variable inspection.
|
425
422
|
|
426
|
-
|
427
|
-
To make that work, it has to be able to safely be 'on'
|
428
|
-
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.
|
429
426
|
|
430
|
-
-
|
431
|
-
-
|
432
|
-
|
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.
|
433
431
|
- Has decent performance characteristics
|
434
432
|
- **Only** becomes active in exception raise/rescue scenarios
|
435
433
|
|
436
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).
|
437
|
-
However, the recommendation is not to use those in production as they use C API extensions. This doesn't.
|
438
|
-
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.
|
439
438
|
|
440
439
|
|
441
440
|
## Performance Considerations
|
442
441
|
|
443
|
-
- **
|
442
|
+
- **Small Overhead**: Since TracePoint is only activated during exception raising and rescuing, the performance impact is negligible during normal operation. (Benchmark included)
|
444
443
|
|
445
444
|
- **TBD**: Memory considerations. This does capture data when an exception happens. EnhancedErrors hides under the bed when it sees **NoMemoryError**.
|
446
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
|