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