enhanced_errors 0.1.7 → 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/binding.rb +2 -1
- data/lib/enhanced_errors.rb +236 -99
- data/lib/error_enhancements.rb +34 -32
- 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/binding.rb
CHANGED
data/lib/enhanced_errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# enhanced_errors.rb
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
require 'json'
|
3
5
|
|
@@ -5,19 +7,27 @@ require_relative 'colors'
|
|
5
7
|
require_relative 'error_enhancements'
|
6
8
|
require_relative 'binding'
|
7
9
|
|
8
|
-
#
|
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
|
9
28
|
|
10
|
-
|
11
|
-
|
12
|
-
NoMemoryError,
|
13
|
-
SignalException,
|
14
|
-
Interrupt,
|
15
|
-
ScriptError,
|
16
|
-
LoadError,
|
17
|
-
NotImplementedError,
|
18
|
-
SyntaxError,
|
19
|
-
SystemStackError
|
20
|
-
]
|
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
|
21
31
|
|
22
32
|
# The EnhancedErrors class provides mechanisms to enhance exception handling by capturing
|
23
33
|
# additional context such as binding information, variables, and method arguments when exceptions are raised.
|
@@ -29,10 +39,10 @@ class EnhancedErrors
|
|
29
39
|
# @return [Boolean]
|
30
40
|
attr_accessor :enabled
|
31
41
|
|
32
|
-
# The TracePoint
|
42
|
+
# The TracePoint objects used for tracing exceptions per thread.
|
33
43
|
#
|
34
|
-
# @return [TracePoint
|
35
|
-
attr_accessor :
|
44
|
+
# @return [Hash{Thread => TracePoint}]
|
45
|
+
attr_accessor :traces
|
36
46
|
|
37
47
|
# The configuration block provided during enhancement.
|
38
48
|
#
|
@@ -64,6 +74,11 @@ class EnhancedErrors
|
|
64
74
|
# @return [Set<Symbol>]
|
65
75
|
attr_accessor :skip_list
|
66
76
|
|
77
|
+
# Determines whether to capture :rescue events.
|
78
|
+
#
|
79
|
+
# @return [Boolean]
|
80
|
+
attr_accessor :capture_rescue
|
81
|
+
|
67
82
|
# Regular expression to identify gem paths.
|
68
83
|
#
|
69
84
|
# @return [Regexp]
|
@@ -137,6 +152,19 @@ class EnhancedErrors
|
|
137
152
|
@capture_let_variables
|
138
153
|
end
|
139
154
|
|
155
|
+
# Gets or sets whether to capture :rescue events.
|
156
|
+
#
|
157
|
+
# @param value [Boolean, nil] The desired state. If `nil`, returns the current value.
|
158
|
+
# @return [Boolean] Whether :rescue events are being captured.
|
159
|
+
def capture_rescue(value = nil)
|
160
|
+
if value.nil?
|
161
|
+
@capture_rescue = @capture_rescue.nil? ? false : @capture_rescue
|
162
|
+
else
|
163
|
+
@capture_rescue = value
|
164
|
+
end
|
165
|
+
@capture_rescue
|
166
|
+
end
|
167
|
+
|
140
168
|
# Retrieves the current skip list, initializing it with default values if not already set.
|
141
169
|
#
|
142
170
|
# @return [Set<Symbol>] The current skip list.
|
@@ -166,21 +194,20 @@ class EnhancedErrors
|
|
166
194
|
# @param options [Hash] Additional configuration options.
|
167
195
|
# @yield [void] A block for additional configuration.
|
168
196
|
# @return [void]
|
169
|
-
def enhance!(enabled: true, debug: false, capture_events:
|
170
|
-
capture_events = Array(capture_events)
|
197
|
+
def enhance!(enabled: true, debug: false, capture_events: nil, **options, &block)
|
171
198
|
@output_format = nil
|
172
199
|
@eligible_for_capture = nil
|
173
200
|
@original_global_variables = nil
|
174
201
|
if enabled == false
|
175
202
|
@original_global_variables = nil
|
176
203
|
@enabled = false
|
177
|
-
|
204
|
+
# Disable TracePoints in all threads
|
205
|
+
@traces.each_value { |trace| trace.disable } if @traces
|
178
206
|
else
|
179
207
|
@enabled = true
|
180
208
|
@debug = debug
|
181
209
|
@original_global_variables = global_variables
|
182
210
|
|
183
|
-
validate_and_set_capture_events(capture_events)
|
184
211
|
options.each do |key, value|
|
185
212
|
setter_method = "#{key}="
|
186
213
|
if respond_to?(setter_method)
|
@@ -195,7 +222,21 @@ class EnhancedErrors
|
|
195
222
|
@config_block = block_given? ? block : nil
|
196
223
|
instance_eval(&@config_block) if @config_block
|
197
224
|
|
198
|
-
|
225
|
+
validate_and_set_capture_events(capture_events)
|
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
|
199
240
|
end
|
200
241
|
end
|
201
242
|
|
@@ -259,7 +300,13 @@ class EnhancedErrors
|
|
259
300
|
def format(captured_bindings = [], output_format = get_default_format_for_environment)
|
260
301
|
result = binding_infos_array_to_string(captured_bindings, output_format)
|
261
302
|
if @on_format_hook
|
262
|
-
|
303
|
+
begin
|
304
|
+
result = @on_format_hook.call(result)
|
305
|
+
rescue => e
|
306
|
+
# Since the on_format_hook failed, do not display the data
|
307
|
+
result = ''
|
308
|
+
# Optionally, log the error safely if logging is guaranteed not to raise exceptions
|
309
|
+
end
|
263
310
|
else
|
264
311
|
result = default_on_format(result)
|
265
312
|
end
|
@@ -272,20 +319,10 @@ class EnhancedErrors
|
|
272
319
|
# @param format [Symbol] The format to use (:json, :plaintext, :terminal).
|
273
320
|
# @return [String] The formatted string representation of the binding information.
|
274
321
|
def binding_infos_array_to_string(captured_bindings, format = :terminal)
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
when :plaintext
|
280
|
-
Colors.enabled = false
|
281
|
-
captured_bindings.map { |binding_info| binding_info_string(binding_info) }.join("\n")
|
282
|
-
when :terminal
|
283
|
-
Colors.enabled = true
|
284
|
-
captured_bindings.map { |binding_info| binding_info_string(binding_info) }.join("\n")
|
285
|
-
else
|
286
|
-
Colors.enabled = false
|
287
|
-
captured_bindings.map { |binding_info| binding_info_string(binding_info) }.join("\n")
|
288
|
-
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")
|
289
326
|
end
|
290
327
|
|
291
328
|
# Determines the default output format based on the current environment.
|
@@ -345,7 +382,7 @@ class EnhancedErrors
|
|
345
382
|
# @return [Hash, nil] The validated binding information or `nil` if invalid.
|
346
383
|
def validate_binding_format(binding_info)
|
347
384
|
unless binding_info.keys.include?(:capture_event) && binding_info[:variables].is_a?(Hash)
|
348
|
-
|
385
|
+
# Log or handle the invalid format as needed
|
349
386
|
return nil
|
350
387
|
end
|
351
388
|
binding_info
|
@@ -356,10 +393,12 @@ class EnhancedErrors
|
|
356
393
|
# @param binding_info [Hash] The binding information to format.
|
357
394
|
# @return [String] The formatted string.
|
358
395
|
def binding_info_string(binding_info)
|
359
|
-
capture_event = binding_info[:capture_event].
|
360
|
-
|
396
|
+
capture_event = safe_to_s(binding_info[:capture_event]).capitalize
|
397
|
+
source = safe_to_s(binding_info[:source])
|
398
|
+
result = "#{Colors.red(capture_event)}: #{Colors.blue(source)}"
|
361
399
|
|
362
|
-
|
400
|
+
method_desc = method_and_args_desc(binding_info[:method_and_args])
|
401
|
+
result += method_desc
|
363
402
|
|
364
403
|
variables = binding_info[:variables] || {}
|
365
404
|
|
@@ -386,22 +425,21 @@ class EnhancedErrors
|
|
386
425
|
end
|
387
426
|
result + "\n"
|
388
427
|
rescue => e
|
389
|
-
#
|
390
|
-
# mess up the original exception handling.
|
391
|
-
puts "EnhancedErrors error in binding_info_string: #{e.message} #{e.backtrace}"
|
428
|
+
# Avoid raising exceptions during formatting
|
392
429
|
return ''
|
393
430
|
end
|
394
431
|
|
395
432
|
private
|
396
433
|
|
397
|
-
# 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.
|
398
435
|
#
|
436
|
+
# @param thread [Thread] The thread to start tracing in.
|
399
437
|
# @return [void]
|
400
|
-
def start_tracing
|
401
|
-
return if @
|
402
|
-
events = @capture_events ? @capture_events.to_a :
|
403
|
-
|
404
|
-
next if Thread.current[:enhanced_errors_processing] || ignored_exception?(tp.raised_exception)
|
438
|
+
def start_tracing(thread)
|
439
|
+
return if @traces[thread]&.enabled?
|
440
|
+
events = @capture_events ? @capture_events.to_a : default_capture_events
|
441
|
+
trace = TracePoint.new(*events) do |tp|
|
442
|
+
next if Thread.current[:enhanced_errors_processing] || Thread.current[:on_capture] || ignored_exception?(tp.raised_exception)
|
405
443
|
Thread.current[:enhanced_errors_processing] = true
|
406
444
|
exception = tp.raised_exception
|
407
445
|
capture_me = !exception.frozen? && EnhancedErrors.eligible_for_capture.call(exception)
|
@@ -411,7 +449,6 @@ class EnhancedErrors
|
|
411
449
|
next
|
412
450
|
end
|
413
451
|
|
414
|
-
exception = tp.raised_exception
|
415
452
|
binding_context = tp.binding
|
416
453
|
|
417
454
|
unless exception.instance_variable_defined?(:@binding_infos)
|
@@ -426,13 +463,13 @@ class EnhancedErrors
|
|
426
463
|
}
|
427
464
|
|
428
465
|
locals = binding_context.local_variables.map { |var|
|
429
|
-
[var, binding_context
|
466
|
+
[var, safe_local_variable_get(binding_context, var)]
|
430
467
|
}.to_h
|
431
468
|
|
432
469
|
instance_vars = binding_context.receiver.instance_variables
|
433
470
|
|
434
471
|
instances = instance_vars.map { |var|
|
435
|
-
[var, (binding_context.receiver
|
472
|
+
[var, safe_instance_variable_get(binding_context.receiver, var)]
|
436
473
|
}.to_h
|
437
474
|
|
438
475
|
# Extract 'let' variables from :@__memoized (RSpec specific)
|
@@ -453,8 +490,8 @@ class EnhancedErrors
|
|
453
490
|
}.to_h
|
454
491
|
end
|
455
492
|
|
456
|
-
capture_event = tp.event
|
457
|
-
location = "#{tp.path}:#{tp.lineno}"
|
493
|
+
capture_event = safe_to_s(tp.event) # 'raise' or 'rescue'
|
494
|
+
location = "#{safe_to_s(tp.path)}:#{safe_to_s(tp.lineno)}"
|
458
495
|
|
459
496
|
binding_info = {
|
460
497
|
source: location,
|
@@ -468,48 +505,86 @@ class EnhancedErrors
|
|
468
505
|
lets: lets,
|
469
506
|
globals: globals
|
470
507
|
},
|
471
|
-
exception: exception.class.name,
|
472
|
-
capture_event: capture_event
|
508
|
+
exception: safe_to_s(exception.class.name),
|
509
|
+
capture_event: capture_event
|
473
510
|
}
|
474
511
|
|
512
|
+
binding_info = default_on_capture(binding_info) # Apply default processing
|
513
|
+
|
475
514
|
if on_capture_hook
|
476
|
-
|
477
|
-
|
478
|
-
|
515
|
+
begin
|
516
|
+
Thread.current[:on_capture] = true
|
517
|
+
binding_info = on_capture_hook.call(binding_info)
|
518
|
+
rescue => e
|
519
|
+
# Since the on_capture_hook failed, do not capture this binding_info
|
520
|
+
binding_info = nil
|
521
|
+
# Optionally, log the error safely if logging is guaranteed not to raise exceptions
|
522
|
+
ensure
|
523
|
+
Thread.current[:on_capture] = false
|
524
|
+
end
|
479
525
|
end
|
480
526
|
|
481
|
-
binding_info
|
482
|
-
|
527
|
+
# Proceed only if binding_info is valid
|
483
528
|
if binding_info
|
484
|
-
|
485
|
-
|
486
|
-
|
529
|
+
binding_info = validate_binding_format(binding_info)
|
530
|
+
if binding_info
|
531
|
+
exception.instance_variable_get(:@binding_infos) << binding_info
|
532
|
+
end
|
487
533
|
end
|
488
534
|
rescue => e
|
489
|
-
|
535
|
+
# Avoid any code here that could raise exceptions
|
490
536
|
ensure
|
491
537
|
Thread.current[:enhanced_errors_processing] = false
|
492
538
|
end
|
493
539
|
|
494
|
-
@trace
|
540
|
+
@traces[thread] = trace
|
541
|
+
trace.enable
|
495
542
|
end
|
496
543
|
|
497
|
-
|
498
|
-
|
499
|
-
|
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
|
500
569
|
end
|
501
|
-
false
|
502
570
|
end
|
503
571
|
|
572
|
+
# Checks if the exception is in the ignored exceptions list.
|
573
|
+
#
|
574
|
+
# @param exception [Exception] The exception to check.
|
575
|
+
# @return [Boolean] `true` if the exception should be ignored, otherwise `false`.
|
576
|
+
def ignored_exception?(exception)
|
577
|
+
IGNORED_EXCEPTIONS.any? { |klass| exception.is_a?(klass) }
|
578
|
+
end
|
504
579
|
|
505
580
|
# Retrieves the current test name from RSpec, if available.
|
506
581
|
#
|
507
582
|
# @return [String, nil] The current test name or `nil` if not in a test context.
|
508
583
|
def test_name
|
509
584
|
if defined?(RSpec)
|
510
|
-
|
585
|
+
RSpec&.current_example&.full_description
|
511
586
|
end
|
512
|
-
rescue
|
587
|
+
rescue
|
513
588
|
nil
|
514
589
|
end
|
515
590
|
|
@@ -517,32 +592,47 @@ class EnhancedErrors
|
|
517
592
|
#
|
518
593
|
# @return [Set<Symbol>] The default set of capture types
|
519
594
|
def default_capture_events
|
520
|
-
|
521
|
-
|
522
|
-
|
595
|
+
events = [:raise]
|
596
|
+
if capture_rescue && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3.0')
|
597
|
+
events << :rescue
|
598
|
+
end
|
599
|
+
Set.new(events)
|
523
600
|
end
|
524
601
|
|
602
|
+
# Validates and sets the capture events for TracePoint.
|
603
|
+
#
|
604
|
+
# @param capture_events [Array<Symbol>, nil] The events to capture.
|
605
|
+
# @return [void]
|
525
606
|
def validate_and_set_capture_events(capture_events)
|
526
|
-
if capture_events.nil?
|
607
|
+
if capture_events.nil?
|
608
|
+
@capture_events = default_capture_events
|
609
|
+
return
|
610
|
+
end
|
611
|
+
|
612
|
+
unless valid_capture_events?(capture_events)
|
527
613
|
puts "EnhancedErrors: Invalid capture_events provided. Falling back to defaults."
|
528
|
-
capture_events = default_capture_events
|
614
|
+
@capture_events = default_capture_events
|
615
|
+
return
|
529
616
|
end
|
530
617
|
|
531
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.0')
|
618
|
+
if capture_events.include?(:rescue) && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.0')
|
532
619
|
puts "EnhancedErrors: Warning: :rescue capture_event is not supported in Ruby versions below 3.3.0 and will be ignored."
|
533
|
-
capture_events
|
620
|
+
capture_events = capture_events - [:rescue]
|
534
621
|
end
|
535
622
|
|
536
623
|
if capture_events.empty?
|
537
624
|
puts "No valid capture_events provided to EnhancedErrors.enhance! Falling back to defaults."
|
538
|
-
capture_events = default_capture_events
|
625
|
+
@capture_events = default_capture_events
|
626
|
+
return
|
539
627
|
end
|
540
628
|
|
541
|
-
@capture_events = capture_events.
|
629
|
+
@capture_events = capture_events.to_set
|
542
630
|
end
|
543
631
|
|
544
|
-
|
545
|
-
#
|
632
|
+
# Validates the capture events.
|
633
|
+
#
|
634
|
+
# @param capture_events [Array<Symbol>] The events to validate.
|
635
|
+
# @return [Boolean] `true` if valid, otherwise `false`.
|
546
636
|
def valid_capture_events?(capture_events)
|
547
637
|
return false unless capture_events.is_a?(Array) || capture_events.is_a?(Set)
|
548
638
|
valid_types = [:raise, :rescue].to_set
|
@@ -564,8 +654,8 @@ class EnhancedErrors
|
|
564
654
|
locals = bind.local_variables
|
565
655
|
|
566
656
|
parameters.map do |(type, name)|
|
567
|
-
value = locals.include?(name) ? bind
|
568
|
-
"#{name}=#{value
|
657
|
+
value = locals.include?(name) ? safe_local_variable_get(bind, name) : nil
|
658
|
+
"#{name}=#{safe_inspect(value)}"
|
569
659
|
rescue => e
|
570
660
|
"#{name}=#<Error getting argument: #{e.message}>"
|
571
661
|
end.join(", ")
|
@@ -581,9 +671,9 @@ class EnhancedErrors
|
|
581
671
|
# @return [String] The formatted object name.
|
582
672
|
def determine_object_name(tp, method_name)
|
583
673
|
if tp.self.is_a?(Class) && tp.self.singleton_class == tp.defined_class
|
584
|
-
"#{tp.self}.#{method_name}"
|
674
|
+
"#{safe_to_s(tp.self)}.#{method_name}"
|
585
675
|
else
|
586
|
-
"#{tp.self.class.name}##{method_name}"
|
676
|
+
"#{safe_to_s(tp.self.class.name)}##{method_name}"
|
587
677
|
end
|
588
678
|
rescue => e
|
589
679
|
"#<Error inspecting value: #{e.message}>"
|
@@ -597,7 +687,7 @@ class EnhancedErrors
|
|
597
687
|
begin
|
598
688
|
var.is_a?(Symbol) ? eval("#{var}") : nil
|
599
689
|
rescue => e
|
600
|
-
"#<Error getting value for #{var}>"
|
690
|
+
"#<Error getting value for #{var}>"
|
601
691
|
end
|
602
692
|
end
|
603
693
|
|
@@ -606,11 +696,14 @@ class EnhancedErrors
|
|
606
696
|
# @param method_info [Hash] Information about the method and its arguments.
|
607
697
|
# @return [String] The formatted description.
|
608
698
|
def method_and_args_desc(method_info)
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
699
|
+
object_name = safe_to_s(method_info[:object_name])
|
700
|
+
args = safe_to_s(method_info[:args])
|
701
|
+
return '' if object_name.empty? && args.empty?
|
702
|
+
arg_str = args.empty? ? '' : "(#{args})"
|
703
|
+
str = object_name + arg_str
|
613
704
|
"\n#{Colors.green('Method: ')}#{Colors.blue(str)}\n"
|
705
|
+
rescue => e
|
706
|
+
''
|
614
707
|
end
|
615
708
|
|
616
709
|
# Generates a formatted description for a set of variables.
|
@@ -621,26 +714,23 @@ class EnhancedErrors
|
|
621
714
|
vars_hash.map do |name, value|
|
622
715
|
" #{Colors.purple(name)}: #{format_variable(value)}\n"
|
623
716
|
end.join
|
717
|
+
rescue => e
|
718
|
+
''
|
624
719
|
end
|
625
720
|
|
626
721
|
# Formats a variable for display, using `awesome_print` if available and enabled.
|
627
722
|
#
|
628
723
|
# @param variable [Object] The variable to format.
|
629
724
|
# @return [String] The formatted variable.
|
630
|
-
|
631
725
|
def format_variable(variable)
|
632
726
|
if awesome_print_available? && Colors.enabled?
|
633
727
|
variable.ai
|
634
728
|
else
|
635
|
-
variable
|
729
|
+
safe_inspect(variable)
|
636
730
|
end
|
637
731
|
rescue => e
|
638
|
-
var_str =
|
639
|
-
|
640
|
-
rescue
|
641
|
-
"[Unprintable variable]"
|
642
|
-
end
|
643
|
-
return "#{var_str}: [Inspection Error]"
|
732
|
+
var_str = safe_to_s(variable)
|
733
|
+
"#{var_str}: [Inspection Error]"
|
644
734
|
end
|
645
735
|
|
646
736
|
# Checks if the `AwesomePrint` gem is available.
|
@@ -651,6 +741,53 @@ class EnhancedErrors
|
|
651
741
|
@awesome_print_available = defined?(AwesomePrint)
|
652
742
|
end
|
653
743
|
|
744
|
+
# Safely calls `inspect` on a variable.
|
745
|
+
#
|
746
|
+
# @param variable [Object] The variable to inspect.
|
747
|
+
# @return [String] The inspected variable or a safe fallback.
|
748
|
+
def safe_inspect(variable)
|
749
|
+
variable.inspect
|
750
|
+
rescue => e
|
751
|
+
safe_to_s(variable)
|
752
|
+
end
|
753
|
+
|
754
|
+
# Safely converts a variable to a string, handling exceptions.
|
755
|
+
#
|
756
|
+
# @param variable [Object] The variable to convert.
|
757
|
+
# @return [String] The string representation or a safe fallback.
|
758
|
+
def safe_to_s(variable)
|
759
|
+
str = variable.to_s
|
760
|
+
if str.length > 120
|
761
|
+
str[0...120] + '...'
|
762
|
+
else
|
763
|
+
str
|
764
|
+
end
|
765
|
+
rescue
|
766
|
+
"[Unprintable variable]"
|
767
|
+
end
|
768
|
+
|
769
|
+
# Safely retrieves a local variable from a binding.
|
770
|
+
#
|
771
|
+
# @param binding_context [Binding] The binding context.
|
772
|
+
# @param var_name [Symbol] The name of the local variable.
|
773
|
+
# @return [Object] The value of the local variable or a safe fallback.
|
774
|
+
def safe_local_variable_get(binding_context, var_name)
|
775
|
+
binding_context.local_variable_get(var_name)
|
776
|
+
rescue
|
777
|
+
"[Error accessing local variable #{var_name}]"
|
778
|
+
end
|
779
|
+
|
780
|
+
# Safely retrieves an instance variable from an object.
|
781
|
+
#
|
782
|
+
# @param obj [Object] The object.
|
783
|
+
# @param var_name [Symbol] The name of the instance variable.
|
784
|
+
# @return [Object] The value of the instance variable or a safe fallback.
|
785
|
+
def safe_instance_variable_get(obj, var_name)
|
786
|
+
obj.instance_variable_get(var_name)
|
787
|
+
rescue
|
788
|
+
"[Error accessing instance variable #{var_name}]"
|
789
|
+
end
|
790
|
+
|
654
791
|
# Default implementation for the on_format hook.
|
655
792
|
#
|
656
793
|
# @param string [String] The formatted exception message.
|
data/lib/error_enhancements.rb
CHANGED
@@ -1,57 +1,59 @@
|
|
1
|
+
# error_enhancements.rb
|
2
|
+
|
1
3
|
module ErrorEnhancements
|
2
4
|
def message
|
3
|
-
original_message =
|
4
|
-
|
5
|
+
original_message = begin
|
6
|
+
super()
|
7
|
+
rescue
|
8
|
+
''
|
9
|
+
end
|
10
|
+
vars_message = variables_message rescue ""
|
11
|
+
if original_message.include?(vars_message)
|
5
12
|
original_message
|
6
13
|
else
|
7
|
-
"#{original_message}\n#{
|
14
|
+
"#{original_message}\n#{vars_message}"
|
8
15
|
end
|
9
16
|
rescue => e
|
10
|
-
original_message
|
17
|
+
original_message || ''
|
11
18
|
end
|
12
19
|
|
13
20
|
def variables_message
|
14
21
|
@variables_message ||= begin
|
15
|
-
|
16
|
-
if defined?(@binding_infos) && @binding_infos && !@binding_infos.empty?
|
22
|
+
if @binding_infos&.any?
|
17
23
|
bindings_of_interest = select_binding_infos(@binding_infos)
|
24
|
+
EnhancedErrors.format(bindings_of_interest)
|
25
|
+
else
|
26
|
+
''
|
18
27
|
end
|
19
|
-
|
20
|
-
|
21
|
-
puts "Error in variables_message: #{e.message}"
|
22
|
-
""
|
28
|
+
rescue
|
29
|
+
''
|
23
30
|
end
|
24
31
|
end
|
25
32
|
|
26
33
|
private
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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.
|
33
40
|
|
34
|
-
|
41
|
+
bindings_of_interest = []
|
35
42
|
|
36
|
-
|
37
|
-
|
38
|
-
bindings_of_interest << info
|
39
|
-
break
|
43
|
+
first_app_raise = binding_infos.find do |info|
|
44
|
+
info[:capture_event] == 'raise' && !info[:library]
|
40
45
|
end
|
41
|
-
|
46
|
+
bindings_of_interest << first_app_raise if first_app_raise
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
if bindings_of_interest.empty? && binding_infos.first
|
49
|
+
bindings_of_interest << binding_infos.first
|
50
|
+
end
|
46
51
|
|
47
|
-
|
48
|
-
|
49
|
-
if info[:capture_event] == 'rescue'
|
50
|
-
bindings_of_interest << info
|
51
|
-
break
|
52
|
+
last_rescue = binding_infos.reverse.find do |info|
|
53
|
+
info[:capture_event] == 'rescue'
|
52
54
|
end
|
53
|
-
|
54
|
-
bindings_of_interest
|
55
|
-
end
|
55
|
+
bindings_of_interest << last_rescue if last_rescue
|
56
56
|
|
57
|
+
bindings_of_interest
|
58
|
+
end
|
57
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
|