scout_apm 4.0.4 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +16 -0
  3. data/lib/scout_apm.rb +1 -0
  4. data/lib/scout_apm/agent/preconditions.rb +3 -3
  5. data/lib/scout_apm/auto_instrument/rails.rb +0 -1
  6. data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
  7. data/lib/scout_apm/background_job_integrations/sidekiq.rb +13 -2
  8. data/lib/scout_apm/error_service.rb +3 -1
  9. data/lib/scout_apm/error_service/middleware.rb +2 -2
  10. data/lib/scout_apm/error_service/payload.rb +1 -1
  11. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -1
  12. data/lib/scout_apm/ignored_uris.rb +3 -1
  13. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +5 -2
  14. data/lib/scout_apm/instruments/action_view.rb +13 -5
  15. data/lib/scout_apm/instruments/active_record.rb +2 -0
  16. data/lib/scout_apm/instruments/typhoeus.rb +8 -6
  17. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
  18. data/lib/scout_apm/logger.rb +1 -1
  19. data/lib/scout_apm/utils/sql_sanitizer.rb +30 -6
  20. data/lib/scout_apm/version.rb +1 -1
  21. data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
  22. data/test/unit/auto_instrument/hanging_method.rb +6 -0
  23. data/test/unit/auto_instrument_test.rb +8 -0
  24. data/test/unit/ignored_uris_test.rb +6 -0
  25. data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
  26. data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
  27. data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
  28. data/test/unit/sql_sanitizer_test.rb +17 -2
  29. metadata +7 -7
  30. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -32
  31. 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: 16b10ca7e3c7474546feb2b367438e9fabeb2c799e85608b96afb3ddea37b647
4
- data.tar.gz: 044ced73909cff452e59bccf14e7dfcf344fd8f6262c5b633b0d3239995609fd
3
+ metadata.gz: 5af3fd0ff7d9f43aa8ec1b205104fb90784ba83b98250ef871d5bd1282734432
4
+ data.tar.gz: aae89c06e87f165d0ee87491fbb58253b7b38cdad29375e555e9e0c1d5607129
5
5
  SHA512:
6
- metadata.gz: c2ae77ef620e77bce7b267fe8c39ff2f91945dfffc3ad008dd01cbd3b45268a8b44624713652d1df2bdeb2789bedb1fdb98aa58742b48326b1821ea01482132d
7
- data.tar.gz: c533bf6cf2822f7688b4377e9f69e005eb1255c0c03cd000dd5d4edaef74b7761b4b8af9b8d39d1229480fd7f2cd0b0f6bcaf1890be5b3f6a9540bf7f8e03f64
6
+ metadata.gz: 304283c6c853ce0b2421eccaf3e331c784bd97bda96014777a3a52b64212250595c8d6f7ff390ee154a090d096d1b37f93f221a5e22b2133e47b4a4e36dc4d8e
7
+ data.tar.gz: 453dd2e29eb7d8bc44ef3527a7ab8ba486d5c9c709b9be275a0223df96deb1d7628c97418a220ca0425e5c6706e1e60c1a8f750f544b223a3b3fcd6c4e924d4f
data/CHANGELOG.markdown CHANGED
@@ -1,9 +1,25 @@
1
+ # 4.1.0
2
+
3
+ * Preload Celluloid in Shoryuken instrumentation (#331)
4
+ * Fix deprecation warning in Rails 6.1+ (#365)
5
+ * Set Typheous's desc more directly (#392)
6
+ * Delegate to ActiveRecord #log more intelligently (#394)
7
+ * Don't delay starting agent when possible (#397)
8
+ * Fix template naming issue in Rails 6+ (#399)
9
+ * Avoid double-counting issue with AutoInstruments (#405)
10
+ * Renaming test files for Remote::{Server|Route|Message} to be included in test run (#409)
11
+ * More robust naming of Sidekiq jobs (#412)
12
+ * Allow render_template instruments to work with older Rails (#413)
13
+ * Fix function to manually capture exceptions (#415)
14
+ * Enhance SQL Sanitization (#417)
15
+
1
16
  # 4.0.4
2
17
 
3
18
  * Add Faktory Support (#385)
4
19
  * Remove Regexp hack for 1.8.7 (no longer supported) (#384)
5
20
  * More robust DelayedJob detection (#382)
6
21
  * Fix kwargs handling in Tracing module (#381)
22
+
7
23
  # 4.0.3
8
24
 
9
25
  * Handle edge case with nil Typhoeus current-layer (#380)
data/lib/scout_apm.rb CHANGED
@@ -193,6 +193,7 @@ require 'scout_apm/tasks/support'
193
193
  require 'scout_apm/extensions/config'
194
194
  require 'scout_apm/extensions/transaction_callback_payload'
195
195
 
196
+ require 'scout_apm/error'
196
197
  require 'scout_apm/error_service'
197
198
  require 'scout_apm/error_service/middleware'
198
199
  require 'scout_apm/error_service/notifier'
@@ -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 [
@@ -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
@@ -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
@@ -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
 
@@ -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 if current_layer
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
@@ -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
@@ -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.4"
2
+ VERSION = "4.1.0"
3
3
  end
@@ -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
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 }
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.4
4
+ version: 4.1.0
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-02-10 00:00:00.000000000 Z
12
+ date: 2021-06-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -398,8 +398,6 @@ files:
398
398
  - lib/scout_apm/utils/numbers.rb
399
399
  - lib/scout_apm/utils/scm.rb
400
400
  - lib/scout_apm/utils/sql_sanitizer.rb
401
- - lib/scout_apm/utils/sql_sanitizer_regex.rb
402
- - lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb
403
401
  - lib/scout_apm/utils/time.rb
404
402
  - lib/scout_apm/utils/unique_id.rb
405
403
  - lib/scout_apm/version.rb
@@ -410,11 +408,13 @@ files:
410
408
  - test/tmp/README.md
411
409
  - test/unit/agent_context_test.rb
412
410
  - test/unit/agent_test.rb
411
+ - test/unit/auto_instrument/anonymous_block_value.rb
413
412
  - test/unit/auto_instrument/assignments-instrumented.rb
414
413
  - test/unit/auto_instrument/assignments.rb
415
414
  - test/unit/auto_instrument/controller-ast.txt
416
415
  - test/unit/auto_instrument/controller-instrumented.rb
417
416
  - test/unit/auto_instrument/controller.rb
417
+ - test/unit/auto_instrument/hanging_method.rb
418
418
  - test/unit/auto_instrument/rescue_from-instrumented.rb
419
419
  - test/unit/auto_instrument/rescue_from.rb
420
420
  - test/unit/auto_instrument_test.rb
@@ -443,9 +443,9 @@ files:
443
443
  - test/unit/limited_layer_test.rb
444
444
  - test/unit/logger_test.rb
445
445
  - test/unit/metric_set_test.rb
446
- - test/unit/remote/test_message.rb
447
- - test/unit/remote/test_router.rb
448
- - test/unit/remote/test_server.rb
446
+ - test/unit/remote/message_test.rb
447
+ - test/unit/remote/route_test.rb
448
+ - test/unit/remote/server_test.rb
449
449
  - test/unit/request_histograms_test.rb
450
450
  - test/unit/scored_item_set_test.rb
451
451
  - test/unit/serializers/payload_serializer_test.rb
@@ -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