enhanced_errors 0.1.7 → 0.1.8
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/enhanced_errors.gemspec +1 -1
- data/lib/binding.rb +2 -1
- data/lib/enhanced_errors.rb +155 -64
- data/lib/error_enhancements.rb +14 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edc10f9326ea041bd8b367e57eb1037767847df8359408529445160ae76876b2
|
4
|
+
data.tar.gz: d5a4aee4cd8f3cca01a57fb10c25b120f5d3e7e1f5bf0255ef107255823704ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f3519f1c11d31cccfe4110deda8a2dcbc26b9cbd7ab0c9a0cd6b9ac65d32c57cc24b35f2a4a0f075888d20be3a69d397a0f631e617741705d1634834096a084
|
7
|
+
data.tar.gz: 0a9d12908d54b1173123cea3f66de3bbfa96863d92fad8990c281459de0bd66b2e26e405910c31cb6336f726f1bba41e99aac2793238835ce144576acdb3004e
|
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.1.
|
3
|
+
spec.version = "0.1.8"
|
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
|
|
@@ -6,7 +8,6 @@ require_relative 'error_enhancements'
|
|
6
8
|
require_relative 'binding'
|
7
9
|
|
8
10
|
# While we could just catch StandardError, we would miss a number of things.
|
9
|
-
|
10
11
|
IGNORED_EXCEPTIONS = [
|
11
12
|
SystemExit,
|
12
13
|
NoMemoryError,
|
@@ -64,6 +65,11 @@ class EnhancedErrors
|
|
64
65
|
# @return [Set<Symbol>]
|
65
66
|
attr_accessor :skip_list
|
66
67
|
|
68
|
+
# Determines whether to capture :rescue events.
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
attr_accessor :capture_rescue
|
72
|
+
|
67
73
|
# Regular expression to identify gem paths.
|
68
74
|
#
|
69
75
|
# @return [Regexp]
|
@@ -137,6 +143,19 @@ class EnhancedErrors
|
|
137
143
|
@capture_let_variables
|
138
144
|
end
|
139
145
|
|
146
|
+
# Gets or sets whether to capture :rescue events.
|
147
|
+
#
|
148
|
+
# @param value [Boolean, nil] The desired state. If `nil`, returns the current value.
|
149
|
+
# @return [Boolean] Whether :rescue events are being captured.
|
150
|
+
def capture_rescue(value = nil)
|
151
|
+
if value.nil?
|
152
|
+
@capture_rescue = @capture_rescue.nil? ? false : @capture_rescue
|
153
|
+
else
|
154
|
+
@capture_rescue = value
|
155
|
+
end
|
156
|
+
@capture_rescue
|
157
|
+
end
|
158
|
+
|
140
159
|
# Retrieves the current skip list, initializing it with default values if not already set.
|
141
160
|
#
|
142
161
|
# @return [Set<Symbol>] The current skip list.
|
@@ -166,8 +185,7 @@ class EnhancedErrors
|
|
166
185
|
# @param options [Hash] Additional configuration options.
|
167
186
|
# @yield [void] A block for additional configuration.
|
168
187
|
# @return [void]
|
169
|
-
def enhance!(enabled: true, debug: false, capture_events:
|
170
|
-
capture_events = Array(capture_events)
|
188
|
+
def enhance!(enabled: true, debug: false, capture_events: nil, **options, &block)
|
171
189
|
@output_format = nil
|
172
190
|
@eligible_for_capture = nil
|
173
191
|
@original_global_variables = nil
|
@@ -180,7 +198,6 @@ class EnhancedErrors
|
|
180
198
|
@debug = debug
|
181
199
|
@original_global_variables = global_variables
|
182
200
|
|
183
|
-
validate_and_set_capture_events(capture_events)
|
184
201
|
options.each do |key, value|
|
185
202
|
setter_method = "#{key}="
|
186
203
|
if respond_to?(setter_method)
|
@@ -195,6 +212,7 @@ class EnhancedErrors
|
|
195
212
|
@config_block = block_given? ? block : nil
|
196
213
|
instance_eval(&@config_block) if @config_block
|
197
214
|
|
215
|
+
validate_and_set_capture_events(capture_events)
|
198
216
|
start_tracing
|
199
217
|
end
|
200
218
|
end
|
@@ -259,7 +277,13 @@ class EnhancedErrors
|
|
259
277
|
def format(captured_bindings = [], output_format = get_default_format_for_environment)
|
260
278
|
result = binding_infos_array_to_string(captured_bindings, output_format)
|
261
279
|
if @on_format_hook
|
262
|
-
|
280
|
+
begin
|
281
|
+
result = @on_format_hook.call(result)
|
282
|
+
rescue => e
|
283
|
+
# Since the on_format_hook failed, do not display the data
|
284
|
+
result = ''
|
285
|
+
# Optionally, log the error safely if logging is guaranteed not to raise exceptions
|
286
|
+
end
|
263
287
|
else
|
264
288
|
result = default_on_format(result)
|
265
289
|
end
|
@@ -345,7 +369,7 @@ class EnhancedErrors
|
|
345
369
|
# @return [Hash, nil] The validated binding information or `nil` if invalid.
|
346
370
|
def validate_binding_format(binding_info)
|
347
371
|
unless binding_info.keys.include?(:capture_event) && binding_info[:variables].is_a?(Hash)
|
348
|
-
|
372
|
+
# Log or handle the invalid format as needed
|
349
373
|
return nil
|
350
374
|
end
|
351
375
|
binding_info
|
@@ -356,10 +380,12 @@ class EnhancedErrors
|
|
356
380
|
# @param binding_info [Hash] The binding information to format.
|
357
381
|
# @return [String] The formatted string.
|
358
382
|
def binding_info_string(binding_info)
|
359
|
-
capture_event = binding_info[:capture_event].
|
360
|
-
|
383
|
+
capture_event = safe_to_s(binding_info[:capture_event]).capitalize
|
384
|
+
source = safe_to_s(binding_info[:source])
|
385
|
+
result = "#{Colors.red(capture_event)}: #{Colors.blue(source)}"
|
361
386
|
|
362
|
-
|
387
|
+
method_desc = method_and_args_desc(binding_info[:method_and_args])
|
388
|
+
result += method_desc
|
363
389
|
|
364
390
|
variables = binding_info[:variables] || {}
|
365
391
|
|
@@ -386,9 +412,7 @@ class EnhancedErrors
|
|
386
412
|
end
|
387
413
|
result + "\n"
|
388
414
|
rescue => e
|
389
|
-
#
|
390
|
-
# mess up the original exception handling.
|
391
|
-
puts "EnhancedErrors error in binding_info_string: #{e.message} #{e.backtrace}"
|
415
|
+
# Avoid raising exceptions during formatting
|
392
416
|
return ''
|
393
417
|
end
|
394
418
|
|
@@ -399,7 +423,7 @@ class EnhancedErrors
|
|
399
423
|
# @return [void]
|
400
424
|
def start_tracing
|
401
425
|
return if @trace && @trace.enabled?
|
402
|
-
events = @capture_events ? @capture_events.to_a :
|
426
|
+
events = @capture_events ? @capture_events.to_a : default_capture_events
|
403
427
|
@trace = TracePoint.new(*events) do |tp|
|
404
428
|
next if Thread.current[:enhanced_errors_processing] || ignored_exception?(tp.raised_exception)
|
405
429
|
Thread.current[:enhanced_errors_processing] = true
|
@@ -411,7 +435,6 @@ class EnhancedErrors
|
|
411
435
|
next
|
412
436
|
end
|
413
437
|
|
414
|
-
exception = tp.raised_exception
|
415
438
|
binding_context = tp.binding
|
416
439
|
|
417
440
|
unless exception.instance_variable_defined?(:@binding_infos)
|
@@ -426,13 +449,13 @@ class EnhancedErrors
|
|
426
449
|
}
|
427
450
|
|
428
451
|
locals = binding_context.local_variables.map { |var|
|
429
|
-
[var, binding_context
|
452
|
+
[var, safe_local_variable_get(binding_context, var)]
|
430
453
|
}.to_h
|
431
454
|
|
432
455
|
instance_vars = binding_context.receiver.instance_variables
|
433
456
|
|
434
457
|
instances = instance_vars.map { |var|
|
435
|
-
[var, (binding_context.receiver
|
458
|
+
[var, safe_instance_variable_get(binding_context.receiver, var)]
|
436
459
|
}.to_h
|
437
460
|
|
438
461
|
# Extract 'let' variables from :@__memoized (RSpec specific)
|
@@ -453,8 +476,8 @@ class EnhancedErrors
|
|
453
476
|
}.to_h
|
454
477
|
end
|
455
478
|
|
456
|
-
capture_event = tp.event
|
457
|
-
location = "#{tp.path}:#{tp.lineno}"
|
479
|
+
capture_event = safe_to_s(tp.event) # 'raise' or 'rescue'
|
480
|
+
location = "#{safe_to_s(tp.path)}:#{safe_to_s(tp.lineno)}"
|
458
481
|
|
459
482
|
binding_info = {
|
460
483
|
source: location,
|
@@ -468,25 +491,31 @@ class EnhancedErrors
|
|
468
491
|
lets: lets,
|
469
492
|
globals: globals
|
470
493
|
},
|
471
|
-
exception: exception.class.name,
|
472
|
-
capture_event: capture_event
|
494
|
+
exception: safe_to_s(exception.class.name),
|
495
|
+
capture_event: capture_event
|
473
496
|
}
|
474
497
|
|
498
|
+
binding_info = default_on_capture(binding_info) # Apply default processing
|
499
|
+
|
475
500
|
if on_capture_hook
|
476
|
-
|
477
|
-
|
478
|
-
|
501
|
+
begin
|
502
|
+
binding_info = on_capture_hook.call(binding_info)
|
503
|
+
rescue => e
|
504
|
+
# Since the on_capture_hook failed, do not capture this binding_info
|
505
|
+
binding_info = nil
|
506
|
+
# Optionally, log the error safely if logging is guaranteed not to raise exceptions
|
507
|
+
end
|
479
508
|
end
|
480
509
|
|
481
|
-
binding_info
|
482
|
-
|
510
|
+
# Proceed only if binding_info is valid
|
483
511
|
if binding_info
|
484
|
-
|
485
|
-
|
486
|
-
|
512
|
+
binding_info = validate_binding_format(binding_info)
|
513
|
+
if binding_info
|
514
|
+
exception.instance_variable_get(:@binding_infos) << binding_info
|
515
|
+
end
|
487
516
|
end
|
488
517
|
rescue => e
|
489
|
-
|
518
|
+
# Avoid any code here that could raise exceptions
|
490
519
|
ensure
|
491
520
|
Thread.current[:enhanced_errors_processing] = false
|
492
521
|
end
|
@@ -494,22 +523,22 @@ class EnhancedErrors
|
|
494
523
|
@trace.enable
|
495
524
|
end
|
496
525
|
|
526
|
+
# Checks if the exception is in the ignored exceptions list.
|
527
|
+
#
|
528
|
+
# @param exception [Exception] The exception to check.
|
529
|
+
# @return [Boolean] `true` if the exception should be ignored, otherwise `false`.
|
497
530
|
def ignored_exception?(exception)
|
498
|
-
IGNORED_EXCEPTIONS.
|
499
|
-
return true if exception.is_a?(klass)
|
500
|
-
end
|
501
|
-
false
|
531
|
+
IGNORED_EXCEPTIONS.any? { |klass| exception.is_a?(klass) }
|
502
532
|
end
|
503
533
|
|
504
|
-
|
505
534
|
# Retrieves the current test name from RSpec, if available.
|
506
535
|
#
|
507
536
|
# @return [String, nil] The current test name or `nil` if not in a test context.
|
508
537
|
def test_name
|
509
538
|
if defined?(RSpec)
|
510
|
-
|
539
|
+
RSpec&.current_example&.full_description
|
511
540
|
end
|
512
|
-
rescue
|
541
|
+
rescue
|
513
542
|
nil
|
514
543
|
end
|
515
544
|
|
@@ -517,32 +546,47 @@ class EnhancedErrors
|
|
517
546
|
#
|
518
547
|
# @return [Set<Symbol>] The default set of capture types
|
519
548
|
def default_capture_events
|
520
|
-
|
521
|
-
|
522
|
-
|
549
|
+
events = [:raise]
|
550
|
+
if capture_rescue && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.3.0')
|
551
|
+
events << :rescue
|
552
|
+
end
|
553
|
+
Set.new(events)
|
523
554
|
end
|
524
555
|
|
556
|
+
# Validates and sets the capture events for TracePoint.
|
557
|
+
#
|
558
|
+
# @param capture_events [Array<Symbol>, nil] The events to capture.
|
559
|
+
# @return [void]
|
525
560
|
def validate_and_set_capture_events(capture_events)
|
526
|
-
if capture_events.nil?
|
561
|
+
if capture_events.nil?
|
562
|
+
@capture_events = default_capture_events
|
563
|
+
return
|
564
|
+
end
|
565
|
+
|
566
|
+
unless valid_capture_events?(capture_events)
|
527
567
|
puts "EnhancedErrors: Invalid capture_events provided. Falling back to defaults."
|
528
|
-
capture_events = default_capture_events
|
568
|
+
@capture_events = default_capture_events
|
569
|
+
return
|
529
570
|
end
|
530
571
|
|
531
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.0')
|
572
|
+
if capture_events.include?(:rescue) && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.0')
|
532
573
|
puts "EnhancedErrors: Warning: :rescue capture_event is not supported in Ruby versions below 3.3.0 and will be ignored."
|
533
|
-
capture_events
|
574
|
+
capture_events = capture_events - [:rescue]
|
534
575
|
end
|
535
576
|
|
536
577
|
if capture_events.empty?
|
537
578
|
puts "No valid capture_events provided to EnhancedErrors.enhance! Falling back to defaults."
|
538
|
-
capture_events = default_capture_events
|
579
|
+
@capture_events = default_capture_events
|
580
|
+
return
|
539
581
|
end
|
540
582
|
|
541
|
-
@capture_events = capture_events.
|
583
|
+
@capture_events = capture_events.to_set
|
542
584
|
end
|
543
585
|
|
544
|
-
|
545
|
-
#
|
586
|
+
# Validates the capture events.
|
587
|
+
#
|
588
|
+
# @param capture_events [Array<Symbol>] The events to validate.
|
589
|
+
# @return [Boolean] `true` if valid, otherwise `false`.
|
546
590
|
def valid_capture_events?(capture_events)
|
547
591
|
return false unless capture_events.is_a?(Array) || capture_events.is_a?(Set)
|
548
592
|
valid_types = [:raise, :rescue].to_set
|
@@ -564,8 +608,8 @@ class EnhancedErrors
|
|
564
608
|
locals = bind.local_variables
|
565
609
|
|
566
610
|
parameters.map do |(type, name)|
|
567
|
-
value = locals.include?(name) ? bind
|
568
|
-
"#{name}=#{value
|
611
|
+
value = locals.include?(name) ? safe_local_variable_get(bind, name) : nil
|
612
|
+
"#{name}=#{safe_inspect(value)}"
|
569
613
|
rescue => e
|
570
614
|
"#{name}=#<Error getting argument: #{e.message}>"
|
571
615
|
end.join(", ")
|
@@ -581,9 +625,9 @@ class EnhancedErrors
|
|
581
625
|
# @return [String] The formatted object name.
|
582
626
|
def determine_object_name(tp, method_name)
|
583
627
|
if tp.self.is_a?(Class) && tp.self.singleton_class == tp.defined_class
|
584
|
-
"#{tp.self}.#{method_name}"
|
628
|
+
"#{safe_to_s(tp.self)}.#{method_name}"
|
585
629
|
else
|
586
|
-
"#{tp.self.class.name}##{method_name}"
|
630
|
+
"#{safe_to_s(tp.self.class.name)}##{method_name}"
|
587
631
|
end
|
588
632
|
rescue => e
|
589
633
|
"#<Error inspecting value: #{e.message}>"
|
@@ -597,7 +641,7 @@ class EnhancedErrors
|
|
597
641
|
begin
|
598
642
|
var.is_a?(Symbol) ? eval("#{var}") : nil
|
599
643
|
rescue => e
|
600
|
-
"#<Error getting value for #{var}>"
|
644
|
+
"#<Error getting value for #{var}>"
|
601
645
|
end
|
602
646
|
end
|
603
647
|
|
@@ -606,11 +650,14 @@ class EnhancedErrors
|
|
606
650
|
# @param method_info [Hash] Information about the method and its arguments.
|
607
651
|
# @return [String] The formatted description.
|
608
652
|
def method_and_args_desc(method_info)
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
653
|
+
object_name = safe_to_s(method_info[:object_name])
|
654
|
+
args = safe_to_s(method_info[:args])
|
655
|
+
return '' if object_name.empty? && args.empty?
|
656
|
+
arg_str = args.empty? ? '' : "(#{args})"
|
657
|
+
str = object_name + arg_str
|
613
658
|
"\n#{Colors.green('Method: ')}#{Colors.blue(str)}\n"
|
659
|
+
rescue => e
|
660
|
+
''
|
614
661
|
end
|
615
662
|
|
616
663
|
# Generates a formatted description for a set of variables.
|
@@ -621,26 +668,23 @@ class EnhancedErrors
|
|
621
668
|
vars_hash.map do |name, value|
|
622
669
|
" #{Colors.purple(name)}: #{format_variable(value)}\n"
|
623
670
|
end.join
|
671
|
+
rescue => e
|
672
|
+
''
|
624
673
|
end
|
625
674
|
|
626
675
|
# Formats a variable for display, using `awesome_print` if available and enabled.
|
627
676
|
#
|
628
677
|
# @param variable [Object] The variable to format.
|
629
678
|
# @return [String] The formatted variable.
|
630
|
-
|
631
679
|
def format_variable(variable)
|
632
680
|
if awesome_print_available? && Colors.enabled?
|
633
681
|
variable.ai
|
634
682
|
else
|
635
|
-
variable
|
683
|
+
safe_inspect(variable)
|
636
684
|
end
|
637
685
|
rescue => e
|
638
|
-
var_str =
|
639
|
-
|
640
|
-
rescue
|
641
|
-
"[Unprintable variable]"
|
642
|
-
end
|
643
|
-
return "#{var_str}: [Inspection Error]"
|
686
|
+
var_str = safe_to_s(variable)
|
687
|
+
"#{var_str}: [Inspection Error]"
|
644
688
|
end
|
645
689
|
|
646
690
|
# Checks if the `AwesomePrint` gem is available.
|
@@ -651,6 +695,53 @@ class EnhancedErrors
|
|
651
695
|
@awesome_print_available = defined?(AwesomePrint)
|
652
696
|
end
|
653
697
|
|
698
|
+
# Safely calls `inspect` on a variable.
|
699
|
+
#
|
700
|
+
# @param variable [Object] The variable to inspect.
|
701
|
+
# @return [String] The inspected variable or a safe fallback.
|
702
|
+
def safe_inspect(variable)
|
703
|
+
variable.inspect
|
704
|
+
rescue => e
|
705
|
+
safe_to_s(variable)
|
706
|
+
end
|
707
|
+
|
708
|
+
# Safely converts a variable to a string, handling exceptions.
|
709
|
+
#
|
710
|
+
# @param variable [Object] The variable to convert.
|
711
|
+
# @return [String] The string representation or a safe fallback.
|
712
|
+
def safe_to_s(variable)
|
713
|
+
str = variable.to_s
|
714
|
+
if str.length > 30
|
715
|
+
str[0...30] + '...'
|
716
|
+
else
|
717
|
+
str
|
718
|
+
end
|
719
|
+
rescue
|
720
|
+
"[Unprintable variable]"
|
721
|
+
end
|
722
|
+
|
723
|
+
# Safely retrieves a local variable from a binding.
|
724
|
+
#
|
725
|
+
# @param binding_context [Binding] The binding context.
|
726
|
+
# @param var_name [Symbol] The name of the local variable.
|
727
|
+
# @return [Object] The value of the local variable or a safe fallback.
|
728
|
+
def safe_local_variable_get(binding_context, var_name)
|
729
|
+
binding_context.local_variable_get(var_name)
|
730
|
+
rescue
|
731
|
+
"[Error accessing local variable #{var_name}]"
|
732
|
+
end
|
733
|
+
|
734
|
+
# Safely retrieves an instance variable from an object.
|
735
|
+
#
|
736
|
+
# @param obj [Object] The object.
|
737
|
+
# @param var_name [Symbol] The name of the instance variable.
|
738
|
+
# @return [Object] The value of the instance variable or a safe fallback.
|
739
|
+
def safe_instance_variable_get(obj, var_name)
|
740
|
+
obj.instance_variable_get(var_name)
|
741
|
+
rescue
|
742
|
+
"[Error accessing instance variable #{var_name}]"
|
743
|
+
end
|
744
|
+
|
654
745
|
# Default implementation for the on_format hook.
|
655
746
|
#
|
656
747
|
# @param string [String] The formatted exception message.
|
data/lib/error_enhancements.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
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
|
@@ -18,7 +25,8 @@ module ErrorEnhancements
|
|
18
25
|
end
|
19
26
|
EnhancedErrors.format(bindings_of_interest)
|
20
27
|
rescue => e
|
21
|
-
puts
|
28
|
+
# Avoid using puts; consider logging instead
|
29
|
+
# Avoid raising exceptions in rescue blocks
|
22
30
|
""
|
23
31
|
end
|
24
32
|
end
|
@@ -44,7 +52,7 @@ module ErrorEnhancements
|
|
44
52
|
bindings_of_interest << binding_infos.first if binding_infos.first
|
45
53
|
end
|
46
54
|
|
47
|
-
#
|
55
|
+
# Find the last rescue binding if there is one
|
48
56
|
binding_infos.reverse.each do |info|
|
49
57
|
if info[:capture_event] == 'rescue'
|
50
58
|
bindings_of_interest << info
|
@@ -53,5 +61,4 @@ module ErrorEnhancements
|
|
53
61
|
end
|
54
62
|
bindings_of_interest
|
55
63
|
end
|
56
|
-
|
57
64
|
end
|