debug_logging 2.0.0 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,7 @@ require 'debug_logging/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'debug_logging'
9
9
  spec.version = DebugLogging::VERSION
10
- spec.authors = ['Peter Boling']
10
+ spec.authors = ['Peter Boling', 'guckin']
11
11
  spec.email = ['peter.boling@gmail.com']
12
12
 
13
13
  spec.summary = 'Drop-in debug logging useful when a call stack gets unruly'
@@ -24,15 +24,19 @@ Automatically log selected methods and their arguments as they are called at run
24
24
  spec.bindir = 'exe'
25
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ['lib']
27
- spec.required_ruby_version = '>= 2.3.0' # Uses magic comments
27
+ spec.required_ruby_version = '>= 2.4.0' # Uses magic comments
28
28
 
29
29
  spec.add_runtime_dependency 'colorize', '>= 0'
30
+ spec.add_development_dependency 'activesupport', '~> 5.2', '>= 5.2.4.4'
30
31
  spec.add_development_dependency 'bundler', '>= 2'
31
32
  spec.add_development_dependency 'byebug', '>= 11'
32
33
  spec.add_development_dependency 'rake', '>= 13'
33
34
  spec.add_development_dependency 'rspec', '>= 3'
34
35
  spec.add_development_dependency 'rspec-pending_for', '>= 0'
35
- spec.add_development_dependency 'rubocop', '>= 0'
36
- spec.add_development_dependency 'rubocop-rspec', '>= 1'
36
+ spec.add_development_dependency 'rubocop', '~> 1.0'
37
+ spec.add_development_dependency 'rubocop-md'
38
+ spec.add_development_dependency 'rubocop-performance'
39
+ spec.add_development_dependency 'rubocop-rake'
40
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.0'
37
41
  spec.add_development_dependency 'silent_stream', '>= 1'
38
42
  end
@@ -5,8 +5,12 @@ require 'colorized_string'
5
5
  require 'digest'
6
6
 
7
7
  require 'debug_logging/version'
8
+ require 'debug_logging/errors'
8
9
  require 'debug_logging/configuration'
10
+ require 'debug_logging/util'
11
+ require 'debug_logging/finalize'
9
12
  require 'debug_logging/argument_printer'
13
+ require 'debug_logging/hooks'
10
14
  require 'debug_logging/instance_logger_modulizer'
11
15
  require 'debug_logging/instance_logger'
12
16
  require 'debug_logging/class_logger'
@@ -61,6 +65,11 @@ module DebugLogging
61
65
  def self.extended(base)
62
66
  base.send(:extend, ArgumentPrinter)
63
67
  base.debug_config_reset(Configuration.new(**debug_logging_configuration.to_hash))
68
+ base.class_eval do
69
+ def base.inherited(subclass)
70
+ subclass.debug_config_reset(Configuration.new(**debug_config.to_hash))
71
+ end
72
+ end
64
73
  end
65
74
 
66
75
  #### API ####
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'debug_logging/log_subscriber'
4
+ require 'debug_logging/class_notifier'
5
+ require 'debug_logging/instance_notifier_modulizer'
6
+ require 'debug_logging/instance_notifier'
@@ -2,11 +2,13 @@
2
2
 
3
3
  module DebugLogging
4
4
  module ArgumentPrinter
5
- def debug_benchmark_to_s(tms: nil)
5
+ def debug_benchmark_to_s(tms:)
6
6
  "completed in #{format('%f', tms.real)}s (#{format('%f', tms.total)}s CPU)"
7
7
  end
8
8
 
9
9
  def debug_invocation_id_to_s(args: nil, config_proxy: nil)
10
+ return '' unless args && config_proxy
11
+
10
12
  if config_proxy.debug_add_invocation_id
11
13
  invocation = " ~#{args.object_id}@#{(Time.now.to_f.to_s % '%#-21a')[4..-4]}~"
12
14
  case config_proxy.debug_add_invocation_id
@@ -21,6 +23,8 @@ module DebugLogging
21
23
  end
22
24
 
23
25
  def debug_invocation_to_s(klass: nil, separator: nil, method_to_log: nil, config_proxy: nil)
26
+ return '' unless config_proxy
27
+
24
28
  klass_string = if config_proxy.debug_colorized_chain_for_class
25
29
  config_proxy.debug_colorized_chain_for_class.call(ColorizedString[klass.to_s])
26
30
  else
@@ -34,7 +38,9 @@ module DebugLogging
34
38
  "#{klass_string}#{separator}#{method_string}"
35
39
  end
36
40
 
37
- def debug_signature_to_s(args: nil, config_proxy: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
41
+ def debug_signature_to_s(args: nil, config_proxy: nil) # rubocop:disable Metrics/CyclomaticComplexity
42
+ return '' unless args && config_proxy
43
+
38
44
  printed_args = ''
39
45
 
40
46
  add_args_ellipsis = false
@@ -65,25 +71,56 @@ module DebugLogging
65
71
  printed_args += ', ' if !other_args_string.empty? && !last_hash_args_string.empty?
66
72
  printed_args += last_hash_args_string if last_hash_args_string && !last_hash_args_string.empty?
67
73
  else
68
- printed_args += args[0..-2].map(&:inspect).join(', ').tap { |x| add_args_ellipsis = x.length > config_proxy.debug_args_max_length }[0..(config_proxy.debug_args_max_length)]
74
+ printed_args += args[0..-2].map(&:inspect).join(', ').tap do |x|
75
+ add_args_ellipsis = x.length > config_proxy.debug_args_max_length
76
+ end[0..(config_proxy.debug_args_max_length)]
69
77
  printed_args += config_proxy.debug_ellipsis if add_args_ellipsis
70
- printed_args += ", #{config_proxy.debug_last_hash_to_s_proc.call(args[-1]).tap { |x| add_last_hash_ellipsis = x.length > config_proxy.debug_last_hash_max_length }[0..(config_proxy.debug_last_hash_max_length)]}"
78
+ printed_args += ", #{config_proxy.debug_last_hash_to_s_proc.call(args[-1]).tap do |x|
79
+ add_last_hash_ellipsis = x.length > config_proxy.debug_last_hash_max_length
80
+ end[0..(config_proxy.debug_last_hash_max_length)]}"
71
81
  printed_args += config_proxy.debug_ellipsis if add_last_hash_ellipsis
72
82
  end
73
83
  else
74
- printed_args += String(config_proxy.debug_last_hash_to_s_proc.call(args[0])).tap { |x| add_last_hash_ellipsis = x.length > config_proxy.debug_last_hash_max_length }[0..(config_proxy.debug_last_hash_max_length)]
84
+ printed_args += String(config_proxy.debug_last_hash_to_s_proc.call(args[0])).tap do |x|
85
+ add_last_hash_ellipsis = x.length > config_proxy.debug_last_hash_max_length
86
+ end[0..(config_proxy.debug_last_hash_max_length)]
75
87
  printed_args += config_proxy.debug_ellipsis if add_last_hash_ellipsis
76
88
  end
77
89
  else
78
- if args.length == 1 && args[0].is_a?(Hash)
79
- # handle double splat
80
- printed_args += ("**#{args.map(&:inspect).join(', ').tap { |x| add_args_ellipsis = x.length > config_proxy.debug_args_max_length }}")[0..(config_proxy.debug_args_max_length)]
81
- else
82
- printed_args += args.map(&:inspect).join(', ').tap { |x| add_args_ellipsis = x.length > config_proxy.debug_args_max_length}[0..(config_proxy.debug_args_max_length)]
83
- end
90
+ printed_args += if args.length == 1 && args[0].is_a?(Hash)
91
+ # handle double splat
92
+ ("**#{args.map(&:inspect).join(', ').tap do |x|
93
+ add_args_ellipsis = x.length > config_proxy.debug_args_max_length
94
+ end }")[0..(config_proxy.debug_args_max_length)]
95
+ else
96
+ args.map(&:inspect).join(', ').tap do |x|
97
+ add_args_ellipsis = x.length > config_proxy.debug_args_max_length
98
+ end[0..(config_proxy.debug_args_max_length)]
99
+ end
84
100
  printed_args += config_proxy.debug_ellipsis if add_args_ellipsis
85
101
  end
86
102
  "(#{printed_args})"
87
103
  end
104
+
105
+ def debug_payload_to_s(payload: nil, config_proxy: nil)
106
+ return '' unless payload && config_proxy
107
+
108
+ if payload
109
+ case config_proxy.debug_add_payload
110
+ when true
111
+ payload.inspect
112
+ else
113
+ config_proxy.debug_add_payload.call(**payload)
114
+ end
115
+ else
116
+ ''
117
+ end
118
+ end
119
+
120
+ module_function
121
+
122
+ def debug_event_name_to_s(method_to_notify: nil)
123
+ "#{method_to_notify}.log"
124
+ end
88
125
  end
89
126
  end
@@ -3,50 +3,55 @@
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
- opts = methods_to_log.last.is_a?(Hash) && methods_to_log.pop
9
- if methods_to_log.first.is_a?(Array)
10
- methods_to_log = methods_to_log.shift
11
- else
12
- # logged :meth1, :meth2, :meth3 without options is valid too
13
- end
14
- methods_to_log.each do |method_to_log|
15
- # method name must be a symbol
16
- 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
+ )
17
17
  original_method = method(method_to_log)
18
18
  (class << self; self; end).class_eval do
19
19
  define_method(method_to_log) do |*args, &block|
20
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('k', method_to_log)))
21
- proxy
22
- else
23
- proxy = if opts
24
- Configuration.new(**debug_config.to_hash.merge(opts))
25
- else
26
- debug_config
27
- end
28
- proxy.register(method_to_log)
29
- instance_variable_set(DebugLogging::Configuration.config_pointer('k', method_to_log), proxy)
30
- proxy
31
- 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
+ )
32
26
  method_return_value = nil
33
27
  log_prefix = nil
34
28
  invocation_id = nil
35
29
  config_proxy.log do
36
- log_prefix = debug_invocation_to_s(klass: to_s, separator: '.', method_to_log: method_to_log, config_proxy: config_proxy)
30
+ paydirt = DebugLogging::Util.payload_instance_vaiable_hydration(scope: self, payload: method_payload)
31
+ log_prefix = debug_invocation_to_s(klass: to_s, separator: '.', method_to_log: method_to_log,
32
+ config_proxy: config_proxy)
37
33
  invocation_id = debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
38
34
  signature = debug_signature_to_s(args: args, config_proxy: config_proxy)
39
- "#{log_prefix}#{signature}#{invocation_id}"
35
+ paymud = debug_payload_to_s(payload: paydirt, config_proxy: config_proxy)
36
+ "#{log_prefix}#{signature}#{invocation_id} debug: #{paymud}"
40
37
  end
41
38
  if config_proxy.benchmarkable_for?(:debug_class_benchmarks)
42
39
  tms = Benchmark.measure do
43
- method_return_value = original_method.call(*args, &block)
40
+ method_return_value = if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
41
+ original_method.call(**harsh, &block)
42
+ else
43
+ original_method.call(*args, &block)
44
+ end
44
45
  end
45
46
  config_proxy.log do
46
47
  "#{log_prefix} #{debug_benchmark_to_s(tms: tms)}#{invocation_id}"
47
48
  end
48
49
  else
49
- method_return_value = original_method.call(*args, &block)
50
+ method_return_value = if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
51
+ original_method.call(**harsh, &block)
52
+ else
53
+ original_method.call(*args, &block)
54
+ end
50
55
  if config_proxy.exit_scope_markable? && invocation_id && !invocation_id.empty?
51
56
  config_proxy.log do
52
57
  "#{log_prefix} completed#{invocation_id}"
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DebugLogging
4
+ module ClassNotifier
5
+ def notifies(*methods_to_notify)
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
+ )
17
+ original_method = method(method_to_notify)
18
+ (class << self; self; end).class_eval do
19
+ define_method(method_to_notify) do |*args, &block|
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)
35
+ ActiveSupport::Notifications.instrument(
36
+ DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify),
37
+ {
38
+ debug_args: args,
39
+ config_proxy: config_proxy,
40
+ **paydirt
41
+ }
42
+ ) do
43
+ if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
44
+ original_method.call(**harsh, &block)
45
+ else
46
+ original_method.call(*args, &block)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -3,13 +3,35 @@
3
3
  module DebugLogging
4
4
  class Configuration
5
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
+
6
30
  # For reference, log levels as integers mapped to symbols:
7
31
  # LEVELS = { 0 => :debug, 1 => :info, 2 => :warn, 3 => :error, 4 => :fatal, 5 => :unknown }
8
- attr_accessor :enabled
9
- attr_accessor :logger, :log_level, :multiple_last_hashes, :last_hash_to_s_proc, :last_hash_max_length,
10
- :args_max_length, :colorized_chain_for_method, :colorized_chain_for_class, :add_invocation_id,
11
- :ellipsis, :mark_scope_exit
12
- attr_reader :instance_benchmarks, :class_benchmarks, :methods_to_log
32
+ attr_accessor(*CONFIG_ATTRS)
33
+ attr_reader :methods_to_log, *CONFIG_READERS
34
+
13
35
  # alias the readers to the debug_* prefix so an instance of this class
14
36
  # can have the same API granted by `extend DebugLogging`
15
37
  #
@@ -28,20 +50,9 @@ module DebugLogging
28
50
  # }
29
51
  # )
30
52
  #
31
- alias debug_enabled enabled
32
- alias debug_logger logger
33
- alias debug_log_level log_level
34
- alias debug_multiple_last_hashes multiple_last_hashes
35
- alias debug_last_hash_to_s_proc last_hash_to_s_proc
36
- alias debug_last_hash_max_length last_hash_max_length
37
- alias debug_args_max_length args_max_length
38
- alias debug_instance_benchmarks instance_benchmarks
39
- alias debug_class_benchmarks class_benchmarks
40
- alias debug_colorized_chain_for_method colorized_chain_for_method
41
- alias debug_colorized_chain_for_class colorized_chain_for_class
42
- alias debug_add_invocation_id add_invocation_id
43
- alias debug_ellipsis ellipsis
44
- alias debug_mark_scope_exit mark_scope_exit
53
+ CONFIG_KEYS.each do |key|
54
+ alias_method :"debug_#{key}", :"#{key}"
55
+ end
45
56
 
46
57
  class << self
47
58
  def config_pointer(type, method_to_log)
@@ -52,20 +63,12 @@ module DebugLogging
52
63
  end
53
64
  end
54
65
  def initialize(**options)
55
- @enabled = options.key?(:enabled) ? options[:enabled] : true
56
- @logger = options.key?(:logger) ? options[:logger] : Logger.new($stdout)
57
- @log_level = options.key?(:log_level) ? options[:log_level] : :debug
58
- @multiple_last_hashes = options.key?(:multiple_last_hashes) ? options[:multiple_last_hashes] : false
59
- @last_hash_to_s_proc = options.key?(:last_hash_to_s_proc) ? options[:last_hash_to_s_proc] : nil
60
- @last_hash_max_length = options.key?(:last_hash_max_length) ? options[:last_hash_max_length] : 1_000
61
- @args_max_length = options.key?(:args_max_length) ? options[:args_max_length] : 1_000
62
- @instance_benchmarks = options.key?(:instance_benchmarks) ? options[:instance_benchmarks] : false
63
- @class_benchmarks = options.key?(:class_benchmarks) ? options[:class_benchmarks] : false
64
- @colorized_chain_for_method = options.key?(:colorized_chain_for_method) ? options[:colorized_chain_for_method] : false
65
- @colorized_chain_for_class = options.key?(:colorized_chain_for_class) ? options[:colorized_chain_for_class] : false
66
- @add_invocation_id = options.key?(:add_invocation_id) ? options[:add_invocation_id] : true
67
- @ellipsis = options.key?(:ellipsis) ? options[:ellipsis] : DEFAULT_ELLIPSIS
68
- @mark_scope_exit = options.key?(:mark_scope_exit) ? options[:mark_scope_exit] : false
66
+ CONFIG_ATTRS.each do |key|
67
+ send("#{key}=", get_attr_from_options(options, key))
68
+ end
69
+ CONFIG_READERS.each do |key|
70
+ send("#{key}=", get_reader_from_options(options, key))
71
+ end
69
72
  @methods_to_log = []
70
73
  end
71
74
 
@@ -73,7 +76,7 @@ module DebugLogging
73
76
  return unless enabled
74
77
  return unless logger
75
78
 
76
- if block_given?
79
+ if block
77
80
  logger.send(log_level, &block)
78
81
  else
79
82
  logger.send(log_level, message)
@@ -108,26 +111,29 @@ module DebugLogging
108
111
  @class_benchmarks = class_benchmarks
109
112
  end
110
113
 
114
+ def active_support_notifications=(active_support_notifications)
115
+ require 'debug_logging/active_support_notifications' if active_support_notifications
116
+ @active_support_notifications = active_support_notifications
117
+ end
118
+
111
119
  def to_hash
112
- {
113
- logger: logger,
114
- log_level: log_level,
115
- multiple_last_hashes: multiple_last_hashes,
116
- last_hash_to_s_proc: last_hash_to_s_proc,
117
- last_hash_max_length: last_hash_max_length,
118
- args_max_length: args_max_length,
119
- instance_benchmarks: instance_benchmarks,
120
- class_benchmarks: class_benchmarks,
121
- colorized_chain_for_method: colorized_chain_for_method,
122
- colorized_chain_for_class: colorized_chain_for_class,
123
- add_invocation_id: add_invocation_id,
124
- ellipsis: ellipsis,
125
- mark_scope_exit: mark_scope_exit
126
- }
120
+ CONFIG_KEYS.each_with_object({}) do |key, hash|
121
+ hash[key] = instance_variable_get("@#{key}")
122
+ end
127
123
  end
128
124
 
129
125
  def register(method_lo_log)
130
126
  @methods_to_log << method_lo_log
131
127
  end
128
+
129
+ private
130
+
131
+ def get_attr_from_options(options, key)
132
+ options.key?(key) ? options[key] : CONFIG_ATTRS_DEFAULTS[key]
133
+ end
134
+
135
+ def get_reader_from_options(options, key)
136
+ options.key?(key) ? options[key] : CONFIG_READERS_DEFAULTS[key]
137
+ end
132
138
  end
133
139
  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