enhanced_errors 2.0.6 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e6ed241407a54376e0c64f2c1adc087e3dae4980ac5b7e84c947d92ff787392
4
- data.tar.gz: 4f949a5145869e73e7f795a9c009800433956162a6d25695c2a9e8ba3a39da05
3
+ metadata.gz: 03cd9eda304184a474e2cccf59199027d622d2a32f5b90e93f75a0d1dbed4530
4
+ data.tar.gz: 48d4974d2b53c00155a6768d22f814ce072fd0d407051fab955d72f156fa81cc
5
5
  SHA512:
6
- metadata.gz: 02312f5287c4214c59b4b4a53e723d53d4dff511f24c335a7f7a84f049bb06638f77ea22f488cba5682fba66f581116b2bb1a4b3a79d8b6a5a290f5f822f1ea8
7
- data.tar.gz: 13d06fa670a3f34bb67624ec40bc9a87810536aace8c2867e64620092c13d8e48d25675241491d821491cb2f3a893c1d8faa2ede7ff2f22c45b50fb506fbbd74
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 pure Ruby gem that enhances exceptions by capturing variables and their values from the scope where the error was raised.
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
- When an exception is raised, EnhancedErrors captures the surrounding context. It works like this:
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
- #### 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
- ```
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 advantage of this approach is that it is only active for your spec runs.
75
- This approach is ideal for CI and local testing because it doesn't make
76
- any changes that should bleed through to production--it doesn't enhance
77
- exceptions except those that pass by during the RSpec run.
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.metadata[:expect_binding] = EnhancedErrors.stop_rspec_binding_capture
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
- ## TODO: Minitest
56
+ <br>
94
57
 
95
58
 
96
- ## Enhancing .message
59
+ ## MiniTest Setup
97
60
 
98
- EnhancedErrors can also append the captured variable description into the Exception's
99
- .message method output if the override_messages argument is true.
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
- This can be very convenient as it lets you capture and diagnose
102
- the context of totally unanticipated exceptions without modifying all your error handlers.
69
+ <br>
70
+
71
+ ### Enhanced Errors In Everyday Ruby Exceptions:
103
72
 
104
- The downside to this approach is that if you have expectations in your tests/specs
105
- around exception messages, those may break. Also, if you are doing something with the error messages,
106
- like storing them in a database, they could be *much* longer and that may pose an issue.
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 has a few big use-cases:
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
- * **Faster TDD**. In general, you can skip the add-instrumentation step and jump to the fix. Usually, you won't have to re-run to see an error.
141
-
142
- * **Heisenbugs** - bugs that disappear when you try to debug them. EnhancedErrors can help you capture the data that causes the bug before it disappears.
143
-
144
- * **Unknown Unknowns** - you can't pre-emptively log variables from failure cases you never imagined.
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
- The approach above activates the TracePoint to start capturing exceptions and their surrounding context.
184
- It also overrides the .message to have the variables.
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
- override_messages: true, but instead just use the exception.captured_variables, which is
188
- a string describing what was found, that is available regardless.
187
+ but instead just use the exception.captured_variables, which is
188
+ a string describing what was found.
189
189
 
190
- Note that a minimalistic approach is taken to generating the string--if no qualifying variables were present, you won't see any message!
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
- In Info mode, variables starting with @_ are also ignored.
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, I cannot stress this enough, a-m-a-z-i-n-g!!! I use them every day--kudos to their creators and maintainers!
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
- This is intended for different use-cases. In sum, the goal of this gem is an every-day driver for __non-interactive__ variable inspection.
421
+ EnhancedErrors is intended as an every-day driver for __non-interactive__ variable inspection.
425
422
 
426
- With EnhancedErrors is that I want extra details when I run into a problem I __didn't anticipate ahead of time__.
427
- To make that work, it has to be able to safely be 'on' all the time, and it has to gather the data in
428
- a way I naturally will see it without requiring extra preparation I obviously didn't know to do.
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
- - That won't interrupt CI, but also, that lets me know what happened without reproduction
431
- - That could, theoretically, also be fine in production (if data security, redaction, access, and encryption concerns were all addressed--Ok, big
432
- list, but another option is to selectively enable targeted capture)
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. This selectively uses
438
- Ruby's TracePoint binding capture very narrowly with no other C API or dependencies, and only to target Exceptions--not to allow universal calls to the prior binding. It doesn't work as a debugger, but that also means it can, with care, operate safely in a narrow scope--becoming active only when exceptions are raised.
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
- - **Minimal Overhead**: Since TracePoint is only activated during exception raising and rescuing, the performance impact is negligible during normal operation. (Benchmark included)
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "enhanced_errors"
3
- spec.version = "2.0.6"
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