scout_apm 4.0.3 → 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +4 -0
  3. data/.travis.yml +3 -0
  4. data/CHANGELOG.markdown +30 -0
  5. data/gems/typhoeus.gemfile +3 -0
  6. data/lib/scout_apm/agent/preconditions.rb +3 -3
  7. data/lib/scout_apm/auto_instrument/rails.rb +0 -1
  8. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -1
  9. data/lib/scout_apm/background_job_integrations/faktory.rb +103 -0
  10. data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
  11. data/lib/scout_apm/background_job_integrations/sidekiq.rb +13 -2
  12. data/lib/scout_apm/config.rb +4 -0
  13. data/lib/scout_apm/environment.rb +2 -1
  14. data/lib/scout_apm/error_service/middleware.rb +2 -2
  15. data/lib/scout_apm/error_service/payload.rb +1 -1
  16. data/lib/scout_apm/error_service.rb +3 -1
  17. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -1
  18. data/lib/scout_apm/ignored_uris.rb +3 -1
  19. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +5 -2
  20. data/lib/scout_apm/instruments/action_view.rb +13 -5
  21. data/lib/scout_apm/instruments/active_record.rb +2 -0
  22. data/lib/scout_apm/instruments/typhoeus.rb +8 -6
  23. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
  24. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +2 -0
  25. data/lib/scout_apm/logger.rb +1 -1
  26. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +11 -25
  27. data/lib/scout_apm/tracer.rb +2 -2
  28. data/lib/scout_apm/utils/sql_sanitizer.rb +30 -6
  29. data/lib/scout_apm/version.rb +1 -1
  30. data/lib/scout_apm.rb +2 -0
  31. data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
  32. data/test/unit/auto_instrument/hanging_method.rb +6 -0
  33. data/test/unit/auto_instrument_test.rb +8 -0
  34. data/test/unit/ignored_uris_test.rb +6 -0
  35. data/test/unit/instruments/typhoeus_test.rb +42 -0
  36. data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
  37. data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
  38. data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
  39. data/test/unit/sql_sanitizer_test.rb +17 -2
  40. data/test/unit/tracer_test.rb +25 -0
  41. metadata +11 -8
  42. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -32
  43. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b992479d1f96a3acca3f9cdbbafad0eb87e6fc88ec53e5de8f6360d01fe21eae
4
- data.tar.gz: 2bd72c79abe8628029ac8c541e0fccff18e54fe5d43558148daebe95e7418e0c
3
+ metadata.gz: dd64c67ede26abdf0cf372b50d0cf025531d5719353b033160e9497fe2abf94f
4
+ data.tar.gz: c31ae7044cce7eb1d869ef8d1130f269b17b53864c9ad8f2c114d822478a1eda
5
5
  SHA512:
6
- metadata.gz: edf84c2a3b8a60961e9f3452b2ca0f10b963501becd9d30889a0acb671ac39267ee44e31279a78ae6a6375365e9c06c9f8d24b8c8f426fa2e512be6c5b141a1f
7
- data.tar.gz: 713bd41d14c74e0655e38002195355263f9413fab013deddb67019437c193169968d26b6a33a058cf8480b8249bd7cf87a6ccf530f2a9b319c8fd1b5875af8e7
6
+ metadata.gz: 9c12b178b6b6f4ee7ea6add58a5e2b13d362684c7825441e7164dcb878ec75befa4312efd7566dab95f5461401f4591eaa2bc86f477ec4776fed175bdb3f6321
7
+ data.tar.gz: 3b832fc6ab0c0e676d574e29e37f15f849ddf886ea1d76972bdf81b42431d9daab5caacbc2867b2b22dfb71c3a31ae94d6b64372a297de0f5ec384d7d9ef45bc
@@ -26,6 +26,9 @@ jobs:
26
26
  - ruby: 2.4
27
27
  - ruby: 2.5
28
28
  - ruby: 2.6
29
+ - ruby: 2.6
30
+ gemfile: gems/typhoeus.gemfile
31
+ test_features: "typhoeus"
29
32
  - ruby: 2.6
30
33
  gemfile: gems/octoshark.gemfile
31
34
  - ruby: 2.6
@@ -36,6 +39,7 @@ jobs:
36
39
 
37
40
  env:
38
41
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
42
+ SCOUT_TEST_FEATURES: ${{ matrix.test_features }}
39
43
 
40
44
  runs-on: ubuntu-latest
41
45
 
data/.travis.yml CHANGED
@@ -13,6 +13,9 @@ matrix:
13
13
  - rvm: 2.6
14
14
  - rvm: 2.7
15
15
  - rvm: 3.0
16
+ - rvm: 2.6
17
+ gemfile: gems/typhoeus.gemfile
18
+ env: "SCOUT_TEST_FEATURES=typhoeus"
16
19
  - rvm: 2.6
17
20
  gemfile: gems/octoshark.gemfile
18
21
  - rvm: 2.6
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,33 @@
1
+ # 4.1.2
2
+
3
+ * Add record_queue_time configuration (PR #422)
4
+
5
+ # 4.1.1
6
+
7
+ * Fix issue with Typheous Hydra instrument (#418)
8
+
9
+ # 4.1.0
10
+
11
+ * Preload Celluloid in Shoryuken instrumentation (#331)
12
+ * Fix deprecation warning in Rails 6.1+ (#365)
13
+ * Set Typheous's desc more directly (#392)
14
+ * Delegate to ActiveRecord #log more intelligently (#394)
15
+ * Don't delay starting agent when possible (#397)
16
+ * Fix template naming issue in Rails 6+ (#399)
17
+ * Avoid double-counting issue with AutoInstruments (#405)
18
+ * Renaming test files for Remote::{Server|Route|Message} to be included in test run (#409)
19
+ * More robust naming of Sidekiq jobs (#412)
20
+ * Allow render_template instruments to work with older Rails (#413)
21
+ * Fix function to manually capture exceptions (#415)
22
+ * Enhance SQL Sanitization (#417)
23
+
24
+ # 4.0.4
25
+
26
+ * Add Faktory Support (#385)
27
+ * Remove Regexp hack for 1.8.7 (no longer supported) (#384)
28
+ * More robust DelayedJob detection (#382)
29
+ * Fix kwargs handling in Tracing module (#381)
30
+
1
31
  # 4.0.3
2
32
 
3
33
  * Handle edge case with nil Typhoeus current-layer (#380)
@@ -0,0 +1,3 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem 'typhoeus'
@@ -27,10 +27,10 @@ module ScoutApm
27
27
  PRECONDITION_DETECTED_SERVER = {
28
28
  :message => proc {|environ| "Deferring agent start. Standing by for first request" },
29
29
  :check => proc { |context|
30
- app_server_missing = !context.environment.app_server_integration(true).found?
31
- background_job_missing = context.environment.background_job_integrations.length == 0
30
+ app_server_found = context.environment.app_server_integration(true).found?
31
+ background_job_integration_found = context.environment.background_job_integrations.length > 0
32
32
 
33
- !app_server_missing && !background_job_missing
33
+ app_server_found || background_job_integration_found
34
34
  },
35
35
  :severity => :info,
36
36
  },
@@ -69,7 +69,6 @@ module ScoutApm
69
69
  end
70
70
 
71
71
  method_name = @method.last.children[0]
72
- class_name = @scope.last.children[1]
73
72
  bt = ["#{file_name}:#{line}:in `#{method_name}'"]
74
73
 
75
74
  return [
@@ -11,7 +11,7 @@ module ScoutApm
11
11
  end
12
12
 
13
13
  def present?
14
- defined?(::Delayed::Job)
14
+ defined?(::Delayed::Worker)
15
15
  end
16
16
 
17
17
  def forking?
@@ -0,0 +1,103 @@
1
+ module ScoutApm
2
+ module BackgroundJobIntegrations
3
+ class Faktory
4
+ attr_reader :logger
5
+
6
+ def name
7
+ :faktory
8
+ end
9
+
10
+ def present?
11
+ defined?(::Faktory)
12
+ end
13
+
14
+ def forking?
15
+ false
16
+ end
17
+
18
+ def install
19
+ add_middleware
20
+ install_processor
21
+ end
22
+
23
+ def add_middleware
24
+ ::Faktory.configure_worker do |config|
25
+ config.worker_middleware do |chain|
26
+ chain.add FaktoryMiddleware
27
+ end
28
+ end
29
+ end
30
+
31
+ def install_processor
32
+ require 'faktory/processor' # sidekiq v4 has not loaded this file by this point
33
+
34
+ ::Faktory::Processor.class_eval do
35
+ def initialize_with_scout(*args)
36
+ agent = ::ScoutApm::Agent.instance
37
+ agent.start
38
+ initialize_without_scout(*args)
39
+ end
40
+
41
+ alias_method :initialize_without_scout, :initialize
42
+ alias_method :initialize, :initialize_with_scout
43
+ end
44
+ end
45
+ end
46
+
47
+ # We insert this middleware into the Sidekiq stack, to capture each job,
48
+ # and time them.
49
+ class FaktoryMiddleware
50
+ def call(worker_instance, job)
51
+ queue = job["queue"]
52
+
53
+ req = ScoutApm::RequestManager.lookup
54
+ req.annotate_request(:queue_latency => latency(job))
55
+
56
+ begin
57
+ req.start_layer(ScoutApm::Layer.new('Queue', queue))
58
+ started_queue = true
59
+ req.start_layer(ScoutApm::Layer.new('Job', job_class(job)))
60
+ started_job = true
61
+
62
+ yield
63
+ rescue
64
+ req.error!
65
+ raise
66
+ ensure
67
+ req.stop_layer if started_job
68
+ req.stop_layer if started_queue
69
+ end
70
+ end
71
+
72
+ UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
73
+ ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::FaktoryAdapter::JobWrapper'.freeze
74
+
75
+ def job_class(job)
76
+ job_class = job.fetch('jobtype', UNKNOWN_CLASS_PLACEHOLDER)
77
+
78
+ if job_class == ACTIVE_JOB_KLASS && job.key?('custom') && job['custom'].key?('wrapped')
79
+ begin
80
+ job_class = job['custom']['wrapped']
81
+ rescue
82
+ ACTIVE_JOB_KLASS
83
+ end
84
+ end
85
+
86
+ job_class
87
+ rescue
88
+ UNKNOWN_CLASS_PLACEHOLDER
89
+ end
90
+
91
+ def latency(job, time = Time.now)
92
+ created_at = Time.parse(job['enqueued_at'] || job['created_at'])
93
+ if created_at
94
+ (time - created_at)
95
+ else
96
+ 0
97
+ end
98
+ rescue
99
+ 0
100
+ end
101
+ end
102
+ end
103
+ end
@@ -37,6 +37,8 @@ module ScoutApm
37
37
  end
38
38
 
39
39
  def install_processor
40
+ # celluloid has not loaded by this point and older versions of `shorykuen/processor` assume that it did
41
+ require 'celluloid' if defined?(::Shoryuken::VERSION) && ::Shoryuken::VERSION < '3'
40
42
  require 'shoryuken/processor' # sidekiq v4 has not loaded this file by this point
41
43
 
42
44
  ::Shoryuken::Processor.class_eval do
@@ -80,12 +80,23 @@ module ScoutApm
80
80
  DELAYED_WRAPPER_KLASS = 'Sidekiq::Extensions::DelayedClass'.freeze
81
81
 
82
82
 
83
+ # Capturing the class name is a little tricky, since we need to handle several cases:
84
+ # 1. ActiveJob, with the class in the key 'wrapped'
85
+ # 2. ActiveJob, but the 'wrapped' key is wrong (due to YAJL serializing weirdly), find it in args.job_class
86
+ # 3. DelayedJob wrapper, deserializing using YAML into the real object, which can be introspected
87
+ # 4. No wrapper, just sidekiq's class
83
88
  def job_class(msg)
84
89
  job_class = msg.fetch('class', UNKNOWN_CLASS_PLACEHOLDER)
85
90
 
86
- if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped')
91
+ if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped') && msg['wrapped'].is_a?(String)
87
92
  begin
88
- job_class = msg['wrapped']
93
+ job_class = msg['wrapped'].to_s
94
+ rescue
95
+ ACTIVE_JOB_KLASS
96
+ end
97
+ elsif job_class == ACTIVE_JOB_KLASS && msg.try(:[], 'args').try(:[], 'job_class')
98
+ begin
99
+ job_class = msg['args']['job_class'].to_s
89
100
  rescue
90
101
  ACTIVE_JOB_KLASS
91
102
  end
@@ -29,6 +29,7 @@ require 'scout_apm/environment'
29
29
  # report_format - 'json' or 'marshal'. Marshal is legacy and will be removed.
30
30
  # scm_subdirectory - if the app root lives in source management in a subdirectory. E.g. #{SCM_ROOT}/src
31
31
  # uri_reporting - 'path' or 'full_path' default is 'full_path', which reports URL params as well as the path.
32
+ # record_queue_time - true/false to enable recording of queuetime.
32
33
  # remote_agent_host - Internal: What host to bind to, and also send messages to for remote. Default: 127.0.0.1.
33
34
  # remote_agent_port - What port to bind the remote webserver to
34
35
  # start_resque_server_instrument - Used in special situations with certain Resque installs
@@ -69,6 +70,7 @@ module ScoutApm
69
70
  'name',
70
71
  'profile',
71
72
  'proxy',
73
+ 'record_queue_time',
72
74
  'remote_agent_host',
73
75
  'remote_agent_port',
74
76
  'report_format',
@@ -178,6 +180,7 @@ module ScoutApm
178
180
  'database_metric_limit' => IntegerCoercion.new,
179
181
  'database_metric_report_limit' => IntegerCoercion.new,
180
182
  'instrument_http_url_length' => IntegerCoercion.new,
183
+ 'record_queue_time' => BooleanCoercion.new,
181
184
  'start_resque_server_instrument' => BooleanCoercion.new,
182
185
  'timeline_traces' => BooleanCoercion.new,
183
186
  'auto_instruments' => BooleanCoercion.new,
@@ -292,6 +295,7 @@ module ScoutApm
292
295
  'instrument_http_url_length' => 300,
293
296
  'start_resque_server_instrument' => true, # still only starts if Resque is detected
294
297
  'collect_remote_ip' => true,
298
+ 'record_queue_time' => true,
295
299
  'timeline_traces' => true,
296
300
  'auto_instruments' => false,
297
301
  'auto_instruments_ignore' => [],
@@ -30,6 +30,7 @@ module ScoutApm
30
30
  ScoutApm::BackgroundJobIntegrations::Sneakers.new,
31
31
  ScoutApm::BackgroundJobIntegrations::DelayedJob.new,
32
32
  ScoutApm::BackgroundJobIntegrations::Que.new,
33
+ ScoutApm::BackgroundJobIntegrations::Faktory.new,
33
34
  ]
34
35
 
35
36
  FRAMEWORK_INTEGRATIONS = [
@@ -197,7 +198,7 @@ module ScoutApm
197
198
  ruby_2? || ruby_3?
198
199
  end
199
200
 
200
- # Returns true if this Ruby version supports Module#prepend.
201
+ # Returns true if this Ruby version makes positional and keyword arguments incompatible
201
202
  def supports_kwarg_delegation?
202
203
  ruby_3? || (ruby_2? && ruby_minor >= 7)
203
204
  end
@@ -9,10 +9,10 @@ module ScoutApm
9
9
  begin
10
10
  response = @app.call(env)
11
11
  rescue Exception => exception
12
- puts "[Scout Error Service] Caught Exception: #{exception.class.name}"
13
-
14
12
  context = ScoutApm::Agent.instance.context
15
13
 
14
+ context.logger.debug "[Scout Error Service] Caught Exception: #{exception.class.name}"
15
+
16
16
  # Bail out early, and reraise if the error is not interesting.
17
17
  if context.ignored_exceptions.ignored?(exception)
18
18
  raise
@@ -12,7 +12,7 @@ module ScoutApm
12
12
  # TODO: Don't use to_json since it isn't supported in Ruby 1.8.7
13
13
  def serialize
14
14
  payload = as_json.to_json
15
- context.logger.info(payload)
15
+ context.logger.debug(payload)
16
16
  payload
17
17
  end
18
18
 
@@ -16,7 +16,9 @@ module ScoutApm
16
16
  # Used internally by SidekiqException
17
17
  def self.capture(exception, params = {})
18
18
  return if disabled?
19
- return if ScoutApm::Agent.instance.context.ignored_exceptions.ignore?(exception)
19
+
20
+ context = ScoutApm::Agent.instance.context
21
+ return if context.ignored_exceptions.ignore?(exception)
20
22
 
21
23
  context.errors_buffer.capture(exception, env)
22
24
  end
@@ -71,7 +71,11 @@ module ScoutApm
71
71
  #
72
72
  # We avoid this issue by not calling .respond_to? here, and instead using the less optimal `rescue nil` approach
73
73
  def raw_database_adapter
74
- adapter = ActiveRecord::Base.connection_config[:adapter].to_s rescue nil
74
+ adapter = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] rescue nil
75
+
76
+ if adapter.nil?
77
+ adapter = ActiveRecord::Base.connection_config[:adapter].to_s rescue nil
78
+ end
75
79
 
76
80
  if adapter.nil?
77
81
  adapter = ActiveRecord::Base.configurations[env]["adapter"]
@@ -4,7 +4,9 @@ module ScoutApm
4
4
  attr_reader :regex
5
5
 
6
6
  def initialize(prefixes)
7
- regexes = Array(prefixes).map {|prefix| %r{\A#{prefix}} }
7
+ regexes = Array(prefixes).
8
+ reject{|prefix| prefix == ""}.
9
+ map {|prefix| %r{\A#{prefix}} }
8
10
  @regex = Regexp.union(*regexes)
9
11
  end
10
12
 
@@ -95,8 +95,11 @@ module ScoutApm
95
95
  req.instant_key = instant_key
96
96
  end
97
97
 
98
- if current_layer && current_layer.type == "Controller"
99
- # Don't start a new layer if ActionController::API or ActionController::Base handled it already.
98
+ # Don't start a new layer if ActionController::API or
99
+ # ActionController::Base handled it already. Needs to account for
100
+ # any layers started during a around_action (most likely
101
+ # AutoInstrument, but could be another custom instrument)
102
+ if current_layer && (current_layer.type == "Controller" || current_layer.type == "AutoInstrument" || req.web?)
100
103
  super
101
104
  else
102
105
  begin
@@ -83,7 +83,7 @@ module ScoutApm
83
83
  maybe_template = args[1]
84
84
 
85
85
  template_name = @template.virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
86
- template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3
86
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3.5
87
87
  template_name ||= "Unknown Partial"
88
88
 
89
89
  layer_name = template_name + "/Rendering"
@@ -105,7 +105,10 @@ module ScoutApm
105
105
  def collection_with_template(*args, **kwargs)
106
106
  req = ScoutApm::RequestManager.lookup
107
107
 
108
- template_name = @template.virtual_path rescue "Unknown Collection"
108
+ maybe_template = args[1]
109
+
110
+ template_name = @template.virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
111
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3.5
109
112
  template_name ||= "Unknown Collection"
110
113
  layer_name = template_name + "/Rendering"
111
114
 
@@ -126,10 +129,15 @@ module ScoutApm
126
129
  end
127
130
 
128
131
  module ActionViewTemplateRendererInstruments
129
- def render_template(*args, **kwargs)
132
+ # Don't forward kwargs here, since Rails 3, 4, 5, 6 don't use them, and
133
+ # it causes annoyances in the instrumentation
134
+ def render_template(*args)
130
135
  req = ScoutApm::RequestManager.lookup
131
136
 
132
- template_name = args[0].virtual_path rescue "Unknown"
137
+ maybe_template = args[1]
138
+
139
+ template_name = args[0].virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
140
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.1.3
133
141
  template_name ||= "Unknown"
134
142
  layer_name = template_name + "/Rendering"
135
143
 
@@ -138,7 +146,7 @@ module ScoutApm
138
146
 
139
147
  begin
140
148
  req.start_layer(layer)
141
- super(*args, **kwargs)
149
+ super(*args)
142
150
  ensure
143
151
  req.stop_layer
144
152
  end
@@ -215,6 +215,7 @@ module ScoutApm
215
215
  end
216
216
  end
217
217
  end
218
+ ruby2_keywords :log if respond_to?(:ruby2_keywords, true)
218
219
  end
219
220
 
220
221
  module ActiveRecordInstruments
@@ -267,6 +268,7 @@ module ScoutApm
267
268
  end
268
269
  end
269
270
  end
271
+ ruby2_keywords :log if respond_to?(:ruby2_keywords, true)
270
272
  end
271
273
 
272
274
  ################################################################################
@@ -29,10 +29,11 @@ module ScoutApm
29
29
 
30
30
  module TyphoeusHydraInstrumentation
31
31
  def run(*args, &block)
32
+ layer = ScoutApm::Layer.new("HTTP", "Hydra")
33
+ layer.desc = scout_desc
34
+
32
35
  req = ScoutApm::RequestManager.lookup
33
- req.start_layer(ScoutApm::Layer.new("HTTP", "Hydra"))
34
- current_layer = req.current_layer
35
- current_layer.desc = scout_desc if current_layer
36
+ req.start_layer(layer)
36
37
 
37
38
  begin
38
39
  super(*args, &block)
@@ -50,10 +51,11 @@ module ScoutApm
50
51
 
51
52
  module TyphoeusInstrumentation
52
53
  def run(*args, &block)
54
+ layer = ScoutApm::Layer.new("HTTP", scout_request_verb)
55
+ layer.desc = scout_desc(scout_request_verb, scout_request_url)
56
+
53
57
  req = ScoutApm::RequestManager.lookup
54
- req.start_layer(ScoutApm::Layer.new("HTTP", scout_request_verb))
55
- current_layer = req.current_layer
56
- current_layer.desc = scout_desc(scout_request_verb, scout_request_url) if current_layer
58
+ req.start_layer(layer)
57
59
 
58
60
  begin
59
61
  super(*args, &block)
@@ -5,6 +5,10 @@
5
5
  # show
6
6
  # render :update
7
7
  # end
8
+
9
+ # This doesn't cache the negative result when searching for a controller / job,
10
+ # so that we can ask again later after more of the request has occurred and
11
+ # correctly find it.
8
12
  module ScoutApm
9
13
  module LayerConverters
10
14
  class FindLayerByType
@@ -11,6 +11,8 @@ module ScoutApm
11
11
  def record!
12
12
  return unless request.web?
13
13
 
14
+ return unless context.config.value('record_queue_time')
15
+
14
16
  return unless headers
15
17
 
16
18
  raw_start = locate_timestamp
@@ -76,7 +76,7 @@ module ScoutApm
76
76
  klass = @opts.fetch(:logger_class, ::Logger)
77
77
  case klass
78
78
  when String
79
- result = KlassHelper.lookup(klass)
79
+ result = Utils::KlassHelper.lookup(klass)
80
80
  if result == :missing_class
81
81
  ::Logger
82
82
  else
@@ -45,31 +45,17 @@ module ScoutApm
45
45
  "{#{str_parts.join(",")}}"
46
46
  end
47
47
 
48
- # Ruby 1.8.7 seems to be fundamentally different in how gsub or regexes
49
- # work. This is a hack and will be removed as soon as we can drop
50
- # support
51
- if RUBY_VERSION == "1.8.7"
52
- ESCAPE_MAPPINGS = {
53
- "\b" => '\\b',
54
- "\t" => '\\t',
55
- "\n" => '\\n',
56
- "\f" => '\\f',
57
- "\r" => '\\r',
58
- '"' => '\\"',
59
- '\\' => '\\\\',
60
- }
61
- else
62
- ESCAPE_MAPPINGS = {
63
- # Stackoverflow answer on gsub matches and backslashes - https://stackoverflow.com/a/4149087/2705125
64
- '\\' => '\\\\\\\\',
65
- "\b" => '\\b',
66
- "\t" => '\\t',
67
- "\n" => '\\n',
68
- "\f" => '\\f',
69
- "\r" => '\\r',
70
- '"' => '\\"',
71
- }
72
- end
48
+ ESCAPE_MAPPINGS = {
49
+ # Stackoverflow answer on gsub matches and backslashes
50
+ # https://stackoverflow.com/a/4149087/2705125
51
+ '\\' => '\\\\\\\\',
52
+ "\b" => '\\b',
53
+ "\t" => '\\t',
54
+ "\n" => '\\n',
55
+ "\f" => '\\f',
56
+ "\r" => '\\r',
57
+ '"' => '\\"',
58
+ }
73
59
 
74
60
  def escape(string)
75
61
  ESCAPE_MAPPINGS.inject(string.to_s) {|s, (bad, good)|
@@ -91,7 +91,7 @@ module ScoutApm
91
91
 
92
92
  def _instrumented_method_string(instrumented_name, uninstrumented_name, type, name, options={})
93
93
  method_str = <<-EOF
94
- def #{instrumented_name}(*args, &block)
94
+ def #{instrumented_name}(*args#{", **kwargs" if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?}, &block)
95
95
  name = begin
96
96
  "#{name}"
97
97
  rescue => e
@@ -103,7 +103,7 @@ module ScoutApm
103
103
  name,
104
104
  {:scope => #{options[:scope] || false}}
105
105
  ) do
106
- #{uninstrumented_name}(*args, &block)
106
+ #{uninstrumented_name}(*args#{", **kwargs" if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?}, &block)
107
107
  end
108
108
  end
109
109
  EOF
@@ -5,12 +5,34 @@ require 'scout_apm/environment'
5
5
  module ScoutApm
6
6
  module Utils
7
7
  class SqlSanitizer
8
- if ScoutApm::Environment.instance.ruby_187?
9
- require 'scout_apm/utils/sql_sanitizer_regex_1_8_7'
10
- else
11
- require 'scout_apm/utils/sql_sanitizer_regex'
12
- end
13
- include ScoutApm::Utils::SqlRegex
8
+ MULTIPLE_SPACES = %r|\s+|.freeze
9
+ MULTIPLE_QUESTIONS = /\?(,\?)+/.freeze
10
+
11
+ PSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*\z|.freeze
12
+ PSQL_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
13
+ PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
14
+ PSQL_AFTER_SELECT = /(?:SELECT\s+).*?(?:WHERE|FROM\z)/im.freeze # Should be everything between a FROM and a WHERE
15
+ PSQL_PLACEHOLDER = /\$\d+/.freeze
16
+ PSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
17
+ PSQL_AFTER_FROM = /(?:FROM\s+).*?(?:WHERE|\z)/im.freeze # Should be everything between a FROM and a WHERE
18
+ PSQL_AFTER_JOIN = /(?:JOIN\s+).*?\z/im.freeze
19
+ PSQL_AFTER_WHERE = /(?:WHERE\s+).*?(?:SELECT|\z)/im.freeze
20
+ PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|\z)/im.freeze
21
+
22
+ MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
23
+ MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
24
+ MYSQL_REMOVE_SINGLE_QUOTE_STRINGS = %r{'(?:\\'|[^']|'')*'}.freeze
25
+ MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS = %r{"(?:\\"|[^"]|"")*"}.freeze
26
+ MYSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
27
+
28
+ SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
29
+ SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
30
+ SQLITE_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
31
+
32
+ # => "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE (age > 50) ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY', N'@0 int', @0 = 10"
33
+ SQLSERVER_EXECUTESQL = /EXEC sp_executesql N'(.*?)'.*/
34
+ SQLSERVER_REMOVE_INTEGERS = /(?<!LIMIT )\b(?<!@)\d+\b/.freeze
35
+ SQLSERVER_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
14
36
 
15
37
  attr_accessor :database_engine
16
38
 
@@ -50,7 +72,9 @@ module ScoutApm
50
72
  def to_s_postgres
51
73
  sql.gsub!(PSQL_PLACEHOLDER, '?')
52
74
  sql.gsub!(PSQL_VAR_INTERPOLATION, '')
75
+ # sql.gsub!(PSQL_REMOVE_STRINGS, '?')
53
76
  sql.gsub!(PSQL_AFTER_WHERE) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
77
+ sql.gsub!(PSQL_AFTER_JOIN) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
54
78
  sql.gsub!(PSQL_AFTER_SET) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
55
79
  sql.gsub!(PSQL_REMOVE_INTEGERS, '?')
56
80
  sql.gsub!(PSQL_IN_CLAUSE, 'IN (?)')
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "4.0.3"
2
+ VERSION = "4.1.2"
3
3
  end
data/lib/scout_apm.rb CHANGED
@@ -58,6 +58,7 @@ require 'scout_apm/server_integrations/webrick'
58
58
  require 'scout_apm/server_integrations/null'
59
59
 
60
60
  require 'scout_apm/background_job_integrations/sidekiq'
61
+ require 'scout_apm/background_job_integrations/faktory'
61
62
  require 'scout_apm/background_job_integrations/delayed_job'
62
63
  require 'scout_apm/background_job_integrations/resque'
63
64
  require 'scout_apm/background_job_integrations/shoryuken'
@@ -192,6 +193,7 @@ require 'scout_apm/tasks/support'
192
193
  require 'scout_apm/extensions/config'
193
194
  require 'scout_apm/extensions/transaction_callback_payload'
194
195
 
196
+ require 'scout_apm/error'
195
197
  require 'scout_apm/error_service'
196
198
  require 'scout_apm/error_service/middleware'
197
199
  require 'scout_apm/error_service/notifier'
@@ -0,0 +1,7 @@
1
+ class TestController < ApplicationController
2
+ def index
3
+ quests = policy_scope(Quest.open)
4
+ quests.each { _1.current_user = current_user }
5
+ respond_with_proto quests
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class TestController < ApplicationController
2
+ end
3
+
4
+ def hanging_method
5
+ Test.first
6
+ end
@@ -51,4 +51,12 @@ class AutoInstrumentTest < Minitest::Test
51
51
  assert_equal instrumented_source("assignments"),
52
52
  normalize_backtrace(::ScoutApm::AutoInstrument::Rails.rewrite(source_path("assignments")))
53
53
  end
54
+
55
+ def test_hanging_method_rewrite
56
+ ::ScoutApm::AutoInstrument::Rails.rewrite(source_path("hanging_method"))
57
+ end
58
+
59
+ def test_anonymous_block_value
60
+ ::ScoutApm::AutoInstrument::Rails.rewrite(source_path("anonymous_block_value"))
61
+ end
54
62
  end if defined? ScoutApm::AutoInstrument
@@ -13,4 +13,10 @@ class IgnoredUrlsTest < Minitest::Test
13
13
  i = ScoutApm::IgnoredUris.new(["/slow", "/health"])
14
14
  assert_equal false, i.ignore?("/users/2/health")
15
15
  end
16
+
17
+ def test_does_not_ignore_empty_string
18
+ i = ScoutApm::IgnoredUris.new(["", "/admin"])
19
+ assert_equal false, i.ignore?("/users/2/health")
20
+ assert_equal true, i.ignore?("/admin/dashboard")
21
+ end
16
22
  end
@@ -0,0 +1,42 @@
1
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("typhoeus")
2
+ require 'test_helper'
3
+
4
+ require 'scout_apm/instruments/typhoeus'
5
+
6
+ require 'typhoeus'
7
+
8
+ class TyphoeusTest < Minitest::Test
9
+ def setup
10
+ @context = ScoutApm::AgentContext.new
11
+ @recorder = FakeRecorder.new
12
+ ScoutApm::Agent.instance.context.recorder = @recorder
13
+ ScoutApm::Instruments::Typhoeus.new(@context).install
14
+ end
15
+
16
+ def test_instruments_typhoeus_hydra
17
+ hydra = Typhoeus::Hydra.new
18
+ 2.times.map{ hydra.queue(Typhoeus::Request.new("example.com", followlocation: true)) }
19
+
20
+ assert_equal "2 requests", hydra.scout_desc
21
+
22
+ hydra.run
23
+ assert_equal "0 requests", hydra.scout_desc
24
+ assert_recorded(@recorder, "HTTP", "Hydra", "2 requests")
25
+ end
26
+
27
+ def test_instruments_typhoeus
28
+ Typhoeus.get("example.com", followlocation: true)
29
+ assert_recorded(@recorder, "HTTP", "get", "example.com")
30
+ end
31
+
32
+ private
33
+
34
+ def assert_recorded(recorder, type, name, desc = nil)
35
+ req = recorder.requests.first
36
+ assert req, "recorder recorded no layers"
37
+ assert_equal type, req.root_layer.type
38
+ assert_equal name, req.root_layer.name
39
+ assert_equal desc, req.root_layer.desc if !desc.nil?
40
+ end
41
+ end
42
+ end
File without changes
@@ -8,8 +8,11 @@ class TestRemoteServer < Minitest::Test
8
8
  logger_io = StringIO.new
9
9
  server = ScoutApm::Remote::Server.new(bind, port, router, Logger.new(logger_io))
10
10
 
11
+ # Cannot test this if we can't require webrick. Ruby 3 stopped including by default
12
+ skip unless server.require_webrick
13
+
11
14
  server.start
12
- sleep 0.01 # Let the server finish starting. The assert should instead allow a time
15
+ sleep 0.05 # Let the server finish starting. The assert should instead allow a time
13
16
  assert server.running?
14
17
  end
15
18
  end
@@ -38,9 +38,9 @@ module ScoutApm
38
38
  end
39
39
 
40
40
  def test_postgres_strips_subquery_strings
41
- raw_sql = %q|"SELECT 'orgs'.* FROM "orgs" WHERE "orgs"."name" = 'Scout' AND "orgs"."created_by_user_id" IN (SELECT 'users'.'id' FROM "users" WHERE (id > AVG(id)) AND "type" = 'USER' AND "created_at" BETWEEN '2019-04-17 12:28:00.000000' AND '2019-04-18 12:28:00.000000')"|
41
+ raw_sql = %q|"SELECT "orgs".* FROM "orgs" WHERE "orgs"."name" = 'Scout' AND "orgs"."created_by_user_id" IN (SELECT "users"."id" FROM "users" WHERE (id > AVG(id)) AND "type" = 'USER' AND "created_at" BETWEEN '2019-04-17 12:28:00.000000' AND '2019-04-18 12:28:00.000000')"|
42
42
  sanitized_sql = SqlSanitizer.new(raw_sql).tap { |it| it.database_engine = :postgres}
43
- expected_sql = %q|"SELECT 'orgs'.* FROM "orgs" WHERE "orgs"."name" = ? AND "orgs"."created_by_user_id" IN (SELECT 'users'.'id' FROM "users" WHERE (id > AVG(id)) AND "type" = ? AND "created_at" BETWEEN ? AND ?)"|
43
+ expected_sql = %q|"SELECT "orgs".* FROM "orgs" WHERE "orgs"."name" = ? AND "orgs"."created_by_user_id" IN (SELECT "users"."id" FROM "users" WHERE (id > AVG(id)) AND "type" = ? AND "created_at" BETWEEN ? AND ?)"|
44
44
  assert_equal expected_sql, sanitized_sql.to_s
45
45
  end
46
46
 
@@ -66,6 +66,21 @@ module ScoutApm
66
66
  end
67
67
  end
68
68
 
69
+ def test_postgres_inner_join_subquery
70
+ sql = %q{SELECT x AS y
71
+ FROM t1
72
+ INNER JOIN (
73
+ SELECT id,
74
+ (ts_rank((to_tsvector('simple', coalesce("pg_search_documents"."content"::text, ''))), (to_tsquery('simple', 'xyz' || 'omg' || 'secret')), ?)) AS rank
75
+ FROM t2
76
+ WHERE name = 'literal') sub ON sub.id = t1.id WHERE age > 10}
77
+
78
+ expected = %q{SELECT x AS y FROM t1 INNER JOIN ( SELECT id, (ts_rank((to_tsvector(?, coalesce("pg_search_documents"."content"::text, ?))), (to_tsquery(?, ? || ? || ?)), ?)) AS rank FROM t2 WHERE name = ?) sub ON sub.id = t1.id WHERE age > ?}
79
+ ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
80
+
81
+ assert_equal expected, ss.to_s
82
+ end
83
+
69
84
  def test_mysql_where
70
85
  sql = %q|SELECT `users`.* FROM `users` WHERE `users`.`name` = ? [["name", "chris"]]|
71
86
  ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
@@ -65,6 +65,31 @@ class TracerTest < Minitest::Test
65
65
  assert_recorded(recorder, "Test", "name")
66
66
  end
67
67
 
68
+ if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
69
+ def test_instrument_method_with_keyword_args
70
+ initial_value = Warning[:deprecated]
71
+ Warning[:deprecated] = true
72
+ recorder = FakeRecorder.new
73
+ ScoutApm::Agent.instance.context.recorder = recorder
74
+
75
+ klass = Class.new { include ScoutApm::Tracer }
76
+
77
+ invoked = false
78
+ klass.send(:define_method, :work) { |run:| invoked = true }
79
+ klass.instrument_method(:work, :type => "Test", :name => "name")
80
+
81
+ args = { run: false }
82
+ assert_output(nil, '') do
83
+ klass.new.work(**args)
84
+ end
85
+
86
+ assert invoked, "instrumented code was not invoked"
87
+ assert_recorded(recorder, "Test", "name")
88
+ ensure
89
+ Warning[:deprecated] = initial_value
90
+ end
91
+ end
92
+
68
93
  private
69
94
 
70
95
  def assert_recorded(recorder, type, name)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 4.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-01-20 00:00:00.000000000 Z
12
+ date: 2021-08-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -238,6 +238,7 @@ files:
238
238
  - gems/rails4.gemfile
239
239
  - gems/rails5.gemfile
240
240
  - gems/rails6.gemfile
241
+ - gems/typhoeus.gemfile
241
242
  - lib/scout_apm.rb
242
243
  - lib/scout_apm/agent.rb
243
244
  - lib/scout_apm/agent/exit_handler.rb
@@ -251,6 +252,7 @@ files:
251
252
  - lib/scout_apm/auto_instrument/parser.rb
252
253
  - lib/scout_apm/auto_instrument/rails.rb
253
254
  - lib/scout_apm/background_job_integrations/delayed_job.rb
255
+ - lib/scout_apm/background_job_integrations/faktory.rb
254
256
  - lib/scout_apm/background_job_integrations/legacy_sneakers.rb
255
257
  - lib/scout_apm/background_job_integrations/que.rb
256
258
  - lib/scout_apm/background_job_integrations/resque.rb
@@ -397,8 +399,6 @@ files:
397
399
  - lib/scout_apm/utils/numbers.rb
398
400
  - lib/scout_apm/utils/scm.rb
399
401
  - lib/scout_apm/utils/sql_sanitizer.rb
400
- - lib/scout_apm/utils/sql_sanitizer_regex.rb
401
- - lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb
402
402
  - lib/scout_apm/utils/time.rb
403
403
  - lib/scout_apm/utils/unique_id.rb
404
404
  - lib/scout_apm/version.rb
@@ -409,11 +409,13 @@ files:
409
409
  - test/tmp/README.md
410
410
  - test/unit/agent_context_test.rb
411
411
  - test/unit/agent_test.rb
412
+ - test/unit/auto_instrument/anonymous_block_value.rb
412
413
  - test/unit/auto_instrument/assignments-instrumented.rb
413
414
  - test/unit/auto_instrument/assignments.rb
414
415
  - test/unit/auto_instrument/controller-ast.txt
415
416
  - test/unit/auto_instrument/controller-instrumented.rb
416
417
  - test/unit/auto_instrument/controller.rb
418
+ - test/unit/auto_instrument/hanging_method.rb
417
419
  - test/unit/auto_instrument/rescue_from-instrumented.rb
418
420
  - test/unit/auto_instrument/rescue_from.rb
419
421
  - test/unit/auto_instrument_test.rb
@@ -434,6 +436,7 @@ files:
434
436
  - test/unit/instruments/active_record_test.rb
435
437
  - test/unit/instruments/net_http_test.rb
436
438
  - test/unit/instruments/percentile_sampler_test.rb
439
+ - test/unit/instruments/typhoeus_test.rb
437
440
  - test/unit/layaway_test.rb
438
441
  - test/unit/layer_children_set_test.rb
439
442
  - test/unit/layer_converters/depth_first_walker_test.rb
@@ -442,9 +445,9 @@ files:
442
445
  - test/unit/limited_layer_test.rb
443
446
  - test/unit/logger_test.rb
444
447
  - test/unit/metric_set_test.rb
445
- - test/unit/remote/test_message.rb
446
- - test/unit/remote/test_router.rb
447
- - test/unit/remote/test_server.rb
448
+ - test/unit/remote/message_test.rb
449
+ - test/unit/remote/route_test.rb
450
+ - test/unit/remote/server_test.rb
448
451
  - test/unit/request_histograms_test.rb
449
452
  - test/unit/scored_item_set_test.rb
450
453
  - test/unit/serializers/payload_serializer_test.rb
@@ -479,7 +482,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
479
482
  - !ruby/object:Gem::Version
480
483
  version: '0'
481
484
  requirements: []
482
- rubygems_version: 3.0.8
485
+ rubygems_version: 3.0.3
483
486
  signing_key:
484
487
  specification_version: 4
485
488
  summary: Ruby application performance monitoring
@@ -1,32 +0,0 @@
1
-
2
- module ScoutApm
3
- module Utils
4
- module SqlRegex
5
- MULTIPLE_SPACES = %r|\s+|.freeze
6
- MULTIPLE_QUESTIONS = /\?(,\?)+/.freeze
7
-
8
- PSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*\z|.freeze
9
- PSQL_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
10
- PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
11
- PSQL_PLACEHOLDER = /\$\d+/.freeze
12
- PSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
13
- PSQL_AFTER_WHERE = /(?:WHERE\s+).*?(?:SELECT|\z)/im.freeze
14
- PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|\z)/im.freeze
15
-
16
- MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
17
- MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
18
- MYSQL_REMOVE_SINGLE_QUOTE_STRINGS = %r{'(?:\\'|[^']|'')*'}.freeze
19
- MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS = %r{"(?:\\"|[^"]|"")*"}.freeze
20
- MYSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
21
-
22
- SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
23
- SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
24
- SQLITE_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
25
-
26
- # => "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE (age > 50) ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY', N'@0 int', @0 = 10"
27
- SQLSERVER_EXECUTESQL = /EXEC sp_executesql N'(.*?)'.*/
28
- SQLSERVER_REMOVE_INTEGERS = /(?<!LIMIT )\b(?<!@)\d+\b/.freeze
29
- SQLSERVER_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
30
- end
31
- end
32
- end
@@ -1,32 +0,0 @@
1
-
2
- module ScoutApm
3
- module Utils
4
- module SqlRegex
5
- MULTIPLE_SPACES = %r|\s+|.freeze
6
- MULTIPLE_QUESTIONS = /\?(,\?)+/.freeze
7
-
8
- PSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
9
- PSQL_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
10
- PSQL_REMOVE_INTEGERS = /\b\d+\b/.freeze
11
- PSQL_PLACEHOLDER = /\$\d+/.freeze
12
- PSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
13
- PSQL_AFTER_WHERE = /(?:WHERE\s+).*?(?:SELECT|$)/i.freeze
14
- PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|$)/i.freeze
15
-
16
- MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
17
- MYSQL_REMOVE_INTEGERS = /\b\d+\b/.freeze
18
- MYSQL_REMOVE_SINGLE_QUOTE_STRINGS = /'(?:\\'|[^']|'')*'/.freeze
19
- MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS = /"(?:\\"|[^"]|"")*"/.freeze
20
- MYSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
21
-
22
- SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
23
- SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
24
- SQLITE_REMOVE_INTEGERS = /\b\d+\b/.freeze
25
-
26
- # This is not officially supported, but will do its best.
27
- SQLSERVER_EXECUTESQL = /EXEC sp_executesql N'(.*?)'.*/
28
- SQLSERVER_REMOVE_INTEGERS = /\b\d+\b/.freeze
29
- SQLSERVER_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
30
- end
31
- end
32
- end