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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edc10f9326ea041bd8b367e57eb1037767847df8359408529445160ae76876b2
4
- data.tar.gz: d5a4aee4cd8f3cca01a57fb10c25b120f5d3e7e1f5bf0255ef107255823704ef
3
+ metadata.gz: 42e0ffde5da4be326f8b1d721652eed031f7bfb20a07d31a256d69c588a383b7
4
+ data.tar.gz: 5ef8303d0e6dd089253643b2abbf4aeef9a195831c946b2c92396390685e0a5b
5
5
  SHA512:
6
- metadata.gz: 4f3519f1c11d31cccfe4110deda8a2dcbc26b9cbd7ab0c9a0cd6b9ac65d32c57cc24b35f2a4a0f075888d20be3a69d397a0f631e617741705d1634834096a084
7
- data.tar.gz: 0a9d12908d54b1173123cea3f66de3bbfa96863d92fad8990c281459de0bd66b2e26e405910c31cb6336f726f1bba41e99aac2793238835ce144576acdb3004e
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. I suggest letting it get well-vetted before making the leap and testing it for both performance and memory under load internally, as well.
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
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "enhanced_errors"
3
- spec.version = "0.1.8"
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."
@@ -7,18 +7,27 @@ require_relative 'colors'
7
7
  require_relative 'error_enhancements'
8
8
  require_relative 'binding'
9
9
 
10
- # While we could just catch StandardError, we would miss a number of things.
11
- IGNORED_EXCEPTIONS = [
12
- SystemExit,
13
- NoMemoryError,
14
- SignalException,
15
- Interrupt,
16
- ScriptError,
17
- LoadError,
18
- NotImplementedError,
19
- SyntaxError,
20
- SystemStackError
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 object used for tracing exceptions.
42
+ # The TracePoint objects used for tracing exceptions per thread.
34
43
  #
35
- # @return [TracePoint, nil]
36
- attr_accessor :trace
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
- @trace.disable if @trace
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
- start_tracing
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
- case format
300
- when :json
301
- Colors.enabled = false
302
- JSON.pretty_generate(captured_bindings)
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 @trace && @trace.enabled?
438
+ def start_tracing(thread)
439
+ return if @traces[thread]&.enabled?
426
440
  events = @capture_events ? @capture_events.to_a : default_capture_events
427
- @trace = TracePoint.new(*events) do |tp|
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.enable
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 > 30
715
- str[0...30] + '...'
760
+ if str.length > 120
761
+ str[0...120] + '...'
716
762
  else
717
763
  str
718
764
  end
@@ -19,46 +19,41 @@ module ErrorEnhancements
19
19
 
20
20
  def variables_message
21
21
  @variables_message ||= begin
22
- bindings_of_interest = []
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
- EnhancedErrors.format(bindings_of_interest)
27
- rescue => e
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
- def select_binding_infos(binding_infos)
37
- # Preference:
38
- # Grab the first raise binding that isn't a library (gem) binding.
39
- # If there are only library bindings, grab the first one.
40
- # Grab the last rescue binding if we have one
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
- bindings_of_interest = []
41
+ bindings_of_interest = []
43
42
 
44
- binding_infos.each do |info|
45
- if info[:capture_event] == 'raise' && !info[:library]
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
- end
46
+ bindings_of_interest << first_app_raise if first_app_raise
50
47
 
51
- if bindings_of_interest.empty?
52
- bindings_of_interest << binding_infos.first if binding_infos.first
53
- end
48
+ if bindings_of_interest.empty? && binding_infos.first
49
+ bindings_of_interest << binding_infos.first
50
+ end
54
51
 
55
- # Find the last rescue binding if there is one
56
- binding_infos.reverse.each do |info|
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.1.8
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-04 00:00:00.000000000 Z
11
+ date: 2024-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print