debug_logging 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efda45f99c32dcb0acc32896a61f2d209c527cf62bd74a74282cf044607f396f
4
- data.tar.gz: d4af27fc700abaa9a536d15c56af36f8a202db7c40d0107a8d8eb1262252aaeb
3
+ metadata.gz: a39fc11d3fc815e37db037fd9ce8b08e74e5d98e7612a50f191231ba3ce95231
4
+ data.tar.gz: 99ef91cf9302dfc413ebba74435464933524cfa1a2e70f9623b97ff64c849729
5
5
  SHA512:
6
- metadata.gz: d857b33f90f75e8bdfb3f2deedfb5c503bd90adbabb6b9aead0ecb305ba72715c25435f9d543ff8471454b5c9c5ff631792b870a97864fdb30e530ba4c0e6fb0
7
- data.tar.gz: 6985ed5caff4dbe017946a61b4f4242867110bcafcf266b62c98ce340d8af28f148b1dee364d3291296d2d5444789a429b254bedcd54bd63be28e4ce5a771b10
6
+ metadata.gz: 22ec37827bdf34889a023d235315ee4ca85642cdc070a22751be328f58c8f4b8b22c952da52c2d6900f3bc44431d8d0dd5800483419532f3fd5a5aca9227d529
7
+ data.tar.gz: 5b5456b83dfff252840e31ea81af17e5d3b50bdc025e090d55731fcd16353d41352b2304ba53818c791f9ec3970a447e4b01624fd269d6663951a444a876ac6d
data/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Unobtrusive, inheritable-overridable-configurable, drop-in debug logging, that won't leave a mess behind when it is time to remove it.
4
4
 
5
+ ## What do I mean by "unobtrusive"?
6
+
7
+ **Ugly** debug logging is added inside the body of a method, so it runs when a method is called. This can create a mess of your git history, and can even introduce new bugs to your code.
8
+
9
+ **Unobtrusive** debug logging stays out of the method, changes no logic, can't break your code, and yet it still runs when your method is called, and tells you everything you wanted to know. It doesn't mess with the git history of the method at all!
10
+
5
11
  | Project | DebugLogging |
6
12
  |------------------------ | ----------------------- |
7
13
  | gem name | [debug_logging](https://rubygems.org/gems/debug_logging) |
@@ -43,9 +49,9 @@ Herein you will find:
43
49
  * 100% clean, 0% obtrusive
44
50
  * 100% tested
45
51
  * 100% Ruby 2.1+ compatible
46
- - use version ~1.0 for Ruby < 2.3
47
- - use version ~2.0 for Ruby 2.3
48
- - use version ~3.0 for Ruby 2.4+
52
+ - use version `gem "debug_logging", "~> 1.0"` for Ruby < 2.3
53
+ - use version `gem "debug_logging", "~> 2.0"` for Ruby 2.3
54
+ - use version `gem "debug_logging", "~> 3.0"` for Ruby 2.4+
49
55
 
50
56
  NOTE: The manner this is made to work for class methods is totally different than the way this is made to work for instance methods.
51
57
 
@@ -136,29 +142,45 @@ Every time a method is called, you can now get logs, optionally with arguments,
136
142
 
137
143
  ```ruby
138
144
  class Car
139
-
140
- # adds the helper methods to the class, all are prefixed with debug_*,
141
- # except for the logged class method, which comes from extending DebugLogging::ClassLogger
145
+ # Adds the helper methods to the class.
146
+ # All helpers prefixed with debug_*,
147
+ # except for the *logged* decorator, which comes from extending DebugLogging::ClassLogger
142
148
  extend DebugLogging
143
149
 
144
150
  # per class configuration overrides!
145
151
  self.debug_class_benchmarks = true
146
152
  self.debug_instance_benchmarks = true
147
153
 
148
- # For instance methods:
149
- # Option 1: specify the exact method(s) to add logging to
150
- include DebugLogging::InstanceLogger.new(i_methods: [:drive, :stop])
151
-
152
- # Provides the `logged` method decorator
154
+ # For class methods
155
+ # Provides the versatile `logged` method decorator / macro
156
+ # For instance methods
157
+ # Provides the versatile `i_logged` method decorator / macro
153
158
  extend DebugLogging::ClassLogger
154
159
 
155
- logged def make; new; end
156
- def design(*args); new; end
157
- def safety(*args); new; end
158
- def dealer_options(*args); new; end
160
+ # == BEGIN CLASS METHODS ==
161
+ # For class methods:
162
+ # Option 1: Use *logged* as a method decorator
163
+ logged def self.make; new; end
164
+ def self.design(*args); new; end
165
+ def self.safety(*args); new; end
166
+ def self.dealer_options(*args); new; end
167
+
168
+ # Option 2: Use *logged* as a macro
159
169
  logged :design, :safety
160
- # override options for any instance method(s), by passing a hash as the last argument
161
- logged :dealer_options, { multiple_last_hashes: true }
170
+ # Override configuration options for any class method(s), by passing a hash as the last argument
171
+ # In the last hash any non-Configuration keys will be data that gets logged,
172
+ # and also made available to last_hash_to_s_proc
173
+ logged :dealer_options, {
174
+ something: 'here', # <= will be logged, and available to last_hash_to_s_proc
175
+ multiple_last_hashes: true # <= Overrides config
176
+ }
177
+ def self.will_not_be_logged; false; end
178
+ # == END CLASS METHODS ==
179
+
180
+ # == BEGIN INSTANCE METHODS ==
181
+ # For instance methods:
182
+ # Option 1: specify the exact method(s) to add logging to
183
+ include DebugLogging::InstanceLogger.new(i_methods: [:drive, :stop])
162
184
 
163
185
  def drive(speed); speed; end
164
186
  def stop(**opts); 0; end
@@ -167,11 +189,15 @@ class Car
167
189
  # Option 2: add logging to all instance methods defined above (but *not* defined below)
168
190
  include DebugLogging::InstanceLogger.new(i_methods: self.instance_methods(false))
169
191
 
170
- # override options for any instance method(s)
171
- include DebugLogging::InstanceLogger.new(i_methods: [:stop], config: { multiple_last_hashes: true })
192
+ def faster(**opts); 0; end
172
193
 
173
- def will_not_be_logged; false; end
194
+ # Override configuration options for any instance method(s), by passing a hash as the last argument
195
+ # In the last hash any non-Configuration keys will be data that gets logged,
196
+ # and also made available to last_hash_to_s_proc
197
+ include DebugLogging::InstanceLogger.new(i_methods: [:faster], config: { add_invocation_id: false })
174
198
 
199
+ def will_not_be_logged; false; end
200
+ # == END INSTANCE METHODS ==
175
201
  end
176
202
  ```
177
203
 
@@ -195,28 +221,45 @@ Every time a method is called, class and instance method events are instrumented
195
221
 
196
222
  ```ruby
197
223
  class Car
198
-
199
- # adds the helper methods to the class, all are prefixed with debug_*,
200
- # except for the instrumented class method, which comes from extending DebugLogging::ClassNotifier
224
+ # Adds the helper methods to the class.
225
+ # All helpers prefixed with debug_*,
226
+ # except for the *notifies* decorator, which comes from extending DebugLogging::ClassNotifier
201
227
  extend DebugLogging
202
228
 
203
229
  # For instance methods:
204
- # Option 1: specify the exact method(s) to add instrumentation to (including capturing instance variable values as part of the event payload)
230
+ # Option 1: specify the exact method(s) to add instrumentation to
231
+ # NOTE: You can capture instance variable values as part of the event payload
205
232
  include DebugLogging::InstanceNotifier.new(i_methods: [:drive,
206
233
  :stop,
207
234
  [:turn, { instance_variables: %i[direction angle] }]])
208
235
 
209
- # Provides the `notifies` method decorator
236
+ # For class methods
237
+ # Provides the versatile `notifies` method decorator / macro
238
+ # For instance methods
239
+ # Provides the versatile `i_notifies` method decorator / macro
210
240
  extend DebugLogging::ClassNotifier
211
241
 
212
- notifies def make; new; end
213
- def design(*args); new; end
214
- def safety(*args); new; end
215
- def dealer_options(*args); new; end
216
- notifies :design, :safety
217
- # adding additional event payload options for any instance method(s), by passing a hash as the last argument
218
- notifies :dealer_options, { sport_package: true }
242
+ # == BEGIN CLASS METHODS ==
243
+ # For class methods:
244
+ # Option 1: Use *notifies* as a method decorator
245
+ notifies def self.make; new; end
246
+ def self.design(*args); new; end
247
+ def self.safety(*args); new; end
248
+ def self.dealer_options(*args); new; end
219
249
 
250
+ # Option 2: Use *logged* as a macro
251
+ notifies :design, :safety
252
+ # Override configuration options for any class method(s), by passing a hash as the last argument
253
+ # In the last hash any non-Configuration keys will be data that gets added to the event payload,
254
+ # and also made available to last_hash_to_s_proc
255
+ notifies :dealer_options, {
256
+ something: 'here', # <= will be added to the event payload, and be available to last_hash_to_s_proc
257
+ add_invocation_id: false # <= Overrides config
258
+ }
259
+ def self.will_not_be_notified; false; end
260
+ # == END CLASS METHODS ==
261
+
262
+ # == BEGIN INSTANCE METHODS ==
220
263
  def drive(speed); speed; end
221
264
  def stop(**opts); 0; end
222
265
 
@@ -224,8 +267,15 @@ class Car
224
267
  # Option 2: add instrumentation to all instance methods defined above (but *not* defined below)
225
268
  include DebugLogging::InstanceNotifier.new(i_methods: self.instance_methods(false))
226
269
 
227
- def will_not_be_logged; false; end
270
+ def faster(**opts); 0; end
271
+
272
+ # Override options for any instance method(s), by passing a hash as the last argument
273
+ # In the last hash any non-Configuration keys will be data that gets added to the event payload,
274
+ # and also made available to last_hash_to_s_proc
275
+ include DebugLogging::InstanceNotifier.new(i_methods: [:faster], config: { add_invocation_id: false })
228
276
 
277
+ def will_not_be_notified; false; end
278
+ # == END INSTANCE METHODS ==
229
279
  end
230
280
  ```
231
281
 
@@ -6,11 +6,9 @@ module DebugLogging
6
6
  "completed in #{format('%f', tms.real)}s (#{format('%f', tms.total)}s CPU)"
7
7
  end
8
8
 
9
- def debug_event_name_to_s(method_to_notify: nil)
10
- "#{method_to_notify}.log"
11
- end
12
-
13
9
  def debug_invocation_id_to_s(args: nil, config_proxy: nil)
10
+ return '' unless args && config_proxy
11
+
14
12
  if config_proxy.debug_add_invocation_id
15
13
  invocation = " ~#{args.object_id}@#{(Time.now.to_f.to_s % '%#-21a')[4..-4]}~"
16
14
  case config_proxy.debug_add_invocation_id
@@ -25,6 +23,8 @@ module DebugLogging
25
23
  end
26
24
 
27
25
  def debug_invocation_to_s(klass: nil, separator: nil, method_to_log: nil, config_proxy: nil)
26
+ return '' unless config_proxy
27
+
28
28
  klass_string = if config_proxy.debug_colorized_chain_for_class
29
29
  config_proxy.debug_colorized_chain_for_class.call(ColorizedString[klass.to_s])
30
30
  else
@@ -39,6 +39,8 @@ module DebugLogging
39
39
  end
40
40
 
41
41
  def debug_signature_to_s(args: nil, config_proxy: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
42
+ return '' unless args && config_proxy
43
+
42
44
  printed_args = ''
43
45
 
44
46
  add_args_ellipsis = false
@@ -69,25 +71,56 @@ module DebugLogging
69
71
  printed_args += ', ' if !other_args_string.empty? && !last_hash_args_string.empty?
70
72
  printed_args += last_hash_args_string if last_hash_args_string && !last_hash_args_string.empty?
71
73
  else
72
- 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)]
73
77
  printed_args += config_proxy.debug_ellipsis if add_args_ellipsis
74
- 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)]}"
75
81
  printed_args += config_proxy.debug_ellipsis if add_last_hash_ellipsis
76
82
  end
77
83
  else
78
- 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)]
79
87
  printed_args += config_proxy.debug_ellipsis if add_last_hash_ellipsis
80
88
  end
81
89
  else
82
- if args.length == 1 && args[0].is_a?(Hash)
83
- # handle double splat
84
- 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)]
85
- else
86
- 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)]
87
- 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
88
100
  printed_args += config_proxy.debug_ellipsis if add_args_ellipsis
89
101
  end
90
102
  "(#{printed_args})"
91
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
92
125
  end
93
126
  end
@@ -5,7 +5,11 @@ module DebugLogging
5
5
  def logged(*methods_to_log)
6
6
  # When opts are present it will always be a new configuration instance per method
7
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
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
9
13
  if methods_to_log.first.is_a?(Array)
10
14
  methods_to_log = methods_to_log.shift
11
15
  else
@@ -17,36 +21,58 @@ module DebugLogging
17
21
  original_method = method(method_to_log)
18
22
  (class << self; self; end).class_eval do
19
23
  define_method(method_to_log) do |*args, &block|
20
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('k', method_to_log)))
24
+ config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('kl',
25
+ method_to_log)))
21
26
  proxy
22
27
  else
23
- proxy = if opts
24
- Configuration.new(**debug_config.to_hash.merge(opts))
25
- else
28
+ proxy = if config_opts.empty?
26
29
  debug_config
30
+ else
31
+ DebugLogging::Configuration.new(**debug_config.to_hash.merge(config_opts))
27
32
  end
28
33
  proxy.register(method_to_log)
29
- instance_variable_set(DebugLogging::Configuration.config_pointer('k', method_to_log), proxy)
34
+ instance_variable_set(DebugLogging::Configuration.config_pointer('kl', method_to_log),
35
+ proxy)
30
36
  proxy
31
37
  end
32
38
  method_return_value = nil
33
39
  log_prefix = nil
34
40
  invocation_id = nil
35
41
  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)
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}")
48
+ end
49
+ 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)
37
54
  invocation_id = debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
38
55
  signature = debug_signature_to_s(args: args, config_proxy: config_proxy)
39
- "#{log_prefix}#{signature}#{invocation_id}"
56
+ paymud = debug_payload_to_s(payload: paydirt, config_proxy: config_proxy)
57
+ "#{log_prefix}#{signature}#{invocation_id} debug: #{paymud}"
40
58
  end
41
59
  if config_proxy.benchmarkable_for?(:debug_class_benchmarks)
42
60
  tms = Benchmark.measure do
43
- method_return_value = original_method.call(*args, &block)
61
+ method_return_value = if args.size == 1 && (harsh = args[0]) && harsh.is_a?(Hash)
62
+ original_method.call(**harsh, &block)
63
+ else
64
+ original_method.call(*args, &block)
65
+ end
44
66
  end
45
67
  config_proxy.log do
46
68
  "#{log_prefix} #{debug_benchmark_to_s(tms: tms)}#{invocation_id}"
47
69
  end
48
70
  else
49
- method_return_value = original_method.call(*args, &block)
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
50
76
  if config_proxy.exit_scope_markable? && invocation_id && !invocation_id.empty?
51
77
  config_proxy.log do
52
78
  "#{log_prefix} completed#{invocation_id}"
@@ -3,7 +3,13 @@
3
3
  module DebugLogging
4
4
  module ClassNotifier
5
5
  def notifies(*methods_to_notify)
6
- payload = methods_to_notify.last.is_a?(Hash) && methods_to_notify.pop || {}
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
7
13
  if methods_to_notify.first.is_a?(Array)
8
14
  methods_to_notify = methods_to_notify.shift
9
15
  else
@@ -13,34 +19,45 @@ module DebugLogging
13
19
  # method name must be a symbol
14
20
  method_to_notify = method_to_notify.to_sym
15
21
  original_method = method(method_to_notify)
16
- config_proxy = nil
17
22
  (class << self; self; end).class_eval do
18
23
  define_method(method_to_notify) do |*args, &block|
19
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('k', method_to_notify)))
24
+ config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('kn',
25
+ method_to_notify)))
20
26
  proxy
21
27
  else
22
- proxy = if !payload.empty?
23
- Configuration.new(**debug_config.to_hash.merge(payload))
24
- else
28
+ proxy = if config_opts.empty?
25
29
  debug_config
30
+ else
31
+ DebugLogging::Configuration.new(**debug_config.to_hash.merge(config_opts))
26
32
  end
27
33
  proxy.register(method_to_notify)
28
- instance_variable_set(DebugLogging::Configuration.config_pointer('k', method_to_notify), proxy)
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
29
43
  proxy
30
44
  end
31
45
  ActiveSupport::Notifications.instrument(
32
- debug_event_name_to_s(method_to_notify: method_to_notify), { args: args }.merge(payload)
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
+ }
33
52
  ) do
34
- original_method.call(*args, &block)
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
35
58
  end
36
59
  end
37
60
  end
38
-
39
- ActiveSupport::Notifications.subscribe(/log/) do |*args|
40
- config_proxy&.log do
41
- DebugLogging::LogSubscriber.log_event(ActiveSupport::Notifications::Event.new(*args))
42
- end
43
- end
44
61
  end
45
62
  end
46
63
  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, :active_support_notifications, :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 :"debug_#{key}" :"#{key}"
55
+ end
45
56
 
46
57
  class << self
47
58
  def config_pointer(type, method_to_log)
@@ -52,21 +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
- @colorized_chain_for_method = options.key?(:colorized_chain_for_method) ? options[:colorized_chain_for_method] : false
63
- @colorized_chain_for_class = options.key?(:colorized_chain_for_class) ? options[:colorized_chain_for_class] : false
64
- @add_invocation_id = options.key?(:add_invocation_id) ? options[:add_invocation_id] : true
65
- @ellipsis = options.key?(:ellipsis) ? options[:ellipsis] : DEFAULT_ELLIPSIS
66
- @mark_scope_exit = options.key?(:mark_scope_exit) ? options[:mark_scope_exit] : false
67
- self.instance_benchmarks = options.key?(:instance_benchmarks) ? options[:instance_benchmarks] : false
68
- self.class_benchmarks = options.key?(:class_benchmarks) ? options[:class_benchmarks] : false
69
- self.active_support_notifications = options.key?(:active_support_notifications) ? options[:active_support_notifications] : 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
70
72
  @methods_to_log = []
71
73
  end
72
74
 
@@ -115,26 +117,23 @@ module DebugLogging
115
117
  end
116
118
 
117
119
  def to_hash
118
- {
119
- logger: logger,
120
- log_level: log_level,
121
- multiple_last_hashes: multiple_last_hashes,
122
- last_hash_to_s_proc: last_hash_to_s_proc,
123
- last_hash_max_length: last_hash_max_length,
124
- args_max_length: args_max_length,
125
- instance_benchmarks: instance_benchmarks,
126
- class_benchmarks: class_benchmarks,
127
- colorized_chain_for_method: colorized_chain_for_method,
128
- colorized_chain_for_class: colorized_chain_for_class,
129
- add_invocation_id: add_invocation_id,
130
- ellipsis: ellipsis,
131
- mark_scope_exit: mark_scope_exit,
132
- active_support_notifications: active_support_notifications
133
- }
120
+ CONFIG_KEYS.each_with_object({}) do |key, hash|
121
+ hash[key] = instance_variable_get("@#{key}")
122
+ end
134
123
  end
135
124
 
136
125
  def register(method_lo_log)
137
126
  @methods_to_log << method_lo_log
138
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
139
138
  end
140
139
  end
@@ -12,7 +12,8 @@ module DebugLogging
12
12
  return unless @instance_methods_to_log
13
13
 
14
14
  base.send(:include, ArgumentPrinter)
15
- 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)
16
17
  base.send(:prepend, instance_method_logger)
17
18
  end
18
19
  end
@@ -5,26 +5,52 @@ module DebugLogging
5
5
  def self.to_mod(methods_to_log: nil, config: nil)
6
6
  Module.new do
7
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
8
13
  # method name must be a symbol
9
- 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|
10
20
  method_return_value = nil
11
- 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)))
12
23
  proxy
13
24
  else
14
25
  proxy = if config
15
- 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))
16
29
  else
17
30
  self.class.debug_config
18
31
  end
19
32
  proxy.register(method_to_log)
20
- 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)
21
35
  proxy
22
36
  end
23
- 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)
24
39
  invocation_id = self.class.debug_invocation_id_to_s(args: args, config_proxy: config_proxy)
25
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
26
51
  signature = self.class.debug_signature_to_s(args: args, config_proxy: config_proxy)
27
- "#{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}"
28
54
  end
29
55
  if config_proxy.benchmarkable_for?(:debug_instance_benchmarks)
30
56
  tms = Benchmark.measure do
@@ -4,42 +4,57 @@ module DebugLogging
4
4
  module InstanceNotifierModulizer
5
5
  def self.to_mod(methods_to_notify: nil)
6
6
  Module.new do
7
+ config_proxy = nil
8
+
7
9
  Array(methods_to_notify).each do |method_to_notify|
8
10
  # method name must be a symbol
9
- payload = method_to_notify.is_a?(Array) && method_to_notify.last.is_a?(Hash) && method_to_notify.pop || {}
10
- if method_to_notify.is_a?(Array)
11
- method_to_notify = method_to_notify.first&.to_sym
12
- else
13
- method_to_notify.to_sym
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) }
14
15
  end
15
- config_proxy = nil
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
16
21
  define_method(method_to_notify) do |*args, &block|
17
- config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('i', method_to_notify)))
22
+ config_proxy = if (proxy = instance_variable_get(DebugLogging::Configuration.config_pointer('inm',
23
+ method_to_notify)))
18
24
  proxy
19
25
  else
20
- proxy = if !payload.empty?
21
- Configuration.new(**self.class.debug_config.to_hash.merge(payload))
22
- else
26
+ proxy = if config_opts.empty?
23
27
  self.class.debug_config
28
+ else
29
+ Configuration.new(**self.class.debug_config.to_hash.merge(config_opts))
24
30
  end
25
31
  proxy.register(method_to_notify)
26
- instance_variable_set(DebugLogging::Configuration.config_pointer('i', method_to_notify), proxy)
32
+ instance_variable_set(
33
+ DebugLogging::Configuration.config_pointer('inm', method_to_notify), proxy
34
+ )
27
35
  proxy
28
36
  end
37
+ paydirt = {}
29
38
  if payload.key?(:instance_variables)
39
+ paydirt.merge!(payload.reject { |k| k == :instance_variables })
30
40
  payload[:instance_variables].each do |k|
31
- payload[k] = instance_variable_get("@#{k}") if instance_variable_get("@#{k}")
41
+ paydirt[k] = instance_variable_get("@#{k}") if instance_variable_defined?("@#{k}")
32
42
  end
33
- payload.delete(:instance_variables)
43
+ else
44
+ paydirt.merge!(payload)
34
45
  end
35
46
  ActiveSupport::Notifications.instrument(
36
- self.class.debug_event_name_to_s(method_to_notify: method_to_notify), { args: args }.merge(payload)
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
37
51
  ) do
38
52
  super(*args, &block)
39
53
  end
40
54
  end
41
-
42
- ActiveSupport::Notifications.subscribe(/log/) do |*args|
55
+ ActiveSupport::Notifications.subscribe(
56
+ DebugLogging::ArgumentPrinter.debug_event_name_to_s(method_to_notify: method_to_notify)
57
+ ) do |*args|
43
58
  config_proxy&.log do
44
59
  DebugLogging::LogSubscriber.log_event(ActiveSupport::Notifications::Event.new(*args))
45
60
  end
@@ -4,31 +4,39 @@ require 'active_support/log_subscriber'
4
4
 
5
5
  module DebugLogging
6
6
  class LogSubscriber < ActiveSupport::LogSubscriber
7
+ EXCLUDE_FROM_PAYLOAD = %i[debug_args config_proxy].freeze
8
+ extend DebugLogging::ArgumentPrinter
9
+
7
10
  class << self
8
11
  attr_accessor :event
9
12
  end
10
13
  attach_to :log
11
14
 
12
- EVENT_FORMAT_STRING = '%<name>s (%<duration>.3f secs) start=%<time>s end=%<end>s payload=%<payload>s'
15
+ EVENT_FORMAT_STRING = '%<name>s (%<duration>.3f secs) start=%<time>s end=%<end>s args=%<args>s payload=%<payload>s'
13
16
 
14
17
  def self.log_event(event)
15
18
  @event = event
16
19
  if event.payload && event.payload[:exception_object]
17
20
  exception = event.payload[:exception_object]
18
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))
19
24
  end
20
- format(EVENT_FORMAT_STRING, event_to_format_options(event))
21
25
  end
22
26
 
23
27
  # @param [ActiveSupport::Notifications::Event]
24
28
  # @return [Hash]
25
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) }
26
33
  {
27
34
  name: event.name,
28
35
  duration: Rational(event.duration, 1000).to_f,
29
36
  time: event.time,
30
37
  end: event.end,
31
- payload: event.payload
38
+ args: debug_signature_to_s(args: args, config_proxy: config_proxy),
39
+ payload: debug_payload_to_s(payload: payload, config_proxy: config_proxy)
32
40
  }
33
41
  end
34
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DebugLogging
4
- VERSION = '3.1.0'
4
+ VERSION = '3.1.1'
5
5
  end
@@ -48,6 +48,7 @@ class SimpleDebugLogging < Module
48
48
  base.send(:prepend, instance_method_logger)
49
49
  base.send(:extend, ClassMethodLogger)
50
50
  end
51
+
51
52
  module ClassMethodLogger
52
53
  def logged(*methods_to_log)
53
54
  methods_to_log.each do |method_to_log|
@@ -67,6 +68,7 @@ class SimpleDebugLogging < Module
67
68
  end
68
69
  end
69
70
  end
71
+
70
72
  module InstanceMethodLoggerModulizer
71
73
  def self.to_mod(methods_to_log = [])
72
74
  Module.new do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debug_logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-24 00:00:00.000000000 Z
11
+ date: 2020-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -195,7 +195,7 @@ homepage: https://github.com/pboling/debug_logging
195
195
  licenses:
196
196
  - MIT
197
197
  metadata: {}
198
- post_install_message:
198
+ post_install_message:
199
199
  rdoc_options: []
200
200
  require_paths:
201
201
  - lib
@@ -211,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
211
  version: '0'
212
212
  requirements: []
213
213
  rubygems_version: 3.1.4
214
- signing_key:
214
+ signing_key:
215
215
  specification_version: 4
216
216
  summary: Drop-in debug logging useful when a call stack gets unruly
217
217
  test_files: []