debug_logging 3.1.2 → 3.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.
@@ -3,83 +3,70 @@
3
3
  module DebugLogging
4
4
  module ClassLogger
5
5
  def logged(*methods_to_log)
6
- # When opts are present it will always be a new configuration instance per method
7
- # When opts are not present it will reuse the class' configuration object
8
- payload = methods_to_log.last.is_a?(Hash) && methods_to_log.pop.dup || {}
9
- config_opts = {}
10
- unless payload.empty?
11
- DebugLogging::Configuration::CONFIG_KEYS.each { |k| config_opts[k] = payload.delete(k) if payload.key?(k) }
12
- end
13
- if methods_to_log.first.is_a?(Array)
14
- methods_to_log = methods_to_log.shift
15
- else
16
- # logged :meth1, :meth2, :meth3 without options is valid too
17
- end
18
- methods_to_log.each do |method_to_log|
19
- # method name must be a symbol
20
- method_to_log = method_to_log.to_sym
6
+ methods_to_log, payload, config_opts = DebugLogging::Util.extract_payload_and_config(
7
+ method_names: methods_to_log,
8
+ payload: nil,
9
+ config: nil
10
+ )
11
+ Array(methods_to_log).each do |method_to_log|
12
+ method_to_log, method_payload, method_config_opts = DebugLogging::Util.extract_payload_and_config(
13
+ method_names: method_to_log,
14
+ payload: payload,
15
+ config: config_opts
16
+ )
21
17
  original_method = method(method_to_log)
22
18
  (class << self; self; end).class_eval do
23
19
  define_method(method_to_log) do |*args, &block|
24
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('kl',
25
- method_to_log)))
26
- proxy
27
- else
28
- proxy = if config_opts.empty?
29
- debug_config
30
- else
31
- DebugLogging::Configuration.new(**debug_config.to_hash.merge(config_opts))
32
- end
33
- proxy.register(method_to_log)
34
- instance_variable_set(DebugLogging::Configuration.config_pointer('kl', method_to_log),
35
- proxy)
36
- proxy
37
- end
20
+ config_proxy = DebugLogging::Util.config_proxy_finder(
21
+ scope: self,
22
+ config_opts: method_config_opts,
23
+ method_name: method_to_log,
24
+ proxy_ref: 'kl'
25
+ )
38
26
  method_return_value = nil
39
27
  log_prefix = nil
40
28
  invocation_id = nil
41
- config_proxy.log do
42
- paydirt = {}
43
- # TODO: Could make instance variable introspection configurable before or after method execution
44
- if payload.key?(:instance_variables)
45
- paydirt.merge!(payload.reject { |k| k == :instance_variables })
46
- payload[:instance_variables].each do |k|
47
- paydirt[k] = instance_variable_get("@#{k}") if instance_variable_defined?("@#{k}")
29
+ begin
30
+ config_proxy.log do
31
+ paydirt = DebugLogging::Util.payload_instance_vaiable_hydration(scope: self, payload: method_payload)
32
+ log_prefix = debug_invocation_to_s(klass: to_s, separator: '.', method_to_log: method_to_log,
33
+ config_proxy: config_proxy)
34
+ invocation_id = debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
35
+ signature = debug_signature_to_s(args: args, config_proxy: config_proxy)
36
+ paymud = debug_payload_to_s(payload: paydirt, config_proxy: config_proxy)
37
+ "#{log_prefix}#{signature}#{invocation_id} debug: #{paymud}"
38
+ end
39
+ if config_proxy.benchmarkable_for?(:debug_class_benchmarks)
40
+ tms = Benchmark.measure do
41
+ method_return_value = if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
42
+ original_method.call(**harsh, &block)
43
+ else
44
+ original_method.call(*args, &block)
45
+ end
46
+ end
47
+ config_proxy.log do
48
+ "#{log_prefix} #{debug_benchmark_to_s(tms: tms)}#{invocation_id}"
48
49
  end
49
50
  else
50
- paydirt.merge!(payload)
51
- end
52
- log_prefix = debug_invocation_to_s(klass: to_s, separator: '.', method_to_log: method_to_log,
53
- config_proxy: config_proxy)
54
- invocation_id = debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
55
- signature = debug_signature_to_s(args: args, config_proxy: config_proxy)
56
- paymud = debug_payload_to_s(payload: paydirt, config_proxy: config_proxy)
57
- "#{log_prefix}#{signature}#{invocation_id} debug: #{paymud}"
58
- end
59
- if config_proxy.benchmarkable_for?(:debug_class_benchmarks)
60
- tms = Benchmark.measure do
61
51
  method_return_value = if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
62
52
  original_method.call(**harsh, &block)
63
53
  else
64
54
  original_method.call(*args, &block)
65
55
  end
66
- end
67
- config_proxy.log do
68
- "#{log_prefix} #{debug_benchmark_to_s(tms: tms)}#{invocation_id}"
69
- end
70
- else
71
- method_return_value = if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
72
- original_method.call(**harsh, &block)
73
- else
74
- original_method.call(*args, &block)
75
- end
76
- if config_proxy.exit_scope_markable? && invocation_id && !invocation_id.empty?
77
- config_proxy.log do
78
- "#{log_prefix} completed#{invocation_id}"
56
+ if config_proxy.exit_scope_markable? && invocation_id && !invocation_id.empty?
57
+ config_proxy.log do
58
+ "#{log_prefix} completed#{invocation_id}"
59
+ end
79
60
  end
80
61
  end
62
+ method_return_value
63
+ rescue => error
64
+ if config_proxy.error_handler_proc
65
+ config_proxy.error_handler_proc.call(config_proxy, error, self, method_to_log, args)
66
+ else
67
+ raise error
68
+ end
81
69
  end
82
- method_return_value
83
70
  end
84
71
  end
85
72
  end
@@ -3,57 +3,55 @@
3
3
  module DebugLogging
4
4
  module ClassNotifier
5
5
  def notifies(*methods_to_notify)
6
- # When opts are present it will always be a new configuration instance per method
7
- # When opts are not present it will reuse the class' configuration object
8
- payload = methods_to_notify.last.is_a?(Hash) && methods_to_notify.pop.dup || {}
9
- config_opts = {}
10
- unless payload.empty?
11
- DebugLogging::Configuration::CONFIG_KEYS.each { |k| config_opts[k] = payload.delete(k) if payload.key?(k) }
12
- end
13
- if methods_to_notify.first.is_a?(Array)
14
- methods_to_notify = methods_to_notify.shift
15
- else
16
- # logged :meth1, :meth2, :meth3 without options is valid too
17
- end
18
- methods_to_notify.each do |method_to_notify|
19
- # method name must be a symbol
20
- method_to_notify = method_to_notify.to_sym
6
+ methods_to_notify, payload, config_opts = DebugLogging::Util.extract_payload_and_config(
7
+ method_names: methods_to_notify,
8
+ payload: nil,
9
+ config: nil
10
+ )
11
+ Array(methods_to_notify).each do |method_to_notify|
12
+ method_to_notify, method_payload, method_config_opts = DebugLogging::Util.extract_payload_and_config(
13
+ method_names: method_to_notify,
14
+ payload: payload,
15
+ config: config_opts
16
+ )
21
17
  original_method = method(method_to_notify)
22
18
  (class << self; self; end).class_eval do
23
19
  define_method(method_to_notify) do |*args, &block|
24
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('kn',
25
- method_to_notify)))
26
- proxy
27
- else
28
- proxy = if config_opts.empty?
29
- debug_config
30
- else
31
- DebugLogging::Configuration.new(**debug_config.to_hash.merge(config_opts))
32
- end
33
- proxy.register(method_to_notify)
34
- instance_variable_set(DebugLogging::Configuration.config_pointer('kn', method_to_notify),
35
- proxy)
36
- ActiveSupport::Notifications.subscribe(
37
- DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify)
38
- ) do |*debug_args|
39
- proxy.log do
40
- DebugLogging::LogSubscriber.log_event(ActiveSupport::Notifications::Event.new(*debug_args))
41
- end
42
- end
43
- proxy
44
- end
20
+ config_proxy = DebugLogging::Util.config_proxy_finder(
21
+ scope: self,
22
+ config_opts: method_config_opts,
23
+ method_name: method_to_notify,
24
+ proxy_ref: 'kn'
25
+ ) do |proxy|
26
+ ActiveSupport::Notifications.subscribe(
27
+ DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify)
28
+ ) do |*debug_args|
29
+ proxy.log do
30
+ DebugLogging::LogSubscriber.log_event(ActiveSupport::Notifications::Event.new(*debug_args))
31
+ end
32
+ end
33
+ end
34
+ paydirt = DebugLogging::Util.payload_instance_vaiable_hydration(scope: self, payload: method_payload)
45
35
  ActiveSupport::Notifications.instrument(
46
36
  DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify),
47
37
  {
48
38
  debug_args: args,
49
39
  config_proxy: config_proxy,
50
- **payload
40
+ **paydirt
51
41
  }
52
42
  ) do
53
- if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
54
- original_method.call(**harsh, &block)
55
- else
56
- original_method.call(*args, &block)
43
+ begin
44
+ if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
45
+ original_method.call(**harsh, &block)
46
+ else
47
+ original_method.call(*args, &block)
48
+ end
49
+ rescue => error
50
+ if config_proxy.error_handler_proc
51
+ config_proxy.error_handler_proc.call(config_proxy, error, self, method_to_notify, args)
52
+ else
53
+ raise error
54
+ end
57
55
  end
58
56
  end
59
57
  end
@@ -2,31 +2,7 @@
2
2
 
3
3
  module DebugLogging
4
4
  class Configuration
5
- DEFAULT_ELLIPSIS = ' ✂️ …'
6
- CONFIG_ATTRS_DEFAULTS = {
7
- enabled: true,
8
- logger: Logger.new($stdout),
9
- log_level: :debug,
10
- multiple_last_hashes: false,
11
- last_hash_to_s_proc: nil,
12
- last_hash_max_length: 1_000,
13
- args_max_length: 1_000,
14
- colorized_chain_for_method: false,
15
- colorized_chain_for_class: false,
16
- add_invocation_id: true,
17
- ellipsis: DEFAULT_ELLIPSIS,
18
- mark_scope_exit: false,
19
- add_payload: true
20
- }.freeze
21
- CONFIG_ATTRS = CONFIG_ATTRS_DEFAULTS.keys
22
- CONFIG_READERS_DEFAULTS = {
23
- instance_benchmarks: false,
24
- class_benchmarks: false,
25
- active_support_notifications: false
26
- }.freeze
27
- CONFIG_READERS = CONFIG_READERS_DEFAULTS.keys
28
- CONFIG_KEYS = CONFIG_ATTRS + CONFIG_READERS
29
-
5
+ include Constants
30
6
  # For reference, log levels as integers mapped to symbols:
31
7
  # LEVELS = { 0 => :debug, 1 => :info, 2 => :warn, 3 => :error, 4 => :fatal, 5 => :unknown }
32
8
  attr_accessor(*CONFIG_ATTRS)
@@ -42,6 +18,7 @@ module DebugLogging
42
18
  # log_level: :debug # at what level do the messages created by this gem sent at?
43
19
  # last_hash_to_s_proc: nil # e.g. ->(hash) { "keys: #{hash.keys}" }
44
20
  # last_hash_max_length: 1_000
21
+ # args_to_s_proc: nil # e.g. ->(record) { "record id: #{record.id}" }
45
22
  # args_max_length: 1_000
46
23
  # instance_benchmarks: false
47
24
  # class_benchmarks: false
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DebugLogging
4
+ module Constants
5
+ DEFAULT_ELLIPSIS = ' ✂️ …'
6
+ CONFIG_ATTRS_DEFAULTS = {
7
+ enabled: true,
8
+ logger: Logger.new($stdout),
9
+ log_level: :debug,
10
+ multiple_last_hashes: false,
11
+ last_hash_to_s_proc: nil,
12
+ last_hash_max_length: 1_000,
13
+ args_to_s_proc: nil,
14
+ args_max_length: 1_000,
15
+ colorized_chain_for_method: false,
16
+ colorized_chain_for_class: false,
17
+ add_invocation_id: true,
18
+ ellipsis: DEFAULT_ELLIPSIS,
19
+ mark_scope_exit: false,
20
+ add_payload: true, # Can also be a proc returning a string, which will be called when printing the payload
21
+ payload_max_length: 1_000,
22
+ error_handler_proc: nil
23
+ }.freeze
24
+ CONFIG_ATTRS = CONFIG_ATTRS_DEFAULTS.keys
25
+ CONFIG_READERS_DEFAULTS = {
26
+ instance_benchmarks: false,
27
+ class_benchmarks: false,
28
+ active_support_notifications: false
29
+ }.freeze
30
+ CONFIG_READERS = CONFIG_READERS_DEFAULTS.keys
31
+ CONFIG_KEYS = CONFIG_ATTRS + CONFIG_READERS
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module DebugLogging
2
+ class Error < StandardError; end
3
+
4
+ class TimeoutError < Error; end
5
+
6
+ class NoBlockGiven < Error; end
7
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # From: https://stackoverflow.com/a/34559282
4
+ # License: https://creativecommons.org/licenses/by-sa/4.0/
5
+ module DebugLogging
6
+ module Finalize
7
+ def self.extended(obj)
8
+ TracePoint.trace(:end) do |t|
9
+ if obj == t.self
10
+ if obj.respond_to?(:debug_finalize)
11
+ obj.debug_finalize
12
+ else
13
+ warn "#{obj} does not define a debug_finalize"
14
+ end
15
+ t.disable
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,82 @@
1
+ require 'debug_logging/errors'
2
+ require 'timeout'
3
+
4
+ module DebugLogging
5
+ module Hooks
6
+ def self.included(mod)
7
+ mod.extend(ClassMethods)
8
+ end
9
+
10
+ def self.extend(mod)
11
+ mod.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ def debug_time_box(time, *names, &blk)
16
+ names.each do |name|
17
+ meth = instance_method(name)
18
+ define_method(name) do |*args, &block|
19
+ begin
20
+ Timeout.timeout(time) do
21
+ meth.bind(self).call(*args, &block)
22
+ end
23
+ rescue Timeout::Error
24
+ error_args = [TimeoutError, 'execution expired', caller]
25
+ raise(*error_args) unless blk
26
+
27
+ instance_exec(*error_args, &blk)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def debug_rescue_on_fail(*names, &blk)
34
+ unless blk
35
+ raise NoBlockGiven,
36
+ '.rescue_on_fail must be called with a block',
37
+ caller
38
+ end
39
+ names.each do |name|
40
+ meth = instance_method(name)
41
+ define_method(name) do |*args, &block|
42
+ begin
43
+ meth.bind(self).call(*args, &block)
44
+ rescue StandardError => e
45
+ instance_exec(e, &blk)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def debug_before(*names, &blk)
52
+ unless blk
53
+ raise NoBlockGiven,
54
+ '.before must be called with a block',
55
+ caller
56
+ end
57
+ names.each do |name|
58
+ meth = instance_method(name)
59
+ define_method name do |*args, &block|
60
+ instance_exec(name, *args, block, &blk)
61
+ meth.bind(self).call(*args, &block)
62
+ end
63
+ end
64
+ end
65
+
66
+ def debug_after(*names, &blk)
67
+ unless blk
68
+ raise NoBlockGiven,
69
+ '.after must be called with a block',
70
+ caller
71
+ end
72
+ names.each do |name|
73
+ meth = instance_method(name)
74
+ define_method name do |*args, &block|
75
+ result = meth.bind(self).call(*args, &block)
76
+ instance_exec(result, &blk)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -2,9 +2,10 @@
2
2
 
3
3
  module DebugLogging
4
4
  class InstanceLogger < Module
5
- def initialize(i_methods: nil, config: nil)
5
+ def initialize(i_methods: nil, payload: nil, config: nil)
6
6
  super()
7
7
  @config = config
8
+ @payload = payload
8
9
  @instance_methods_to_log = Array(i_methods) if i_methods
9
10
  end
10
11
 
@@ -13,6 +14,7 @@ module DebugLogging
13
14
 
14
15
  base.send(:include, ArgumentPrinter)
15
16
  instance_method_logger = DebugLogging::InstanceLoggerModulizer.to_mod(methods_to_log: @instance_methods_to_log,
17
+ payload: @payload,
16
18
  config: @config)
17
19
  base.send(:prepend, instance_method_logger)
18
20
  end
@@ -2,52 +2,32 @@
2
2
 
3
3
  module DebugLogging
4
4
  module InstanceLoggerModulizer
5
- def self.to_mod(methods_to_log: nil, config: nil)
5
+ def self.to_mod(methods_to_log: nil, payload: nil, config: nil)
6
6
  Module.new do
7
+ methods_to_log, payload, config_opts = DebugLogging::Util.extract_payload_and_config(
8
+ method_names: Array(methods_to_log),
9
+ payload: payload,
10
+ config: config
11
+ )
7
12
  Array(methods_to_log).each do |method_to_log|
8
- payload = (method_to_log.is_a?(Array) && method_to_log.last.is_a?(Hash) && method_to_log.pop.dup) || {}
9
- config_opts = {}
10
- unless payload.empty?
11
- DebugLogging::Configuration::CONFIG_KEYS.each { |k| config_opts[k] = payload.delete(k) if payload.key?(k) }
12
- end
13
- # method name must be a symbol
14
- method_to_log = if method_to_log.is_a?(Array)
15
- method_to_log.first&.to_sym
16
- else
17
- method_to_log.to_sym
18
- end
13
+ method_to_log, method_payload, method_config_opts = DebugLogging::Util.extract_payload_and_config(
14
+ method_names: method_to_log,
15
+ payload: payload,
16
+ config: config_opts
17
+ )
19
18
  define_method(method_to_log) do |*args, &block|
20
19
  method_return_value = nil
21
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('ilm',
22
- method_to_log)))
23
- proxy
24
- else
25
- proxy = if config
26
- Configuration.new(**self.class.debug_config.to_hash.merge(config.merge(config_opts)))
27
- elsif !config_opts.empty?
28
- Configuration.new(**self.class.debug_config.to_hash.merge(config_opts))
29
- else
30
- self.class.debug_config
31
- end
32
- proxy.register(method_to_log)
33
- instance_variable_set(DebugLogging::Configuration.config_pointer('ilm', method_to_log),
34
- proxy)
35
- proxy
36
- end
20
+ config_proxy = DebugLogging::Util.config_proxy_finder(
21
+ scope: self.class,
22
+ config_opts: method_config_opts,
23
+ method_name: method_to_log,
24
+ proxy_ref: 'ilm'
25
+ )
37
26
  log_prefix = self.class.debug_invocation_to_s(klass: self.class.to_s, separator: '#',
38
27
  method_to_log: method_to_log, config_proxy: config_proxy)
39
28
  invocation_id = self.class.debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
40
29
  config_proxy.log do
41
- paydirt = {}
42
- # TODO: Could make instance variable introspection configurable before or after method execution
43
- if payload.key?(:instance_variables)
44
- paydirt.merge!(payload.reject { |k| k == :instance_variables })
45
- payload[:instance_variables].each do |k|
46
- paydirt[k] = instance_variable_get("@#{k}") if instance_variable_defined?("@#{k}")
47
- end
48
- else
49
- paydirt.merge!(payload)
50
- end
30
+ paydirt = DebugLogging::Util.payload_instance_vaiable_hydration(scope: self, payload: method_payload)
51
31
  signature = self.class.debug_signature_to_s(args: args, config_proxy: config_proxy)
52
32
  paymud = debug_payload_to_s(payload: paydirt, config_proxy: config_proxy)
53
33
  "#{log_prefix}#{signature}#{invocation_id} debug: #{paymud}"
@@ -60,7 +40,15 @@ module DebugLogging
60
40
  "#{log_prefix} #{self.class.debug_benchmark_to_s(tms: tms)}#{invocation_id}"
61
41
  end
62
42
  else
63
- method_return_value = super(*args, &block)
43
+ begin
44
+ method_return_value = super(*args, &block)
45
+ rescue => error
46
+ if config_proxy.error_handler_proc
47
+ config_proxy.error_handler_proc.call(config_proxy, error, self, method_to_log, args)
48
+ else
49
+ raise error
50
+ end
51
+ end
64
52
  if config_proxy.exit_scope_markable? && invocation_id && !invocation_id.empty?
65
53
  config_proxy.log do
66
54
  "#{log_prefix} completed#{invocation_id}"