enhanced_errors 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|