scout_apm 2.6.4 → 2.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +34 -0
- data/Gemfile +4 -0
- data/gems/rails3.gemfile +1 -0
- data/lib/scout_apm.rb +2 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +1 -1
- data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -2
- data/lib/scout_apm/background_job_integrations/sneakers.rb +11 -11
- data/lib/scout_apm/config.rb +3 -1
- data/lib/scout_apm/detailed_trace.rb +2 -1
- data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +1 -0
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +47 -26
- data/lib/scout_apm/instruments/action_view.rb +7 -2
- data/lib/scout_apm/job_record.rb +4 -2
- data/lib/scout_apm/layaway_file.rb +4 -0
- data/lib/scout_apm/layer_children_set.rb +9 -8
- data/lib/scout_apm/layer_converters/trace_converter.rb +3 -0
- data/lib/scout_apm/remote/message.rb +4 -0
- data/lib/scout_apm/reporter.rb +1 -2
- data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
- data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
- data/lib/scout_apm/utils/marshal_logging.rb +90 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +10 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +7 -0
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +6 -0
- data/lib/scout_apm/version.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/layer_children_set_test.rb +9 -0
- data/test/unit/sql_sanitizer_test.rb +47 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54f1c7e07f92a0d5d67a22354b0273c4894e8408ec00bcdada275457ed2f00f2
|
4
|
+
data.tar.gz: 50cf2d441c948e769b2f2222895c84aadb0a5e3e3f75d16e647a2096a86551e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27a012457a6871cdbed206f2a55914163571e81af1b1f6253079c2fe9954354a9888b59627d832f3635a536ddf3f331c066b2bc4e053956f5135c706ffea1a17
|
7
|
+
data.tar.gz: cfc5d80fb3b0ccebc2e78246a394a23a8e16d52a89868cff024931d6ce5b3539aafc42ef6f491105db43f8c544750b270cbbc7cc1fed556ab543bb0d963f1c75
|
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
# 2.6.9
|
2
|
+
|
3
|
+
* Add `ssl_cert_file` config option (#352)
|
4
|
+
* Improve sanitization of Postgres UPDATE SQL (#351)
|
5
|
+
* Allow custom URL sanitization (#341)
|
6
|
+
|
7
|
+
# 2.6.8
|
8
|
+
|
9
|
+
* Lock rake version for 1.8.7 to older version (#329)
|
10
|
+
* Delete unneeded .DS_Store file that snuck in (#334)
|
11
|
+
* Fix typo in "queue_time_ms"
|
12
|
+
* Fix Rails 6 deprecation warning at boot time (#337)
|
13
|
+
* Fix partial naming on Rails 6.0 (#339)
|
14
|
+
* Support Sidekiq 6.1 instrumentation (#340)
|
15
|
+
|
16
|
+
# 2.6.7
|
17
|
+
|
18
|
+
* Remove accidental call to `as_json`
|
19
|
+
|
20
|
+
# 2.6.6
|
21
|
+
|
22
|
+
* Add basic support for parsing Microsoft SQLServer queries (#317)
|
23
|
+
* Refine Postgresql Sanitization with subqueries and JSON operations (#262)
|
24
|
+
|
25
|
+
# 2.6.5
|
26
|
+
|
27
|
+
* Add a tag to any requests that reach maximum number of spans (#316)
|
28
|
+
* Update testing library Mocha (#315)
|
29
|
+
* Fix case sensitivity mismatch in Job renaming (#314)
|
30
|
+
* Add support for Sneakers 2.5 (#313)
|
31
|
+
* Fix edge case with Resque instrumentation (#312)
|
32
|
+
* Fix missing source code when used with BugSnag (#308)
|
33
|
+
|
1
34
|
# 2.6.4
|
2
35
|
|
3
36
|
* Add defensive check against a nil @address in Net/HTTP instruments (#306)
|
@@ -26,6 +59,7 @@
|
|
26
59
|
# 2.5.3
|
27
60
|
|
28
61
|
* Add Que support (#265)
|
62
|
+
* Add Memcached support (#279)
|
29
63
|
|
30
64
|
# 2.5.2
|
31
65
|
|
data/Gemfile
CHANGED
data/gems/rails3.gemfile
CHANGED
data/lib/scout_apm.rb
CHANGED
@@ -63,6 +63,7 @@ require 'scout_apm/background_job_integrations/resque'
|
|
63
63
|
require 'scout_apm/background_job_integrations/shoryuken'
|
64
64
|
require 'scout_apm/background_job_integrations/sneakers'
|
65
65
|
require 'scout_apm/background_job_integrations/que'
|
66
|
+
require 'scout_apm/background_job_integrations/legacy_sneakers'
|
66
67
|
|
67
68
|
require 'scout_apm/framework_integrations/rails_2'
|
68
69
|
require 'scout_apm/framework_integrations/rails_3_or_4'
|
@@ -112,6 +113,7 @@ require 'scout_apm/utils/time'
|
|
112
113
|
require 'scout_apm/utils/unique_id'
|
113
114
|
require 'scout_apm/utils/numbers'
|
114
115
|
require 'scout_apm/utils/gzip_helper'
|
116
|
+
require 'scout_apm/utils/marshal_logging'
|
115
117
|
|
116
118
|
require 'scout_apm/config'
|
117
119
|
require 'scout_apm/environment'
|
@@ -8,7 +8,7 @@ module ScoutApm
|
|
8
8
|
if Rails.controller_path?(path) & !Rails.ignore?(path)
|
9
9
|
begin
|
10
10
|
new_code = Rails.rewrite(path)
|
11
|
-
return self.compile(new_code,
|
11
|
+
return self.compile(new_code, path, path)
|
12
12
|
rescue
|
13
13
|
warn "Failed to apply auto-instrumentation to #{path}: #{$!}"
|
14
14
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# This is different than other BackgroundJobIntegrations and must be prepended
|
2
|
+
# manually in each job.
|
3
|
+
#
|
4
|
+
# class MyWorker
|
5
|
+
# prepend ScoutApm::BackgroundJobIntegrations::LegacySneakers
|
6
|
+
#
|
7
|
+
# def work(msg)
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
module ScoutApm
|
12
|
+
module BackgroundJobIntegrations
|
13
|
+
module LegacySneakers
|
14
|
+
UNKNOWN_QUEUE_PLACEHOLDER = 'default'.freeze
|
15
|
+
|
16
|
+
def self.prepended(base)
|
17
|
+
ScoutApm::Agent.instance.logger.info("Prepended LegacySneakers in #{base}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
super
|
22
|
+
|
23
|
+
# Save off the existing value to call the correct existing work
|
24
|
+
# function in the instrumentation. But then override Sneakers to always
|
25
|
+
# use the extra-argument version, which has data Scout needs
|
26
|
+
@call_work = respond_to?(:work)
|
27
|
+
end
|
28
|
+
|
29
|
+
def work_with_params(msg, delivery_info, metadata)
|
30
|
+
queue = delivery_info[:routing_key] || UNKNOWN_QUEUE_PLACEHOLDER
|
31
|
+
job_class = self.class.name
|
32
|
+
req = ScoutApm::RequestManager.lookup
|
33
|
+
|
34
|
+
begin
|
35
|
+
req.start_layer(ScoutApm::Layer.new('Queue', queue))
|
36
|
+
started_queue = true
|
37
|
+
req.start_layer(ScoutApm::Layer.new('Job', job_class))
|
38
|
+
started_job = true
|
39
|
+
|
40
|
+
if @call_work
|
41
|
+
work(msg)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
rescue Exception
|
46
|
+
req.error!
|
47
|
+
raise
|
48
|
+
ensure
|
49
|
+
req.stop_layer if started_job
|
50
|
+
req.stop_layer if started_queue
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -40,10 +40,10 @@ module ScoutApm
|
|
40
40
|
require 'sidekiq/processor' # sidekiq v4 has not loaded this file by this point
|
41
41
|
|
42
42
|
::Sidekiq::Processor.class_eval do
|
43
|
-
def initialize_with_scout(
|
43
|
+
def initialize_with_scout(*args)
|
44
44
|
agent = ::ScoutApm::Agent.instance
|
45
45
|
agent.start
|
46
|
-
initialize_without_scout(
|
46
|
+
initialize_without_scout(*args)
|
47
47
|
end
|
48
48
|
|
49
49
|
alias_method :initialize_without_scout, :initialize
|
@@ -1,14 +1,22 @@
|
|
1
1
|
module ScoutApm
|
2
2
|
module BackgroundJobIntegrations
|
3
3
|
class Sneakers
|
4
|
-
attr_reader :logger
|
5
|
-
|
6
4
|
def name
|
7
5
|
:sneakers
|
8
6
|
end
|
9
7
|
|
10
8
|
def present?
|
11
|
-
defined?(::Sneakers)
|
9
|
+
defined?(::Sneakers) && supported_version?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Only support Sneakers 2.7 and up
|
13
|
+
def supported_version?
|
14
|
+
result = Gem::Version.new(::Sneakers::VERSION) > Gem::Version.new("2.7.0")
|
15
|
+
ScoutApm::Agent.instance.logger.info("Skipping Sneakers instrumentation. Only versions 2.7+ are supported. See docs or contact support@scoutapm.com for instrumentation of older versions.")
|
16
|
+
result
|
17
|
+
rescue
|
18
|
+
ScoutApm::Agent.instance.logger.info("Failed comparing Sneakers Version. Skipping")
|
19
|
+
false
|
12
20
|
end
|
13
21
|
|
14
22
|
def forking?
|
@@ -69,14 +77,6 @@ module ScoutApm
|
|
69
77
|
alias_method :process_work_without_scout, :process_work
|
70
78
|
alias_method :process_work, :process_work_with_scout
|
71
79
|
end
|
72
|
-
|
73
|
-
# msg = {
|
74
|
-
# "job_class":"DummyWorker",
|
75
|
-
# "job_id":"ea23ba1c-3022-4e05-870b-c3bcb1c4f328",
|
76
|
-
# "queue_name":"default",
|
77
|
-
# "arguments":["fjdkl"],
|
78
|
-
# "locale":"en"
|
79
|
-
# }
|
80
80
|
end
|
81
81
|
|
82
82
|
ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper'.freeze
|
data/lib/scout_apm/config.rb
CHANGED
@@ -75,6 +75,7 @@ module ScoutApm
|
|
75
75
|
'revision_sha',
|
76
76
|
'scm_subdirectory',
|
77
77
|
'start_resque_server_instrument',
|
78
|
+
'ssl_cert_file',
|
78
79
|
'uri_reporting',
|
79
80
|
'instrument_http_url_length',
|
80
81
|
'timeline_traces',
|
@@ -284,7 +285,8 @@ module ScoutApm
|
|
284
285
|
'collect_remote_ip' => true,
|
285
286
|
'timeline_traces' => true,
|
286
287
|
'auto_instruments' => false,
|
287
|
-
'auto_instruments_ignore' => []
|
288
|
+
'auto_instruments_ignore' => [],
|
289
|
+
'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] )
|
288
290
|
}.freeze
|
289
291
|
|
290
292
|
def value(key)
|
@@ -26,7 +26,7 @@ module ScoutApm
|
|
26
26
|
# The time in queue of the transaction in ms. If not present, +nil+ is returned as this is unknown.
|
27
27
|
def queue_time_ms
|
28
28
|
# Controller logic
|
29
|
-
if converter_results[:queue_time] && converter_results[:
|
29
|
+
if converter_results[:queue_time] && converter_results[:queue_time].any?
|
30
30
|
converter_results[:queue_time].values.first.total_call_time*1000 # ms
|
31
31
|
# Job logic
|
32
32
|
elsif converter_results[:job]
|
@@ -17,46 +17,73 @@ module ScoutApm
|
|
17
17
|
@installed
|
18
18
|
end
|
19
19
|
|
20
|
+
def installed!
|
21
|
+
@installed = true
|
22
|
+
end
|
23
|
+
|
20
24
|
def install
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
if !defined?(::ActiveSupport)
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
# The block below runs with `self` equal to the ActionController::Base or ::API module, not this class we're in now. By saving an instance of ourselves into the `this` variable, we can continue accessing what we need.
|
30
|
+
this = self
|
27
31
|
|
32
|
+
ActiveSupport.on_load(:action_controller) do
|
33
|
+
if this.installed?
|
34
|
+
this.logger.info("Skipping ActionController - Already Ran")
|
35
|
+
next
|
36
|
+
else
|
37
|
+
this.logger.info("Instrumenting ActionController (on_load)")
|
38
|
+
this.installed!
|
39
|
+
end
|
40
|
+
|
41
|
+
# We previously instrumented ActionController::Metal, which missed
|
42
|
+
# before and after filter timing. Instrumenting Base includes those
|
43
|
+
# filters, at the expense of missing out on controllers that don't use
|
44
|
+
# the full Rails stack.
|
28
45
|
if defined?(::ActionController::Base)
|
29
|
-
logger.info "Instrumenting ActionController::Base"
|
46
|
+
this.logger.info "Instrumenting ActionController::Base"
|
30
47
|
::ActionController::Base.class_eval do
|
31
|
-
# include ScoutApm::Tracer
|
32
48
|
include ScoutApm::Instruments::ActionControllerBaseInstruments
|
33
49
|
end
|
34
50
|
end
|
35
51
|
|
36
52
|
if defined?(::ActionController::Metal)
|
37
|
-
logger.info "Instrumenting ActionController::Metal"
|
53
|
+
this.logger.info "Instrumenting ActionController::Metal"
|
38
54
|
::ActionController::Metal.class_eval do
|
39
55
|
include ScoutApm::Instruments::ActionControllerMetalInstruments
|
40
56
|
end
|
41
57
|
end
|
42
58
|
|
43
59
|
if defined?(::ActionController::API)
|
44
|
-
logger.info "Instrumenting ActionController::Api"
|
60
|
+
this.logger.info "Instrumenting ActionController::Api"
|
45
61
|
::ActionController::API.class_eval do
|
46
62
|
include ScoutApm::Instruments::ActionControllerAPIInstruments
|
47
63
|
end
|
48
64
|
end
|
49
65
|
end
|
50
66
|
|
51
|
-
|
52
|
-
# we can insert this multiple times into the ancestors
|
53
|
-
# stack. Otherwise it only exists the first time you include it
|
54
|
-
# (under Metal, instead of under API) and we miss instrumenting
|
55
|
-
# before_action callbacks
|
67
|
+
ScoutApm::Agent.instance.context.logger.info("Instrumenting ActionController (hook installed)")
|
56
68
|
end
|
57
69
|
|
70
|
+
# Returns a new anonymous module each time it is called. So
|
71
|
+
# we can insert this multiple times into the ancestors
|
72
|
+
# stack. Otherwise it only exists the first time you include it
|
73
|
+
# (under Metal, instead of under API) and we miss instrumenting
|
74
|
+
# before_action callbacks
|
58
75
|
def self.build_instrument_module
|
59
76
|
Module.new do
|
77
|
+
# Determine the URI of this request to capture. Overridable by users in their controller.
|
78
|
+
def scout_transaction_uri(config=ScoutApm::Agent.instance.context.config)
|
79
|
+
case config.value("uri_reporting")
|
80
|
+
when 'path'
|
81
|
+
request.path # strips off the query string for more security
|
82
|
+
else # default handles filtered params
|
83
|
+
request.filtered_path
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
60
87
|
def process_action(*args)
|
61
88
|
req = ScoutApm::RequestManager.lookup
|
62
89
|
current_layer = req.current_layer
|
@@ -72,7 +99,11 @@ module ScoutApm
|
|
72
99
|
# Don't start a new layer if ActionController::API or ActionController::Base handled it already.
|
73
100
|
super
|
74
101
|
else
|
75
|
-
|
102
|
+
begin
|
103
|
+
uri = scout_transaction_uri
|
104
|
+
req.annotate_request(:uri => uri)
|
105
|
+
rescue
|
106
|
+
end
|
76
107
|
|
77
108
|
# IP Spoofing Protection can throw an exception, just move on w/o remote ip
|
78
109
|
if agent_context.config.value('collect_remote_ip')
|
@@ -95,16 +126,6 @@ module ScoutApm
|
|
95
126
|
end
|
96
127
|
end
|
97
128
|
|
98
|
-
# Given an +ActionDispatch::Request+, formats the uri based on config settings.
|
99
|
-
# XXX: Don't lookup context like this - find a way to pass it through
|
100
|
-
def self.scout_transaction_uri(request, config=ScoutApm::Agent.instance.context.config)
|
101
|
-
case config.value("uri_reporting")
|
102
|
-
when 'path'
|
103
|
-
request.path # strips off the query string for more security
|
104
|
-
else # default handles filtered params
|
105
|
-
request.filtered_path
|
106
|
-
end
|
107
|
-
end
|
108
129
|
end
|
109
130
|
|
110
131
|
module ActionControllerMetalInstruments
|
@@ -75,13 +75,18 @@ module ScoutApm
|
|
75
75
|
end
|
76
76
|
|
77
77
|
module ActionViewPartialRendererInstruments
|
78
|
+
# In Rails 6, the signature changed to pass the view & template args directly, as opposed to through the instance var
|
79
|
+
# New signature is: def render_partial(view, template)
|
78
80
|
def render_partial(*args)
|
79
81
|
req = ScoutApm::RequestManager.lookup
|
80
82
|
|
81
|
-
|
83
|
+
maybe_template = args[1]
|
84
|
+
|
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
|
82
87
|
template_name ||= "Unknown Partial"
|
83
|
-
layer_name = template_name + "/Rendering"
|
84
88
|
|
89
|
+
layer_name = template_name + "/Rendering"
|
85
90
|
layer = ScoutApm::Layer.new("View", layer_name)
|
86
91
|
layer.subscopable!
|
87
92
|
|
data/lib/scout_apm/job_record.rb
CHANGED
@@ -32,8 +32,10 @@ module ScoutApm
|
|
32
32
|
|
33
33
|
# Modifies self and returns self, after merging in `other`.
|
34
34
|
def combine!(other)
|
35
|
-
|
36
|
-
|
35
|
+
if !self.eql?(other)
|
36
|
+
ScoutApm::Agent.instance.logger.debug("Mismatched Merge of Background Job: (Queue #{queue_name} == #{other.queue_name}) (Name #{job_name} == #{other.job_name}) (Hash #{hash} == #{other.hash})")
|
37
|
+
return self
|
38
|
+
end
|
37
39
|
|
38
40
|
@errors += other.errors
|
39
41
|
@metric_set = metric_set.combine!(other.metric_set)
|
@@ -30,6 +30,10 @@ module ScoutApm
|
|
30
30
|
|
31
31
|
def serialize(data)
|
32
32
|
Marshal.dump(data)
|
33
|
+
rescue
|
34
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling LayawayFile")
|
35
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
|
36
|
+
raise
|
33
37
|
end
|
34
38
|
|
35
39
|
def deserialize(data)
|
@@ -46,9 +46,15 @@ module ScoutApm
|
|
46
46
|
set = child_set(metric_type)
|
47
47
|
|
48
48
|
if set.size >= unique_cutoff
|
49
|
-
# find limited_layer
|
50
|
-
@limited_layers
|
51
|
-
@limited_layers
|
49
|
+
# find or create limited_layer
|
50
|
+
@limited_layers ||= Hash.new
|
51
|
+
layer = if @limited_layers.has_key?(metric_type)
|
52
|
+
@limited_layers[metric_type]
|
53
|
+
else
|
54
|
+
@limited_layers[metric_type] = LimitedLayer.new(metric_type)
|
55
|
+
end
|
56
|
+
|
57
|
+
layer.absorb(child)
|
52
58
|
else
|
53
59
|
# we have space just add it
|
54
60
|
set << child
|
@@ -76,10 +82,5 @@ module ScoutApm
|
|
76
82
|
def size
|
77
83
|
@children.size
|
78
84
|
end
|
79
|
-
|
80
|
-
# hold off initializing this until we know we need it
|
81
|
-
def init_limited_layers
|
82
|
-
@limited_layers ||= Hash.new { |hash, key| hash[key] = LimitedLayer.new(key) }
|
83
|
-
end
|
84
85
|
end
|
85
86
|
end
|
@@ -17,6 +17,10 @@ module ScoutApm
|
|
17
17
|
|
18
18
|
def encode
|
19
19
|
Marshal.dump(self)
|
20
|
+
rescue
|
21
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling Remote::Message")
|
22
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(self).dive) rescue nil
|
23
|
+
raise
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
data/lib/scout_apm/reporter.rb
CHANGED
@@ -2,7 +2,6 @@ require 'openssl'
|
|
2
2
|
|
3
3
|
module ScoutApm
|
4
4
|
class Reporter
|
5
|
-
CA_FILE = File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] )
|
6
5
|
VERIFY_MODE = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
7
6
|
|
8
7
|
attr_reader :type
|
@@ -123,7 +122,7 @@ module ScoutApm
|
|
123
122
|
proxy_uri.password).new(url.host, url.port)
|
124
123
|
if url.is_a?(URI::HTTPS)
|
125
124
|
http.use_ssl = true
|
126
|
-
http.ca_file =
|
125
|
+
http.ca_file = config.value("ssl_cert_file")
|
127
126
|
http.verify_mode = VERIFY_MODE
|
128
127
|
end
|
129
128
|
http
|
@@ -5,6 +5,10 @@ module ScoutApm
|
|
5
5
|
class AppServerLoadSerializer
|
6
6
|
def self.serialize(data)
|
7
7
|
Marshal.dump(data)
|
8
|
+
rescue
|
9
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling AppServerLoad")
|
10
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
|
11
|
+
raise
|
8
12
|
end
|
9
13
|
|
10
14
|
def self.deserialize(data)
|
@@ -5,6 +5,10 @@ module ScoutApm
|
|
5
5
|
class DirectiveSerializer
|
6
6
|
def self.serialize(data)
|
7
7
|
Marshal.dump(data)
|
8
|
+
rescue
|
9
|
+
ScoutApm::Agent.instance.logger.info("Failed Marshalling Directive")
|
10
|
+
ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
|
11
|
+
raise
|
8
12
|
end
|
9
13
|
|
10
14
|
def self.deserialize(data)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module Utils
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
class InstanceVar
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :obj
|
8
|
+
|
9
|
+
def initialize(name, obj, parent)
|
10
|
+
@name = name
|
11
|
+
@obj = obj
|
12
|
+
@parent = parent
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{@name} - #{obj.class}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def history
|
20
|
+
(@parent.nil? ? [] : @parent.history) + [to_s]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MarshalLogging
|
25
|
+
def initialize(base_obj)
|
26
|
+
@base_obj = base_obj
|
27
|
+
end
|
28
|
+
|
29
|
+
def dive
|
30
|
+
to_investigate = [InstanceVar.new('Root', @base_obj, nil)]
|
31
|
+
max_to_check = 10000
|
32
|
+
checked = 0
|
33
|
+
|
34
|
+
while (var = to_investigate.shift)
|
35
|
+
checked += 1
|
36
|
+
if checked > max_to_check
|
37
|
+
return "Limiting Checks (max = #{max_to_check})"
|
38
|
+
end
|
39
|
+
|
40
|
+
obj = var.obj
|
41
|
+
|
42
|
+
if offending_hash?(obj)
|
43
|
+
return "Found undumpable object: #{var.history}"
|
44
|
+
end
|
45
|
+
|
46
|
+
if !dumps?(obj)
|
47
|
+
if obj.is_a? Hash
|
48
|
+
keys = obj.keys
|
49
|
+
keys.each do |key|
|
50
|
+
to_investigate.push(
|
51
|
+
InstanceVar.new(key.to_s, obj[key], var)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
elsif obj.is_a? Array
|
55
|
+
obj.each_with_index do |value, idx|
|
56
|
+
to_investigate.push(
|
57
|
+
InstanceVar.new("Index #{idx}", value, var)
|
58
|
+
)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
symbols = obj.instance_variables
|
62
|
+
if !symbols.any?
|
63
|
+
return "Found undumpable object: #{var.history}"
|
64
|
+
end
|
65
|
+
|
66
|
+
symbols.each do |sym|
|
67
|
+
to_investigate.push(
|
68
|
+
InstanceVar.new(sym, obj.instance_variable_get(sym), var)
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def dumps?(obj)
|
79
|
+
Marshal.dump(obj)
|
80
|
+
true
|
81
|
+
rescue TypeError
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
def offending_hash?(obj)
|
86
|
+
obj.is_a?(Hash) && !obj.default_proc.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -34,15 +34,24 @@ module ScoutApm
|
|
34
34
|
when :postgres then to_s_postgres
|
35
35
|
when :mysql then to_s_mysql
|
36
36
|
when :sqlite then to_s_sqlite
|
37
|
+
when :sqlserver then to_s_sqlserver
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
41
|
private
|
41
42
|
|
43
|
+
def to_s_sqlserver
|
44
|
+
sql.gsub!(SQLSERVER_EXECUTESQL, '\1')
|
45
|
+
sql.gsub!(SQLSERVER_REMOVE_INTEGERS, '?')
|
46
|
+
sql.gsub!(SQLSERVER_IN_CLAUSE, 'IN (?)')
|
47
|
+
sql
|
48
|
+
end
|
49
|
+
|
42
50
|
def to_s_postgres
|
43
51
|
sql.gsub!(PSQL_PLACEHOLDER, '?')
|
44
52
|
sql.gsub!(PSQL_VAR_INTERPOLATION, '')
|
45
|
-
sql.gsub!(PSQL_REMOVE_STRINGS, '?')
|
53
|
+
sql.gsub!(PSQL_AFTER_WHERE) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
54
|
+
sql.gsub!(PSQL_AFTER_SET) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
|
46
55
|
sql.gsub!(PSQL_REMOVE_INTEGERS, '?')
|
47
56
|
sql.gsub!(PSQL_IN_CLAUSE, 'IN (?)')
|
48
57
|
sql.gsub!(MULTIPLE_SPACES, ' ')
|
@@ -10,6 +10,8 @@ module ScoutApm
|
|
10
10
|
PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
11
11
|
PSQL_PLACEHOLDER = /\$\d+/.freeze
|
12
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
|
13
15
|
|
14
16
|
MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
15
17
|
MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
|
@@ -20,6 +22,11 @@ module ScoutApm
|
|
20
22
|
SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
21
23
|
SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
|
22
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
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
@@ -10,6 +10,8 @@ module ScoutApm
|
|
10
10
|
PSQL_REMOVE_INTEGERS = /\b\d+\b/.freeze
|
11
11
|
PSQL_PLACEHOLDER = /\$\d+/.freeze
|
12
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
|
13
15
|
|
14
16
|
MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
|
15
17
|
MYSQL_REMOVE_INTEGERS = /\b\d+\b/.freeze
|
@@ -21,6 +23,10 @@ module ScoutApm
|
|
21
23
|
SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
|
22
24
|
SQLITE_REMOVE_INTEGERS = /\b\d+\b/.freeze
|
23
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
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
data/lib/scout_apm/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -71,6 +71,15 @@ class LayerChildrenSetTest < Minitest::Test
|
|
71
71
|
limited_layers.each { |ml| assert_equal 5, ml.count }
|
72
72
|
end
|
73
73
|
|
74
|
+
def test_works_with_marshal
|
75
|
+
s = SET.new(5)
|
76
|
+
10.times do
|
77
|
+
s << make_layer("LayerType", "LayerName")
|
78
|
+
end
|
79
|
+
|
80
|
+
Marshal.dump(s)
|
81
|
+
end
|
82
|
+
|
74
83
|
#############
|
75
84
|
# Helpers #
|
76
85
|
#############
|
@@ -28,7 +28,23 @@ module ScoutApm
|
|
28
28
|
sql = %q|SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" WHERE (blogs.title = 'hello world')|
|
29
29
|
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
|
30
30
|
assert_equal %q|SELECT "users".* FROM "users" INNER JOIN "blogs" ON "blogs"."user_id" = "users"."id" WHERE (blogs.title = ?)|, ss.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_postgres_strips_after_where
|
34
|
+
raw_sql = %q|SELECT DISTINCT ON (flagged_traces.metric_name) flagged_traces.metric_name, "flagged_traces"."trace_id", "flagged_traces"."trace_type", "flagged_traces"."trace_occurred_at", flagged_traces.details ->> 'uri' as uri, (flagged_traces.details ->> 'n_sum_millis')::float as potential_savings, (flagged_traces.details ->> 'n_count')::float as num_queries FROM "flagged_traces" WHERE "flagged_traces"."app_id" = 5 AND "flagged_traces"."trace_type" = 'Request' AND ("flagged_traces"."trace_occurred_at" BETWEEN '2019-04-17 12:28:00.000000' AND '2019-04-18 12:28:00.000000') AND "flagged_traces"."flag_type" = 'nplusone' ORDER BY "flagged_traces"."metric_name" ASC, potential_savings DESC|
|
35
|
+
sanitized_sql = SqlSanitizer.new(raw_sql).tap { |it| it.database_engine = :postgres}
|
36
|
+
expected_sql = %q|SELECT DISTINCT ON (flagged_traces.metric_name) flagged_traces.metric_name, "flagged_traces"."trace_id", "flagged_traces"."trace_type", "flagged_traces"."trace_occurred_at", flagged_traces.details ->> 'uri' as uri, (flagged_traces.details ->> 'n_sum_millis')::float as potential_savings, (flagged_traces.details ->> 'n_count')::float as num_queries FROM "flagged_traces" WHERE "flagged_traces"."app_id" = ? AND "flagged_traces"."trace_type" = ? AND ("flagged_traces"."trace_occurred_at" BETWEEN ? AND ?) AND "flagged_traces"."flag_type" = ? ORDER BY "flagged_traces"."metric_name" ASC, potential_savings DESC|
|
37
|
+
assert_equal expected_sql, sanitized_sql.to_s
|
38
|
+
end
|
39
|
+
|
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')"|
|
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 ?)"|
|
44
|
+
assert_equal expected_sql, sanitized_sql.to_s
|
45
|
+
end
|
31
46
|
|
47
|
+
def test_postgres_strips_integers
|
32
48
|
# Strip integers
|
33
49
|
sql = %q|SELECT "blogs".* FROM "blogs" WHERE (view_count > 10)|
|
34
50
|
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
|
@@ -89,6 +105,30 @@ module ScoutApm
|
|
89
105
|
assert_equal %q|INSERT INTO `users` VALUES (?, ?)|, ss.to_s
|
90
106
|
end
|
91
107
|
|
108
|
+
def test_sqlserver_integers
|
109
|
+
skip "SQLServer Support requires Ruby 1.9+ For Regexes"
|
110
|
+
|
111
|
+
sql = "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"
|
112
|
+
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :sqlserver }
|
113
|
+
assert_equal %q|SELECT [users].* FROM [users] WHERE (age > ?) ORDER BY [users].[id] ASC OFFSET ? ROWS FETCH NEXT @0 ROWS ONLY|, ss.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_sqlserver_strings
|
117
|
+
skip "SQLServer Support requires Ruby 1.9+ For Regexes"
|
118
|
+
|
119
|
+
sql = "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE [users].[email] = @0 ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @1 ROWS ONLY', N'@0 nvarchar(4000), @1 int', @0 = N'foo', @1 = 10"
|
120
|
+
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :sqlserver }
|
121
|
+
assert_equal %q|SELECT [users].* FROM [users] WHERE [users].[email] = @0 ORDER BY [users].[id] ASC OFFSET ? ROWS FETCH NEXT @1 ROWS ONLY|, ss.to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_sqlserver_in_clause
|
125
|
+
skip "SQLServer Support requires Ruby 1.9+ For Regexes"
|
126
|
+
|
127
|
+
sql = "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE (id IN (1,2,3)) ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY', N'@0 int', @0 = 10"
|
128
|
+
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :sqlserver }
|
129
|
+
assert_equal %q|SELECT [users].* FROM [users] WHERE (id IN (?)) ORDER BY [users].[id] ASC OFFSET ? ROWS FETCH NEXT @0 ROWS ONLY|, ss.to_s
|
130
|
+
end
|
131
|
+
|
92
132
|
def test_scrubs_invalid_encoding
|
93
133
|
skip "Ruby 1.8.7 has no concept of encoding" if RUBY_VERSION.start_with?("1.8.")
|
94
134
|
|
@@ -99,6 +139,13 @@ module ScoutApm
|
|
99
139
|
assert_equal %q|SELECT `blogs`.* FROM `blogs` WHERE (title = ?)|, ss.to_s
|
100
140
|
end
|
101
141
|
|
142
|
+
def test_set_columns
|
143
|
+
sql = %q|UPDATE "mytable" SET "myfield" = 'fieldcontent', "countofthings" = 10 WHERE "user_id" = 10|
|
144
|
+
|
145
|
+
ss = SqlSanitizer.new(sql).tap{ |it| it.database_engine = :postgres }
|
146
|
+
assert_equal %q|UPDATE "mytable" SET "myfield" = ?, "countofthings" = ? WHERE "user_id" = ?|, ss.to_s
|
147
|
+
end
|
148
|
+
|
102
149
|
def assert_faster_than(target_seconds)
|
103
150
|
t1 = ::Time.now
|
104
151
|
yield
|
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: 2.6.
|
4
|
+
version: 2.6.9
|
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: 2020-08-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -250,6 +250,7 @@ files:
|
|
250
250
|
- lib/scout_apm/auto_instrument/parser.rb
|
251
251
|
- lib/scout_apm/auto_instrument/rails.rb
|
252
252
|
- lib/scout_apm/background_job_integrations/delayed_job.rb
|
253
|
+
- lib/scout_apm/background_job_integrations/legacy_sneakers.rb
|
253
254
|
- lib/scout_apm/background_job_integrations/que.rb
|
254
255
|
- lib/scout_apm/background_job_integrations/resque.rb
|
255
256
|
- lib/scout_apm/background_job_integrations/shoryuken.rb
|
@@ -280,7 +281,6 @@ files:
|
|
280
281
|
- lib/scout_apm/instant/middleware.rb
|
281
282
|
- lib/scout_apm/instant_reporting.rb
|
282
283
|
- lib/scout_apm/instrument_manager.rb
|
283
|
-
- lib/scout_apm/instruments/.DS_Store
|
284
284
|
- lib/scout_apm/instruments/action_controller_rails_2.rb
|
285
285
|
- lib/scout_apm/instruments/action_controller_rails_3_rails4.rb
|
286
286
|
- lib/scout_apm/instruments/action_view.rb
|
@@ -376,6 +376,7 @@ files:
|
|
376
376
|
- lib/scout_apm/utils/gzip_helper.rb
|
377
377
|
- lib/scout_apm/utils/installed_gems.rb
|
378
378
|
- lib/scout_apm/utils/klass_helper.rb
|
379
|
+
- lib/scout_apm/utils/marshal_logging.rb
|
379
380
|
- lib/scout_apm/utils/numbers.rb
|
380
381
|
- lib/scout_apm/utils/scm.rb
|
381
382
|
- lib/scout_apm/utils/sql_sanitizer.rb
|
@@ -459,7 +460,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
459
460
|
- !ruby/object:Gem::Version
|
460
461
|
version: '0'
|
461
462
|
requirements: []
|
462
|
-
rubygems_version: 3.0.
|
463
|
+
rubygems_version: 3.0.6
|
463
464
|
signing_key:
|
464
465
|
specification_version: 4
|
465
466
|
summary: Ruby application performance monitoring
|