scout_apm 3.0.0.pre27 → 4.0.3
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 +5 -5
- data/.github/workflows/test.yml +49 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +5 -5
- data/.travis.yml +19 -14
- data/CHANGELOG.markdown +139 -4
- data/Gemfile +1 -7
- data/README.markdown +13 -4
- data/Rakefile +1 -1
- data/ext/allocations/allocations.c +2 -0
- data/gems/README.md +28 -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/lib/scout_apm.rb +38 -9
- data/lib/scout_apm/agent.rb +29 -10
- data/lib/scout_apm/agent/exit_handler.rb +0 -1
- data/lib/scout_apm/agent_context.rb +22 -3
- data/lib/scout_apm/app_server_load.rb +7 -2
- data/lib/scout_apm/attribute_arranger.rb +0 -2
- data/lib/scout_apm/auto_instrument.rb +5 -0
- 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 +175 -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/resque.rb +6 -2
- data/lib/scout_apm/background_job_integrations/shoryuken.rb +124 -0
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +5 -19
- data/lib/scout_apm/background_job_integrations/sneakers.rb +87 -0
- data/lib/scout_apm/config.rb +45 -8
- data/lib/scout_apm/detailed_trace.rb +217 -0
- data/lib/scout_apm/environment.rb +19 -1
- data/lib/scout_apm/error.rb +27 -0
- data/lib/scout_apm/error_service.rb +32 -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/extensions/transaction_callback_payload.rb +1 -1
- data/lib/scout_apm/fake_store.rb +3 -0
- data/lib/scout_apm/framework_integrations/rails_2.rb +2 -1
- data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +3 -1
- data/lib/scout_apm/git_revision.rb +6 -3
- data/lib/scout_apm/instant/middleware.rb +2 -1
- data/lib/scout_apm/instrument_manager.rb +9 -7
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +3 -1
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +56 -55
- data/lib/scout_apm/instruments/action_view.rb +122 -26
- data/lib/scout_apm/instruments/active_record.rb +66 -18
- data/lib/scout_apm/instruments/http.rb +48 -0
- data/lib/scout_apm/instruments/memcached.rb +43 -0
- data/lib/scout_apm/instruments/mongoid.rb +9 -4
- data/lib/scout_apm/instruments/net_http.rb +8 -1
- data/lib/scout_apm/instruments/typhoeus.rb +88 -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 +6 -57
- data/lib/scout_apm/layer_children_set.rb +9 -8
- data/lib/scout_apm/layer_converters/converter_base.rb +15 -30
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +12 -2
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +14 -4
- data/lib/scout_apm/layer_converters/trace_converter.rb +184 -0
- data/lib/scout_apm/limited_layer.rb +0 -7
- data/lib/scout_apm/metric_stats.rb +0 -8
- 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/payload_serializer.rb +2 -2
- data/lib/scout_apm/serializers/payload_serializer_to_json.rb +30 -15
- data/lib/scout_apm/slow_job_record.rb +5 -1
- 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/slow_transaction.rb +3 -1
- data/lib/scout_apm/store.rb +0 -1
- data/lib/scout_apm/tracked_request.rb +39 -30
- 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 +10 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex.rb +8 -1
- data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +6 -0
- data/lib/scout_apm/utils/unique_id.rb +27 -0
- data/lib/scout_apm/version.rb +1 -1
- data/scout_apm.gemspec +13 -7
- data/test/test_helper.rb +2 -2
- data/test/unit/agent_context_test.rb +29 -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/rescue_from-instrumented.rb +13 -0
- data/test/unit/auto_instrument/rescue_from.rb +13 -0
- data/test/unit/auto_instrument_test.rb +54 -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/instruments/active_record_test.rb +40 -0
- data/test/unit/layer_children_set_test.rb +9 -0
- data/test/unit/request_histograms_test.rb +17 -0
- data/test/unit/serializers/payload_serializer_test.rb +39 -5
- data/test/unit/slow_request_policy_test.rb +41 -13
- data/test/unit/sql_sanitizer_test.rb +78 -0
- metadata +101 -62
- data/ext/stacks/extconf.rb +0 -37
- data/ext/stacks/scout_atomics.h +0 -86
- data/ext/stacks/stacks.c +0 -814
- data/lib/scout_apm/slow_job_policy.rb +0 -111
- data/lib/scout_apm/trace_compactor.rb +0 -312
- data/lib/scout_apm/utils/fake_stacks.rb +0 -88
- data/test/unit/instruments/active_record_instruments_test.rb +0 -5
- data/test/unit/slow_job_policy_test.rb +0 -6
- data/tester.rb +0 -53
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Encapsulates the management and checking of ignored exceptions. Allows using
|
|
2
|
+
# string matches on the class name, or arbitrary matching with a callback
|
|
3
|
+
module ScoutApm
|
|
4
|
+
module ErrorService
|
|
5
|
+
class IgnoredExceptions
|
|
6
|
+
attr_reader :ignored_exceptions
|
|
7
|
+
attr_reader :blocks
|
|
8
|
+
|
|
9
|
+
def initialize(context, from_config)
|
|
10
|
+
@context = context
|
|
11
|
+
@ignored_exceptions = Array(from_config).map{ |e| normalize_as_klass(e) }
|
|
12
|
+
@blocks = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Add a single ignored exception by class name
|
|
16
|
+
def add(klass_or_str)
|
|
17
|
+
@ignored_exceptions << normalize_as_klass(klass_or_str)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Add a callback block that will be called on every error. If it returns
|
|
21
|
+
# Signature of blocks: ->(exception object): truthy or falsy value
|
|
22
|
+
def add_callback(&block)
|
|
23
|
+
@blocks << block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ignored?(exception_object)
|
|
27
|
+
klass = normalize_as_klass(exception_object)
|
|
28
|
+
|
|
29
|
+
# Check if we ignored this error by name (typical way to ignore)
|
|
30
|
+
if ignored_exceptions.any? { |ignored| klass.ancestors.include?(ignored) }
|
|
31
|
+
return true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# For each block, see if it says we should ignore this error
|
|
35
|
+
blocks.each do |b|
|
|
36
|
+
if b.call(exception_object)
|
|
37
|
+
return true
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def normalize_as_klass(klass_or_str)
|
|
47
|
+
if Module === klass_or_str
|
|
48
|
+
return klass_or_str
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if klass_or_str.is_a?(Exception)
|
|
52
|
+
return klass_or_str.class
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if String === klass_or_str
|
|
56
|
+
maybe = ScoutApm::Utils::KlassHelper.lookup(klass_or_str)
|
|
57
|
+
if Module === maybe
|
|
58
|
+
return maybe
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
klass_or_str
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Middleware
|
|
4
|
+
def initialize(app)
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
begin
|
|
10
|
+
response = @app.call(env)
|
|
11
|
+
rescue Exception => exception
|
|
12
|
+
puts "[Scout Error Service] Caught Exception: #{exception.class.name}"
|
|
13
|
+
|
|
14
|
+
context = ScoutApm::Agent.instance.context
|
|
15
|
+
|
|
16
|
+
# Bail out early, and reraise if the error is not interesting.
|
|
17
|
+
if context.ignored_exceptions.ignored?(exception)
|
|
18
|
+
raise
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Capture the error for further processing and shipping
|
|
22
|
+
context.error_buffer.capture(exception, env)
|
|
23
|
+
|
|
24
|
+
# Finally re-raise
|
|
25
|
+
raise
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
response
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Notifier
|
|
4
|
+
attr_reader :context
|
|
5
|
+
attr_reader :reporter
|
|
6
|
+
|
|
7
|
+
def initialize(context)
|
|
8
|
+
@context = context
|
|
9
|
+
@reporter = ScoutApm::Reporter.new(context, :errors)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ship
|
|
13
|
+
error_records = context.error_buffer.get_and_reset_error_records
|
|
14
|
+
if error_records.any?
|
|
15
|
+
payload = ScoutApm::ErrorService::Payload.new(context, error_records)
|
|
16
|
+
reporter.report(
|
|
17
|
+
payload.serialize,
|
|
18
|
+
default_headers.merge("X-Error-Count" => error_records.length.to_s)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def default_headers
|
|
26
|
+
{
|
|
27
|
+
"Content-Type" => "application/json",
|
|
28
|
+
"Accept" => "application/json"
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Payload
|
|
4
|
+
attr_reader :context
|
|
5
|
+
attr_reader :errors
|
|
6
|
+
|
|
7
|
+
def initialize(context, errors)
|
|
8
|
+
@context = context
|
|
9
|
+
@errors = errors
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# TODO: Don't use to_json since it isn't supported in Ruby 1.8.7
|
|
13
|
+
def serialize
|
|
14
|
+
payload = as_json.to_json
|
|
15
|
+
context.logger.info(payload)
|
|
16
|
+
payload
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def as_json
|
|
20
|
+
serialized_errors = errors.map do |error_record|
|
|
21
|
+
serialize_error_record(error_record)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
:notifier => "scout_apm_ruby",
|
|
26
|
+
:environment => context.environment.env,
|
|
27
|
+
:root => context.environment.root,
|
|
28
|
+
:problems => serialized_errors,
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serialize_error_record(error_record)
|
|
33
|
+
{
|
|
34
|
+
:exception_class => error_record.exception_class,
|
|
35
|
+
:message => error_record.message,
|
|
36
|
+
:request_uri => error_record.request_uri,
|
|
37
|
+
:request_params => error_record.request_params,
|
|
38
|
+
:request_session => error_record.request_session,
|
|
39
|
+
:environment => error_record.environment,
|
|
40
|
+
:trace => error_record.trace,
|
|
41
|
+
:request_components => error_record.request_components,
|
|
42
|
+
:context => error_record.context,
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class PeriodicWork
|
|
4
|
+
attr_reader :context
|
|
5
|
+
|
|
6
|
+
def initialize(context)
|
|
7
|
+
@context = context
|
|
8
|
+
@notifier = ScoutApm::ErrorService::Notifier.new(context)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Expected to be called many times over the life of the agent
|
|
12
|
+
def run
|
|
13
|
+
@notifier.ship
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module ScoutApm
|
|
2
|
+
module ErrorService
|
|
3
|
+
class Railtie < Rails::Railtie
|
|
4
|
+
initializer "scoutapm_error_service.middleware" do |app|
|
|
5
|
+
next if ScoutApm::Agent.instance.config.value("error_service")
|
|
6
|
+
|
|
7
|
+
app.config.middleware.insert_after ActionDispatch::DebugExceptions, ScoutApm::ErrorService::Rack
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -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
|
|
@@ -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]
|
data/lib/scout_apm/fake_store.rb
CHANGED
|
@@ -15,7 +15,8 @@ module ScoutApm
|
|
|
15
15
|
|
|
16
16
|
def present?
|
|
17
17
|
defined?(::Rails) &&
|
|
18
|
-
defined?(
|
|
18
|
+
defined?(::Rails::VERSION) &&
|
|
19
|
+
defined?(ActionController) &&
|
|
19
20
|
Rails::VERSION::MAJOR >= 3
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -47,6 +48,7 @@ module ScoutApm
|
|
|
47
48
|
when "sqlite" then :sqlite
|
|
48
49
|
when "mysql" then :mysql
|
|
49
50
|
when "mysql2" then :mysql
|
|
51
|
+
when "sqlserver" then :sqlserver
|
|
50
52
|
else default
|
|
51
53
|
end
|
|
52
54
|
else
|
|
@@ -17,7 +17,7 @@ module ScoutApm
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
def detect
|
|
20
|
-
|
|
20
|
+
detect_from_config ||
|
|
21
21
|
detect_from_heroku ||
|
|
22
22
|
detect_from_capistrano ||
|
|
23
23
|
detect_from_git
|
|
@@ -27,8 +27,11 @@ module ScoutApm
|
|
|
27
27
|
ENV['HEROKU_SLUG_COMMIT']
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
# Config will locate the value from:
|
|
31
|
+
# ENV variable - SCOUT_REVISION_SHA
|
|
32
|
+
# YAML setting - revision_sha
|
|
33
|
+
def detect_from_config
|
|
34
|
+
context.config.value('revision_sha')
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
def detect_from_capistrano
|
|
@@ -94,7 +94,8 @@ module ScoutApm
|
|
|
94
94
|
|
|
95
95
|
def preconditions_met?
|
|
96
96
|
if dev_trace_disabled?
|
|
97
|
-
|
|
97
|
+
# The line below is very noise as it is called on every request.
|
|
98
|
+
# logger.debug("DevTrace: isn't activated via config. Try: SCOUT_DEV_TRACE=true rails server")
|
|
98
99
|
return false
|
|
99
100
|
end
|
|
100
101
|
|
|
@@ -30,7 +30,9 @@ 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::Memcached)
|
|
34
36
|
install_instrument(ScoutApm::Instruments::Redis)
|
|
35
37
|
install_instrument(ScoutApm::Instruments::InfluxDB)
|
|
36
38
|
install_instrument(ScoutApm::Instruments::Elasticsearch)
|
|
@@ -41,13 +43,19 @@ module ScoutApm
|
|
|
41
43
|
logger.warn $!.backtrace
|
|
42
44
|
end
|
|
43
45
|
|
|
46
|
+
# Allows users to skip individual instruments via the config file
|
|
47
|
+
def skip_instrument?(instrument_klass)
|
|
48
|
+
instrument_short_name = instrument_klass.name.split("::").last
|
|
49
|
+
(config.value("disabled_instruments") || []).include?(instrument_short_name)
|
|
50
|
+
end
|
|
51
|
+
|
|
44
52
|
private
|
|
45
53
|
|
|
46
54
|
def install_instrument(instrument_klass)
|
|
47
55
|
return if already_installed?(instrument_klass)
|
|
48
56
|
|
|
49
57
|
if skip_instrument?(instrument_klass)
|
|
50
|
-
logger.info "Skipping Disabled Instrument: #{
|
|
58
|
+
logger.info "Skipping Disabled Instrument: #{instrument_klass} - To re-enable, change `disabled_instruments` key in scout_apm.yml"
|
|
51
59
|
return
|
|
52
60
|
end
|
|
53
61
|
|
|
@@ -56,12 +64,6 @@ module ScoutApm
|
|
|
56
64
|
instance.install
|
|
57
65
|
end
|
|
58
66
|
|
|
59
|
-
# Allows users to skip individual instruments via the config file
|
|
60
|
-
def skip_instrument?(instrument_klass)
|
|
61
|
-
instrument_short_name = instrument_klass.name.split("::").last
|
|
62
|
-
(config.value("disabled_instruments") || []).include?(instrument_short_name)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
67
|
def already_installed?(instrument_klass)
|
|
66
68
|
@installed_instruments.any? do |already_installed_instrument|
|
|
67
69
|
instrument_klass === already_installed_instrument
|
|
@@ -54,7 +54,9 @@ module ScoutApm
|
|
|
54
54
|
def perform_action_with_scout_instruments(*args, &block)
|
|
55
55
|
req = ScoutApm::RequestManager.lookup
|
|
56
56
|
req.annotate_request(:uri => request.path) # for security by-default, we don't use request.fullpath which could reveal filtered params.
|
|
57
|
-
|
|
57
|
+
if ScoutApm::Agent.instance.context.config.value('collect_remote_ip')
|
|
58
|
+
req.context.add_user(:ip => request.remote_ip)
|
|
59
|
+
end
|
|
58
60
|
req.set_headers(request.headers)
|
|
59
61
|
req.start_layer( ScoutApm::Layer.new("Controller", "#{controller_path}/#{action_name}") )
|
|
60
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module ScoutApm
|
|
2
2
|
module Instruments
|
|
3
|
-
# instrumentation for Rails 3
|
|
3
|
+
# instrumentation for Rails 3 and Rails 4 is the same.
|
|
4
4
|
class ActionControllerRails3Rails4
|
|
5
5
|
attr_reader :context
|
|
6
6
|
|
|
@@ -17,37 +17,54 @@ 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
|
|
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
|
|
27
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
|
|
|
67
|
+
ScoutApm::Agent.instance.context.logger.info("Instrumenting ActionController (hook installed)")
|
|
51
68
|
end
|
|
52
69
|
|
|
53
70
|
# Returns a new anonymous module each time it is called. So
|
|
@@ -57,13 +74,24 @@ module ScoutApm
|
|
|
57
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
|
|
90
|
+
agent_context = ScoutApm::Agent.instance.context
|
|
63
91
|
|
|
64
92
|
# Check if this this request is to be reported instantly
|
|
65
93
|
if instant_key = request.cookies['scoutapminstant']
|
|
66
|
-
|
|
94
|
+
agent_context.logger.info "Instant trace request with key=#{instant_key} for path=#{path}"
|
|
67
95
|
req.instant_key = instant_key
|
|
68
96
|
end
|
|
69
97
|
|
|
@@ -71,25 +99,20 @@ module ScoutApm
|
|
|
71
99
|
# Don't start a new layer if ActionController::API or ActionController::Base handled it already.
|
|
72
100
|
super
|
|
73
101
|
else
|
|
74
|
-
|
|
102
|
+
begin
|
|
103
|
+
uri = scout_transaction_uri
|
|
104
|
+
req.annotate_request(:uri => uri)
|
|
105
|
+
rescue
|
|
106
|
+
end
|
|
75
107
|
|
|
76
108
|
# IP Spoofing Protection can throw an exception, just move on w/o remote ip
|
|
77
|
-
|
|
109
|
+
if agent_context.config.value('collect_remote_ip')
|
|
110
|
+
req.context.add_user(:ip => request.remote_ip) rescue nil
|
|
111
|
+
end
|
|
78
112
|
req.set_headers(request.headers)
|
|
79
113
|
|
|
80
114
|
resolved_name = scout_action_name(*args)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if enable_scoutprof? && ScoutApm::Agent.instance.context.config.value('profile') && ScoutApm::Instruments::Stacks::ENABLED
|
|
84
|
-
if defined?(ScoutApm::Instruments::Stacks::INSTALLED) && ScoutApm::Instruments::Stacks::INSTALLED
|
|
85
|
-
# Capture ScoutProf if we can
|
|
86
|
-
req.enable_profiled_thread!
|
|
87
|
-
layer.set_root_class(self.class)
|
|
88
|
-
layer.traced!
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
req.start_layer(layer)
|
|
115
|
+
req.start_layer( ScoutApm::Layer.new("Controller", "#{controller_path}/#{resolved_name}") )
|
|
93
116
|
begin
|
|
94
117
|
super
|
|
95
118
|
rescue
|
|
@@ -103,39 +126,25 @@ module ScoutApm
|
|
|
103
126
|
end
|
|
104
127
|
end
|
|
105
128
|
|
|
106
|
-
# Given an +ActionDispatch::Request+, formats the uri based on config settings.
|
|
107
|
-
# XXX: Don't lookup context like this - find a way to pass it through
|
|
108
|
-
def self.scout_transaction_uri(request, config=ScoutApm::Agent.instance.context.config)
|
|
109
|
-
case config.value("uri_reporting")
|
|
110
|
-
when 'path'
|
|
111
|
-
request.path # strips off the query string for more security
|
|
112
|
-
else # default handles filtered params
|
|
113
|
-
request.filtered_path
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
129
|
end
|
|
117
130
|
|
|
118
|
-
module
|
|
131
|
+
module ActionControllerMetalInstruments
|
|
119
132
|
include ScoutApm::Instruments::ActionControllerRails3Rails4.build_instrument_module
|
|
120
133
|
|
|
121
134
|
def scout_action_name(*args)
|
|
122
|
-
action_name
|
|
135
|
+
action_name = args[0]
|
|
123
136
|
end
|
|
137
|
+
end
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
139
|
+
# Empty, noop module to provide compatibility w/ previous manual instrumentation
|
|
140
|
+
module ActionControllerRails3Rails4Instruments
|
|
128
141
|
end
|
|
129
142
|
|
|
130
|
-
module
|
|
143
|
+
module ActionControllerBaseInstruments
|
|
131
144
|
include ScoutApm::Instruments::ActionControllerRails3Rails4.build_instrument_module
|
|
132
145
|
|
|
133
146
|
def scout_action_name(*args)
|
|
134
|
-
action_name
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def enable_scoutprof?
|
|
138
|
-
false
|
|
147
|
+
action_name
|
|
139
148
|
end
|
|
140
149
|
end
|
|
141
150
|
|
|
@@ -145,14 +154,6 @@ module ScoutApm
|
|
|
145
154
|
def scout_action_name(*args)
|
|
146
155
|
action_name
|
|
147
156
|
end
|
|
148
|
-
|
|
149
|
-
def enable_scoutprof?
|
|
150
|
-
false
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Empty, noop module to provide compatibility w/ previous manual instrumentation
|
|
155
|
-
module ActionControllerRails3Rails4Instruments
|
|
156
157
|
end
|
|
157
158
|
end
|
|
158
159
|
end
|