debug_logging 1.0.16 → 3.1.1

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,22 +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
+
30
+ # For reference, log levels as integers mapped to symbols:
4
31
  # LEVELS = { 0 => :debug, 1 => :info, 2 => :warn, 3 => :error, 4 => :fatal, 5 => :unknown }
5
- attr_accessor :enabled
6
- attr_accessor :logger
7
- attr_accessor :log_level
8
- attr_accessor :multiple_last_hashes
9
- attr_accessor :last_hash_to_s_proc
10
- attr_accessor :last_hash_max_length
11
- attr_accessor :args_max_length
12
- attr_accessor :instance_benchmarks
13
- attr_accessor :class_benchmarks
14
- attr_accessor :colorized_chain_for_method
15
- attr_accessor :colorized_chain_for_class
16
- attr_accessor :add_invocation_id
17
- attr_accessor :ellipsis
18
- attr_accessor :mark_scope_exit
19
- attr_reader :methods_to_log
32
+ attr_accessor(*CONFIG_ATTRS)
33
+ attr_reader :methods_to_log, *CONFIG_READERS
34
+
20
35
  # alias the readers to the debug_* prefix so an instance of this class
21
36
  # can have the same API granted by `extend DebugLogging`
22
37
  #
@@ -35,21 +50,10 @@ module DebugLogging
35
50
  # }
36
51
  # )
37
52
  #
38
- alias :debug_enabled :enabled
39
- alias :debug_logger :logger
40
- alias :debug_log_level :log_level
41
- alias :debug_multiple_last_hashes :multiple_last_hashes
42
- alias :debug_last_hash_to_s_proc :last_hash_to_s_proc
43
- alias :debug_last_hash_max_length :last_hash_max_length
44
- alias :debug_args_max_length :args_max_length
45
- alias :debug_instance_benchmarks :instance_benchmarks
46
- alias :debug_class_benchmarks :class_benchmarks
47
- alias :debug_colorized_chain_for_method :colorized_chain_for_method
48
- alias :debug_colorized_chain_for_class :colorized_chain_for_class
49
- alias :debug_add_invocation_id :add_invocation_id
50
- alias :debug_ellipsis :ellipsis
51
- alias :debug_mark_scope_exit :mark_scope_exit
52
-
53
+ CONFIG_KEYS.each do |key|
54
+ alias :"debug_#{key}" :"#{key}"
55
+ end
56
+
53
57
  class << self
54
58
  def config_pointer(type, method_to_log)
55
59
  # Methods names that do not match the following regex can't be part of an ivar name
@@ -59,70 +63,77 @@ module DebugLogging
59
63
  end
60
64
  end
61
65
  def initialize(**options)
62
- @enabled = options.key?(:enabled) ? options[:enabled] : true
63
- @logger = options.key?(:logger) ? options[:logger] : Logger.new(STDOUT)
64
- @log_level = options.key?(:log_level) ? options[:log_level] : :debug
65
- @multiple_last_hashes = options.key?(:multiple_last_hashes) ? options[:multiple_last_hashes] : false
66
- @last_hash_to_s_proc = options.key?(:last_hash_to_s_proc) ? options[:last_hash_to_s_proc] : nil
67
- @last_hash_max_length = options.key?(:last_hash_max_length) ? options[:last_hash_max_length] : 1_000
68
- @args_max_length = options.key?(:args_max_length) ? options[:args_max_length] : 1_000
69
- @instance_benchmarks = options.key?(:instance_benchmarks) ? options[:instance_benchmarks] : false
70
- @class_benchmarks = options.key?(:class_benchmarks) ? options[:class_benchmarks] : false
71
- @colorized_chain_for_method = options.key?(:colorized_chain_for_method) ? options[:colorized_chain_for_method] : false
72
- @colorized_chain_for_class = options.key?(:colorized_chain_for_class) ? options[:colorized_chain_for_class] : false
73
- @add_invocation_id = options.key?(:add_invocation_id) ? options[:add_invocation_id] : true
74
- @ellipsis = options.key?(:ellipsis) ? options[:ellipsis] : DEFAULT_ELLIPSIS
75
- @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
76
72
  @methods_to_log = []
77
73
  end
74
+
78
75
  def log(message = nil, &block)
79
76
  return unless enabled
80
77
  return unless logger
78
+
81
79
  if block_given?
82
80
  logger.send(log_level, &block)
83
81
  else
84
82
  logger.send(log_level, message)
85
83
  end
86
84
  end
85
+
87
86
  def loggable?
88
87
  return @loggable if defined?(@loggable)
88
+
89
89
  @loggable = logger.send("#{log_level}?")
90
90
  end
91
+
91
92
  def benchmarkable_for?(benchmarks)
92
93
  return @benchmarkable if defined?(@benchmarkable)
93
- @benchmarkable = loggable? && self.send(benchmarks)
94
+
95
+ @benchmarkable = loggable? && send(benchmarks)
94
96
  end
97
+
95
98
  def exit_scope_markable?
96
99
  return @exit_scope_markable if defined?(@exit_scope_markable)
100
+
97
101
  @exit_scope_markable = loggable? && mark_scope_exit
98
102
  end
103
+
99
104
  def instance_benchmarks=(instance_benchmarks)
100
- require "benchmark" if instance_benchmarks
105
+ require 'benchmark' if instance_benchmarks
101
106
  @instance_benchmarks = instance_benchmarks
102
107
  end
108
+
103
109
  def class_benchmarks=(class_benchmarks)
104
- require "benchmark" if class_benchmarks
110
+ require 'benchmark' if class_benchmarks
105
111
  @class_benchmarks = class_benchmarks
106
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
+
107
119
  def to_hash
108
- {
109
- logger: logger,
110
- log_level: log_level,
111
- multiple_last_hashes: multiple_last_hashes,
112
- last_hash_to_s_proc: last_hash_to_s_proc,
113
- last_hash_max_length: last_hash_max_length,
114
- args_max_length: args_max_length,
115
- instance_benchmarks: instance_benchmarks,
116
- class_benchmarks: class_benchmarks,
117
- colorized_chain_for_method: colorized_chain_for_method,
118
- colorized_chain_for_class: colorized_chain_for_class,
119
- add_invocation_id: add_invocation_id,
120
- ellipsis: ellipsis,
121
- mark_scope_exit: mark_scope_exit
122
- }
120
+ CONFIG_KEYS.each_with_object({}) do |key, hash|
121
+ hash[key] = instance_variable_get("@#{key}")
122
+ end
123
123
  end
124
+
124
125
  def register(method_lo_log)
125
126
  @methods_to_log << method_lo_log
126
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
127
138
  end
128
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