scout_apm 2.5.1 → 5.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +68 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -5
- data/CHANGELOG.markdown +176 -3
- data/Gemfile +1 -7
- data/LICENSE.md +21 -28
- data/gems/README.md +28 -0
- data/gems/instruments.gemfile +6 -0
- data/gems/octoshark.gemfile +4 -0
- data/gems/rails3.gemfile +5 -0
- data/gems/rails4.gemfile +4 -0
- data/gems/rails5.gemfile +4 -0
- data/gems/rails6.gemfile +4 -0
- data/gems/sidekiq.gemfile +4 -0
- data/gems/typhoeus.gemfile +3 -0
- data/lib/scout_apm/agent/preconditions.rb +3 -3
- data/lib/scout_apm/agent.rb +22 -0
- data/lib/scout_apm/agent_context.rb +21 -2
- data/lib/scout_apm/app_server_load.rb +7 -2
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
- data/lib/scout_apm/auto_instrument/layer.rb +23 -0
- data/lib/scout_apm/auto_instrument/parser.rb +27 -0
- data/lib/scout_apm/auto_instrument/rails.rb +174 -0
- data/lib/scout_apm/auto_instrument.rb +5 -0
- 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/legacy_sneakers.rb +55 -0
- data/lib/scout_apm/background_job_integrations/que.rb +134 -0
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +15 -10
- data/lib/scout_apm/background_job_integrations/sneakers.rb +11 -11
- data/lib/scout_apm/config.rb +54 -6
- data/lib/scout_apm/detailed_trace.rb +3 -2
- data/lib/scout_apm/environment.rb +18 -1
- data/lib/scout_apm/error.rb +27 -0
- data/lib/scout_apm/error_service/error_buffer.rb +39 -0
- data/lib/scout_apm/error_service/error_record.rb +211 -0
- data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
- data/lib/scout_apm/error_service/middleware.rb +32 -0
- data/lib/scout_apm/error_service/notifier.rb +33 -0
- data/lib/scout_apm/error_service/payload.rb +47 -0
- data/lib/scout_apm/error_service/periodic_work.rb +17 -0
- data/lib/scout_apm/error_service/railtie.rb +11 -0
- data/lib/scout_apm/error_service/sidekiq.rb +80 -0
- data/lib/scout_apm/error_service.rb +34 -0
- data/lib/scout_apm/exceptions.rb +12 -0
- data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
- data/lib/scout_apm/external_service_metric_set.rb +97 -0
- data/lib/scout_apm/external_service_metric_stats.rb +85 -0
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +7 -2
- data/lib/scout_apm/git_revision.rb +9 -0
- data/lib/scout_apm/ignored_uris.rb +3 -1
- data/lib/scout_apm/instant/middleware.rb +4 -1
- data/lib/scout_apm/instrument_manager.rb +22 -1
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +53 -29
- data/lib/scout_apm/instruments/action_view.rb +30 -9
- data/lib/scout_apm/instruments/active_record.rb +69 -19
- data/lib/scout_apm/instruments/elasticsearch.rb +93 -42
- data/lib/scout_apm/instruments/grape.rb +1 -1
- data/lib/scout_apm/instruments/http.rb +68 -0
- data/lib/scout_apm/instruments/http_client.rb +33 -14
- data/lib/scout_apm/instruments/influxdb.rb +2 -2
- data/lib/scout_apm/instruments/memcached.rb +58 -0
- data/lib/scout_apm/instruments/middleware_detailed.rb +1 -1
- data/lib/scout_apm/instruments/middleware_summary.rb +1 -1
- data/lib/scout_apm/instruments/mongoid.rb +10 -5
- data/lib/scout_apm/instruments/moped.rb +44 -19
- data/lib/scout_apm/instruments/net_http.rb +51 -16
- data/lib/scout_apm/instruments/rails_router.rb +1 -1
- data/lib/scout_apm/instruments/redis.rb +27 -12
- data/lib/scout_apm/instruments/redis5.rb +59 -0
- data/lib/scout_apm/instruments/sinatra.rb +3 -1
- data/lib/scout_apm/instruments/typhoeus.rb +90 -0
- data/lib/scout_apm/job_record.rb +4 -2
- data/lib/scout_apm/layaway_file.rb +4 -0
- data/lib/scout_apm/layer.rb +5 -2
- data/lib/scout_apm/layer_children_set.rb +9 -8
- data/lib/scout_apm/layer_converters/external_service_converter.rb +65 -0
- data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
- data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +2 -0
- data/lib/scout_apm/layer_converters/trace_converter.rb +7 -4
- data/lib/scout_apm/logger.rb +5 -1
- data/lib/scout_apm/middleware.rb +1 -1
- data/lib/scout_apm/periodic_work.rb +19 -0
- data/lib/scout_apm/remote/message.rb +4 -0
- data/lib/scout_apm/remote/server.rb +13 -1
- data/lib/scout_apm/reporter.rb +8 -3
- data/lib/scout_apm/reporting.rb +2 -1
- data/lib/scout_apm/request_histograms.rb +8 -0
- 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/serializers/external_service_serializer_to_json.rb +15 -0
- data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +10 -3
- data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
- data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
- data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
- data/lib/scout_apm/slow_policy/policy.rb +21 -0
- data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
- data/lib/scout_apm/slow_request_policy.rb +18 -77
- data/lib/scout_apm/store.rb +31 -1
- data/lib/scout_apm/tracer.rb +2 -2
- data/lib/scout_apm/tracked_request.rb +35 -4
- data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
- data/lib/scout_apm/utils/marshal_logging.rb +90 -0
- data/lib/scout_apm/utils/sql_sanitizer.rb +47 -7
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +46 -1
- data/scout_apm.gemspec +14 -9
- data/test/test_helper.rb +2 -2
- data/test/tmp/README.md +17 -0
- data/test/unit/agent_context_test.rb +29 -0
- data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
- data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
- data/test/unit/auto_instrument/assignments.rb +31 -0
- data/test/unit/auto_instrument/controller-ast.txt +57 -0
- data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
- data/test/unit/auto_instrument/controller.rb +49 -0
- data/test/unit/auto_instrument/hanging_method.rb +6 -0
- data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
- data/test/unit/auto_instrument/rescue_from.rb +13 -0
- data/test/unit/auto_instrument_test.rb +62 -0
- data/test/unit/background_job_integrations/sidekiq_test.rb +17 -0
- data/test/unit/environment_test.rb +2 -2
- data/test/unit/error_service/error_buffer_test.rb +25 -0
- data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
- data/test/unit/external_service_metric_set_test.rb +67 -0
- data/test/unit/external_service_metric_stats_test.rb +106 -0
- data/test/unit/ignored_uris_test.rb +6 -0
- data/test/unit/instruments/active_record_test.rb +40 -0
- data/test/unit/instruments/http_client_test.rb +24 -0
- data/test/unit/instruments/http_test.rb +24 -0
- data/test/unit/instruments/moped_test.rb +24 -0
- data/test/unit/instruments/net_http_test.rb +11 -1
- data/test/unit/instruments/redis_test.rb +24 -0
- data/test/unit/instruments/typhoeus_test.rb +42 -0
- data/test/unit/layer_children_set_test.rb +9 -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/request_histograms_test.rb +17 -0
- data/test/unit/serializers/payload_serializer_test.rb +39 -3
- data/test/unit/slow_request_policy_test.rb +41 -13
- data/test/unit/sql_sanitizer_test.rb +106 -0
- data/test/unit/tracer_test.rb +25 -0
- metadata +118 -60
- data/.travis.yml +0 -25
- data/lib/scout_apm/instruments/.DS_Store +0 -0
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -25
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -26
- data/test/unit/instruments/active_record_instruments_test.rb +0 -5
- data/test/unit/slow_job_policy_test.rb +0 -6
@@ -0,0 +1,80 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module ErrorService
|
3
|
+
class Sidekiq
|
4
|
+
def initialize
|
5
|
+
@context = ScoutApm::Agent.instance.context
|
6
|
+
end
|
7
|
+
|
8
|
+
def install
|
9
|
+
return false unless defined?(::Sidekiq)
|
10
|
+
|
11
|
+
if ::Sidekiq::VERSION < "3"
|
12
|
+
install_sidekiq_with_middleware
|
13
|
+
else
|
14
|
+
install_sidekiq_with_error_handler
|
15
|
+
end
|
16
|
+
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def install_sidekiq_with_middleware
|
21
|
+
# old behavior
|
22
|
+
::Sidekiq.configure_server do |config|
|
23
|
+
config.server_middleware do |chain|
|
24
|
+
chain.add ScoutApm::ErrorService::Sidekiq::SidekiqExceptionMiddleware
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def install_sidekiq_with_error_handler
|
30
|
+
::Sidekiq.configure_server do |config|
|
31
|
+
config.error_handlers << proc { |exception, job_info|
|
32
|
+
context = ScoutApm::Agent.instance.context
|
33
|
+
|
34
|
+
# Bail out early, and reraise if the error is not interesting.
|
35
|
+
if context.ignored_exceptions.ignored?(exception)
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
|
39
|
+
job_class =
|
40
|
+
begin
|
41
|
+
job_class = job_info[:job]["class"]
|
42
|
+
job_class = job_info[:job]["args"][0]["job_class"] if job_class == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
43
|
+
job_class
|
44
|
+
rescue
|
45
|
+
"UnknownJob"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Capture the error for further processing and shipping
|
49
|
+
context.error_buffer.capture(exception, job_info.merge(:custom_controller => job_class))
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class SidekiqExceptionMiddleware
|
55
|
+
def call(worker, msg, queue)
|
56
|
+
yield
|
57
|
+
rescue => exception
|
58
|
+
context = ScoutApm::Agent.instance.context
|
59
|
+
|
60
|
+
# Bail out early, and reraise if the error is not interesting.
|
61
|
+
if context.ignored_exceptions.ignored?(exception)
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
|
65
|
+
# Capture the error for further processing and shipping
|
66
|
+
context.error_buffer.capture(
|
67
|
+
exception,
|
68
|
+
{
|
69
|
+
:custom_params => msg,
|
70
|
+
:custom_controller => msg["class"]
|
71
|
+
}
|
72
|
+
)
|
73
|
+
|
74
|
+
# Finally, reraise
|
75
|
+
raise exception
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "net/https"
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module ScoutApm
|
6
|
+
module ErrorService
|
7
|
+
API_VERSION = "1"
|
8
|
+
|
9
|
+
HEADERS = {
|
10
|
+
"Content-type" => "application/json",
|
11
|
+
"Accept" => "application/json"
|
12
|
+
}
|
13
|
+
|
14
|
+
# Public API to force a given exception to be captured.
|
15
|
+
# Still obeys the ignore list
|
16
|
+
# Used internally by SidekiqException
|
17
|
+
def self.capture(exception, params = {})
|
18
|
+
return if disabled?
|
19
|
+
|
20
|
+
context = ScoutApm::Agent.instance.context
|
21
|
+
return if context.ignored_exceptions.ignore?(exception)
|
22
|
+
|
23
|
+
context.errors_buffer.capture(exception, env)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.enabled?
|
27
|
+
ScoutApm::Agent.instance.context.config.value("errors_enabled")
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.disabled?
|
31
|
+
!enabled?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
module AllExceptionsExceptOnesWeMustNotRescue
|
3
|
+
# Borrowed from https://github.com/rspec/rspec-support/blob/v3.8.0/lib/rspec/support.rb#L132-L140
|
4
|
+
# These exceptions are dangerous to rescue as rescuing them
|
5
|
+
# would interfere with things we should not interfere with.
|
6
|
+
AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit]
|
7
|
+
|
8
|
+
def self.===(exception)
|
9
|
+
AVOID_RESCUING.none? { |ar| ar === exception }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -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]
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Note, this class must be Marshal Dumpable
|
2
|
+
module ScoutApm
|
3
|
+
class ExternalServiceMetricSet
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :metrics # the raw metrics. You probably want #metrics_to_report
|
7
|
+
|
8
|
+
def marshal_dump
|
9
|
+
[ @metrics ]
|
10
|
+
end
|
11
|
+
|
12
|
+
def marshal_load(array)
|
13
|
+
@metrics = array.first
|
14
|
+
@context = ScoutApm::Agent.instance.context
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(context)
|
18
|
+
@context = context
|
19
|
+
|
20
|
+
# A hash of ExternalServiceMetricStats values, keyed by ExternalServiceMetricStats.key
|
21
|
+
@metrics = Hash.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Need to look this up again if we end up as nil. Which I guess can happen
|
25
|
+
# after a Marshal load?
|
26
|
+
def context
|
27
|
+
@context ||= ScoutApm::Agent.instance.context
|
28
|
+
end
|
29
|
+
|
30
|
+
def each
|
31
|
+
metrics.each do |_key, external_service_metric_stat|
|
32
|
+
yield external_service_metric_stat
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Looks up a ExternalServiceMetricStats instance in the +@metrics+ hash. Sets the value to +other+ if no key
|
37
|
+
# Returns a ExternalServiceMetricStats instance
|
38
|
+
def lookup(other)
|
39
|
+
metrics[other.key] ||= other
|
40
|
+
end
|
41
|
+
|
42
|
+
# Take another set, and merge it with this one
|
43
|
+
def combine!(other)
|
44
|
+
other.each do |metric|
|
45
|
+
self << metric
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a single ExternalServiceMetricStats object to this set.
|
51
|
+
#
|
52
|
+
# Looks up an existing one under this key and merges, or just saves a new
|
53
|
+
# one under the key
|
54
|
+
def <<(stat)
|
55
|
+
existing_stat = metrics[stat.key]
|
56
|
+
if existing_stat
|
57
|
+
existing_stat.combine!(stat)
|
58
|
+
elsif at_limit?
|
59
|
+
# We're full up, can't add any more.
|
60
|
+
# Should I log this? It may get super noisy?
|
61
|
+
else
|
62
|
+
metrics[stat.key] = stat
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def increment_transaction_count!
|
67
|
+
metrics.each do |_key, external_service_metric_stat|
|
68
|
+
external_service_metric_stat.increment_transaction_count!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def metrics_to_report
|
73
|
+
report_limit = context.config.value('external_service_metric_report_limit')
|
74
|
+
if metrics.size > report_limit
|
75
|
+
metrics.
|
76
|
+
values.
|
77
|
+
sort_by {|stat| stat.call_time }.
|
78
|
+
reverse.
|
79
|
+
take(report_limit)
|
80
|
+
else
|
81
|
+
metrics.values
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def inspect
|
86
|
+
metrics.map {|key, metric|
|
87
|
+
"#{key.inspect} - Count: #{metric.call_count}, Total Time: #{"%.2f" % metric.call_time}"
|
88
|
+
}.join("\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
def at_limit?
|
92
|
+
@limit ||= context.config.value('external_service_metric_limit')
|
93
|
+
metrics.size >= @limit
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ScoutApm
|
2
|
+
class ExternalServiceMetricStats
|
3
|
+
|
4
|
+
DEFAULT_HISTOGRAM_SIZE = 50
|
5
|
+
|
6
|
+
attr_reader :domain_name
|
7
|
+
attr_reader :operation
|
8
|
+
attr_reader :scope
|
9
|
+
|
10
|
+
attr_reader :transaction_count
|
11
|
+
|
12
|
+
attr_reader :call_count
|
13
|
+
attr_reader :call_time
|
14
|
+
|
15
|
+
attr_reader :min_call_time
|
16
|
+
attr_reader :max_call_time
|
17
|
+
|
18
|
+
attr_reader :histogram
|
19
|
+
|
20
|
+
def initialize(domain_name, operation, scope, call_count, call_time)
|
21
|
+
@domain_name = domain_name
|
22
|
+
@operation = operation
|
23
|
+
|
24
|
+
@call_count = call_count
|
25
|
+
|
26
|
+
@call_time = call_time
|
27
|
+
@min_call_time = call_time
|
28
|
+
@max_call_time = call_time
|
29
|
+
|
30
|
+
# This histogram is for call_time
|
31
|
+
@histogram = NumericHistogram.new(DEFAULT_HISTOGRAM_SIZE)
|
32
|
+
@histogram.add(call_time)
|
33
|
+
|
34
|
+
@transaction_count = 0
|
35
|
+
|
36
|
+
@scope = scope
|
37
|
+
end
|
38
|
+
|
39
|
+
# Merge data in this scope. Used in ExternalServiceMetricSet
|
40
|
+
def key
|
41
|
+
@key ||= [domain_name, operation, scope]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Combine data from another ExternalServiceMetricStats into +self+. Modifies and returns +self+
|
45
|
+
def combine!(other)
|
46
|
+
return self if other == self
|
47
|
+
|
48
|
+
@transaction_count += other.transaction_count
|
49
|
+
@call_count += other.call_count
|
50
|
+
@call_time += other.call_time
|
51
|
+
|
52
|
+
@min_call_time = other.min_call_time if @min_call_time.zero? or other.min_call_time < @min_call_time
|
53
|
+
@max_call_time = other.max_call_time if other.max_call_time > @max_call_time
|
54
|
+
|
55
|
+
@histogram.combine!(other.histogram)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def as_json
|
60
|
+
json_attributes = [
|
61
|
+
:domain_name,
|
62
|
+
:operation,
|
63
|
+
:scope,
|
64
|
+
|
65
|
+
:transaction_count,
|
66
|
+
:call_count,
|
67
|
+
|
68
|
+
:histogram,
|
69
|
+
:call_time,
|
70
|
+
:max_call_time,
|
71
|
+
:min_call_time,
|
72
|
+
]
|
73
|
+
|
74
|
+
ScoutApm::AttributeArranger.call(self, json_attributes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Called by the Set on each ExternalServiceMetricStats object that it holds, only
|
78
|
+
# once during the recording of a transaction.
|
79
|
+
#
|
80
|
+
# Don't call elsewhere, and don't set to 1 in the initializer.
|
81
|
+
def increment_transaction_count!
|
82
|
+
@transaction_count += 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/scout_apm/fake_store.rb
CHANGED
@@ -48,6 +48,7 @@ module ScoutApm
|
|
48
48
|
when "sqlite" then :sqlite
|
49
49
|
when "mysql" then :mysql
|
50
50
|
when "mysql2" then :mysql
|
51
|
+
when "sqlserver" then :sqlserver
|
51
52
|
else default
|
52
53
|
end
|
53
54
|
else
|
@@ -70,10 +71,14 @@ module ScoutApm
|
|
70
71
|
#
|
71
72
|
# We avoid this issue by not calling .respond_to? here, and instead using the less optimal `rescue nil` approach
|
72
73
|
def raw_database_adapter
|
73
|
-
adapter = ActiveRecord::Base.
|
74
|
+
adapter = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] rescue nil
|
74
75
|
|
75
76
|
if adapter.nil?
|
76
|
-
adapter = ActiveRecord::Base.
|
77
|
+
adapter = ActiveRecord::Base.connection_config[:adapter].to_s rescue nil
|
78
|
+
end
|
79
|
+
|
80
|
+
if adapter.nil?
|
81
|
+
adapter = ActiveRecord::Base.configurations.to_h[env]["adapter"]
|
77
82
|
end
|
78
83
|
|
79
84
|
return adapter
|
@@ -20,6 +20,7 @@ module ScoutApm
|
|
20
20
|
detect_from_config ||
|
21
21
|
detect_from_heroku ||
|
22
22
|
detect_from_capistrano ||
|
23
|
+
detect_from_mina ||
|
23
24
|
detect_from_git
|
24
25
|
end
|
25
26
|
|
@@ -43,6 +44,14 @@ module ScoutApm
|
|
43
44
|
nil
|
44
45
|
end
|
45
46
|
|
47
|
+
# https://github.com/mina-deploy/mina
|
48
|
+
def detect_from_mina
|
49
|
+
File.read(File.join(app_root, '.mina_git_revision')).strip
|
50
|
+
rescue
|
51
|
+
logger.debug "Unable to detect Git Revision from Mina: #{$!.message}"
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
46
55
|
def detect_from_git
|
47
56
|
if File.directory?(".git")
|
48
57
|
`git rev-parse --short HEAD`.strip
|
@@ -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
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
1
3
|
module ScoutApm
|
2
4
|
module Instant
|
3
5
|
|
@@ -94,7 +96,8 @@ module ScoutApm
|
|
94
96
|
|
95
97
|
def preconditions_met?
|
96
98
|
if dev_trace_disabled?
|
97
|
-
|
99
|
+
# The line below is very noise as it is called on every request.
|
100
|
+
# logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
|
98
101
|
return false
|
99
102
|
end
|
100
103
|
|
@@ -30,8 +30,12 @@ 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)
|
35
|
+
install_instrument(ScoutApm::Instruments::HTTP)
|
36
|
+
install_instrument(ScoutApm::Instruments::Memcached)
|
34
37
|
install_instrument(ScoutApm::Instruments::Redis)
|
38
|
+
install_instrument(ScoutApm::Instruments::Redis5)
|
35
39
|
install_instrument(ScoutApm::Instruments::InfluxDB)
|
36
40
|
install_instrument(ScoutApm::Instruments::Elasticsearch)
|
37
41
|
install_instrument(ScoutApm::Instruments::Grape)
|
@@ -47,6 +51,23 @@ module ScoutApm
|
|
47
51
|
(config.value("disabled_instruments") || []).include?(instrument_short_name)
|
48
52
|
end
|
49
53
|
|
54
|
+
def prepend_for_instrument?(instrument_klass)
|
55
|
+
instrument_short_name = instrument_klass.name.split("::").last
|
56
|
+
|
57
|
+
# `use_prepend` defaults to false, which means we use `alias_method` by default.
|
58
|
+
# If `use_prepend` is `true`, then we should default to using `prepend` unless
|
59
|
+
# the instrument is explicitly listed in the `alias_method_instruments` config array.
|
60
|
+
if config.value("use_prepend")
|
61
|
+
return false if (config.value("alias_method_instruments") || []).include?(instrument_short_name)
|
62
|
+
return true
|
63
|
+
else
|
64
|
+
# `use_prepend` is false, but we should use `prepend` if the instrument is
|
65
|
+
# explicitly listed in the `prepend_instruments` array.
|
66
|
+
return true if (config.value("prepend_instruments") || []).include?(instrument_short_name)
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
50
71
|
private
|
51
72
|
|
52
73
|
def install_instrument(instrument_klass)
|
@@ -59,7 +80,7 @@ module ScoutApm
|
|
59
80
|
|
60
81
|
instance = instrument_klass.new(context)
|
61
82
|
@installed_instruments << instance
|
62
|
-
instance.install
|
83
|
+
instance.install(prepend: prepend_for_instrument?(instrument_klass))
|
63
84
|
end
|
64
85
|
|
65
86
|
def already_installed?(instrument_klass)
|
@@ -17,46 +17,73 @@ module ScoutApm
|
|
17
17
|
@installed
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
if defined?(::
|
26
|
-
|
20
|
+
def installed!
|
21
|
+
@installed = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def install(prepend:)
|
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
|
@@ -68,11 +95,18 @@ module ScoutApm
|
|
68
95
|
req.instant_key = instant_key
|
69
96
|
end
|
70
97
|
|
71
|
-
|
72
|
-
|
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?)
|
73
103
|
super
|
74
104
|
else
|
75
|
-
|
105
|
+
begin
|
106
|
+
uri = scout_transaction_uri
|
107
|
+
req.annotate_request(:uri => uri)
|
108
|
+
rescue
|
109
|
+
end
|
76
110
|
|
77
111
|
# IP Spoofing Protection can throw an exception, just move on w/o remote ip
|
78
112
|
if agent_context.config.value('collect_remote_ip')
|
@@ -95,16 +129,6 @@ module ScoutApm
|
|
95
129
|
end
|
96
130
|
end
|
97
131
|
|
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
132
|
end
|
109
133
|
|
110
134
|
module ActionControllerMetalInstruments
|
@@ -29,7 +29,7 @@ module ScoutApm
|
|
29
29
|
context.environment.supports_module_prepend?
|
30
30
|
end
|
31
31
|
|
32
|
-
def install
|
32
|
+
def install(prepend:)
|
33
33
|
return unless defined?(::ActionView) && defined?(::ActionView::PartialRenderer)
|
34
34
|
|
35
35
|
if prependable?
|
@@ -75,28 +75,40 @@ module ScoutApm
|
|
75
75
|
end
|
76
76
|
|
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
|
+
# New signature is: def render_partial(view, template)
|
80
|
+
def render_partial(*args, **kwargs)
|
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.5
|
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
|
|
88
93
|
begin
|
89
94
|
req.start_layer(layer)
|
90
|
-
|
95
|
+
if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
|
96
|
+
super(*args, **kwargs)
|
97
|
+
else
|
98
|
+
super(*args)
|
99
|
+
end
|
91
100
|
ensure
|
92
101
|
req.stop_layer
|
93
102
|
end
|
94
103
|
end
|
95
104
|
|
96
|
-
def collection_with_template(*args)
|
105
|
+
def collection_with_template(*args, **kwargs)
|
97
106
|
req = ScoutApm::RequestManager.lookup
|
98
107
|
|
99
|
-
|
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
|
100
112
|
template_name ||= "Unknown Collection"
|
101
113
|
layer_name = template_name + "/Rendering"
|
102
114
|
|
@@ -105,7 +117,11 @@ module ScoutApm
|
|
105
117
|
|
106
118
|
begin
|
107
119
|
req.start_layer(layer)
|
108
|
-
|
120
|
+
if ScoutApm::Agent.instance.context.environment.supports_kwarg_delegation?
|
121
|
+
super(*args, **kwargs)
|
122
|
+
else
|
123
|
+
super(*args)
|
124
|
+
end
|
109
125
|
ensure
|
110
126
|
req.stop_layer
|
111
127
|
end
|
@@ -113,10 +129,15 @@ module ScoutApm
|
|
113
129
|
end
|
114
130
|
|
115
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
|
116
134
|
def render_template(*args)
|
117
135
|
req = ScoutApm::RequestManager.lookup
|
118
136
|
|
119
|
-
|
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
|
120
141
|
template_name ||= "Unknown"
|
121
142
|
layer_name = template_name + "/Rendering"
|
122
143
|
|