stackify-ruby-apm 0.9.0 → 1.0.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +18 -0
  3. data/Gemfile.lock +181 -8
  4. data/LICENSE.md +66 -0
  5. data/README.md +5 -3
  6. data/Rakefile +4 -0
  7. data/docker-compose.yml +46 -0
  8. data/lib/stackify/agent.rb +22 -9
  9. data/lib/stackify/config.rb +65 -29
  10. data/lib/stackify/context/response.rb +13 -0
  11. data/lib/stackify/context_builder.rb +0 -2
  12. data/lib/stackify/error.rb +2 -2
  13. data/lib/stackify/error_builder.rb +1 -1
  14. data/lib/stackify/instrumenter.rb +4 -4
  15. data/lib/stackify/logger/logger_high_version.rb +65 -0
  16. data/lib/stackify/logger/logger_lower_version.rb +63 -0
  17. data/lib/stackify/middleware.rb +0 -4
  18. data/lib/stackify/normalizers.rb +0 -1
  19. data/lib/stackify/normalizers/active_record.rb +3 -1
  20. data/lib/stackify/railtie.rb +0 -1
  21. data/lib/stackify/serializers/errors.rb +8 -7
  22. data/lib/stackify/serializers/transactions.rb +3 -2
  23. data/lib/stackify/span.rb +1 -2
  24. data/lib/stackify/span/context.rb +5 -1
  25. data/lib/stackify/spies/action_dispatch.rb +8 -1
  26. data/lib/stackify/spies/httpclient.rb +14 -5
  27. data/lib/stackify/spies/httprb.rb +45 -0
  28. data/lib/stackify/spies/mongo.rb +13 -1
  29. data/lib/stackify/spies/net_http.rb +14 -3
  30. data/lib/stackify/spies/redis.rb +66 -0
  31. data/lib/stackify/spies/sinatra.rb +3 -1
  32. data/lib/stackify/spies/sinatra_activerecord/mysql_adapter.rb +177 -0
  33. data/lib/stackify/spies/sinatra_activerecord/postgresql_adapter.rb +96 -0
  34. data/lib/stackify/spies/sinatra_activerecord/sqlite_adapter.rb +48 -0
  35. data/lib/stackify/spies/tilt.rb +8 -1
  36. data/lib/stackify/stacktrace_builder.rb +4 -3
  37. data/lib/stackify/subscriber.rb +4 -4
  38. data/lib/stackify/trace_logger.rb +6 -23
  39. data/lib/stackify/transaction.rb +10 -1
  40. data/lib/stackify/util.rb +0 -13
  41. data/lib/stackify/version.rb +1 -1
  42. data/lib/stackify/worker.rb +5 -5
  43. data/lib/stackify_ruby_apm.rb +2 -6
  44. data/run-test.sh +75 -0
  45. data/stackify-ruby-apm.gemspec +0 -1
  46. metadata +12 -17
  47. data/lib/stackify/logger.rb +0 -10
@@ -37,11 +37,13 @@ module StackifyRubyAPM
37
37
  "oracle"
38
38
  elsif driver.include? "db2"
39
39
  "db2"
40
+ elsif driver.include? "sqlite"
41
+ "sqlite"
40
42
  end
41
43
  end
42
44
 
43
45
  def query_variables(payload)
44
- debug "@stackify_ruby [SqlNormalizer] [normalizers/active_record.rb] query_variables payload:"
46
+ debug "[SqlNormalizer] query_variables payload:"
45
47
  debug payload.inspect
46
48
 
47
49
  if payload[:type_casted_binds]
@@ -24,7 +24,6 @@ module StackifyRubyAPM
24
24
  # This will overwrite the default values of the config based from stackify_apm.yml initialized
25
25
 
26
26
  initializer 'stackify_apm.initialize' do |app|
27
- # puts '@stackify_ruby [Railtie] [lib/railtie.rb] initializer'
28
27
  config = app.config.stackify.merge(app: app).tap do |c|
29
28
  # Prepend Rails.root to log_path if present
30
29
  if c.log_path && !c.log_path.start_with?('/')
@@ -10,14 +10,15 @@ module StackifyRubyAPM
10
10
  def build(error)
11
11
 
12
12
  if (exception = error.exception)
13
+ current_timestamp = error.timestamp
13
14
 
14
- base = {
15
- CaughtBy: exception.module,
16
- Exception: exception.type,
17
- Message: exception.message,
18
- Timestamp: micros_to_time(error.timestamp).utc.iso8601(3),
19
- Frames: exception.stacktrace.to_a,
20
- }
15
+ base = {
16
+ CaughtBy: exception.module,
17
+ Exception: exception.type,
18
+ Message: exception.message,
19
+ Timestamp: "#{current_timestamp.round}",
20
+ Frames: exception.stacktrace.to_a,
21
+ }
21
22
 
22
23
  end
23
24
 
@@ -17,7 +17,9 @@ module StackifyRubyAPM
17
17
  call: transaction.name,
18
18
  reqBegin: transaction.timestamp,
19
19
  reqEnd: transaction.duration,
20
- props: RootInfo.build(config, transaction)
20
+ props: RootInfo.build(config, transaction),
21
+ exceptions: transaction.exceptions,
22
+ stacks: []
21
23
  }
22
24
 
23
25
  # serialize all the spans
@@ -27,7 +29,6 @@ module StackifyRubyAPM
27
29
  end
28
30
 
29
31
  add_children_spans(root_span_json, children_spans_json)
30
-
31
32
  root_span_json
32
33
  end
33
34
 
data/lib/stackify/span.rb CHANGED
@@ -27,7 +27,6 @@ module StackifyRubyAPM
27
27
  @parent_id = parent_id
28
28
  @context = context
29
29
  @http_status = http_status
30
-
31
30
  @stacktrace = nil
32
31
  @original_backtrace = nil
33
32
  end
@@ -68,4 +67,4 @@ module StackifyRubyAPM
68
67
  private
69
68
 
70
69
  end
71
- end
70
+ end
@@ -20,7 +20,11 @@ module StackifyRubyAPM
20
20
  :METHOD,
21
21
  :URL,
22
22
  :STATUS,
23
- :PROVIDER
23
+ :PROVIDER,
24
+ :OPERATION,
25
+ :CACHEKEY,
26
+ :CACHENAME,
27
+ :THREAD_ID
24
28
  end
25
29
  end
26
30
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
- # This module monkey patches usually in Exceptions class
2
+ #
3
+ # Monkey patch for the ActionDispatch::ShowExceptions class which handles exception events.
4
+ #
5
+
3
6
  module StackifyRubyAPM
4
7
  # @api private
5
8
  module Spies
@@ -10,6 +13,8 @@ module StackifyRubyAPM
10
13
  alias render_exception_without_apm render_exception
11
14
 
12
15
  def render_exception(env, exception)
16
+ # Creates exception log report
17
+ #
13
18
  StackifyRubyAPM.report(exception)
14
19
  render_exception_without_apm env, exception
15
20
  end
@@ -17,6 +22,8 @@ module StackifyRubyAPM
17
22
  end
18
23
  end
19
24
 
25
+ # Registers ActionDispatch spy, go to: /stackify/spies.rb
26
+ #
20
27
  register(
21
28
  'ActionDispatch::ShowExceptions',
22
29
  'action_dispatch/show_exception',
@@ -1,14 +1,11 @@
1
- require 'stackify/log'
2
- require 'stackify/logger'
1
+ # Monkey patch for the HTTPClient class for sending & receiving HTTP responses.
2
+ #
3
3
 
4
4
  module StackifyRubyAPM
5
5
  # @api private
6
6
  module Spies
7
7
  # @api private
8
8
  class HTTPClientSpy
9
- include Log
10
-
11
- # This class monkeypatches the HTTPClient class with request method.
12
9
  def install
13
10
  HTTPClient.class_eval do
14
11
  alias request_without_apm request
@@ -18,12 +15,20 @@ module StackifyRubyAPM
18
15
  unless StackifyRubyAPM.current_transaction
19
16
  return request_without_apm(method, uri, *args, &block)
20
17
  end
18
+
19
+ # Data configuration
20
+ #
21
21
  method = method.upcase
22
22
  uri = uri.strip
23
23
  name = "#{method} #{uri}"
24
24
  type = "ext.httpclient.#{method}"
25
25
 
26
+ # Submits HTTP request
27
+ #
26
28
  req = request_without_apm(method, uri, *args, &block)
29
+
30
+ # Builds span context
31
+ #
27
32
  ctx = Span::Context.new(
28
33
  CATEGORY: 'Web External',
29
34
  SUBCATEGORY: 'Execute',
@@ -32,6 +37,8 @@ module StackifyRubyAPM
32
37
  METHOD: method
33
38
  )
34
39
 
40
+ # Creates new span from HTTP result
41
+ #
35
42
  StackifyRubyAPM.span name, type, context: ctx do
36
43
  req
37
44
  end
@@ -42,6 +49,8 @@ module StackifyRubyAPM
42
49
  # rubocop:enable Metrics/MethodLength
43
50
  end
44
51
 
52
+ # Registers HTTPClient spy, go to: /stackify/spies.rb
53
+ #
45
54
  register 'HTTPClient', 'httpclient', HTTPClientSpy.new
46
55
  end
47
56
  end
@@ -0,0 +1,45 @@
1
+ #
2
+ # This class monkey patch the http.rb gem with the request method.
3
+ #
4
+ module StackifyRubyAPM
5
+ # @api private
6
+ module Spies
7
+ # @api private
8
+ class HTTPRbSpy
9
+
10
+ def install
11
+ HTTP::Client.class_eval do
12
+ alias request_without_apm request
13
+
14
+ # Make HTTP request
15
+ def request(verb, uri, opts = {})
16
+ unless StackifyRubyAPM.current_transaction
17
+ return request_without_apm(verb, uri, opts = {})
18
+ end
19
+
20
+ method = verb.upcase
21
+ uri = uri.strip
22
+ name = "#{method} #{uri}"
23
+ type = "ext.httprb.#{method}"
24
+
25
+ req = request_without_apm(verb, uri, opts = {})
26
+ ctx = Span::Context.new(
27
+ CATEGORY: 'Web External',
28
+ SUBCATEGORY: 'Execute',
29
+ URL: uri,
30
+ STATUS: req.code,
31
+ METHOD: method
32
+ )
33
+
34
+ StackifyRubyAPM.span name, type, context: ctx do
35
+ req
36
+ end
37
+ end
38
+ end
39
+ end
40
+ # rubocop:enable Metrics/MethodLength
41
+ end
42
+
43
+ register 'HTTP::Client', 'http/client', HTTPRbSpy.new
44
+ end
45
+ end
@@ -1,8 +1,11 @@
1
+ # Monkey patch for the Mongo::Monitoring::Global module in mongoDB(http://api.mongodb.com/ruby/current/Mongo/Monitoring/Global.html)
2
+ # -> Allows subscribing to events for all Mongo clients.
3
+ #
4
+
1
5
  module StackifyRubyAPM
2
6
  # @api private
3
7
  module Spies
4
8
  # @api private
5
- # This class monkeypatch the Mongo::Monitoring::Global module in mongoDB(http://api.mongodb.com/ruby/current/Mongo/Monitoring/Global.html)
6
9
  class MongoSpy
7
10
  def install
8
11
  ::Mongo::Monitoring::Global.subscribe(
@@ -36,6 +39,8 @@ module StackifyRubyAPM
36
39
  def push_event(event)
37
40
  return unless StackifyRubyAPM.current_transaction
38
41
 
42
+ # Fetches collection name from Mongo event
43
+ #
39
44
  document = event.command.find
40
45
  col = nil
41
46
  if document
@@ -44,10 +49,15 @@ module StackifyRubyAPM
44
49
  end
45
50
  end
46
51
 
52
+ # Builds span context
53
+ #
47
54
  ctx = Span::Context.new(
48
55
  CATEGORY: 'MongoDB',
49
56
  MONGODB_COLLECTION: col,
50
57
  )
58
+
59
+ # Creates new span from Mongo event
60
+ #
51
61
  span = StackifyRubyAPM.span(event.command_name, TYPE, context: ctx)
52
62
  @events[event.operation_id] = span
53
63
  end
@@ -61,6 +71,8 @@ module StackifyRubyAPM
61
71
  end
62
72
  end
63
73
 
74
+ # Registers Mongo spy, go to: /stackify/spies.rb
75
+ #
64
76
  register 'Mongo', 'mongo', MongoSpy.new
65
77
  end
66
78
  end
@@ -1,9 +1,11 @@
1
+ # Monkey patch for the Net::HTTP class for sending & receiving HTTP responses.
2
+ #
3
+
1
4
  module StackifyRubyAPM
2
5
  # @api private
3
6
  module Spies
4
7
  # @api private
5
8
  class NetHTTPSpy
6
- # This class monkeypatch the Net::HTTP class with request method.
7
9
  def install
8
10
  Net::HTTP.class_eval do
9
11
  alias request_without_apm request
@@ -14,6 +16,8 @@ module StackifyRubyAPM
14
16
  request_without_apm(req, body, &block)
15
17
  end
16
18
 
19
+ # Data configuration
20
+ #
17
21
  host, = req['host'] && req['host'].split(':')
18
22
  method = req.method
19
23
 
@@ -22,16 +26,22 @@ module StackifyRubyAPM
22
26
  name = "#{method} #{host}"
23
27
  type = "ext.net_http.#{method}"
24
28
 
29
+ # Submits HTTP request
30
+ #
25
31
  result = request_without_apm(req, body, &block)
26
32
 
33
+ # Builds span context
34
+ #
27
35
  ctx = Span::Context.new(
28
36
  CATEGORY: 'Web External',
29
37
  SUBCATEGORY: 'Execute',
30
- URL: req.uri,
38
+ URL: req.uri.nil? ? host : req.uri,
31
39
  STATUS: result.code.to_i,
32
40
  METHOD: method
33
41
  )
34
42
 
43
+ # Creates new span from HTTP result
44
+ #
35
45
  StackifyRubyAPM.span name, type, context: ctx do
36
46
  return result
37
47
  end
@@ -41,7 +51,8 @@ module StackifyRubyAPM
41
51
  # rubocop:enable Metrics/MethodLength
42
52
  end
43
53
 
44
-
54
+ # Registers Net::HTTP spy, go to: /stackify/spies.rb
55
+ #
45
56
  register 'Net::HTTP', 'net/http', NetHTTPSpy.new
46
57
  end
47
58
  end
@@ -0,0 +1,66 @@
1
+ # Monkey patch for Redis.
2
+ #
3
+
4
+ module StackifyRubyAPM
5
+ # @api private
6
+ module Spies
7
+ # @api private
8
+ class RedisSpy
9
+
10
+ def install
11
+ ::Redis::Client.class_eval do
12
+ alias call_without_apm call
13
+
14
+ def call(command, &block)
15
+ name = command[0].upcase.to_s
16
+ type = 'db.redis'
17
+ redis_details = command[1].is_a?(String) ? command[1].split(":") : []
18
+ redis_nspace = !redis_details.blank? ? redis_details[0] : ""
19
+ redis_key = ""
20
+
21
+ # Checks CACHEKEY value
22
+ if !redis_details.blank? && redis_details[1]
23
+ # Initially sets the CACHEKEY value
24
+ args = redis_details[1].split("/")
25
+ redis_key = args[0]
26
+
27
+ # If command has passed __method__, it will be included in the CACHEKEY value
28
+ # Possible formats:
29
+ # `<namespace:key/method_name/expires_in=300/ttl=60/>`
30
+ # `<namespace:key/expires_in=300/ttl=60/>`
31
+ if args.length > 1 && !args[1].include?("=")
32
+ redis_key = args[0..1].join("/")
33
+ end
34
+ end
35
+
36
+ return call_without_apm(command, &block) if command[0] == :auth
37
+
38
+ context = {
39
+ PROVIDER: 'Redis',
40
+ CATEGORY: 'Cache',
41
+ SUBCATEGORY: 'Execute',
42
+ COMPONENT_CATEGORY: 'Cache',
43
+ COMPONENT_DETAIL: 'Execute',
44
+ THREAD_ID: Thread.current.object_id,
45
+ OPERATION: name
46
+ }.tap do |hash|
47
+ hash[:CACHEKEY] = redis_key unless redis_key.empty?
48
+ hash[:CACHENAME] = redis_nspace unless redis_nspace.empty?
49
+ end
50
+
51
+ ctx = Span::Context.new(context)
52
+
53
+ StackifyRubyAPM.span name, type, context: ctx do
54
+ call_without_apm(command, &block)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ # Registers Redis spy, go to: /stackify/spies.rb
63
+ #
64
+ register 'Redis', 'redis', RedisSpy.new
65
+ end
66
+ end
@@ -25,7 +25,7 @@ module StackifyRubyAPM
25
25
  end
26
26
  end
27
27
 
28
- # Tilt engine templating
28
+ # Tilt engine template
29
29
  #
30
30
  def compile_template(engine, data, opts, *args, &block)
31
31
  opts[:__stackify_apm_template_name] =
@@ -45,6 +45,8 @@ module StackifyRubyAPM
45
45
  #
46
46
  register 'Sinatra::Base', 'sinatra/base', SinatraSpy.new
47
47
 
48
+ # Tilt template helper & span creator
49
+ #
48
50
  require 'stackify/spies/tilt'
49
51
  end
50
52
  end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+ # Spies for active record when sinatra framework is used and active_record is being extended. (mysql adapter)
3
+ #
4
+ module StackifyRubyAPM
5
+ # @api private
6
+ module Spies
7
+ # @api private
8
+ class MysqlAdapterSpy
9
+ TYPE = 'db.sinatra_active_record.sql'.freeze
10
+ if ActiveRecord::VERSION::MAJOR.to_i >= 5
11
+ # rubocop:disable Metrics/MethodLength
12
+ def install
13
+ ActiveRecord::ConnectionAdapters::MySQL::DatabaseStatements.class_eval do
14
+ alias exec_query_without_apm exec_query
15
+ alias exec_delete_without_apm exec_delete
16
+ alias exec_update_without_apm exec_update
17
+
18
+ def exec_update(sql, name = nil, binds = [])
19
+ result = nil
20
+ unless StackifyRubyAPM.current_transaction
21
+ exec_update_without_apm(sql, name, binds)
22
+ end
23
+
24
+ ctx = Span::Context.new(
25
+ CATEGORY: 'Database',
26
+ SUBCATEGORY: 'Execute',
27
+ COMPONENT_CATEGORY: 'DB Query',
28
+ COMPONENT_DETAIL: 'Execute SQL Query',
29
+ SQL: sql,
30
+ PROVIDER: "mysql"
31
+ )
32
+ result = exec_update_without_apm(sql, name, binds)
33
+ StackifyRubyAPM.span name, TYPE, context: ctx do
34
+ return result
35
+ end
36
+ end
37
+
38
+ def exec_delete(sql, name = nil, binds = [])
39
+ result = nil
40
+ unless StackifyRubyAPM.current_transaction
41
+ exec_delete_without_apm(sql, name, binds)
42
+ end
43
+
44
+ ctx = Span::Context.new(
45
+ CATEGORY: 'Database',
46
+ SUBCATEGORY: 'Execute',
47
+ COMPONENT_CATEGORY: 'DB Query',
48
+ COMPONENT_DETAIL: 'Execute SQL Query',
49
+ SQL: sql,
50
+ PROVIDER: "mysql"
51
+ )
52
+
53
+ result = exec_delete_without_apm(sql, name, binds)
54
+ StackifyRubyAPM.span name, TYPE, context: ctx do
55
+ return result
56
+ end
57
+ end
58
+
59
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
60
+ result = nil
61
+ unless StackifyRubyAPM.current_transaction
62
+ exec_query_without_apm(sql, name, binds)
63
+ end
64
+
65
+ ctx = Span::Context.new(
66
+ CATEGORY: 'Database',
67
+ SUBCATEGORY: 'Execute',
68
+ COMPONENT_CATEGORY: 'DB Query',
69
+ COMPONENT_DETAIL: 'Execute SQL Query',
70
+ SQL: sql,
71
+ PROVIDER: "mysql"
72
+ )
73
+
74
+ result = exec_query_without_apm(sql, name, binds)
75
+ StackifyRubyAPM.span name, TYPE, context: ctx do
76
+ return result
77
+ end
78
+ end
79
+ end
80
+ end
81
+ # rubocop:enable Metrics/MethodLength
82
+ else
83
+ # rubocop:disable Metrics/MethodLength
84
+ def install
85
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
86
+ alias exec_query_without_apm exec_query
87
+ alias exec_delete_without_apm exec_delete
88
+ alias exec_update_without_apm exec_update
89
+ alias exec_insert_without_apm exec_insert
90
+
91
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
92
+ result = nil
93
+ unless StackifyRubyAPM.current_transaction
94
+ exec_insert_without_apm(sql, name, binds, pk = nil, sequence_name = nil)
95
+ end
96
+ ctx = Span::Context.new(
97
+ CATEGORY: 'Database',
98
+ SUBCATEGORY: 'Execute',
99
+ COMPONENT_CATEGORY: 'DB Query',
100
+ COMPONENT_DETAIL: 'Execute SQL Query',
101
+ SQL: sql,
102
+ PROVIDER: "mysql"
103
+ )
104
+ result = exec_insert_without_apm(sql, name, binds, pk = nil, sequence_name = nil)
105
+ StackifyRubyAPM.span name, TYPE, context: ctx do
106
+ return result
107
+ end
108
+ end
109
+
110
+ def exec_update(sql, name, binds)
111
+ result = nil
112
+ unless StackifyRubyAPM.current_transaction
113
+ exec_update_without_apm(sql, name, binds)
114
+ end
115
+ ctx = Span::Context.new(
116
+ CATEGORY: 'Database',
117
+ SUBCATEGORY: 'Execute',
118
+ COMPONENT_CATEGORY: 'DB Query',
119
+ COMPONENT_DETAIL: 'Execute SQL Query',
120
+ SQL: sql,
121
+ PROVIDER: "mysql"
122
+ )
123
+ result = exec_update_without_apm(sql, name, binds)
124
+ StackifyRubyAPM.span name, TYPE, context: ctx do
125
+ return result
126
+ end
127
+ end
128
+
129
+ def exec_delete(sql, name, binds)
130
+ result = nil
131
+ unless StackifyRubyAPM.current_transaction
132
+ exec_delete_without_apm(sql, name, binds)
133
+ end
134
+ ctx = Span::Context.new(
135
+ CATEGORY: 'Database',
136
+ SUBCATEGORY: 'Execute',
137
+ COMPONENT_CATEGORY: 'DB Query',
138
+ COMPONENT_DETAIL: 'Execute SQL Query',
139
+ SQL: sql,
140
+ PROVIDER: "mysql"
141
+ )
142
+ result = exec_delete_without_apm(sql, name, binds)
143
+ StackifyRubyAPM.span name, TYPE, context: ctx do
144
+ return result
145
+ end
146
+ end
147
+
148
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
149
+ result = nil
150
+ unless StackifyRubyAPM.current_transaction
151
+ exec_query_without_apm(sql, name, binds)
152
+ end
153
+ ctx = Span::Context.new(
154
+ CATEGORY: 'Database',
155
+ SUBCATEGORY: 'Execute',
156
+ COMPONENT_CATEGORY: 'DB Query',
157
+ COMPONENT_DETAIL: 'Execute SQL Query',
158
+ SQL: sql,
159
+ PROVIDER: "mysql"
160
+ )
161
+ result = exec_query_without_apm(sql, name, binds)
162
+ StackifyRubyAPM.span name, TYPE, context: ctx do
163
+ return result
164
+ end
165
+ end
166
+ end
167
+ end
168
+ # rubocop:enable Metrics/MethodLength
169
+ end
170
+ end
171
+ if ActiveRecord::VERSION::MAJOR.to_i >= 5
172
+ register 'ActiveRecord::ConnectionAdapters::MySQL::DatabaseStatements', 'active_record/connection_adapters/mysql/database_statements', MysqlAdapterSpy.new
173
+ else
174
+ register 'ActiveRecord::ConnectionAdapters::Mysql2Adapter', 'active_record/connection_adapters/mysql2_adapter', MysqlAdapterSpy.new
175
+ end
176
+ end
177
+ end