debug_logging 3.1.1 → 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)
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)
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
@@ -51,7 +28,7 @@ module DebugLogging
51
28
  # )
52
29
  #
53
30
  CONFIG_KEYS.each do |key|
54
- alias :"debug_#{key}" :"#{key}"
31
+ alias_method :"debug_#{key}", :"#{key}"
55
32
  end
56
33
 
57
34
  class << self
@@ -76,7 +53,7 @@ module DebugLogging
76
53
  return unless enabled
77
54
  return unless logger
78
55
 
79
- if block_given?
56
+ if block
80
57
  logger.send(log_level, &block)
81
58
  else
82
59
  logger.send(log_level, message)
@@ -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