scout_apm 5.1.1 → 5.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +15 -0
  3. data/CHANGELOG.markdown +15 -0
  4. data/gems/instruments.gemfile +6 -0
  5. data/gems/sidekiq.gemfile +4 -0
  6. data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -8
  7. data/lib/scout_apm/config.rb +16 -1
  8. data/lib/scout_apm/instrument_manager.rb +18 -1
  9. data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -1
  10. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -1
  11. data/lib/scout_apm/instruments/action_view.rb +1 -1
  12. data/lib/scout_apm/instruments/active_record.rb +1 -1
  13. data/lib/scout_apm/instruments/elasticsearch.rb +91 -42
  14. data/lib/scout_apm/instruments/grape.rb +1 -1
  15. data/lib/scout_apm/instruments/http.rb +36 -16
  16. data/lib/scout_apm/instruments/http_client.rb +33 -14
  17. data/lib/scout_apm/instruments/influxdb.rb +2 -2
  18. data/lib/scout_apm/instruments/memcached.rb +26 -11
  19. data/lib/scout_apm/instruments/middleware_detailed.rb +1 -1
  20. data/lib/scout_apm/instruments/middleware_summary.rb +1 -1
  21. data/lib/scout_apm/instruments/mongoid.rb +1 -1
  22. data/lib/scout_apm/instruments/moped.rb +44 -19
  23. data/lib/scout_apm/instruments/net_http.rb +49 -21
  24. data/lib/scout_apm/instruments/rails_router.rb +1 -1
  25. data/lib/scout_apm/instruments/redis.rb +26 -11
  26. data/lib/scout_apm/instruments/sinatra.rb +1 -1
  27. data/lib/scout_apm/instruments/typhoeus.rb +1 -1
  28. data/lib/scout_apm/store.rb +6 -0
  29. data/lib/scout_apm/version.rb +1 -1
  30. data/test/unit/background_job_integrations/sidekiq_test.rb +17 -0
  31. data/test/unit/instruments/active_record_test.rb +1 -1
  32. data/test/unit/instruments/http_client_test.rb +24 -0
  33. data/test/unit/instruments/http_test.rb +24 -0
  34. data/test/unit/instruments/moped_test.rb +24 -0
  35. data/test/unit/instruments/net_http_test.rb +11 -1
  36. data/test/unit/instruments/redis_test.rb +24 -0
  37. data/test/unit/instruments/typhoeus_test.rb +1 -1
  38. metadata +12 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a1da3776ca231a4d81c9ab5935e6b644ab46ae5609cef60402ccd5258e07795
4
- data.tar.gz: f583776f39fa426aa43d1942bd83e4bcd23ca3a6f71a2e883df22a74cb2db868
3
+ metadata.gz: c3d15e4a9b9e98af3e71ba6dde0b666b8807b742f967e35fe36ebd747ae83483
4
+ data.tar.gz: f8e88492777b44cdc9362500b7ccdcb68daaa756d6c98ec2e114e231e240071d
5
5
  SHA512:
6
- metadata.gz: bc2f10c76ea8abbb6f74cbe86cbd0e0d1265143a19008307609be35adb57737f474c9a2f59545e2f36b00b5006c5f1b9726f59e70c3e327e6487eb2445e07df6
7
- data.tar.gz: 8863c891bbd5f1e87d56a06f8275cbcf711b15964116cbddbb648398825eed65fa6ce73cd3848815e8ab8a1eec1f8a2e4a878682e2d47d457778786532ee3db4
6
+ metadata.gz: bb7224bc3294417f72279a97713b9be39bd9657efa1b9c39fb969164f353cdd04c8ced4114e6b717d7ed315c86b5412d8591132f3adde543fb09f8ea908e0308
7
+ data.tar.gz: 354cd7af52c07bb78380614d7aafdde9942a1b3ea973ad0616df187a2f2048b3a04fee9cff737d836c14caef142fec31aafef22e5ee4dfeb2125d31c6ea39f70
@@ -35,11 +35,26 @@ jobs:
35
35
  gemfile: gems/rails3.gemfile
36
36
  bundler: 1.17.3
37
37
  - ruby: 2.7
38
+ - ruby: 2.7
39
+ prepend: true
40
+ - ruby: 3.0
41
+ - ruby: 3.0
42
+ prepend: true
43
+ - ruby: 3.0
44
+ gemfile: gems/instruments.gemfile
45
+ test_features: "instruments"
46
+ - ruby: 3.0
47
+ gemfile: gems/instruments.gemfile
48
+ prepend: true
49
+ test_features: "instruments"
38
50
  - ruby: 3.0
51
+ gemfile: gems/sidekiq.gemfile
52
+ test_features: "sidekiq_install"
39
53
 
40
54
  env:
41
55
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
42
56
  SCOUT_TEST_FEATURES: ${{ matrix.test_features }}
57
+ SCOUT_USE_PREPEND: ${{ matrix.prepend }}
43
58
 
44
59
  runs-on: ubuntu-latest
45
60
 
data/CHANGELOG.markdown CHANGED
@@ -1,5 +1,20 @@
1
1
  # Unreleased
2
2
 
3
+ # 5.3.1
4
+
5
+ * Fix typo in HTTPClient prepend instrumentation (#457)
6
+ # 5.3.0
7
+
8
+ * Add configuraiton option to use `Module#prepend` instead of `Module#alias_method` (default)
9
+ for instrumentation (#448). The default method for instrumentation has not changed, but
10
+ configuration options were added to allow switching to `Module#prepend` for most
11
+ instrumentation. Refer to the documentation for more information:
12
+ [Library Instrumentation Method](https://scoutapm.com/docs/ruby/configuration#library-instrumentation-method)
13
+
14
+ # 5.2.0
15
+
16
+ * Use Sidekiq lifecycle hooks to start Scout agent on Sidekiq start. (#449)
17
+
3
18
  # 5.1.1
4
19
 
5
20
  * Improvements to SqlServer scrubbing in SqlSanitizer (#422)
@@ -0,0 +1,6 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem 'httpclient'
4
+ gem 'http'
5
+ gem 'redis'
6
+ gem 'moped'
@@ -0,0 +1,4 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ # https://github.com/scoutapp/scout_apm_ruby/issues/449
4
+ gem 'sidekiq', '>= 6.5.0'
@@ -37,17 +37,11 @@ module ScoutApm
37
37
  end
38
38
 
39
39
  def install_processor
40
- require 'sidekiq/processor' # sidekiq v4 has not loaded this file by this point
41
-
42
- ::Sidekiq::Processor.class_eval do
43
- def initialize_with_scout(*args)
40
+ ::Sidekiq.configure_server do |config|
41
+ config.on(:startup) do
44
42
  agent = ::ScoutApm::Agent.instance
45
43
  agent.start
46
- initialize_without_scout(*args)
47
44
  end
48
-
49
- alias_method :initialize_without_scout, :initialize
50
- alias_method :initialize, :initialize_with_scout
51
45
  end
52
46
  end
53
47
  end
@@ -35,7 +35,13 @@ require 'scout_apm/environment'
35
35
  # start_resque_server_instrument - Used in special situations with certain Resque installs
36
36
  # timeline_traces - true/false to enable sending of of the timeline trace format.
37
37
  # auto_instruments - true/false whether to install autoinstruments. Only installed if on a supported Ruby version.
38
- # auto_instruments_ignore - An array of file names to exclude from autoinstruments (Ex: ['application_controller']).
38
+ # auto_instruments_ignore - An array of file names to exclude from autoinstruments (Ex: ['application_controller']).
39
+ # use_prepend - Whether to apply instrumentation using Module#Prepend instead
40
+ # of Module#alias_method (Default: false)
41
+ # alias_method_instruments - If `use_prepend` is true, continue to use Module#alias_method for
42
+ # any instruments listed in this array. Default: []
43
+ # prepend_instruments - If `use_prepend` is false, force using Module#prepend for any
44
+ # instruments listed in this array. Default: []
39
45
  #
40
46
  # Any of these config settings can be set with an environment variable prefixed
41
47
  # by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
@@ -85,6 +91,9 @@ module ScoutApm
85
91
  'timeline_traces',
86
92
  'auto_instruments',
87
93
  'auto_instruments_ignore',
94
+ 'use_prepend',
95
+ 'alias_method_instruments',
96
+ 'prepend_instruments',
88
97
 
89
98
  # Error Service Related Configuration
90
99
  'errors_enabled',
@@ -189,6 +198,9 @@ module ScoutApm
189
198
  'timeline_traces' => BooleanCoercion.new,
190
199
  'auto_instruments' => BooleanCoercion.new,
191
200
  'auto_instruments_ignore' => JsonCoercion.new,
201
+ 'use_prepend' => BooleanCoercion.new,
202
+ 'alias_method_instruments' => JsonCoercion.new,
203
+ 'prepend_instruments' => JsonCoercion.new,
192
204
  'errors_enabled' => BooleanCoercion.new,
193
205
  'errors_ignored_exceptions' => JsonCoercion.new,
194
206
  'errors_filtered_params' => JsonCoercion.new,
@@ -305,6 +317,9 @@ module ScoutApm
305
317
  'timeline_traces' => true,
306
318
  'auto_instruments' => false,
307
319
  'auto_instruments_ignore' => [],
320
+ 'use_prepend' => false,
321
+ 'alias_method_instruments' => [],
322
+ 'prepend_instruments' => [],
308
323
  'ssl_cert_file' => File.join( File.dirname(__FILE__), *%w[.. .. data cacert.pem] ),
309
324
  'errors_enabled' => false,
310
325
  'errors_ignored_exceptions' => %w(ActiveRecord::RecordNotFound ActionController::RoutingError),
@@ -50,6 +50,23 @@ module ScoutApm
50
50
  (config.value("disabled_instruments") || []).include?(instrument_short_name)
51
51
  end
52
52
 
53
+ def prepend_for_instrument?(instrument_klass)
54
+ instrument_short_name = instrument_klass.name.split("::").last
55
+
56
+ # `use_prepend` defaults to false, which means we use `alias_method` by default.
57
+ # If `use_prepend` is `true`, then we should default to using `prepend` unless
58
+ # the instrument is explicitly listed in the `alias_method_instruments` config array.
59
+ if config.value("use_prepend")
60
+ return false if (config.value("alias_method_instruments") || []).include?(instrument_short_name)
61
+ return true
62
+ else
63
+ # `use_prepend` is false, but we should use `prepend` if the instrument is
64
+ # explicitly listed in the `prepend_instruments` array.
65
+ return true if (config.value("prepend_instruments") || []).include?(instrument_short_name)
66
+ return false
67
+ end
68
+ end
69
+
53
70
  private
54
71
 
55
72
  def install_instrument(instrument_klass)
@@ -62,7 +79,7 @@ module ScoutApm
62
79
 
63
80
  instance = instrument_klass.new(context)
64
81
  @installed_instruments << instance
65
- instance.install
82
+ instance.install(prepend: prepend_for_instrument?(instrument_klass))
66
83
  end
67
84
 
68
85
  def already_installed?(instrument_klass)
@@ -16,7 +16,7 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::ActionController) && defined?(::ActionController::Base)
21
21
  @installed = true
22
22
 
@@ -21,7 +21,7 @@ module ScoutApm
21
21
  @installed = true
22
22
  end
23
23
 
24
- def install
24
+ def install(prepend:)
25
25
  if !defined?(::ActiveSupport)
26
26
  return
27
27
  end
@@ -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?
@@ -50,7 +50,7 @@ module ScoutApm
50
50
  @installed
51
51
  end
52
52
 
53
- def install
53
+ def install(prepend:)
54
54
  if install_via_after_initialize?
55
55
  Rails.configuration.after_initialize do
56
56
  add_instruments
@@ -18,66 +18,115 @@ module ScoutApm
18
18
  @installed
19
19
  end
20
20
 
21
- def install
21
+ def install(prepend:)
22
22
  if defined?(::Elasticsearch) &&
23
23
  defined?(::Elasticsearch::Transport) &&
24
24
  defined?(::Elasticsearch::Transport::Client)
25
25
 
26
26
  @installed = true
27
27
 
28
- logger.info "Instrumenting Elasticsearch"
28
+ logger.info "Instrumenting Elasticsearch. Prepend: #{prepend}"
29
29
 
30
- ::Elasticsearch::Transport::Client.class_eval do
31
- include ScoutApm::Tracer
30
+ if prepend
31
+ ::Elasticsearch::Transport::Client.send(:include, ScoutApm::Tracer)
32
+ ::Elasticsearch::Transport::Client.send(:prepend, ElasticsearchTransportClientInstrumentationPrepend)
33
+ else
34
+ ::Elasticsearch::Transport::Client.class_eval do
35
+ include ScoutApm::Tracer
32
36
 
33
- def perform_request_with_scout_instruments(*args, &block)
34
- name = _sanitize_name(args[1])
37
+ def perform_request_with_scout_instruments(*args, &block)
38
+ name = _sanitize_name(args[1])
35
39
 
36
- self.class.instrument("Elasticsearch", name, :ignore_children => true) do
37
- perform_request_without_scout_instruments(*args, &block)
40
+ self.class.instrument("Elasticsearch", name, :ignore_children => true) do
41
+ perform_request_without_scout_instruments(*args, &block)
42
+ end
38
43
  end
39
- end
40
44
 
41
- alias_method :perform_request_without_scout_instruments, :perform_request
42
- alias_method :perform_request, :perform_request_with_scout_instruments
43
-
44
- def _sanitize_name(name)
45
- name = name.split("/").last.gsub(/^_/, '')
46
- allowed_names = ["bench",
47
- "bulk",
48
- "count",
49
- "exists",
50
- "explain",
51
- "field_stats",
52
- "health",
53
- "mget",
54
- "mlt",
55
- "mpercolate",
56
- "msearch",
57
- "mtermvectors",
58
- "percolate",
59
- "query",
60
- "scroll",
61
- "search_shards",
62
- "source",
63
- "suggest",
64
- "template",
65
- "termvectors",
66
- "update",
67
- "search", ]
68
-
69
- if allowed_names.include?(name)
70
- name
71
- else
45
+ alias_method :perform_request_without_scout_instruments, :perform_request
46
+ alias_method :perform_request, :perform_request_with_scout_instruments
47
+
48
+ def _sanitize_name(name)
49
+ name = name.split("/").last.gsub(/^_/, '')
50
+ allowed_names = ["bench",
51
+ "bulk",
52
+ "count",
53
+ "exists",
54
+ "explain",
55
+ "field_stats",
56
+ "health",
57
+ "mget",
58
+ "mlt",
59
+ "mpercolate",
60
+ "msearch",
61
+ "mtermvectors",
62
+ "percolate",
63
+ "query",
64
+ "scroll",
65
+ "search_shards",
66
+ "source",
67
+ "suggest",
68
+ "template",
69
+ "termvectors",
70
+ "update",
71
+ "search", ]
72
+
73
+ if allowed_names.include?(name)
74
+ name
75
+ else
76
+ "Unknown"
77
+ end
78
+ rescue
72
79
  "Unknown"
73
80
  end
74
- rescue
75
- "Unknown"
76
81
  end
77
82
  end
78
83
  end
79
84
  end
80
85
  end
86
+
87
+ module ElasticsearchTransportClientInstrumentationPrepend
88
+ def perform_request(*args, &block)
89
+ name = _sanitize_name(args[1])
90
+
91
+ self.class.instrument("Elasticsearch", name, :ignore_children => true) do
92
+ super(*args, &block)
93
+ end
94
+ end
95
+
96
+ def _sanitize_name(name)
97
+ name = name.split("/").last.gsub(/^_/, '')
98
+ allowed_names = ["bench",
99
+ "bulk",
100
+ "count",
101
+ "exists",
102
+ "explain",
103
+ "field_stats",
104
+ "health",
105
+ "mget",
106
+ "mlt",
107
+ "mpercolate",
108
+ "msearch",
109
+ "mtermvectors",
110
+ "percolate",
111
+ "query",
112
+ "scroll",
113
+ "search_shards",
114
+ "source",
115
+ "suggest",
116
+ "template",
117
+ "termvectors",
118
+ "update",
119
+ "search", ]
120
+
121
+ if allowed_names.include?(name)
122
+ name
123
+ else
124
+ "Unknown"
125
+ end
126
+ rescue
127
+ "Unknown"
128
+ end
129
+ end
81
130
  end
82
131
  end
83
132
 
@@ -16,7 +16,7 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Grape) && defined?(::Grape::Endpoint)
21
21
  @installed = true
22
22
 
@@ -16,33 +16,53 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::HTTP) && defined?(::HTTP::Client)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting HTTP::Client"
23
+ logger.info "Instrumenting HTTP::Client. Prepend: #{prepend}"
24
24
 
25
- ::HTTP::Client.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::HTTP::Client.send(:include, ScoutApm::Tracer)
27
+ ::HTTP::Client.send(:prepend, HTTPInstrumentationPrepend)
28
+ else
29
+ ::HTTP::Client.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def request_with_scout_instruments(verb, uri, opts = {})
29
- self.class.instrument("HTTP", verb, :ignore_children => true, :desc => request_scout_description(verb, uri)) do
30
- request_without_scout_instruments(verb, uri, opts)
32
+ def request_with_scout_instruments(verb, uri, opts = {})
33
+ self.class.instrument("HTTP", verb, :ignore_children => true, :desc => request_scout_description(verb, uri)) do
34
+ request_without_scout_instruments(verb, uri, opts)
35
+ end
31
36
  end
32
- end
33
37
 
34
- def request_scout_description(verb, uri)
35
- max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
36
- (String(uri).split('?').first)[0..(max_length - 1)]
37
- rescue
38
- ""
39
- end
38
+ def request_scout_description(verb, uri)
39
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
40
+ (String(uri).split('?').first)[0..(max_length - 1)]
41
+ rescue
42
+ ""
43
+ end
40
44
 
41
- alias request_without_scout_instruments request
42
- alias request request_with_scout_instruments
45
+ alias request_without_scout_instruments request
46
+ alias request request_with_scout_instruments
47
+ end
43
48
  end
44
49
  end
45
50
  end
46
51
  end
52
+
53
+ module HTTPInstrumentationPrepend
54
+ def request(verb, uri, opts = {})
55
+ self.class.instrument("HTTP", verb, :ignore_children => true, :desc => request_scout_description(verb, uri)) do
56
+ super(verb, uri, opts)
57
+ end
58
+ end
59
+
60
+ def request_scout_description(verb, uri)
61
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
62
+ (String(uri).split('?').first)[0..(max_length - 1)]
63
+ rescue
64
+ ""
65
+ end
66
+ end
47
67
  end
48
68
  end
@@ -16,33 +16,52 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::HTTPClient)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting HTTPClient"
23
+ logger.info "Instrumenting HTTPClient. Prepend: #{prepend}"
24
24
 
25
- ::HTTPClient.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::HTTPClient.send(:include, ScoutApm::Tracer)
27
+ ::HTTPClient.send(:prepend, HttpClientInstrumentationPrepend)
28
+ else
29
+ ::HTTPClient.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def request_with_scout_instruments(*args, &block)
32
+ def request_with_scout_instruments(*args, &block)
29
33
 
30
- method = args[0].to_s
31
- url = args[1]
34
+ method = args[0].to_s
35
+ url = args[1]
32
36
 
33
- max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
34
- url = url && url.to_s[0..(max_length - 1)]
37
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
38
+ url = url && url.to_s[0..(max_length - 1)]
35
39
 
36
- self.class.instrument("HTTP", method, :desc => url) do
37
- request_without_scout_instruments(*args, &block)
40
+ self.class.instrument("HTTP", method, :desc => url) do
41
+ request_without_scout_instruments(*args, &block)
42
+ end
38
43
  end
39
- end
40
44
 
41
- alias request_without_scout_instruments request
42
- alias request request_with_scout_instruments
45
+ alias request_without_scout_instruments request
46
+ alias request request_with_scout_instruments
47
+ end
43
48
  end
44
49
  end
45
50
  end
46
51
  end
52
+
53
+ module HttpClientInstrumentationPrepend
54
+ def request(*args, &block)
55
+ method = args[0].to_s
56
+ url = args[1]
57
+
58
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
59
+ url = url && url.to_s[0..(max_length - 1)]
60
+
61
+ self.class.instrument("HTTP", method, :desc => url) do
62
+ super(*args, &block)
63
+ end
64
+ end
65
+ end
47
66
  end
48
67
  end
@@ -16,11 +16,11 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::InfluxDB)
21
21
  @installed = true
22
22
 
23
- logger.debug "Instrumenting InfluxDB"
23
+ logger.debug "Instrumenting InfluxDB."
24
24
 
25
25
  ::InfluxDB::Client.class_eval do
26
26
  include ScoutApm::Tracer
@@ -16,28 +16,43 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Dalli) && defined?(::Dalli::Client)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Memcached"
23
+ logger.info "Instrumenting Memcached. Prepend: #{prepend}"
24
24
 
25
- ::Dalli::Client.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Dalli::Client.send(:include, ScoutApm::Tracer)
27
+ ::Dalli::Client.send(:prepend, MemcachedInstrumentationPrepend)
28
+ else
29
+ ::Dalli::Client.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def perform_with_scout_instruments(*args, &block)
29
- command = args.first rescue "Unknown"
32
+ def perform_with_scout_instruments(*args, &block)
33
+ command = args.first rescue "Unknown"
30
34
 
31
- self.class.instrument("Memcached", command) do
32
- perform_without_scout_instruments(*args, &block)
35
+ self.class.instrument("Memcached", command) do
36
+ perform_without_scout_instruments(*args, &block)
37
+ end
33
38
  end
34
- end
35
39
 
36
- alias_method :perform_without_scout_instruments, :perform
37
- alias_method :perform, :perform_with_scout_instruments
40
+ alias_method :perform_without_scout_instruments, :perform
41
+ alias_method :perform, :perform_with_scout_instruments
42
+ end
38
43
  end
39
44
  end
40
45
  end
41
46
  end
47
+
48
+ module MemcachedInstrumentationPrepend
49
+ def perform(*args, &block)
50
+ command = args.first rescue "Unknown"
51
+
52
+ self.class.instrument("Memcached", command) do
53
+ super(*args, &block)
54
+ end
55
+ end
56
+ end
42
57
  end
43
58
  end
@@ -24,7 +24,7 @@ module ScoutApm
24
24
  @installed
25
25
  end
26
26
 
27
- def install
27
+ def install(prepend:)
28
28
  if defined?(ActionDispatch) && defined?(ActionDispatch::MiddlewareStack) && defined?(ActionDispatch::MiddlewareStack::Middleware)
29
29
  @installed = true
30
30
 
@@ -21,7 +21,7 @@ module ScoutApm
21
21
  @installed
22
22
  end
23
23
 
24
- def install
24
+ def install(prepend:)
25
25
  if defined?(ActionDispatch) && defined?(ActionDispatch::MiddlewareStack)
26
26
  @installed = true
27
27
 
@@ -16,7 +16,7 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  @installed = true
21
21
 
22
22
  # Mongoid versions that use Moped should instrument Moped.
@@ -16,32 +16,37 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Moped)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Moped"
23
+ logger.info "Instrumenting Moped. Prepend: #{prepend}"
24
24
 
25
- ::Moped::Node.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Moped::Node.send(:include, ScoutApm::Tracer)
27
+ ::Moped::Node.send(:prepend, MopedInstrumentationPrepend)
28
+ else
29
+ ::Moped::Node.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def process_with_scout_instruments(operation, &callback)
29
- if operation.respond_to?(:collection)
30
- collection = operation.collection
31
- name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
32
- self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
33
- process_without_scout_instruments(operation, &callback)
32
+ def process_with_scout_instruments(operation, &callback)
33
+ if operation.respond_to?(:collection)
34
+ collection = operation.collection
35
+ name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
36
+ self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
37
+ process_without_scout_instruments(operation, &callback)
38
+ end
34
39
  end
35
40
  end
36
- end
37
- alias_method :process_without_scout_instruments, :process
38
- alias_method :process, :process_with_scout_instruments
39
-
40
- # replaces values w/ ?
41
- def scout_sanitize_log(log)
42
- return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
43
- log.gsub(/(=>")((?:[^"]|"")*)"/) do
44
- $1 + '?' + '"'
41
+ alias_method :process_without_scout_instruments, :process
42
+ alias_method :process, :process_with_scout_instruments
43
+
44
+ # replaces values w/ ?
45
+ def scout_sanitize_log(log)
46
+ return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
47
+ log.gsub(/(=>")((?:[^"]|"")*)"/) do
48
+ $1 + '?' + '"'
49
+ end
45
50
  end
46
51
  end
47
52
  end
@@ -49,6 +54,26 @@ module ScoutApm
49
54
  end
50
55
 
51
56
  end
57
+
58
+ module MopedInstrumentationPrepend
59
+ def process(operation, &callback)
60
+ if operation.respond_to?(:collection)
61
+ collection = operation.collection
62
+ name = "Process/#{collection}/#{operation.class.to_s.split('::').last}"
63
+ self.class.instrument("MongoDB", name, :annotate_layer => { :query => scout_sanitize_log(operation.log_inspect) }) do
64
+ super(operation, &callback)
65
+ end
66
+ end
67
+ end
68
+
69
+ # replaces values w/ ?
70
+ def scout_sanitize_log(log)
71
+ return nil if log.length > 1000 # safeguard - don't sanitize large SQL statements
72
+ log.gsub(/(=>")((?:[^"]|"")*)"/) do
73
+ $1 + '?' + '"'
74
+ end
75
+ end
76
+ end
52
77
  end
53
78
  end
54
79
 
@@ -16,41 +16,69 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Net) && defined?(::Net::HTTP)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Net::HTTP"
23
+ logger.info "Instrumenting Net::HTTP. Prepend: #{prepend}"
24
24
 
25
- ::Net::HTTP.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Net::HTTP.send(:include, ScoutApm::Tracer)
27
+ ::Net::HTTP.send(:prepend, NetHttpInstrumentationPrepend)
28
+ else
29
+ ::Net::HTTP.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def request_with_scout_instruments(*args, &block)
29
- self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
30
- request_without_scout_instruments(*args, &block)
32
+ def request_with_scout_instruments(*args, &block)
33
+ self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
34
+ request_without_scout_instruments(*args, &block)
35
+ end
31
36
  end
32
- end
33
37
 
34
- def request_scout_description(req)
35
- path = req.path
36
- path = path.path if path.respond_to?(:path)
38
+ def request_scout_description(req)
39
+ path = req.path
40
+ path = path.path if path.respond_to?(:path)
41
+
42
+ # Protect against a nil address value
43
+ if @address.nil?
44
+ return "No Address Found"
45
+ end
37
46
 
38
- # Protect against a nil address value
39
- if @address.nil?
40
- return "No Address Found"
47
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
48
+ (@address + path.split('?').first)[0..(max_length - 1)]
49
+ rescue
50
+ ""
41
51
  end
42
52
 
43
- max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
44
- (@address + path.split('?').first)[0..(max_length - 1)]
45
- rescue
46
- ""
53
+ alias request_without_scout_instruments request
54
+ alias request request_with_scout_instruments
47
55
  end
48
-
49
- alias request_without_scout_instruments request
50
- alias request request_with_scout_instruments
51
56
  end
52
57
  end
53
58
  end
54
59
  end
60
+
61
+ module NetHttpInstrumentationPrepend
62
+ def request(request, *args, &block)
63
+ self.class.instrument("HTTP", "request", :ignore_children => true, :desc => request_scout_description(args.first)) do
64
+ super(request, *args, &block)
65
+ end
66
+ end
67
+
68
+ def request_scout_description(req)
69
+ path = req.path
70
+ path = path.path if path.respond_to?(:path)
71
+
72
+ # Protect against a nil address value
73
+ if @address.nil?
74
+ return "No Address Found"
75
+ end
76
+
77
+ max_length = ScoutApm::Agent.instance.context.config.value('instrument_http_url_length')
78
+ (@address + path.split('?').first)[0..(max_length - 1)]
79
+ rescue
80
+ ""
81
+ end
82
+ end
55
83
  end
56
84
  end
@@ -16,7 +16,7 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(ActionDispatch) && defined?(ActionDispatch::Routing) && defined?(ActionDispatch::Routing::RouteSet)
21
21
  @installed = true
22
22
 
@@ -16,28 +16,43 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Redis) && defined?(::Redis::Client)
21
21
  @installed = true
22
22
 
23
- logger.info "Instrumenting Redis"
23
+ logger.info "Instrumenting Redis. Prepend: #{prepend}"
24
24
 
25
- ::Redis::Client.class_eval do
26
- include ScoutApm::Tracer
25
+ if prepend
26
+ ::Redis::Client.send(:include, ScoutApm::Tracer)
27
+ ::Redis::Client.send(:prepend, RedisClientInstrumentationPrepend)
28
+ else
29
+ ::Redis::Client.class_eval do
30
+ include ScoutApm::Tracer
27
31
 
28
- def call_with_scout_instruments(*args, &block)
29
- command = args.first.first rescue "Unknown"
32
+ def call_with_scout_instruments(*args, &block)
33
+ command = args.first.first rescue "Unknown"
30
34
 
31
- self.class.instrument("Redis", command) do
32
- call_without_scout_instruments(*args, &block)
35
+ self.class.instrument("Redis", command) do
36
+ call_without_scout_instruments(*args, &block)
37
+ end
33
38
  end
34
- end
35
39
 
36
- alias_method :call_without_scout_instruments, :call
37
- alias_method :call, :call_with_scout_instruments
40
+ alias_method :call_without_scout_instruments, :call
41
+ alias_method :call, :call_with_scout_instruments
42
+ end
38
43
  end
39
44
  end
40
45
  end
41
46
  end
47
+
48
+ module RedisClientInstrumentationPrepend
49
+ def call(*args, &block)
50
+ command = args.first.first rescue "Unknown"
51
+
52
+ self.class.instrument("Redis", command) do
53
+ super(*args, &block)
54
+ end
55
+ end
56
+ end
42
57
  end
43
58
  end
@@ -15,7 +15,7 @@ module ScoutApm
15
15
  @installed
16
16
  end
17
17
 
18
- def install
18
+ def install(prepend:)
19
19
  if defined?(::Sinatra) && defined?(::Sinatra::Base) && ::Sinatra::Base.private_method_defined?(:dispatch!)
20
20
  @installed = true
21
21
 
@@ -16,7 +16,7 @@ module ScoutApm
16
16
  @installed
17
17
  end
18
18
 
19
- def install
19
+ def install(prepend:)
20
20
  if defined?(::Typhoeus)
21
21
  @installed = true
22
22
 
@@ -217,6 +217,7 @@ module ScoutApm
217
217
 
218
218
  def initialize(timestamp, context)
219
219
  @timestamp = timestamp
220
+ @context = context
220
221
 
221
222
  @request_traces = ScoredItemSet.new(context.config.value('max_traces'))
222
223
  @job_traces = ScoredItemSet.new(context.config.value('max_traces'))
@@ -230,6 +231,11 @@ module ScoutApm
230
231
  @jobs = Hash.new
231
232
  end
232
233
 
234
+ def logger
235
+ @context.logger
236
+ end
237
+ private :logger
238
+
233
239
  # Merges another StoreReportingPeriod into this one
234
240
  def merge(other)
235
241
  self.
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "5.1.1"
2
+ VERSION = "5.3.1"
3
3
  end
@@ -3,8 +3,25 @@ require 'scout_apm/request_manager'
3
3
  require 'scout_apm/background_job_integrations/sidekiq'
4
4
 
5
5
  class SidekiqTest < Minitest::Test
6
+ SidekiqIntegration = ScoutApm::BackgroundJobIntegrations::Sidekiq
6
7
  SidekiqMiddleware = ScoutApm::BackgroundJobIntegrations::SidekiqMiddleware
7
8
 
9
+ ########################################
10
+ # Install
11
+ ########################################
12
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("sidekiq_install")
13
+ require 'sidekiq'
14
+
15
+ # Sidekiq::CLI needs to be defined in order for `Sidekiq.configure_server` to work
16
+ Sidekiq::CLI = nil
17
+
18
+ def test_starts_on_startup
19
+ ::ScoutApm::Agent.any_instance.expects(:start)
20
+ SidekiqIntegration.new.install
21
+ Sidekiq.options[:lifecycle_events][:startup].map(&:call)
22
+ end
23
+ end
24
+
8
25
  ########################################
9
26
  # Middleware
10
27
  ########################################
@@ -29,7 +29,7 @@ class ActiveRecordTest < Minitest::Test
29
29
  agent_context.recorder = recorder
30
30
 
31
31
  instrument = ScoutApm::Instruments::ActiveRecord.new(agent_context)
32
- instrument.install
32
+ instrument.install(prepend: false)
33
33
 
34
34
  ScoutApm::Tracer.instrument("Controller", "foo/bar") do
35
35
  user = User.create
@@ -0,0 +1,24 @@
1
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
2
+ require 'test_helper'
3
+
4
+ require 'scout_apm/instruments/http_client'
5
+
6
+ require 'httpclient'
7
+
8
+ class HttpClientTest < Minitest::Test
9
+ def setup
10
+ @context = ScoutApm::AgentContext.new
11
+ @instance = ScoutApm::Instruments::HttpClient.new(@context)
12
+ @instrument_manager = ScoutApm::InstrumentManager.new(@context)
13
+ @instance.install(prepend: @instrument_manager.prepend_for_instrument?(@instance.class))
14
+ end
15
+
16
+ def test_installs_using_proper_method
17
+ if @instrument_manager.prepend_for_instrument?(@instance.class) == true
18
+ assert ::HTTPClient.ancestors.include?(ScoutApm::Instruments::HttpClientInstrumentationPrepend)
19
+ else
20
+ assert_equal false, ::HTTPClient.ancestors.include?(ScoutApm::Instruments::HttpClientInstrumentationPrepend)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
2
+ require 'test_helper'
3
+
4
+ require 'scout_apm/instruments/http'
5
+
6
+ require 'http'
7
+
8
+ class HttpTest < Minitest::Test
9
+ def setup
10
+ @context = ScoutApm::AgentContext.new
11
+ @instance = ScoutApm::Instruments::HTTP.new(@context)
12
+ @instrument_manager = ScoutApm::InstrumentManager.new(@context)
13
+ @instance.install(prepend: @instrument_manager.prepend_for_instrument?(@instance.class))
14
+ end
15
+
16
+ def test_installs_using_proper_method
17
+ if @instrument_manager.prepend_for_instrument?(@instance.class) == true
18
+ assert ::HTTP::Client.ancestors.include?(ScoutApm::Instruments::HTTPInstrumentationPrepend)
19
+ else
20
+ assert_equal false, ::HTTP::Client.ancestors.include?(ScoutApm::Instruments::HTTPInstrumentationPrepend)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
2
+ require 'test_helper'
3
+
4
+ require 'scout_apm/instruments/moped'
5
+
6
+ require 'moped'
7
+
8
+ class MopedTest < Minitest::Test
9
+ def setup
10
+ @context = ScoutApm::AgentContext.new
11
+ @instance = ScoutApm::Instruments::Moped.new(@context)
12
+ @instrument_manager = ScoutApm::InstrumentManager.new(@context)
13
+ @instance.install(prepend: @instrument_manager.prepend_for_instrument?(@instance.class))
14
+ end
15
+
16
+ def test_installs_using_proper_method
17
+ if @instrument_manager.prepend_for_instrument?(@instance.class) == true
18
+ assert ::Moped::Node.ancestors.include?(ScoutApm::Instruments::MopedInstrumentationPrepend)
19
+ else
20
+ assert_equal false, ::Moped::Node.ancestors.include?(ScoutApm::Instruments::MopedInstrumentationPrepend)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -7,7 +7,9 @@ require 'addressable/uri'
7
7
  class NetHttpTest < Minitest::Test
8
8
  def setup
9
9
  @context = ScoutApm::AgentContext.new
10
- ScoutApm::Instruments::NetHttp.new(@context).install
10
+ @instance = ScoutApm::Instruments::NetHttp.new(@context)
11
+ @instrument_manager = ScoutApm::InstrumentManager.new(@context)
12
+ @instance.install(prepend: @instrument_manager.prepend_for_instrument?(@instance.class))
11
13
  end
12
14
 
13
15
  def test_request_scout_description_for_uri
@@ -24,4 +26,12 @@ class NetHttpTest < Minitest::Test
24
26
  req = Net::HTTP::Get.new(Addressable::URI.parse('http://example.org/here'))
25
27
  assert_equal '/here', Net::HTTP.new('').request_scout_description(req)
26
28
  end
29
+
30
+ def test_installs_using_proper_method
31
+ if @instrument_manager.prepend_for_instrument?(@instance.class) == true
32
+ assert Net::HTTP.ancestors.include?(ScoutApm::Instruments::NetHttpInstrumentationPrepend)
33
+ else
34
+ assert_equal false, Net::HTTP.ancestors.include?(ScoutApm::Instruments::NetHttpInstrumentationPrepend)
35
+ end
36
+ end
27
37
  end
@@ -0,0 +1,24 @@
1
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
2
+ require 'test_helper'
3
+
4
+ require 'scout_apm/instruments/redis'
5
+
6
+ require 'redis'
7
+
8
+ class RedisTest < Minitest::Test
9
+ def setup
10
+ @context = ScoutApm::AgentContext.new
11
+ @instance = ScoutApm::Instruments::Redis.new(@context)
12
+ @instrument_manager = ScoutApm::InstrumentManager.new(@context)
13
+ @instance.install(prepend: @instrument_manager.prepend_for_instrument?(@instance.class))
14
+ end
15
+
16
+ def test_installs_using_proper_method
17
+ if @instrument_manager.prepend_for_instrument?(@instance.class) == true
18
+ assert ::Redis::Client.ancestors.include?(ScoutApm::Instruments::RedisClientInstrumentationPrepend)
19
+ else
20
+ assert_equal false, ::Redis::Client.ancestors.include?(ScoutApm::Instruments::RedisClientInstrumentationPrepend)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -10,7 +10,7 @@ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("typhoeus")
10
10
  @context = ScoutApm::AgentContext.new
11
11
  @recorder = FakeRecorder.new
12
12
  ScoutApm::Agent.instance.context.recorder = @recorder
13
- ScoutApm::Instruments::Typhoeus.new(@context).install
13
+ ScoutApm::Instruments::Typhoeus.new(@context).install(prepend: false)
14
14
  end
15
15
 
16
16
  def test_instruments_typhoeus_hydra
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 5.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
8
8
  - Andre Lewis
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-12-14 00:00:00.000000000 Z
12
+ date: 2022-08-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -232,11 +232,13 @@ files:
232
232
  - ext/rusage/extconf.rb
233
233
  - ext/rusage/rusage.c
234
234
  - gems/README.md
235
+ - gems/instruments.gemfile
235
236
  - gems/octoshark.gemfile
236
237
  - gems/rails3.gemfile
237
238
  - gems/rails4.gemfile
238
239
  - gems/rails5.gemfile
239
240
  - gems/rails6.gemfile
241
+ - gems/sidekiq.gemfile
240
242
  - gems/typhoeus.gemfile
241
243
  - lib/scout_apm.rb
242
244
  - lib/scout_apm/agent.rb
@@ -440,8 +442,12 @@ files:
440
442
  - test/unit/histogram_test.rb
441
443
  - test/unit/ignored_uris_test.rb
442
444
  - test/unit/instruments/active_record_test.rb
445
+ - test/unit/instruments/http_client_test.rb
446
+ - test/unit/instruments/http_test.rb
447
+ - test/unit/instruments/moped_test.rb
443
448
  - test/unit/instruments/net_http_test.rb
444
449
  - test/unit/instruments/percentile_sampler_test.rb
450
+ - test/unit/instruments/redis_test.rb
445
451
  - test/unit/instruments/typhoeus_test.rb
446
452
  - test/unit/layaway_test.rb
447
453
  - test/unit/layer_children_set_test.rb
@@ -472,7 +478,7 @@ homepage: https://github.com/scoutapp/scout_apm_ruby
472
478
  licenses:
473
479
  - MIT
474
480
  metadata: {}
475
- post_install_message:
481
+ post_install_message:
476
482
  rdoc_options: []
477
483
  require_paths:
478
484
  - lib
@@ -488,8 +494,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
488
494
  - !ruby/object:Gem::Version
489
495
  version: '0'
490
496
  requirements: []
491
- rubygems_version: 3.0.3
492
- signing_key:
497
+ rubygems_version: 3.3.7
498
+ signing_key:
493
499
  specification_version: 4
494
500
  summary: Ruby application performance monitoring
495
501
  test_files: []