stackify-ruby-apm 1.10.0 → 1.12.3

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stackify_apm/agent.rb +0 -2
  3. data/lib/stackify_apm/config.rb +33 -5
  4. data/lib/stackify_apm/context.rb +4 -1
  5. data/lib/stackify_apm/context/prefix.rb +30 -0
  6. data/lib/stackify_apm/context/request/headers.rb +30 -0
  7. data/lib/stackify_apm/context_builder.rb +1 -0
  8. data/lib/stackify_apm/helper/database_helper.rb +39 -1
  9. data/lib/stackify_apm/instrumenter_helper.rb +90 -4
  10. data/lib/stackify_apm/logger/log_device.rb +7 -5
  11. data/lib/stackify_apm/logger/logger_high_version.rb +5 -1
  12. data/lib/stackify_apm/logger/logger_lower_version.rb +5 -1
  13. data/lib/stackify_apm/middleware.rb +21 -3
  14. data/lib/stackify_apm/normalizers/active_record.rb +16 -8
  15. data/lib/stackify_apm/root_info.rb +7 -1
  16. data/lib/stackify_apm/serializers/transactions.rb +5 -0
  17. data/lib/stackify_apm/span/context.rb +39 -1
  18. data/lib/stackify_apm/spies.rb +4 -2
  19. data/lib/stackify_apm/spies/action_dispatch.rb +6 -1
  20. data/lib/stackify_apm/spies/curb.rb +41 -20
  21. data/lib/stackify_apm/spies/curb/easy.rb +220 -92
  22. data/lib/stackify_apm/spies/curb/multi.rb +26 -12
  23. data/lib/stackify_apm/spies/custom_instrumenter.rb +25 -4
  24. data/lib/stackify_apm/spies/delayed_job.rb +49 -0
  25. data/lib/stackify_apm/spies/httparty.rb +45 -24
  26. data/lib/stackify_apm/spies/httpclient.rb +41 -20
  27. data/lib/stackify_apm/spies/httprb.rb +39 -18
  28. data/lib/stackify_apm/spies/log4r.rb +59 -0
  29. data/lib/stackify_apm/spies/logger.rb +116 -0
  30. data/lib/stackify_apm/spies/logging.rb +65 -0
  31. data/lib/stackify_apm/spies/net_http.rb +38 -20
  32. data/lib/stackify_apm/spies/redis.rb +36 -30
  33. data/lib/stackify_apm/spies/sequel.rb +28 -11
  34. data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +27 -25
  35. data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +27 -24
  36. data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +18 -8
  37. data/lib/stackify_apm/spies/stackify_logger.rb +28 -16
  38. data/lib/stackify_apm/spies/yell.rb +64 -0
  39. data/lib/stackify_apm/util.rb +10 -9
  40. data/lib/stackify_apm/version.rb +1 -1
  41. data/stackify-ruby-apm.gemspec +1 -0
  42. metadata +23 -2
@@ -14,19 +14,33 @@ module StackifyRubyAPM
14
14
  def self.http(urls_with_config, _multi_options = {}, &blk)
15
15
  return http_without_apm(urls_with_config, _multi_options = {}, &blk) unless StackifyRubyAPM.current_transaction
16
16
  http_without_apm(urls_with_config, _multi_options = {}) do |c, code, method|
17
- status_code = code.zero? ? 404 : code
18
- method = method.upcase
19
- uri = c.url.to_s.strip
20
- name = "#{method} #{uri}"
21
- type = "ext.Curb.Multi.#{method}"
17
+ begin
18
+ status_code = code.zero? ? 404 : code
19
+ method = method.upcase
20
+ uri = c.url.to_s.strip
21
+ name = "#{method} #{uri}"
22
+ type = "ext.Curb.Multi.#{method}"
23
+
24
+ ctx = Span::Context.new(
25
+ CATEGORY: 'Web External',
26
+ SUBCATEGORY: 'Execute',
27
+ URL: uri,
28
+ STATUS: status_code,
29
+ METHOD: method
30
+ )
31
+
32
+ if StackifyRubyAPM.agent.config.prefix_enabled
33
+ ctx.update_request_body(c.post_body || "")
34
+ ctx.update_request_headers(c.headers || Hash.new)
35
+ ctx.update_response_body(c.body || "")
36
+ ctx.update_response_headers(c.proxy_headers || Hash.new)
37
+ end
38
+ rescue Exception => e
39
+ StackifyRubyAPM.agent.error "[CurbMultiSpy] Error: creating span context."
40
+ StackifyRubyAPM.agent.error "[CurbMultiSpy] #{e.inspect}"
41
+ return blk.call(c, code, method)
42
+ end
22
43
 
23
- ctx = Span::Context.new(
24
- CATEGORY: 'Web External',
25
- SUBCATEGORY: 'Execute',
26
- URL: uri,
27
- STATUS: status_code,
28
- METHOD: method
29
- )
30
44
  # Creates new span from HTTP result
31
45
  StackifyRubyAPM.span name, type, context: ctx do
32
46
  blk.call(c, code, method)
@@ -24,16 +24,37 @@ module StackifyRubyAPM
24
24
  return unless !to_instrument.nil? && !to_instrument.empty? && defined?(to_instrument['instrumentation']) && (to_instrument['instrumentation'].count > 0)
25
25
 
26
26
  to_instrument['instrumentation'].each do |custom_spy|
27
- current_class = custom_spy['class']
27
+ current_class = defined?(custom_spy['class']) ? custom_spy['class'] : nil
28
+ current_module = defined?(custom_spy['module']) ? custom_spy['module'] : nil
28
29
  current_method = custom_spy['method']
29
30
  tracked_func = custom_spy['trackedFunction']
30
31
  tracked_func_name = defined?(custom_spy['trackedFunctionName']) ? custom_spy['trackedFunctionName'] : ''
31
32
  transaction = defined?(custom_spy['transaction']) ? custom_spy['transaction'] : nil
32
33
  file_path = defined?(custom_spy['file_path']) ? custom_spy['file_path'] : nil
33
34
 
34
- tracked_function_tpl = tracked_func_name.nil? ? '{{ClassName}}.{{MethodName}}' : tracked_func_name
35
- tracked_function_name = tracked_function_tpl.sub '{{ClassName}}', current_class
36
- tracked_function_name = tracked_function_name.sub '{{MethodName}}', current_method
35
+ if current_class
36
+ tracked_function_tpl = tracked_func_name.nil? ? '{{ClassName}}.{{MethodName}}' : tracked_func_name
37
+ tracked_function_name = tracked_function_tpl.to_s.sub '{{ClassName}}', current_class
38
+ tracked_function_name = tracked_function_name.to_s.sub '{{MethodName}}', current_method
39
+ elsif current_module
40
+ tracked_function_tpl = tracked_func_name.nil? ? '{{ModuleName}}.{{MethodName}}' : tracked_func_name
41
+ tracked_function_name = tracked_function_tpl.to_s.sub '{{ModuleName}}', current_module
42
+ tracked_function_name = tracked_function_name.to_s.sub '{{MethodName}}', current_method
43
+ end
44
+
45
+ begin
46
+ if current_module
47
+ if file_path.nil?
48
+ config.logger.send(:info, "[StackifyRubyAPM] Error: Missing file_path in module which is required in custom instrumentation.")
49
+ else
50
+ require file_path
51
+ StackifyRubyAPM::InstrumenterHelper.patched_module(tracked_func, current_module, file_path,
52
+ current_method: current_method, tracked_function_name: tracked_function_name, is_transaction: transaction)
53
+ end
54
+ end
55
+ rescue => e
56
+ throw e
57
+ end
37
58
 
38
59
  if !class_exists?(current_class) && !file_path.nil?
39
60
  begin
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch for the delayed_job class for running async tasks.
4
+ #
5
+
6
+ module StackifyRubyAPM
7
+ # @api private
8
+ module Spies
9
+ # @api private
10
+ class DelayedJobSpy
11
+ def install
12
+ Delayed::Backend::Base.class_eval do
13
+ alias_method 'invoke_job_without_apm', 'invoke_job'
14
+
15
+ def invoke_job(*args, &block)
16
+ ret = nil
17
+ begin
18
+ name = nil
19
+ if payload_object.is_a?(::Delayed::PerformableMethod)
20
+ object = payload_object.object
21
+ klass = object.is_a?(Class) ? object : object.class
22
+ class_name = klass.name
23
+ separator = payload_object.object.is_a?(Class) ? '.' : '#'
24
+ method_name = payload_object.method_name
25
+ name = "#{class_name}#{separator}#{method_name}"
26
+ else
27
+ name = payload_object.class.name
28
+ end
29
+ ctx = StackifyRubyAPM::Context.new
30
+ ctx.category = 'Delayed::Job'
31
+ transaction = StackifyRubyAPM.transaction name, 'TASK', context: ctx
32
+ ret = invoke_job_without_apm(*args, &block)
33
+ rescue StackifyRubyAPM::InternalError
34
+ raise # Don't report StackifyRubyAPM errors
35
+ rescue StandardError => e
36
+ StackifyRubyAPM.report e
37
+ raise e
38
+ ensure
39
+ transaction.submit()
40
+ end
41
+ ret
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ register 'Delayed::Backend::Base', 'delayed/backend/base', DelayedJobSpy.new
48
+ end
49
+ end
@@ -21,34 +21,55 @@ module StackifyRubyAPM
21
21
  def perform_request(http_method, path, options, &block)
22
22
  req = nil
23
23
  return perform_request_without_apm(http_method, path, options, &block) unless StackifyRubyAPM.current_transaction
24
- # Data configuration
25
- #
26
- method = http_method.to_s.gsub('Net::HTTP::', '').upcase
27
- uri = path.strip
28
- name = "#{method} #{uri}"
29
- type = "ext.HTTParty.#{method}"
30
- # Submits HTTP request
31
- #
32
- # req = perform_request_without_apm(http_method, path, options, &block)
33
- # Builds span context
34
- #
35
- # status_code = req.code
36
-
37
- ctx = Span::Context.new(
38
- CATEGORY: 'Web External',
39
- SUBCATEGORY: 'Execute',
40
- URL: uri,
41
- STATUS: '',
42
- METHOD: method
43
- )
24
+
25
+ begin
26
+ # Data configuration
27
+ #
28
+ method = http_method.to_s.gsub('Net::HTTP::', '').upcase
29
+ uri = path.strip
30
+ name = "#{method} #{uri}"
31
+ type = "ext.HTTParty.#{method}"
32
+ # Submits HTTP request
33
+ #
34
+ # req = perform_request_without_apm(http_method, path, options, &block)
35
+ # Builds span context
36
+ #
37
+ # status_code = req.code
38
+
39
+ ctx = Span::Context.new(
40
+ CATEGORY: 'Web External',
41
+ SUBCATEGORY: 'Execute',
42
+ URL: uri,
43
+ STATUS: '',
44
+ METHOD: method
45
+ )
46
+ rescue Exception => e
47
+ StackifyRubyAPM.agent.error "[HTTPartySpy] Error: creating span context."
48
+ StackifyRubyAPM.agent.error "[HTTPartySpy] #{e.inspect}"
49
+ return perform_request_without_apm(http_method, path, options, &block)
50
+ end
51
+
44
52
  # Creates new span from HTTP result
45
53
  #
46
54
  # class_info = { 'classname' => 'httparty', 'hostname' => URI.parse(uri).host }
47
55
  StackifyRubyAPM.span name, type, context: ctx do
48
- req = perform_request_without_apm(http_method, path, options, &block)
49
- status_code = req.code
50
- ctx.update_status(status_code)
51
- req
56
+ res = perform_request_without_apm(http_method, path, options, &block)
57
+
58
+ begin
59
+ status_code = res.code
60
+ ctx.update_status(status_code)
61
+
62
+ if StackifyRubyAPM.agent.config.prefix_enabled
63
+ ctx.update_request_body(options[:body] || "")
64
+ ctx.update_request_headers(options[:headers] || Hash.new)
65
+ ctx.update_response_body(res.body || "")
66
+ ctx.update_response_headers(res.each_header || Hash.new)
67
+ end
68
+ rescue Exception => e
69
+ StackifyRubyAPM.agent.error '[HTTPartySpy] Error: getting status code or updating request/response context.'
70
+ StackifyRubyAPM.agent.error "[HTTPartySpy] #{e.inspect}"
71
+ end
72
+ res
52
73
  end
53
74
  end
54
75
  end
@@ -16,31 +16,52 @@ module StackifyRubyAPM
16
16
  req = nil
17
17
  return request_without_apm(method, uri, *args, &block) unless StackifyRubyAPM.current_transaction
18
18
 
19
- # Data configuration
20
- #
21
- method = method.upcase
22
- uri = uri.strip
23
- name = "#{method} #{uri}"
24
- type = "ext.httpclient.#{method}"
25
-
26
- # Submits HTTP request
27
- #
28
- req = request_without_apm(method, uri, *args, &block)
19
+ begin
20
+ # Data configuration
21
+ #
22
+ method = method.upcase
23
+ uri = uri.strip
24
+ name = "#{method} #{uri}"
25
+ type = "ext.httpclient.#{method}"
29
26
 
30
- # Builds span context
31
- #
32
- ctx = Span::Context.new(
33
- CATEGORY: 'Web External',
34
- SUBCATEGORY: 'Execute',
35
- URL: uri,
36
- STATUS: req.status_code,
37
- METHOD: method
38
- )
27
+ # Builds span context
28
+ #
29
+ ctx = Span::Context.new(
30
+ CATEGORY: 'Web External',
31
+ SUBCATEGORY: 'Execute',
32
+ URL: uri,
33
+ STATUS: '',
34
+ METHOD: method
35
+ )
36
+ rescue Exception => e
37
+ StackifyRubyAPM.agent.error "[HTTPClientSpy] Error: creating span context."
38
+ StackifyRubyAPM.agent.error "[HTTPClientSpy] #{e.inspect}"
39
+ return request_without_apm(method, uri, *args, &block)
40
+ end
39
41
 
40
42
  # Creates new span from HTTP result
41
43
  #
42
44
  StackifyRubyAPM.span name, type, context: ctx do
43
- req
45
+ # Submits HTTP request
46
+ #
47
+ res = request_without_apm(method, uri, *args, &block)
48
+
49
+ begin
50
+ ctx.update_status(res.status_code)
51
+
52
+ if StackifyRubyAPM.agent.config.prefix_enabled
53
+ options = args && args[0] || Hash.new
54
+ ctx.update_request_body(options[:body] || "")
55
+ ctx.update_request_headers(options[:header] || Hash.new)
56
+ ctx.update_response_body(res.body || "")
57
+ ctx.update_response_headers(res.headers || Hash.new)
58
+ end
59
+ rescue Exception => e
60
+ StackifyRubyAPM.agent.error '[HTTPClientSpy] Error: getting status code or updating request/response context.'
61
+ StackifyRubyAPM.agent.error "[HTTPClientSpy] #{e.inspect}"
62
+ end
63
+
64
+ res
44
65
  end
45
66
  end
46
67
  end
@@ -10,28 +10,49 @@ module StackifyRubyAPM
10
10
  class HTTPRbSpy
11
11
  def install
12
12
  HTTP::Client.class_eval do
13
- alias_method 'request_without_apm', 'request'
13
+ alias_method 'perform_without_apm', 'perform'
14
14
 
15
15
  # Make HTTP request
16
- def request(verb, uri, _opts = {})
17
- return request_without_apm(verb, uri, _opts = {}) unless StackifyRubyAPM.current_transaction
18
-
19
- method = verb.upcase
20
- uri = uri.strip
21
- name = "#{method} #{uri}"
22
- type = "ext.httprb.#{method}"
23
-
24
- req = request_without_apm(verb, uri, _opts = {})
25
- ctx = Span::Context.new(
26
- CATEGORY: 'Web External',
27
- SUBCATEGORY: 'Execute',
28
- URL: uri,
29
- STATUS: req.code,
30
- METHOD: method
31
- )
16
+ def perform(req, options)
17
+ return perform_without_apm(req, options) unless StackifyRubyAPM.current_transaction
18
+
19
+ begin
20
+ method = req.verb.upcase
21
+ uri = req.uri.to_s.strip
22
+ name = "#{method} #{uri}"
23
+ type = "ext.httprb.#{method}"
24
+
25
+ ctx = Span::Context.new(
26
+ CATEGORY: 'Web External',
27
+ SUBCATEGORY: 'Execute',
28
+ URL: uri,
29
+ STATUS: '',
30
+ METHOD: method
31
+ )
32
+ rescue Exception => e
33
+ StackifyRubyAPM.agent.error "[HTTPRbSpy] Error: creating span context."
34
+ StackifyRubyAPM.agent.error "[HTTPRbSpy] #{e.inspect}"
35
+ return perform_without_apm(req, options)
36
+ end
32
37
 
33
38
  StackifyRubyAPM.span name, type, context: ctx do
34
- req
39
+ res = perform_without_apm(req, options)
40
+
41
+ begin
42
+ ctx.update_status(res.code)
43
+
44
+ if StackifyRubyAPM.agent.config.prefix_enabled
45
+ ctx.update_request_body(req.body.source || "")
46
+ ctx.update_request_headers(req.headers || Hash.new)
47
+ ctx.update_response_body(res.body || "")
48
+ ctx.update_response_headers(res.headers || Hash.new)
49
+ end
50
+ rescue Exception => e
51
+ StackifyRubyAPM.agent.error '[HTTPRbSpy] Error: getting status code or updating request/response context.'
52
+ StackifyRubyAPM.agent.error "[HTTPRbSpy] #{e.inspect}"
53
+ end
54
+
55
+ res
35
56
  end
36
57
  end
37
58
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Monkey patch for the Log4r::Outputter class for logging log message.
4
+ #
5
+ module StackifyRubyAPM
6
+ module Spies
7
+ class Log4rSpy
8
+ def install
9
+ Log4r::Outputter.module_eval do
10
+ alias_method 'canonical_log_without_apm', 'canonical_log'
11
+
12
+ # Log message formatted to a logevent object
13
+ def canonical_log(logevent)
14
+ return canonical_log_without_apm(logevent) unless StackifyRubyAPM.current_transaction
15
+
16
+ begin
17
+ name = 'log4r'
18
+ type = 'ext.log4r'
19
+ log_message = ''
20
+ exception = nil
21
+ msg = logevent.data
22
+
23
+ case msg
24
+ when ::String
25
+ log_message = msg
26
+ when ::Exception
27
+ log_message = msg.message
28
+ exception = "(#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }"
29
+ else
30
+ log_message = msg.inspect
31
+ end
32
+
33
+ ctx = Span::Context.new(
34
+ CATEGORY: 'Log',
35
+ SUBCATEGORY: 'Log4r',
36
+ LEVEL: Log4r::LNAMES[logevent.level] || 'ANY',
37
+ MESSAGE: log_message
38
+ )
39
+
40
+ if exception
41
+ ctx.EXCEPTION = exception
42
+ end
43
+ rescue Exception => e
44
+ StackifyRubyAPM.agent.error "[Log4rSpy] Error: creating span context."
45
+ StackifyRubyAPM.agent.error "[Log4rSpy] #{e.inspect}"
46
+ return canonical_log_without_apm(logevent)
47
+ end
48
+
49
+ StackifyRubyAPM.span name, type, context: ctx do
50
+ canonical_log_without_apm(logevent)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ register 'Log4r::Outputter', 'log4r', Log4rSpy.new
58
+ end
59
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Monkey patch for the Logger class for logging log messages.
5
+ #
6
+ module StackifyRubyAPM
7
+ module Spies
8
+ class LoggerSpy
9
+ NAME = 'logger'
10
+ TYPE = 'core.devlog'
11
+
12
+ def install
13
+ Logger.class_eval do
14
+ alias_method 'debug_without_apm', 'debug'
15
+ alias_method 'info_without_apm', 'info'
16
+ alias_method 'warn_without_apm', 'warn'
17
+ alias_method 'error_without_apm', 'error'
18
+ alias_method 'fatal_without_apm', 'fatal'
19
+ alias_method 'unknown_without_apm', 'unknown'
20
+
21
+ def debug(progname = nil, &block)
22
+ return debug_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
23
+ ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('DEBUG', nil, progname, &block)
24
+ StackifyRubyAPM.span NAME, TYPE, context: ctx do
25
+ debug_without_apm(progname, &block)
26
+ end
27
+ end
28
+
29
+ def info(progname = nil, &block)
30
+ return info_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
31
+ ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('INFO', nil, progname, &block)
32
+ StackifyRubyAPM.span NAME, TYPE, context: ctx do
33
+ info_without_apm(progname, &block)
34
+ end
35
+ end
36
+
37
+ def warn(progname = nil, &block)
38
+ return warn_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
39
+ ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('WARN', nil, progname, &block)
40
+ StackifyRubyAPM.span NAME, TYPE, context: ctx do
41
+ warn_without_apm(progname, &block)
42
+ end
43
+ end
44
+
45
+ def error(progname = nil, &block)
46
+ return error_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
47
+ ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('ERROR', nil, progname, &block)
48
+ StackifyRubyAPM.span NAME, TYPE, context: ctx do
49
+ error_without_apm(progname, &block)
50
+ end
51
+ end
52
+
53
+ def fatal(progname = nil, &block)
54
+ return fatal_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
55
+ ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('FATAL', nil, progname, &block)
56
+ StackifyRubyAPM.span NAME, TYPE, context: ctx do
57
+ fatal_without_apm(progname, &block)
58
+ end
59
+ end
60
+
61
+ def unknown(progname = nil, &block)
62
+ return unknown_without_apm(progname, &block) unless StackifyRubyAPM.current_transaction
63
+ ctx = StackifyRubyAPM::Spies::LoggerSpy.get_log_context('UNKNOWN', nil, progname, &block)
64
+ StackifyRubyAPM.span NAME, TYPE, context: ctx do
65
+ unknown_without_apm(progname, &block)
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # create and return logger span context
72
+ def self.get_log_context(severity, message, progname)
73
+ ctx = nil
74
+
75
+ begin
76
+ log_message = ''
77
+ exception = nil
78
+
79
+ if message.nil?
80
+ msg = progname
81
+ else
82
+ msg = message
83
+ end
84
+
85
+ case msg
86
+ when ::String
87
+ log_message = msg
88
+ when ::Exception
89
+ log_message = msg.message
90
+ exception = "(#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }"
91
+ else
92
+ log_message = msg.inspect
93
+ end
94
+
95
+ ctx = Span::Context.new(
96
+ CATEGORY: 'Log',
97
+ SUBCATEGORY: 'Logger',
98
+ LEVEL: severity || 'ANY',
99
+ MESSAGE: log_message
100
+ )
101
+
102
+ if exception
103
+ ctx.EXCEPTION = exception
104
+ end
105
+ rescue Exception => e
106
+ StackifyRubyAPM.agent.error "[LoggerSpy] Error: creating span context."
107
+ StackifyRubyAPM.agent.error "[LoggerSpy] #{e.inspect}"
108
+ end
109
+
110
+ ctx
111
+ end
112
+ end
113
+
114
+ register 'Logger', 'logger', LoggerSpy.new
115
+ end
116
+ end