enhanced_errors 0.1.8 → 1.0.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 +3 -1
- data/enhanced_errors.gemspec +1 -1
- data/lib/enhanced_errors.rb +85 -39
- data/lib/error_enhancements.rb +23 -28
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42e0ffde5da4be326f8b1d721652eed031f7bfb20a07d31a256d69c588a383b7
|
4
|
+
data.tar.gz: 5ef8303d0e6dd089253643b2abbf4aeef9a195831c946b2c92396390685e0a5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb553e2a7f127a2d7efbf77c25867a9706d500ffb70e6e48060cd42390cdd903f140d60a899ed61f0d3340697b3ec4458a704545aa4380dad928d64a76d9b158
|
7
|
+
data.tar.gz: 467e3f82cb5a170081b43f417dfa0b53c12df77a6a2614d13377d47227a00df0623d515a92707b52fd5cc8461f3862c16ae83c3aa264beff2ee63f88072299ce
|
data/README.md
CHANGED
@@ -384,7 +384,9 @@ Ruby's TracePoint binding capture very narrowly with no other C API or dependenc
|
|
384
384
|
|
385
385
|
- **TBD**: Memory considerations. This does capture data when an exception happens. EnhancedErrors hides under the bed when it sees **NoMemoryError**.
|
386
386
|
|
387
|
-
- **Goal: Production Safety**: The gem is designed to, once vetted, be safe for production use, giving you valuable insights without compromising performance.
|
387
|
+
- **Goal: Production Safety**: The gem is designed to, once vetted, be safe for production use, giving you valuable insights without compromising performance.
|
388
|
+
I suggest letting it get well-vetted before making the leap and testing it for both performance and memory under load internally, as well.
|
389
|
+
I would not enable it in production *yet*.
|
388
390
|
|
389
391
|
## Contributing
|
390
392
|
|
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 = "0.
|
3
|
+
spec.version = "1.0.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."
|
data/lib/enhanced_errors.rb
CHANGED
@@ -7,18 +7,27 @@ require_relative 'colors'
|
|
7
7
|
require_relative 'error_enhancements'
|
8
8
|
require_relative 'binding'
|
9
9
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
# Exception class names to ignore. Using strings to avoid uninitialized constant errors.
|
11
|
+
IGNORED_EXCEPTION_NAMES = %w[SystemExit NoMemoryError SignalException Interrupt
|
12
|
+
ScriptError LoadError NotImplementedError SyntaxError
|
13
|
+
SystemStackError Psych::BadAlias]
|
14
|
+
|
15
|
+
# Helper method to safely resolve class names to constants
|
16
|
+
def resolve_exception_class(name)
|
17
|
+
names = name.split('::')
|
18
|
+
names.inject(Object) do |mod, name_part|
|
19
|
+
if mod.const_defined?(name_part, false)
|
20
|
+
mod.const_get(name_part)
|
21
|
+
else
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue NameError
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Attempt to resolve the exception classes, ignoring any that are not defined
|
30
|
+
IGNORED_EXCEPTIONS = IGNORED_EXCEPTION_NAMES.map { |name| resolve_exception_class(name) }.compact
|
22
31
|
|
23
32
|
# The EnhancedErrors class provides mechanisms to enhance exception handling by capturing
|
24
33
|
# additional context such as binding information, variables, and method arguments when exceptions are raised.
|
@@ -30,10 +39,10 @@ class EnhancedErrors
|
|
30
39
|
# @return [Boolean]
|
31
40
|
attr_accessor :enabled
|
32
41
|
|
33
|
-
# The TracePoint
|
42
|
+
# The TracePoint objects used for tracing exceptions per thread.
|
34
43
|
#
|
35
|
-
# @return [TracePoint
|
36
|
-
attr_accessor :
|
44
|
+
# @return [Hash{Thread => TracePoint}]
|
45
|
+
attr_accessor :traces
|
37
46
|
|
38
47
|
# The configuration block provided during enhancement.
|
39
48
|
#
|
@@ -192,7 +201,8 @@ class EnhancedErrors
|
|
192
201
|
if enabled == false
|
193
202
|
@original_global_variables = nil
|
194
203
|
@enabled = false
|
195
|
-
|
204
|
+
# Disable TracePoints in all threads
|
205
|
+
@traces.each_value { |trace| trace.disable } if @traces
|
196
206
|
else
|
197
207
|
@enabled = true
|
198
208
|
@debug = debug
|
@@ -213,7 +223,20 @@ class EnhancedErrors
|
|
213
223
|
instance_eval(&@config_block) if @config_block
|
214
224
|
|
215
225
|
validate_and_set_capture_events(capture_events)
|
216
|
-
|
226
|
+
|
227
|
+
# Initialize @traces hash to keep track of TracePoints per thread
|
228
|
+
@traces ||= {}
|
229
|
+
# Set up TracePoint in the main thread
|
230
|
+
start_tracing(Thread.current)
|
231
|
+
|
232
|
+
# Set up TracePoint in all existing threads
|
233
|
+
Thread.list.each do |thread|
|
234
|
+
next if thread == Thread.current
|
235
|
+
start_tracing(thread)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Hook into Thread creation to set up TracePoint in new threads
|
239
|
+
override_thread_new
|
217
240
|
end
|
218
241
|
end
|
219
242
|
|
@@ -296,20 +319,10 @@ class EnhancedErrors
|
|
296
319
|
# @param format [Symbol] The format to use (:json, :plaintext, :terminal).
|
297
320
|
# @return [String] The formatted string representation of the binding information.
|
298
321
|
def binding_infos_array_to_string(captured_bindings, format = :terminal)
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
when :plaintext
|
304
|
-
Colors.enabled = false
|
305
|
-
captured_bindings.map { |binding_info| binding_info_string(binding_info) }.join("\n")
|
306
|
-
when :terminal
|
307
|
-
Colors.enabled = true
|
308
|
-
captured_bindings.map { |binding_info| binding_info_string(binding_info) }.join("\n")
|
309
|
-
else
|
310
|
-
Colors.enabled = false
|
311
|
-
captured_bindings.map { |binding_info| binding_info_string(binding_info) }.join("\n")
|
312
|
-
end
|
322
|
+
Colors.enabled = format == :terminal
|
323
|
+
formatted_bindings = captured_bindings.map { |binding_info| binding_info_string(binding_info) }
|
324
|
+
|
325
|
+
format == :json ? JSON.pretty_generate(captured_bindings) : formatted_bindings.join("\n")
|
313
326
|
end
|
314
327
|
|
315
328
|
# Determines the default output format based on the current environment.
|
@@ -418,14 +431,15 @@ class EnhancedErrors
|
|
418
431
|
|
419
432
|
private
|
420
433
|
|
421
|
-
# Starts the TracePoint for capturing exceptions based on configured events.
|
434
|
+
# Starts the TracePoint for capturing exceptions based on configured events in a specific thread.
|
422
435
|
#
|
436
|
+
# @param thread [Thread] The thread to start tracing in.
|
423
437
|
# @return [void]
|
424
|
-
def start_tracing
|
425
|
-
return if @
|
438
|
+
def start_tracing(thread)
|
439
|
+
return if @traces[thread]&.enabled?
|
426
440
|
events = @capture_events ? @capture_events.to_a : default_capture_events
|
427
|
-
|
428
|
-
next if Thread.current[:enhanced_errors_processing] || ignored_exception?(tp.raised_exception)
|
441
|
+
trace = TracePoint.new(*events) do |tp|
|
442
|
+
next if Thread.current[:enhanced_errors_processing] || Thread.current[:on_capture] || ignored_exception?(tp.raised_exception)
|
429
443
|
Thread.current[:enhanced_errors_processing] = true
|
430
444
|
exception = tp.raised_exception
|
431
445
|
capture_me = !exception.frozen? && EnhancedErrors.eligible_for_capture.call(exception)
|
@@ -499,11 +513,14 @@ class EnhancedErrors
|
|
499
513
|
|
500
514
|
if on_capture_hook
|
501
515
|
begin
|
516
|
+
Thread.current[:on_capture] = true
|
502
517
|
binding_info = on_capture_hook.call(binding_info)
|
503
518
|
rescue => e
|
504
519
|
# Since the on_capture_hook failed, do not capture this binding_info
|
505
520
|
binding_info = nil
|
506
521
|
# Optionally, log the error safely if logging is guaranteed not to raise exceptions
|
522
|
+
ensure
|
523
|
+
Thread.current[:on_capture] = false
|
507
524
|
end
|
508
525
|
end
|
509
526
|
|
@@ -520,7 +537,36 @@ class EnhancedErrors
|
|
520
537
|
Thread.current[:enhanced_errors_processing] = false
|
521
538
|
end
|
522
539
|
|
523
|
-
@trace
|
540
|
+
@traces[thread] = trace
|
541
|
+
trace.enable
|
542
|
+
end
|
543
|
+
|
544
|
+
# Overrides Thread.new and Thread.start to ensure TracePoint is enabled in new threads.
|
545
|
+
#
|
546
|
+
# @return [void]
|
547
|
+
def override_thread_new
|
548
|
+
return if @thread_overridden
|
549
|
+
@thread_overridden = true
|
550
|
+
|
551
|
+
class << Thread
|
552
|
+
alias_method :original_new, :new
|
553
|
+
|
554
|
+
def new(*args, &block)
|
555
|
+
original_new(*args) do |*block_args|
|
556
|
+
EnhancedErrors.send(:start_tracing, Thread.current)
|
557
|
+
block.call(*block_args)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
alias_method :original_start, :start
|
562
|
+
|
563
|
+
def start(*args, &block)
|
564
|
+
original_start(*args) do |*block_args|
|
565
|
+
EnhancedErrors.send(:start_tracing, Thread.current)
|
566
|
+
block.call(*block_args)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
524
570
|
end
|
525
571
|
|
526
572
|
# Checks if the exception is in the ignored exceptions list.
|
@@ -711,8 +757,8 @@ class EnhancedErrors
|
|
711
757
|
# @return [String] The string representation or a safe fallback.
|
712
758
|
def safe_to_s(variable)
|
713
759
|
str = variable.to_s
|
714
|
-
if str.length >
|
715
|
-
str[0...
|
760
|
+
if str.length > 120
|
761
|
+
str[0...120] + '...'
|
716
762
|
else
|
717
763
|
str
|
718
764
|
end
|
data/lib/error_enhancements.rb
CHANGED
@@ -19,46 +19,41 @@ module ErrorEnhancements
|
|
19
19
|
|
20
20
|
def variables_message
|
21
21
|
@variables_message ||= begin
|
22
|
-
|
23
|
-
if defined?(@binding_infos) && @binding_infos && !@binding_infos.empty?
|
22
|
+
if @binding_infos&.any?
|
24
23
|
bindings_of_interest = select_binding_infos(@binding_infos)
|
24
|
+
EnhancedErrors.format(bindings_of_interest)
|
25
|
+
else
|
26
|
+
''
|
25
27
|
end
|
26
|
-
|
27
|
-
|
28
|
-
# Avoid using puts; consider logging instead
|
29
|
-
# Avoid raising exceptions in rescue blocks
|
30
|
-
""
|
28
|
+
rescue
|
29
|
+
''
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
34
33
|
private
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def select_binding_infos(binding_infos)
|
36
|
+
# Preference:
|
37
|
+
# 1. First 'raise' binding that isn't from a library (gem).
|
38
|
+
# 2. If none, the first binding.
|
39
|
+
# 3. The last 'rescue' binding if available.
|
41
40
|
|
42
|
-
|
41
|
+
bindings_of_interest = []
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
bindings_of_interest << info
|
47
|
-
break
|
43
|
+
first_app_raise = binding_infos.find do |info|
|
44
|
+
info[:capture_event] == 'raise' && !info[:library]
|
48
45
|
end
|
49
|
-
|
46
|
+
bindings_of_interest << first_app_raise if first_app_raise
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
if bindings_of_interest.empty? && binding_infos.first
|
49
|
+
bindings_of_interest << binding_infos.first
|
50
|
+
end
|
54
51
|
|
55
|
-
|
56
|
-
|
57
|
-
if info[:capture_event] == 'rescue'
|
58
|
-
bindings_of_interest << info
|
59
|
-
break
|
52
|
+
last_rescue = binding_infos.reverse.find do |info|
|
53
|
+
info[:capture_event] == 'rescue'
|
60
54
|
end
|
55
|
+
bindings_of_interest << last_rescue if last_rescue
|
56
|
+
|
57
|
+
bindings_of_interest
|
61
58
|
end
|
62
|
-
bindings_of_interest
|
63
|
-
end
|
64
59
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: enhanced_errors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Beland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_print
|