debug_logging 1.0.17 → 3.1.2

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.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DebugLogging
4
+ module ClassNotifier
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
21
+ original_method = method(method_to_notify)
22
+ (class << self; self; end).class_eval do
23
+ 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
45
+ ActiveSupport::Notifications.instrument(
46
+ DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify),
47
+ {
48
+ debug_args: args,
49
+ config_proxy: config_proxy,
50
+ **payload
51
+ }
52
+ ) 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)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,23 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DebugLogging
2
4
  class Configuration
3
- DEFAULT_ELLIPSIS = " ✂️ …".freeze
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
+
4
30
  # For reference, log levels as integers mapped to symbols:
5
31
  # LEVELS = { 0 => :debug, 1 => :info, 2 => :warn, 3 => :error, 4 => :fatal, 5 => :unknown }
6
- attr_accessor :enabled
7
- attr_accessor :logger
8
- attr_accessor :log_level
9
- attr_accessor :multiple_last_hashes
10
- attr_accessor :last_hash_to_s_proc
11
- attr_accessor :last_hash_max_length
12
- attr_accessor :args_max_length
13
- attr_accessor :instance_benchmarks
14
- attr_accessor :class_benchmarks
15
- attr_accessor :colorized_chain_for_method
16
- attr_accessor :colorized_chain_for_class
17
- attr_accessor :add_invocation_id
18
- attr_accessor :ellipsis
19
- attr_accessor :mark_scope_exit
20
- attr_reader :methods_to_log
32
+ attr_accessor(*CONFIG_ATTRS)
33
+ attr_reader :methods_to_log, *CONFIG_READERS
34
+
21
35
  # alias the readers to the debug_* prefix so an instance of this class
22
36
  # can have the same API granted by `extend DebugLogging`
23
37
  #
@@ -36,21 +50,10 @@ module DebugLogging
36
50
  # }
37
51
  # )
38
52
  #
39
- alias :debug_enabled :enabled
40
- alias :debug_logger :logger
41
- alias :debug_log_level :log_level
42
- alias :debug_multiple_last_hashes :multiple_last_hashes
43
- alias :debug_last_hash_to_s_proc :last_hash_to_s_proc
44
- alias :debug_last_hash_max_length :last_hash_max_length
45
- alias :debug_args_max_length :args_max_length
46
- alias :debug_instance_benchmarks :instance_benchmarks
47
- alias :debug_class_benchmarks :class_benchmarks
48
- alias :debug_colorized_chain_for_method :colorized_chain_for_method
49
- alias :debug_colorized_chain_for_class :colorized_chain_for_class
50
- alias :debug_add_invocation_id :add_invocation_id
51
- alias :debug_ellipsis :ellipsis
52
- alias :debug_mark_scope_exit :mark_scope_exit
53
-
53
+ CONFIG_KEYS.each do |key|
54
+ alias_method :"debug_#{key}", :"#{key}"
55
+ end
56
+
54
57
  class << self
55
58
  def config_pointer(type, method_to_log)
56
59
  # Methods names that do not match the following regex can't be part of an ivar name
@@ -60,70 +63,77 @@ module DebugLogging
60
63
  end
61
64
  end
62
65
  def initialize(**options)
63
- @enabled = options.key?(:enabled) ? options[:enabled] : true
64
- @logger = options.key?(:logger) ? options[:logger] : Logger.new(STDOUT)
65
- @log_level = options.key?(:log_level) ? options[:log_level] : :debug
66
- @multiple_last_hashes = options.key?(:multiple_last_hashes) ? options[:multiple_last_hashes] : false
67
- @last_hash_to_s_proc = options.key?(:last_hash_to_s_proc) ? options[:last_hash_to_s_proc] : nil
68
- @last_hash_max_length = options.key?(:last_hash_max_length) ? options[:last_hash_max_length] : 1_000
69
- @args_max_length = options.key?(:args_max_length) ? options[:args_max_length] : 1_000
70
- @instance_benchmarks = options.key?(:instance_benchmarks) ? options[:instance_benchmarks] : false
71
- @class_benchmarks = options.key?(:class_benchmarks) ? options[:class_benchmarks] : false
72
- @colorized_chain_for_method = options.key?(:colorized_chain_for_method) ? options[:colorized_chain_for_method] : false
73
- @colorized_chain_for_class = options.key?(:colorized_chain_for_class) ? options[:colorized_chain_for_class] : false
74
- @add_invocation_id = options.key?(:add_invocation_id) ? options[:add_invocation_id] : true
75
- @ellipsis = options.key?(:ellipsis) ? options[:ellipsis] : DEFAULT_ELLIPSIS
76
- @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
77
72
  @methods_to_log = []
78
73
  end
74
+
79
75
  def log(message = nil, &block)
80
76
  return unless enabled
81
77
  return unless logger
82
- if block_given?
78
+
79
+ if block
83
80
  logger.send(log_level, &block)
84
81
  else
85
82
  logger.send(log_level, message)
86
83
  end
87
84
  end
85
+
88
86
  def loggable?
89
87
  return @loggable if defined?(@loggable)
88
+
90
89
  @loggable = logger.send("#{log_level}?")
91
90
  end
91
+
92
92
  def benchmarkable_for?(benchmarks)
93
93
  return @benchmarkable if defined?(@benchmarkable)
94
- @benchmarkable = loggable? && self.send(benchmarks)
94
+
95
+ @benchmarkable = loggable? && send(benchmarks)
95
96
  end
97
+
96
98
  def exit_scope_markable?
97
99
  return @exit_scope_markable if defined?(@exit_scope_markable)
100
+
98
101
  @exit_scope_markable = loggable? && mark_scope_exit
99
102
  end
103
+
100
104
  def instance_benchmarks=(instance_benchmarks)
101
- require "benchmark" if instance_benchmarks
105
+ require 'benchmark' if instance_benchmarks
102
106
  @instance_benchmarks = instance_benchmarks
103
107
  end
108
+
104
109
  def class_benchmarks=(class_benchmarks)
105
- require "benchmark" if class_benchmarks
110
+ require 'benchmark' if class_benchmarks
106
111
  @class_benchmarks = class_benchmarks
107
112
  end
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
+
108
119
  def to_hash
109
- {
110
- logger: logger,
111
- log_level: log_level,
112
- multiple_last_hashes: multiple_last_hashes,
113
- last_hash_to_s_proc: last_hash_to_s_proc,
114
- last_hash_max_length: last_hash_max_length,
115
- args_max_length: args_max_length,
116
- instance_benchmarks: instance_benchmarks,
117
- class_benchmarks: class_benchmarks,
118
- colorized_chain_for_method: colorized_chain_for_method,
119
- colorized_chain_for_class: colorized_chain_for_class,
120
- add_invocation_id: add_invocation_id,
121
- ellipsis: ellipsis,
122
- mark_scope_exit: mark_scope_exit
123
- }
120
+ CONFIG_KEYS.each_with_object({}) do |key, hash|
121
+ hash[key] = instance_variable_get("@#{key}")
122
+ end
124
123
  end
124
+
125
125
  def register(method_lo_log)
126
126
  @methods_to_log << method_lo_log
127
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
128
138
  end
129
139
  end
@@ -1,13 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DebugLogging
2
4
  class InstanceLogger < Module
3
5
  def initialize(i_methods: nil, config: nil)
6
+ super()
4
7
  @config = config
5
8
  @instance_methods_to_log = Array(i_methods) if i_methods
6
9
  end
10
+
7
11
  def included(base)
8
12
  return unless @instance_methods_to_log
13
+
9
14
  base.send(:include, ArgumentPrinter)
10
- instance_method_logger = DebugLogging::InstanceLoggerModulizer.to_mod(methods_to_log: @instance_methods_to_log, config: @config)
15
+ instance_method_logger = DebugLogging::InstanceLoggerModulizer.to_mod(methods_to_log: @instance_methods_to_log,
16
+ config: @config)
11
17
  base.send(:prepend, instance_method_logger)
12
18
  end
13
19
  end
@@ -1,28 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DebugLogging
2
4
  module InstanceLoggerModulizer
3
5
  def self.to_mod(methods_to_log: nil, config: nil)
4
6
  Module.new do
5
7
  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
6
13
  # method name must be a symbol
7
- define_method(method_to_log.to_sym) do |*args, &block|
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
19
+ define_method(method_to_log) do |*args, &block|
8
20
  method_return_value = nil
9
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('i', method_to_log)))
21
+ config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('ilm',
22
+ method_to_log)))
10
23
  proxy
11
24
  else
12
25
  proxy = if config
13
- Configuration.new(**(self.class.debug_config.to_hash.merge(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))
14
29
  else
15
30
  self.class.debug_config
16
31
  end
17
32
  proxy.register(method_to_log)
18
- instance_variable_set(DebugLogging::Configuration.config_pointer('i', method_to_log), proxy)
33
+ instance_variable_set(DebugLogging::Configuration.config_pointer('ilm', method_to_log),
34
+ proxy)
19
35
  proxy
20
36
  end
21
- log_prefix = self.class.debug_invocation_to_s(klass: self.class.to_s, separator: "#", method_to_log: method_to_log, config_proxy: config_proxy)
37
+ log_prefix = self.class.debug_invocation_to_s(klass: self.class.to_s, separator: '#',
38
+ method_to_log: method_to_log, config_proxy: config_proxy)
22
39
  invocation_id = self.class.debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
23
40
  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
24
51
  signature = self.class.debug_signature_to_s(args: args, config_proxy: config_proxy)
25
- "#{log_prefix}#{signature}#{invocation_id}"
52
+ paymud = debug_payload_to_s(payload: paydirt, config_proxy: config_proxy)
53
+ "#{log_prefix}#{signature}#{invocation_id} debug: #{paymud}"
26
54
  end
27
55
  if config_proxy.benchmarkable_for?(:debug_instance_benchmarks)
28
56
  tms = Benchmark.measure do
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DebugLogging
4
+ class InstanceNotifier < Module
5
+ def initialize(i_methods: nil)
6
+ super()
7
+ @instance_methods_to_notify = Array(i_methods) if i_methods
8
+ end
9
+
10
+ def included(base)
11
+ return unless @instance_methods_to_notify
12
+
13
+ base.send(:include, ArgumentPrinter)
14
+ instance_method_notifier = DebugLogging::InstanceNotifierModulizer.to_mod(methods_to_notify: @instance_methods_to_notify)
15
+ base.send(:prepend, instance_method_notifier)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DebugLogging
4
+ module InstanceNotifierModulizer
5
+ def self.to_mod(methods_to_notify: nil)
6
+ Module.new do
7
+ config_proxy = nil
8
+
9
+ Array(methods_to_notify).each do |method_to_notify|
10
+ # method name must be a symbol
11
+ payload = (method_to_notify.is_a?(Array) && method_to_notify.last.is_a?(Hash) && method_to_notify.pop.dup) || {}
12
+ config_opts = {}
13
+ unless payload.empty?
14
+ DebugLogging::Configuration::CONFIG_KEYS.each { |k| config_opts[k] = payload.delete(k) if payload.key?(k) }
15
+ end
16
+ method_to_notify = if method_to_notify.is_a?(Array)
17
+ method_to_notify.first&.to_sym
18
+ else
19
+ method_to_notify.to_sym
20
+ end
21
+ define_method(method_to_notify) do |*args, &block|
22
+ config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('inm',
23
+ method_to_notify)))
24
+ proxy
25
+ else
26
+ proxy = if config_opts.empty?
27
+ self.class.debug_config
28
+ else
29
+ Configuration.new(**self.class.debug_config.to_hash.merge(config_opts))
30
+ end
31
+ proxy.register(method_to_notify)
32
+ instance_variable_set(
33
+ DebugLogging::Configuration.config_pointer('inm', method_to_notify), proxy
34
+ )
35
+ proxy
36
+ end
37
+ paydirt = {}
38
+ if payload.key?(:instance_variables)
39
+ paydirt.merge!(payload.reject { |k| k == :instance_variables })
40
+ payload[:instance_variables].each do |k|
41
+ paydirt[k] = instance_variable_get("@#{k}") if instance_variable_defined?("@#{k}")
42
+ end
43
+ else
44
+ paydirt.merge!(payload)
45
+ end
46
+ ActiveSupport::Notifications.instrument(
47
+ DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify),
48
+ debug_args: args,
49
+ config_proxy: config_proxy,
50
+ **paydirt
51
+ ) do
52
+ super(*args, &block)
53
+ end
54
+ end
55
+ ActiveSupport::Notifications.subscribe(
56
+ DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify)
57
+ ) do |*args|
58
+ config_proxy&.log do
59
+ DebugLogging::LogSubscriber.log_event(ActiveSupport::Notifications::Event.new(*args))
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/log_subscriber'
4
+
5
+ module DebugLogging
6
+ class LogSubscriber < ActiveSupport::LogSubscriber
7
+ EXCLUDE_FROM_PAYLOAD = %i[debug_args config_proxy].freeze
8
+ extend DebugLogging::ArgumentPrinter
9
+
10
+ class << self
11
+ attr_accessor :event
12
+ end
13
+ attach_to :log
14
+
15
+ EVENT_FORMAT_STRING = '%<name>s (%<duration>.3f secs) start=%<time>s end=%<end>s args=%<args>s payload=%<payload>s'
16
+
17
+ def self.log_event(event)
18
+ @event = event
19
+ if event.payload && event.payload[:exception_object]
20
+ exception = event.payload[:exception_object]
21
+ "#{event.name} [ERROR] : \n#{exception.class} : #{exception.message}\n" + exception.backtrace.join("\n")
22
+ else
23
+ format(EVENT_FORMAT_STRING, event_to_format_options(event))
24
+ end
25
+ end
26
+
27
+ # @param [ActiveSupport::Notifications::Event]
28
+ # @return [Hash]
29
+ def self.event_to_format_options(event)
30
+ args = event.payload[:debug_args]
31
+ config_proxy = event.payload[:config_proxy]
32
+ payload = event.payload.reject { |k, _| EXCLUDE_FROM_PAYLOAD.include?(k) }
33
+ {
34
+ name: event.name,
35
+ duration: Rational(event.duration, 1000).to_f,
36
+ time: event.time,
37
+ end: event.end,
38
+ args: debug_signature_to_s(args: args, config_proxy: config_proxy),
39
+ payload: debug_payload_to_s(payload: payload, config_proxy: config_proxy)
40
+ }
41
+ end
42
+ end
43
+ end