scout_apm 4.0.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +49 -0
- data/.rubocop.yml +2 -1
- data/.travis.yml +3 -1
- data/CHANGELOG.markdown +37 -0
- data/gems/rails6.gemfile +1 -1
- data/lib/scout_apm.rb +3 -0
- data/lib/scout_apm/agent/preconditions.rb +3 -3
- data/lib/scout_apm/auto_instrument/rails.rb +0 -1
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -1
- data/lib/scout_apm/background_job_integrations/faktory.rb +103 -0
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +13 -2
- data/lib/scout_apm/environment.rb +17 -1
- data/lib/scout_apm/error_service.rb +3 -1
- data/lib/scout_apm/error_service/middleware.rb +2 -2
- data/lib/scout_apm/error_service/payload.rb +1 -1
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -1
- data/lib/scout_apm/ignored_uris.rb +3 -1
- data/lib/scout_apm/instrument_manager.rb +1 -0
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +5 -2
- data/lib/scout_apm/instruments/action_view.rb +23 -7
- data/lib/scout_apm/instruments/active_record.rb +7 -1
- data/lib/scout_apm/instruments/typhoeus.rb +90 -0
- data/lib/scout_apm/layer.rb +1 -1
- data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
- data/lib/scout_apm/logger.rb +1 -1
- data/lib/scout_apm/remote/server.rb +13 -1
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +11 -25
- data/lib/scout_apm/tracer.rb +2 -2
- data/lib/scout_apm/utils/sql_sanitizer.rb +30 -6
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +1 -1
- data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
- data/test/unit/auto_instrument/hanging_method.rb +6 -0
- data/test/unit/auto_instrument_test.rb +8 -0
- data/test/unit/environment_test.rb +2 -2
- data/test/unit/ignored_uris_test.rb +6 -0
- data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
- data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
- data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
- data/test/unit/sql_sanitizer_test.rb +48 -2
- data/test/unit/tracer_test.rb +25 -0
- metadata +12 -9
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -32
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5af3fd0ff7d9f43aa8ec1b205104fb90784ba83b98250ef871d5bd1282734432
|
4
|
+
data.tar.gz: aae89c06e87f165d0ee87491fbb58253b7b38cdad29375e555e9e0c1d5607129
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 304283c6c853ce0b2421eccaf3e331c784bd97bda96014777a3a52b64212250595c8d6f7ff390ee154a090d096d1b37f93f221a5e22b2133e47b4a4e36dc4d8e
|
7
|
+
data.tar.gz: 453dd2e29eb7d8bc44ef3527a7ab8ba486d5c9c709b9be275a0223df96deb1d7628c97418a220ca0425e5c6706e1e60c1a8f750f544b223a3b3fcd6c4e924d4f
|
@@ -0,0 +1,49 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
lint:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
bundler-cache: true
|
14
|
+
ruby-version: 2.6
|
15
|
+
- run: bundle exec rubocop
|
16
|
+
|
17
|
+
test:
|
18
|
+
strategy:
|
19
|
+
fail-fast: false
|
20
|
+
matrix:
|
21
|
+
include:
|
22
|
+
- ruby: 2.1
|
23
|
+
gemfile: gems/rails3.gemfile
|
24
|
+
- ruby: 2.2
|
25
|
+
- ruby: 2.3
|
26
|
+
- ruby: 2.4
|
27
|
+
- ruby: 2.5
|
28
|
+
- ruby: 2.6
|
29
|
+
- ruby: 2.6
|
30
|
+
gemfile: gems/octoshark.gemfile
|
31
|
+
- ruby: 2.6
|
32
|
+
gemfile: gems/rails3.gemfile
|
33
|
+
bundler: 1.17.3
|
34
|
+
- ruby: 2.7
|
35
|
+
- ruby: 3.0
|
36
|
+
|
37
|
+
env:
|
38
|
+
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
39
|
+
|
40
|
+
runs-on: ubuntu-latest
|
41
|
+
|
42
|
+
steps:
|
43
|
+
- uses: actions/checkout@v2
|
44
|
+
- uses: ruby/setup-ruby@v1
|
45
|
+
with:
|
46
|
+
bundler-cache: true
|
47
|
+
bundler: ${{matrix.bundler}}
|
48
|
+
ruby-version: ${{ matrix.ruby }}
|
49
|
+
- run: bundle exec rake
|
data/.rubocop.yml
CHANGED
@@ -4,9 +4,10 @@ AllCops:
|
|
4
4
|
Exclude:
|
5
5
|
- 'test/unit/auto_instrument/*'
|
6
6
|
- vendor/bundle/**/*
|
7
|
+
SuggestExtensions: false
|
7
8
|
|
8
9
|
# 80 is stifling, especially with a few levels of nesting before we even start.
|
9
10
|
# So bump it to 100 to keep really long lines from creeping in.
|
10
|
-
|
11
|
+
Layout/LineLength:
|
11
12
|
Enabled: false
|
12
13
|
Max: 100
|
data/.travis.yml
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,40 @@
|
|
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
|
+
|
16
|
+
# 4.0.4
|
17
|
+
|
18
|
+
* Add Faktory Support (#385)
|
19
|
+
* Remove Regexp hack for 1.8.7 (no longer supported) (#384)
|
20
|
+
* More robust DelayedJob detection (#382)
|
21
|
+
* Fix kwargs handling in Tracing module (#381)
|
22
|
+
|
23
|
+
# 4.0.3
|
24
|
+
|
25
|
+
* Handle edge case with nil Typhoeus current-layer (#380)
|
26
|
+
* Fix args passing to render_partial (#379)
|
27
|
+
|
28
|
+
# 4.0.2
|
29
|
+
|
30
|
+
* Add Typhoeus instrumentation (#376)
|
31
|
+
|
32
|
+
# 4.0.1
|
33
|
+
|
34
|
+
* Add support for Ruby 3.0 (#374)
|
35
|
+
* Use Github Actions for CI (#370)
|
36
|
+
* Fix edge case in sanitization of Postgres SQL (#368)
|
37
|
+
|
1
38
|
# 4.0.0
|
2
39
|
|
3
40
|
* Require Ruby >= 2.1 (#270)
|
data/gems/rails6.gemfile
CHANGED
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'
|
@@ -78,6 +79,7 @@ require 'scout_apm/histogram'
|
|
78
79
|
|
79
80
|
require 'scout_apm/instruments/net_http'
|
80
81
|
require 'scout_apm/instruments/http_client'
|
82
|
+
require 'scout_apm/instruments/typhoeus'
|
81
83
|
require 'scout_apm/instruments/moped'
|
82
84
|
require 'scout_apm/instruments/mongoid'
|
83
85
|
require 'scout_apm/instruments/memcached'
|
@@ -191,6 +193,7 @@ require 'scout_apm/tasks/support'
|
|
191
193
|
require 'scout_apm/extensions/config'
|
192
194
|
require 'scout_apm/extensions/transaction_callback_payload'
|
193
195
|
|
196
|
+
require 'scout_apm/error'
|
194
197
|
require 'scout_apm/error_service'
|
195
198
|
require 'scout_apm/error_service/middleware'
|
196
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
|
-
|
31
|
-
|
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
|
-
|
33
|
+
app_server_found || background_job_integration_found
|
34
34
|
},
|
35
35
|
:severity => :info,
|
36
36
|
},
|
@@ -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
|
@@ -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 = [
|
@@ -182,9 +183,24 @@ module ScoutApm
|
|
182
183
|
@ruby_2 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^2/)
|
183
184
|
end
|
184
185
|
|
186
|
+
def ruby_3?
|
187
|
+
return @ruby_3 if defined?(@ruby_3)
|
188
|
+
@ruby_3 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^3/)
|
189
|
+
end
|
190
|
+
|
191
|
+
def ruby_minor
|
192
|
+
return @ruby_minor if defined?(@ruby_minor)
|
193
|
+
@ruby_minor = defined?(RUBY_VERSION) && RUBY_VERSION.split(".")[1].to_i
|
194
|
+
end
|
195
|
+
|
185
196
|
# Returns true if this Ruby version supports Module#prepend.
|
186
197
|
def supports_module_prepend?
|
187
|
-
ruby_2?
|
198
|
+
ruby_2? || ruby_3?
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns true if this Ruby version makes positional and keyword arguments incompatible
|
202
|
+
def supports_kwarg_delegation?
|
203
|
+
ruby_3? || (ruby_2? && ruby_minor >= 7)
|
188
204
|
end
|
189
205
|
|
190
206
|
# Returns a string representation of the OS (ex: darwin, linux)
|
@@ -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
|
-
|
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
|
@@ -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.
|
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).
|
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
|
|
@@ -30,6 +30,7 @@ module ScoutApm
|
|
30
30
|
install_instrument(ScoutApm::Instruments::Moped)
|
31
31
|
install_instrument(ScoutApm::Instruments::Mongoid)
|
32
32
|
install_instrument(ScoutApm::Instruments::NetHttp)
|
33
|
+
install_instrument(ScoutApm::Instruments::Typhoeus)
|
33
34
|
install_instrument(ScoutApm::Instruments::HttpClient)
|
34
35
|
install_instrument(ScoutApm::Instruments::Memcached)
|
35
36
|
install_instrument(ScoutApm::Instruments::Redis)
|
@@ -95,8 +95,11 @@ module ScoutApm
|
|
95
95
|
req.instant_key = instant_key
|
96
96
|
end
|
97
97
|
|
98
|
-
|
99
|
-
|
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
|
@@ -77,13 +77,13 @@ module ScoutApm
|
|
77
77
|
module ActionViewPartialRendererInstruments
|
78
78
|
# In Rails 6, the signature changed to pass the view & template args directly, as opposed to through the instance var
|
79
79
|
# New signature is: def render_partial(view, template)
|
80
|
-
def render_partial(*args)
|
80
|
+
def render_partial(*args, **kwargs)
|
81
81
|
req = ScoutApm::RequestManager.lookup
|
82
82
|
|
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"
|
@@ -92,16 +92,23 @@ module ScoutApm
|
|
92
92
|
|
93
93
|
begin
|
94
94
|
req.start_layer(layer)
|
95
|
-
|
95
|
+
if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
|
96
|
+
super(*args, **kwargs)
|
97
|
+
else
|
98
|
+
super(*args)
|
99
|
+
end
|
96
100
|
ensure
|
97
101
|
req.stop_layer
|
98
102
|
end
|
99
103
|
end
|
100
104
|
|
101
|
-
def collection_with_template(*args)
|
105
|
+
def collection_with_template(*args, **kwargs)
|
102
106
|
req = ScoutApm::RequestManager.lookup
|
103
107
|
|
104
|
-
|
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
|
105
112
|
template_name ||= "Unknown Collection"
|
106
113
|
layer_name = template_name + "/Rendering"
|
107
114
|
|
@@ -110,7 +117,11 @@ module ScoutApm
|
|
110
117
|
|
111
118
|
begin
|
112
119
|
req.start_layer(layer)
|
113
|
-
|
120
|
+
if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
|
121
|
+
super(*args, **kwargs)
|
122
|
+
else
|
123
|
+
super(*args)
|
124
|
+
end
|
114
125
|
ensure
|
115
126
|
req.stop_layer
|
116
127
|
end
|
@@ -118,10 +129,15 @@ module ScoutApm
|
|
118
129
|
end
|
119
130
|
|
120
131
|
module ActionViewTemplateRendererInstruments
|
132
|
+
# Don't forward kwargs here, since Rails 3, 4, 5, 6 don't use them, and
|
133
|
+
# it causes annoyances in the instrumentation
|
121
134
|
def render_template(*args)
|
122
135
|
req = ScoutApm::RequestManager.lookup
|
123
136
|
|
124
|
-
|
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
|
125
141
|
template_name ||= "Unknown"
|
126
142
|
layer_name = template_name + "/Rendering"
|
127
143
|
|
@@ -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
|
################################################################################
|
@@ -315,7 +317,11 @@ module ScoutApm
|
|
315
317
|
req.start_layer(layer)
|
316
318
|
req.ignore_children!
|
317
319
|
begin
|
318
|
-
|
320
|
+
if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
|
321
|
+
find_by_sql_without_scout_instruments(*args, **kwargs, &block)
|
322
|
+
else
|
323
|
+
find_by_sql_without_scout_instruments(*args, &block)
|
324
|
+
end
|
319
325
|
ensure
|
320
326
|
req.acknowledge_children!
|
321
327
|
req.stop_layer
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Instruments
|
3
|
+
class Typhoeus
|
4
|
+
attr_reader :context
|
5
|
+
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
@installed = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def logger
|
12
|
+
context.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def installed?
|
16
|
+
@installed
|
17
|
+
end
|
18
|
+
|
19
|
+
def install
|
20
|
+
if defined?(::Typhoeus)
|
21
|
+
@installed = true
|
22
|
+
|
23
|
+
logger.info "Instrumenting Typhoeus"
|
24
|
+
|
25
|
+
::Typhoeus::Request.send(:prepend, TyphoeusInstrumentation)
|
26
|
+
::Typhoeus::Hydra.send(:prepend, TyphoeusHydraInstrumentation)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module TyphoeusHydraInstrumentation
|
31
|
+
def run(*args, &block)
|
32
|
+
layer = ScoutApm::Layer.new("HTTP", "Hydra")
|
33
|
+
layer.desc = scout_desc if current_layer
|
34
|
+
|
35
|
+
req = ScoutApm::RequestManager.lookup
|
36
|
+
req.start_layer(layer)
|
37
|
+
|
38
|
+
begin
|
39
|
+
super(*args, &block)
|
40
|
+
ensure
|
41
|
+
req.stop_layer
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def scout_desc
|
46
|
+
"#{self.queued_requests.count} requests"
|
47
|
+
rescue
|
48
|
+
""
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module TyphoeusInstrumentation
|
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
|
+
|
57
|
+
req = ScoutApm::RequestManager.lookup
|
58
|
+
req.start_layer(layer)
|
59
|
+
|
60
|
+
begin
|
61
|
+
super(*args, &block)
|
62
|
+
ensure
|
63
|
+
req.stop_layer
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def scout_desc(verb, uri)
|
68
|
+
max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
|
69
|
+
(String(uri).split('?').first)[0..(max_length - 1)]
|
70
|
+
rescue
|
71
|
+
""
|
72
|
+
end
|
73
|
+
|
74
|
+
def scout_request_url
|
75
|
+
self.url
|
76
|
+
rescue
|
77
|
+
""
|
78
|
+
end
|
79
|
+
|
80
|
+
def scout_request_verb
|
81
|
+
self.options[:method].to_s
|
82
|
+
rescue
|
83
|
+
""
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/scout_apm/layer.rb
CHANGED
@@ -116,7 +116,7 @@ module ScoutApm
|
|
116
116
|
# In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
|
117
117
|
def caller_array
|
118
118
|
# omits the first several callers which are in the ScoutAPM stack.
|
119
|
-
if ScoutApm::Agent.instance.context.environment.ruby_2?
|
119
|
+
if ScoutApm::Agent.instance.context.environment.ruby_2? || ScoutApm::Agent.instance.context.environment.ruby_3?
|
120
120
|
caller(3...BACKTRACE_CALLER_LIMIT)
|
121
121
|
else
|
122
122
|
caller[3...BACKTRACE_CALLER_LIMIT]
|
@@ -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
|
data/lib/scout_apm/logger.rb
CHANGED
@@ -16,8 +16,20 @@ module ScoutApm
|
|
16
16
|
@server = nil
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def require_webrick
|
20
20
|
require 'webrick'
|
21
|
+
true
|
22
|
+
rescue LoadError
|
23
|
+
@logger.warn(
|
24
|
+
%q|Could not require Webrick. Ruby 3.0 stopped bundling it
|
25
|
+
automatically, but it is required to instrument Resque. Please add
|
26
|
+
Webrick to your Gemfile.|
|
27
|
+
)
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
return false unless require_webrick
|
21
33
|
|
22
34
|
@server = WEBrick::HTTPServer.new(
|
23
35
|
:BindAddress => bind,
|
@@ -45,31 +45,17 @@ module ScoutApm
|
|
45
45
|
"{#{str_parts.join(",")}}"
|
46
46
|
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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)|
|
data/lib/scout_apm/tracer.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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 (?)')
|
data/lib/scout_apm/version.rb
CHANGED
data/scout_apm.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.extensions << 'ext/allocations/extconf.rb'
|
22
22
|
s.extensions << 'ext/rusage/extconf.rb'
|
23
23
|
|
24
|
-
s.required_ruby_version = '
|
24
|
+
s.required_ruby_version = '>= 2.1'
|
25
25
|
|
26
26
|
s.add_development_dependency "minitest"
|
27
27
|
s.add_development_dependency "mocha"
|
@@ -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
|
@@ -44,8 +44,8 @@ class EnvironmentTest < Minitest::Test
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def clean_fake_rails
|
47
|
-
Kernel.
|
48
|
-
Kernel.
|
47
|
+
Kernel.send(:remove_const, "Rails") if defined?(Kernel::Rails)
|
48
|
+
Kernel.send(:remove_const, "ActionController") if defined?(Kernel::ActionController)
|
49
49
|
end
|
50
50
|
|
51
51
|
def fake_sinatra
|
@@ -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
|
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.
|
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
|
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
|
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 }
|
@@ -146,6 +161,37 @@ module ScoutApm
|
|
146
161
|
assert_equal %q|UPDATE "mytable" SET "myfield" = ?, "countofthings" = ? WHERE "user_id" = ?|, ss.to_s
|
147
162
|
end
|
148
163
|
|
164
|
+
def test_postgres_multiline_sql
|
165
|
+
sql = %q|
|
166
|
+
SELECT "html_form_payloads".*
|
167
|
+
FROM "html_form_payloads"
|
168
|
+
INNER JOIN "leads" ON "leads"."payload_id" = "html_form_payloads"."id"
|
169
|
+
AND "leads"."payload_type" = ?
|
170
|
+
WHERE html_form_payloads.id < 10
|
171
|
+
AND "form_type" = 'xyz'
|
172
|
+
AND (params::varchar = '{"name":"Chris","resident":"Yes","e-content":"Secret content"}')
|
173
|
+
AND (leads.url = 'http://example.com')
|
174
|
+
ORDER BY "html_form_payloads"."id" ASC
|
175
|
+
LIMIT ?
|
176
|
+
|
|
177
|
+
|
178
|
+
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
|
179
|
+
assert_equal %q|SELECT "html_form_payloads".* FROM "html_form_payloads" INNER JOIN "leads" ON "leads"."payload_id" = "html_form_payloads"."id" AND "leads"."payload_type" = ? WHERE html_form_payloads.id < ? AND "form_type" = ? AND (params::varchar = ?) AND (leads.url = ?) ORDER BY "html_form_payloads"."id" ASC LIMIT ?|, ss.to_s
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_mysql_multiline
|
183
|
+
sql = %q|
|
184
|
+
SELECT `blogs`.*
|
185
|
+
FROM `blogs`
|
186
|
+
WHERE (title = 'abc')
|
187
|
+
|
|
188
|
+
|
189
|
+
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :mysql }
|
190
|
+
assert_equal %q|SELECT `blogs`.*
|
191
|
+
FROM `blogs`
|
192
|
+
WHERE (title = ?)|, ss.to_s
|
193
|
+
end
|
194
|
+
|
149
195
|
def assert_faster_than(target_seconds)
|
150
196
|
t1 = ::Time.now
|
151
197
|
yield
|
data/test/unit/tracer_test.rb
CHANGED
@@ -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.
|
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:
|
12
|
+
date: 2021-06-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -216,6 +216,7 @@ extensions:
|
|
216
216
|
- ext/rusage/extconf.rb
|
217
217
|
extra_rdoc_files: []
|
218
218
|
files:
|
219
|
+
- ".github/workflows/test.yml"
|
219
220
|
- ".gitignore"
|
220
221
|
- ".rubocop.yml"
|
221
222
|
- ".travis.yml"
|
@@ -250,6 +251,7 @@ files:
|
|
250
251
|
- lib/scout_apm/auto_instrument/parser.rb
|
251
252
|
- lib/scout_apm/auto_instrument/rails.rb
|
252
253
|
- lib/scout_apm/background_job_integrations/delayed_job.rb
|
254
|
+
- lib/scout_apm/background_job_integrations/faktory.rb
|
253
255
|
- lib/scout_apm/background_job_integrations/legacy_sneakers.rb
|
254
256
|
- lib/scout_apm/background_job_integrations/que.rb
|
255
257
|
- lib/scout_apm/background_job_integrations/resque.rb
|
@@ -315,6 +317,7 @@ files:
|
|
315
317
|
- lib/scout_apm/instruments/resque.rb
|
316
318
|
- lib/scout_apm/instruments/samplers.rb
|
317
319
|
- lib/scout_apm/instruments/sinatra.rb
|
320
|
+
- lib/scout_apm/instruments/typhoeus.rb
|
318
321
|
- lib/scout_apm/job_record.rb
|
319
322
|
- lib/scout_apm/layaway.rb
|
320
323
|
- lib/scout_apm/layaway_file.rb
|
@@ -395,8 +398,6 @@ files:
|
|
395
398
|
- lib/scout_apm/utils/numbers.rb
|
396
399
|
- lib/scout_apm/utils/scm.rb
|
397
400
|
- lib/scout_apm/utils/sql_sanitizer.rb
|
398
|
-
- lib/scout_apm/utils/sql_sanitizer_regex.rb
|
399
|
-
- lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb
|
400
401
|
- lib/scout_apm/utils/time.rb
|
401
402
|
- lib/scout_apm/utils/unique_id.rb
|
402
403
|
- lib/scout_apm/version.rb
|
@@ -407,11 +408,13 @@ files:
|
|
407
408
|
- test/tmp/README.md
|
408
409
|
- test/unit/agent_context_test.rb
|
409
410
|
- test/unit/agent_test.rb
|
411
|
+
- test/unit/auto_instrument/anonymous_block_value.rb
|
410
412
|
- test/unit/auto_instrument/assignments-instrumented.rb
|
411
413
|
- test/unit/auto_instrument/assignments.rb
|
412
414
|
- test/unit/auto_instrument/controller-ast.txt
|
413
415
|
- test/unit/auto_instrument/controller-instrumented.rb
|
414
416
|
- test/unit/auto_instrument/controller.rb
|
417
|
+
- test/unit/auto_instrument/hanging_method.rb
|
415
418
|
- test/unit/auto_instrument/rescue_from-instrumented.rb
|
416
419
|
- test/unit/auto_instrument/rescue_from.rb
|
417
420
|
- test/unit/auto_instrument_test.rb
|
@@ -440,9 +443,9 @@ files:
|
|
440
443
|
- test/unit/limited_layer_test.rb
|
441
444
|
- test/unit/logger_test.rb
|
442
445
|
- test/unit/metric_set_test.rb
|
443
|
-
- test/unit/remote/
|
444
|
-
- test/unit/remote/
|
445
|
-
- test/unit/remote/
|
446
|
+
- test/unit/remote/message_test.rb
|
447
|
+
- test/unit/remote/route_test.rb
|
448
|
+
- test/unit/remote/server_test.rb
|
446
449
|
- test/unit/request_histograms_test.rb
|
447
450
|
- test/unit/scored_item_set_test.rb
|
448
451
|
- test/unit/serializers/payload_serializer_test.rb
|
@@ -468,7 +471,7 @@ require_paths:
|
|
468
471
|
- data
|
469
472
|
required_ruby_version: !ruby/object:Gem::Requirement
|
470
473
|
requirements:
|
471
|
-
- - "
|
474
|
+
- - ">="
|
472
475
|
- !ruby/object:Gem::Version
|
473
476
|
version: '2.1'
|
474
477
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -477,7 +480,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
477
480
|
- !ruby/object:Gem::Version
|
478
481
|
version: '0'
|
479
482
|
requirements: []
|
480
|
-
rubygems_version: 3.
|
483
|
+
rubygems_version: 3.1.2
|
481
484
|
signing_key:
|
482
485
|
specification_version: 4
|
483
486
|
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*$|.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|$)/i.freeze
|
14
|
-
PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|$)/i.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
|