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 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