scout_apm 5.8.0 → 6.0.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/CHANGELOG.markdown +14 -2
  4. data/README.markdown +20 -8
  5. data/gems/instruments.gemfile +1 -0
  6. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +2 -1
  7. data/lib/scout_apm/auto_instrument/parser.rb +150 -2
  8. data/lib/scout_apm/auto_instrument/prism.rb +357 -0
  9. data/lib/scout_apm/auto_instrument/rails.rb +9 -155
  10. data/lib/scout_apm/auto_instrument/requirements.rb +11 -0
  11. data/lib/scout_apm/background_job_integrations/delayed_job.rb +15 -1
  12. data/lib/scout_apm/background_job_integrations/sidekiq.rb +89 -1
  13. data/lib/scout_apm/config.rb +32 -7
  14. data/lib/scout_apm/context.rb +3 -1
  15. data/lib/scout_apm/error_service/error_record.rb +1 -1
  16. data/lib/scout_apm/instrument_manager.rb +2 -0
  17. data/lib/scout_apm/instruments/http_client.rb +10 -0
  18. data/lib/scout_apm/instruments/httpx.rb +119 -0
  19. data/lib/scout_apm/instruments/opensearch.rb +131 -0
  20. data/lib/scout_apm/sampling.rb +25 -13
  21. data/lib/scout_apm/server_integrations/puma.rb +21 -4
  22. data/lib/scout_apm/version.rb +1 -1
  23. data/lib/scout_apm.rb +9 -3
  24. data/test/unit/auto_instrument/controller-ast.prism.txt +1015 -0
  25. data/test/unit/auto_instrument/controller-instrumented.rb +36 -11
  26. data/test/unit/auto_instrument/controller.rb +25 -0
  27. data/test/unit/auto_instrument/hash_shorthand_controller-instrumented.rb +28 -10
  28. data/test/unit/auto_instrument/hash_shorthand_controller.rb +19 -1
  29. data/test/unit/auto_instrument_test.rb +7 -1
  30. data/test/unit/background_job_integrations/sidekiq_test.rb +38 -0
  31. data/test/unit/config_test.rb +14 -0
  32. data/test/unit/error_service/error_buffer_test.rb +31 -0
  33. data/test/unit/error_test.rb +1 -1
  34. data/test/unit/ignored_uris_test.rb +7 -0
  35. data/test/unit/instruments/http_client_test.rb +0 -2
  36. data/test/unit/instruments/httpx_test.rb +78 -0
  37. data/test/unit/sampling_test.rb +10 -10
  38. metadata +8 -2
  39. /data/test/unit/auto_instrument/{controller-ast.txt → controller-ast.parser.txt} +0 -0
@@ -10,40 +10,65 @@ class ClientsController < ApplicationController
10
10
  end
11
11
  end
12
12
 
13
+ def new
14
+ ::ScoutApm::AutoInstrument("super do |something|...",["ROOT/test/unit/auto_instrument/controller.rb:14:in `new'"]){super do |something|
15
+ @client = Client.new
16
+ end}
17
+ end
18
+
13
19
  def create
14
- @client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["ROOT/test/unit/auto_instrument/controller.rb:14:in `create'"]){Client.new(params[:client])}
15
- if ::ScoutApm::AutoInstrument("@client.save",["ROOT/test/unit/auto_instrument/controller.rb:15:in `create'"]){@client.save}
16
- ::ScoutApm::AutoInstrument("redirect_to @client",["ROOT/test/unit/auto_instrument/controller.rb:16:in `create'"]){redirect_to @client}
20
+ @client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["ROOT/test/unit/auto_instrument/controller.rb:20:in `create'"]){Client.new(params[:client])}
21
+ if ::ScoutApm::AutoInstrument("@client.save",["ROOT/test/unit/auto_instrument/controller.rb:21:in `create'"]){@client.save}
22
+ ::ScoutApm::AutoInstrument("redirect_to @client",["ROOT/test/unit/auto_instrument/controller.rb:22:in `create'"]){redirect_to @client}
17
23
  else
18
24
  # This line overrides the default rendering behavior, which
19
25
  # would have been to render the "create" view.
20
- ::ScoutApm::AutoInstrument("render \"new\"",["ROOT/test/unit/auto_instrument/controller.rb:20:in `create'"]){render "new"}
26
+ ::ScoutApm::AutoInstrument("render \"new\"",["ROOT/test/unit/auto_instrument/controller.rb:26:in `create'"]){render "new"}
21
27
  end
22
28
  end
23
29
 
24
30
  def edit
25
- @client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["ROOT/test/unit/auto_instrument/controller.rb:25:in `edit'"]){Client.new(params[:client])}
31
+ @client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["ROOT/test/unit/auto_instrument/controller.rb:31:in `edit'"]){Client.new(params[:client])}
26
32
 
27
- if ::ScoutApm::AutoInstrument("request.post?",["ROOT/test/unit/auto_instrument/controller.rb:27:in `edit'"]){request.post?}
28
- ::ScoutApm::AutoInstrument("@client.transaction do...",["ROOT/test/unit/auto_instrument/controller.rb:28:in `edit'"]){@client.transaction do
33
+ if ::ScoutApm::AutoInstrument("request.post?",["ROOT/test/unit/auto_instrument/controller.rb:33:in `edit'"]){request.post?}
34
+ ::ScoutApm::AutoInstrument("@client.transaction do...",["ROOT/test/unit/auto_instrument/controller.rb:34:in `edit'"]){@client.transaction do
29
35
  @client.update_attributes(params[:client])
30
36
  end}
31
37
  end
32
38
  end
33
39
 
34
40
  def data
35
- @clients = ::ScoutApm::AutoInstrument("Client.all",["ROOT/test/unit/auto_instrument/controller.rb:35:in `data'"]){Client.all}
41
+ @clients = ::ScoutApm::AutoInstrument("Client.all",["ROOT/test/unit/auto_instrument/controller.rb:41:in `data'"]){Client.all}
36
42
 
37
- formatter = ::ScoutApm::AutoInstrument("proc do |row|...",["ROOT/test/unit/auto_instrument/controller.rb:37:in `data'"]){proc do |row|
43
+ formatter = ::ScoutApm::AutoInstrument("proc do |row|...",["ROOT/test/unit/auto_instrument/controller.rb:43:in `data'"]){proc do |row|
38
44
  row.to_json
39
45
  end}
40
46
 
41
- ::ScoutApm::AutoInstrument("respond_with @clients.each(&formatter).join(\"\\n\"), :content_type => 'application/json; boundary=NL'",["ROOT/test/unit/auto_instrument/controller.rb:41:in `data'"]){respond_with @clients.each(&formatter).join("\n"), :content_type => 'application/json; boundary=NL'}
47
+ ::ScoutApm::AutoInstrument("respond_with @clients.each(&formatter).join(\"\\n\"), :content_type => 'application/json; boundary=NL'",["ROOT/test/unit/auto_instrument/controller.rb:47:in `data'"]){respond_with @clients.each(&formatter).join("\n"), :content_type => 'application/json; boundary=NL'}
42
48
  end
43
49
 
44
50
  def things
45
51
  x = {}
46
52
  x[:this] ||= 'foo'
47
- x[:that] &&= ::ScoutApm::AutoInstrument("'foo'.size",["ROOT/test/unit/auto_instrument/controller.rb:47:in `things'"]){'foo'.size}
53
+ x[:that] &&= ::ScoutApm::AutoInstrument("'foo'.size",["ROOT/test/unit/auto_instrument/controller.rb:53:in `things'"]){'foo'.size}
54
+ end
55
+
56
+ def do_something
57
+ ::ScoutApm::AutoInstrument("wrap_call(\"123\",...",["ROOT/test/unit/auto_instrument/controller.rb:57:in `do_something'"]){wrap_call("123",
58
+ something: -> { puts "Do something" }
59
+ ) do
60
+
61
+ raw_data = '{ "key": "123" }'
62
+
63
+ payload = begin
64
+ Marshal.load(raw_data)
65
+ rescue
66
+ puts 'Failed with bad/unhelpful error message'
67
+ end
68
+ end}
69
+ end
70
+
71
+ def wrap_call(*args, **kwargs)
72
+ yield
48
73
  end
49
74
  end
@@ -10,6 +10,12 @@ class ClientsController < ApplicationController
10
10
  end
11
11
  end
12
12
 
13
+ def new
14
+ super do |something|
15
+ @client = Client.new
16
+ end
17
+ end
18
+
13
19
  def create
14
20
  @client = Client.new(params[:client])
15
21
  if @client.save
@@ -46,4 +52,23 @@ class ClientsController < ApplicationController
46
52
  x[:this] ||= 'foo'
47
53
  x[:that] &&= 'foo'.size
48
54
  end
55
+
56
+ def do_something
57
+ wrap_call("123",
58
+ something: -> { puts "Do something" }
59
+ ) do
60
+
61
+ raw_data = '{ "key": "123" }'
62
+
63
+ payload = begin
64
+ Marshal.load(raw_data)
65
+ rescue
66
+ puts 'Failed with bad/unhelpful error message'
67
+ end
68
+ end
69
+ end
70
+
71
+ def wrap_call(*args, **kwargs)
72
+ yield
73
+ end
49
74
  end
@@ -1,28 +1,46 @@
1
1
 
2
2
  class HashShorthandController < ApplicationController
3
3
  def hash
4
+ ::ScoutApm::AutoInstrument("THREAD.current[:ternary_check] = true",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:4:in `hash'"]){THREAD.current[:ternary_check] = true}
4
5
  json = {
5
6
  static: "static",
6
- shorthand: ::ScoutApm::AutoInstrument("shorthand:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:6:in `hash'"]){shorthand},
7
- longhand: ::ScoutApm::AutoInstrument("longhand: longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:7:in `hash'"]){longhand},
8
- longhand_different_key: ::ScoutApm::AutoInstrument("longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:8:in `hash'"]){longhand},
9
- hash_rocket: ::ScoutApm::AutoInstrument(":hash_rocket => hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:9:in `hash'"]){hash_rocket},
10
- :hash_rocket_different_key => ::ScoutApm::AutoInstrument("hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:10:in `hash'"]){hash_rocket},
11
- non_nil_receiver: ::ScoutApm::AutoInstrument("non_nil_receiver.value",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:11:in `hash'"]){non_nil_receiver.value},
7
+ shorthand: ::ScoutApm::AutoInstrument("shorthand:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:7:in `hash'"]){shorthand},
8
+ longhand: ::ScoutApm::AutoInstrument("longhand: longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:8:in `hash'"]){longhand},
9
+ longhand_different_key: ::ScoutApm::AutoInstrument("longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:9:in `hash'"]){longhand},
10
+ hash_rocket: ::ScoutApm::AutoInstrument(":hash_rocket => hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:10:in `hash'"]){hash_rocket},
11
+ :hash_rocket_different_key => ::ScoutApm::AutoInstrument("hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:11:in `hash'"]){hash_rocket},
12
+ non_nil_receiver: ::ScoutApm::AutoInstrument("non_nil_receiver.value",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:12:in `hash'"]){non_nil_receiver.value},
12
13
  nested: {
13
- shorthand: ::ScoutApm::AutoInstrument("shorthand:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:13:in `hash'"]){shorthand},
14
+ shorthand: ::ScoutApm::AutoInstrument("shorthand:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:14:in `hash'"]){shorthand},
14
15
  },
15
- nested_call: ::ScoutApm::AutoInstrument("nested_call(params[\"timestamp\"])",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:15:in `hash'"]){nested_call(params["timestamp"])}
16
+ nested_call: ::ScoutApm::AutoInstrument("nested_call(params[\"timestamp\"])",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:16:in `hash'"]){nested_call(params["timestamp"])},
17
+ nested_with_ternaries: {
18
+ truthy: ::ScoutApm::AutoInstrument("THREAD.current[:ternary_check] == true",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:18:in `hash'"]){THREAD.current[:ternary_check] == true} ? 1 : 0,
19
+ falsy: ::ScoutApm::AutoInstrument("THREAD.current[:ternary_check] == false",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:19:in `hash'"]){THREAD.current[:ternary_check] == false} ? 1 : 0,
20
+ },
21
+ ternary: ::ScoutApm::AutoInstrument("ternary",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:21:in `hash'"]){ternary} ? ::ScoutApm::AutoInstrument("ternary",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:21:in `hash'"]){ternary} : nil,
16
22
  }
17
- ::ScoutApm::AutoInstrument("render json:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:17:in `hash'"]){render json:}
23
+ ::ScoutApm::AutoInstrument("render json:",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:23:in `hash'"]){render json:}
18
24
  end
19
25
 
20
26
  private
21
27
 
28
+ def simple_method
29
+ "simple"
30
+ end
31
+
32
+ def inner_method
33
+ "inner"
34
+ end
35
+
22
36
  def nested_call(noop)
23
37
  noop
24
38
  end
25
39
 
40
+ def ternary
41
+ true
42
+ end
43
+
26
44
  def shorthand
27
45
  "shorthand"
28
46
  end
@@ -36,6 +54,6 @@ class HashShorthandController < ApplicationController
36
54
  end
37
55
 
38
56
  def non_nil_receiver
39
- ::ScoutApm::AutoInstrument("OpenStruct.new(value: \"value\")",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:39:in `non_nil_receiver'"]){OpenStruct.new(value: "value")}
57
+ ::ScoutApm::AutoInstrument("OpenStruct.new(value: \"value\")",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:57:in `non_nil_receiver'"]){OpenStruct.new(value: "value")}
40
58
  end
41
59
  end
@@ -1,6 +1,7 @@
1
1
 
2
2
  class HashShorthandController < ApplicationController
3
3
  def hash
4
+ THREAD.current[:ternary_check] = true
4
5
  json = {
5
6
  static: "static",
6
7
  shorthand:,
@@ -12,17 +13,34 @@ class HashShorthandController < ApplicationController
12
13
  nested: {
13
14
  shorthand:,
14
15
  },
15
- nested_call: nested_call(params["timestamp"])
16
+ nested_call: nested_call(params["timestamp"]),
17
+ nested_with_ternaries: {
18
+ truthy: THREAD.current[:ternary_check] == true ? 1 : 0,
19
+ falsy: THREAD.current[:ternary_check] == false ? 1 : 0,
20
+ },
21
+ ternary: ternary ? ternary : nil,
16
22
  }
17
23
  render json:
18
24
  end
19
25
 
20
26
  private
21
27
 
28
+ def simple_method
29
+ "simple"
30
+ end
31
+
32
+ def inner_method
33
+ "inner"
34
+ end
35
+
22
36
  def nested_call(noop)
23
37
  noop
24
38
  end
25
39
 
40
+ def ternary
41
+ true
42
+ end
43
+
26
44
  def shorthand
27
45
  "shorthand"
28
46
  end
@@ -1,3 +1,8 @@
1
+ begin
2
+ require 'prism'
3
+ rescue LoadError
4
+ end
5
+
1
6
  require 'test_helper'
2
7
 
3
8
  require 'scout_apm/auto_instrument'
@@ -21,7 +26,8 @@ class AutoInstrumentTest < Minitest::Test
21
26
  # test controller.rb file, which will be different on different environments.
22
27
  # This normalizes backtraces across environments.
23
28
  def normalize_backtrace(string)
24
- string.gsub(ROOT, "ROOT")
29
+ # Keep tests simple and just use UTF-8 encoding.
30
+ result = string.gsub(ROOT, "ROOT").force_encoding("UTF-8")
25
31
  end
26
32
 
27
33
  # Use this to automatically update the test fixtures.
@@ -125,4 +125,42 @@ class SidekiqTest < Minitest::Test
125
125
  msg = {}
126
126
  assert_equal 0, SidekiqMiddleware.new.latency(msg, 200)
127
127
  end
128
+
129
+ ########################################
130
+ # Job Arguments Capture
131
+ ########################################
132
+ def test_capture_job_args_when_enabled_with_valid_job
133
+ test_job_class = Class.new do
134
+ def self.name
135
+ 'TestJobWithArgs'
136
+ end
137
+
138
+ def perform(user_id, email, filter_param, dict_val)
139
+ end
140
+ end
141
+
142
+ Object.const_set('TestJobWithArgs', test_job_class)
143
+
144
+ SidekiqMiddleware.any_instance.stubs(:capture_job_args?).returns(true)
145
+ SidekiqMiddleware.any_instance.stubs(:filtered_params_config).returns(["filter_param"])
146
+
147
+ fake_request = mock
148
+ fake_request.expects(:annotate_request)
149
+ fake_request.expects(:start_layer).twice
150
+ fake_request.expects(:stop_layer).twice
151
+
152
+ ScoutApm::RequestManager.stubs(:lookup).returns(fake_request)
153
+ ScoutApm::Context.expects(:add).with({ user_id: 123, email: 'test@example.com', filter_param: '[FILTERED]', dict_val: '[UNSUPPORTED TYPE]' })
154
+
155
+ msg = {
156
+ 'class' => 'TestJobWithArgs',
157
+ 'args' => [{ 'arguments' => [123, 'test@example.com', 'secret', {a: 1}] }]
158
+ }
159
+
160
+ block_called = false
161
+ SidekiqMiddleware.new.call(nil, msg, "defaultqueue") { block_called = true }
162
+ assert block_called
163
+
164
+ Object.send(:remove_const, 'TestJobWithArgs')
165
+ end
128
166
  end
@@ -92,6 +92,20 @@ class ConfigTest < Minitest::Test
92
92
  assert_nil coercion.coerce(nil)
93
93
  end
94
94
 
95
+ def test_sample_rate_coercion
96
+ coercion = ScoutApm::Config::SampleRateCoercion.new
97
+ assert_in_delta 1.0, coercion.coerce("1")
98
+ assert_in_delta 0.015, coercion.coerce("1.5")
99
+ assert_in_delta 1.0, coercion.coerce(1)
100
+ assert_in_delta 0.015, coercion.coerce(1.5)
101
+ assert_in_delta 0.0, coercion.coerce("0")
102
+ assert_in_delta 0.0, coercion.coerce(0)
103
+ assert_in_delta 0.0, coercion.coerce("")
104
+ assert_in_delta 0.0, coercion.coerce(nil)
105
+ assert_in_delta 0.5, coercion.coerce("0.5")
106
+ assert_in_delta 0, coercion.coerce("-2.5")
107
+ end
108
+
95
109
  def test_any_keys_found
96
110
  ENV.stubs(:has_key?).returns(nil)
97
111
 
@@ -68,4 +68,35 @@ class ErrorBufferTest < Minitest::Test
68
68
  def ex(msg="Whoops")
69
69
  FakeError.new(msg)
70
70
  end
71
+
72
+ def test_user_context_is_flattened_with_user_prefix
73
+ config = make_fake_config(
74
+ 'errors_enabled' => true,
75
+ 'errors_env_capture' => %w()
76
+ )
77
+ test_context = ScoutApm::AgentContext.new().tap { |c| c.config = config }
78
+
79
+ ScoutApm::Agent.instance.stub(:context, test_context) do
80
+ eb = ScoutApm::ErrorService::ErrorBuffer.new(test_context)
81
+
82
+ # Set user context using add_user
83
+ ScoutApm::Context.add_user(id: 2)
84
+ # Set extra context using add
85
+ ScoutApm::Context.add(organization: 3)
86
+
87
+ eb.capture(ex, env)
88
+ exceptions = eb.instance_variable_get(:@error_records)
89
+
90
+ assert_equal 1, exceptions.length
91
+ exception = exceptions[0]
92
+
93
+ # User context should be flattened with "user_" prefix
94
+ assert_equal 2, exception.context["user_id"]
95
+ # Extra context should remain as-is
96
+ assert_equal 3, exception.context[:organization]
97
+
98
+ # Should NOT have nested :user key
99
+ refute exception.context.key?(:user)
100
+ end
101
+ end
71
102
  end
@@ -85,7 +85,7 @@ class ErrorTest < Minitest::Test
85
85
  end
86
86
 
87
87
  def assert_default_context
88
- {user: {}, transaction_id: ScoutApm::RequestManager.lookup.transaction_id}
88
+ {transaction_id: ScoutApm::RequestManager.lookup.transaction_id}
89
89
  end
90
90
 
91
91
  def ex(msg="Whoops")
@@ -19,4 +19,11 @@ class IgnoredUrlsTest < Minitest::Test
19
19
  assert_equal false, i.ignore?("/users/2/health")
20
20
  assert_equal true, i.ignore?("/admin/dashboard")
21
21
  end
22
+
23
+ def test_ignores_prefix_regex
24
+ i = ScoutApm::IgnoredUris.new(["/slow/\\d+/notifications", "/health"])
25
+ assert_equal true, i.ignore?("/slow/123/notifications")
26
+ assert_equal false, i.ignore?("/slow/abcd/notifications")
27
+ assert_equal true, i.ignore?("/health")
28
+ end
22
29
  end
@@ -3,8 +3,6 @@ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
3
3
 
4
4
  require 'scout_apm/instruments/http_client'
5
5
 
6
- require 'httpclient'
7
-
8
6
  class HttpClientTest < Minitest::Test
9
7
  def setup
10
8
  @context = ScoutApm::AgentContext.new
@@ -0,0 +1,78 @@
1
+ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments")
2
+ require 'test_helper'
3
+
4
+ require 'scout_apm/instruments/httpx'
5
+
6
+ require 'httpx'
7
+
8
+ class HTTPXTest < Minitest::Test
9
+ def setup
10
+ @context = ScoutApm::AgentContext.new
11
+ @recorder = FakeRecorder.new
12
+ ScoutApm::Agent.instance.context.recorder = @recorder
13
+ ScoutApm::Instruments::HTTPX.new(@context).install(prepend: false)
14
+ end
15
+
16
+
17
+ def test_httpx
18
+ responses = HTTPX.get(
19
+ "https://news.ycombinator.com/news",
20
+ "https://news.ycombinator.com/news?p=2",
21
+ "https://google.com/q=me"
22
+ )
23
+
24
+ assert_equal 1, @recorder.requests.length
25
+
26
+ assert_recorded(@recorder, "HTTP", "GET", "3 requests")
27
+ end
28
+
29
+ def test_httpx_post_request
30
+ HTTPX.post("https://httpbin.org/post", json: { test: "data" })
31
+ assert_recorded(@recorder, "HTTP", "POST", "httpbin.org/post")
32
+ end
33
+
34
+ def test_instruments_httpx_error_handling
35
+ begin
36
+ HTTPX.get("https://thisshouldnotexistatall12345.com")
37
+ rescue
38
+ end
39
+
40
+ assert_equal 1, @recorder.requests.length
41
+ end
42
+
43
+ def test_httpx_request_retry
44
+ begin
45
+ HTTPX.with(timeout: { connect_timeout: 0.25, request_timeout: 0.25 })
46
+ .get("https://httpbin.org/delay/5")
47
+ rescue
48
+ end
49
+ assert_equal 1, @recorder.requests.length
50
+ end
51
+
52
+ def test_multiple_plugins
53
+ session = HTTPX.plugin(:persistent).plugin(:follow_redirects)
54
+
55
+ session.get("https://news.ycombinator.com/news")
56
+ begin
57
+ session.get("http://httpbin.org/redirect/2")
58
+ rescue
59
+ skip "httpbin.org not available"
60
+ end
61
+
62
+ assert_equal 2, @recorder.requests.length, "Expected 2 requests to be recorded"
63
+ end
64
+
65
+ private
66
+
67
+ def assert_recorded(recorder, type, name, desc = nil)
68
+ req = recorder.requests.first
69
+ assert req, "recorder recorded no layers"
70
+ assert_equal type, req.root_layer.type
71
+ assert_equal name, req.root_layer.name
72
+ if !desc.nil?
73
+ assert req.root_layer.desc.include?(desc),
74
+ "Expected description to include '#{desc}', got '#{req.root_layer.desc}'"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -7,7 +7,7 @@ class SamplingTest < Minitest::Test
7
7
  def setup
8
8
  @global_sample_config = FakeConfigOverlay.new(
9
9
  {
10
- 'sample_rate' => 80,
10
+ 'sample_rate' => 0.80,
11
11
  }
12
12
  )
13
13
 
@@ -23,7 +23,7 @@ class SamplingTest < Minitest::Test
23
23
 
24
24
  def test_individual_sample_to_hash
25
25
  sampling = ScoutApm::Sampling.new(@individual_config)
26
- assert_equal({'/foo/bar' => 100, '/foo' => 50, '/bar/zap' => 80}, sampling.individual_sample_to_hash(@individual_config.value('sample_endpoints')))
26
+ assert_equal({'/foo/bar' => 1, '/foo' => 0.50, '/bar/zap' => 0.80}, sampling.individual_sample_to_hash(@individual_config.value('sample_endpoints')))
27
27
 
28
28
  sampling = ScoutApm::Sampling.new(@global_sample_config)
29
29
  assert_nil sampling.individual_sample_to_hash(@global_sample_config.value('sample_endpoints'))
@@ -38,7 +38,7 @@ class SamplingTest < Minitest::Test
38
38
  def test_uri_sample
39
39
  sampling = ScoutApm::Sampling.new(@individual_config)
40
40
  rate = sampling.web_sample_rate('/foo/far')
41
- assert_equal 50, rate
41
+ assert_in_delta 0.50, rate
42
42
 
43
43
  rate = sampling.web_sample_rate('/bar')
44
44
  assert_nil rate
@@ -47,7 +47,7 @@ class SamplingTest < Minitest::Test
47
47
  assert_nil rate
48
48
 
49
49
  rate = sampling.web_sample_rate('/foo/bar/baz')
50
- assert_equal 100, rate
50
+ assert_equal 1.0, rate
51
51
  end
52
52
 
53
53
  def test_job_ignore
@@ -58,18 +58,18 @@ class SamplingTest < Minitest::Test
58
58
 
59
59
  def test_job_sample
60
60
  sampling = ScoutApm::Sampling.new(@individual_config)
61
- assert_equal 50, sampling.job_sample_rate('joba')
62
- assert_equal 95, sampling.job_sample_rate('Foo::BarJob')
61
+ assert_in_delta 0.50, sampling.job_sample_rate('joba')
62
+ assert_in_delta 0.95, sampling.job_sample_rate('Foo::BarJob')
63
63
  assert_nil sampling.job_sample_rate('jobb')
64
64
  end
65
65
 
66
66
  def test_sample
67
67
  sampling = ScoutApm::Sampling.new(@individual_config)
68
68
  sampling.stub(:rand, 0.01) do
69
- assert_equal(false, sampling.sample?(50))
69
+ assert_equal(false, sampling.downsample?(0.50))
70
70
  end
71
71
  sampling.stub(:rand, 0.99) do
72
- assert_equal(true, sampling.sample?(50))
72
+ assert_equal(true, sampling.downsample?(0.50))
73
73
  end
74
74
  end
75
75
 
@@ -104,7 +104,7 @@ class SamplingTest < Minitest::Test
104
104
  end
105
105
 
106
106
  def test_web_reqeust_general_sampling
107
- config = FakeConfigOverlay.new(@individual_config.values.merge({'endpoint_sample_rate' => 80}))
107
+ config = FakeConfigOverlay.new(@individual_config.values.merge({'endpoint_sample_rate' => 0.80}))
108
108
  sampling = ScoutApm::Sampling.new(config)
109
109
 
110
110
  transaction = FakeTrackedRequest.new_web_request('/foo/far')
@@ -171,7 +171,7 @@ class SamplingTest < Minitest::Test
171
171
  end
172
172
 
173
173
  def test_job_general_sampling
174
- config = FakeConfigOverlay.new(@individual_config.values.merge({'job_sample_rate' => 80}))
174
+ config = FakeConfigOverlay.new(@individual_config.values.merge({'job_sample_rate' => 0.80}))
175
175
  sampling = ScoutApm::Sampling.new(config)
176
176
 
177
177
  transaction = FakeTrackedRequest.new_job_request('joba')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.8.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -255,7 +255,9 @@ files:
255
255
  - lib/scout_apm/auto_instrument/instruction_sequence.rb
256
256
  - lib/scout_apm/auto_instrument/layer.rb
257
257
  - lib/scout_apm/auto_instrument/parser.rb
258
+ - lib/scout_apm/auto_instrument/prism.rb
258
259
  - lib/scout_apm/auto_instrument/rails.rb
260
+ - lib/scout_apm/auto_instrument/requirements.rb
259
261
  - lib/scout_apm/background_job_integrations/delayed_job.rb
260
262
  - lib/scout_apm/background_job_integrations/faktory.rb
261
263
  - lib/scout_apm/background_job_integrations/good_job.rb
@@ -313,6 +315,7 @@ files:
313
315
  - lib/scout_apm/instruments/grape.rb
314
316
  - lib/scout_apm/instruments/http.rb
315
317
  - lib/scout_apm/instruments/http_client.rb
318
+ - lib/scout_apm/instruments/httpx.rb
316
319
  - lib/scout_apm/instruments/influxdb.rb
317
320
  - lib/scout_apm/instruments/memcached.rb
318
321
  - lib/scout_apm/instruments/middleware_detailed.rb
@@ -320,6 +323,7 @@ files:
320
323
  - lib/scout_apm/instruments/mongoid.rb
321
324
  - lib/scout_apm/instruments/moped.rb
322
325
  - lib/scout_apm/instruments/net_http.rb
326
+ - lib/scout_apm/instruments/opensearch.rb
323
327
  - lib/scout_apm/instruments/percentile_sampler.rb
324
328
  - lib/scout_apm/instruments/process/process_cpu.rb
325
329
  - lib/scout_apm/instruments/process/process_memory.rb
@@ -425,7 +429,8 @@ files:
425
429
  - test/unit/auto_instrument/anonymous_block_value.rb
426
430
  - test/unit/auto_instrument/assignments-instrumented.rb
427
431
  - test/unit/auto_instrument/assignments.rb
428
- - test/unit/auto_instrument/controller-ast.txt
432
+ - test/unit/auto_instrument/controller-ast.parser.txt
433
+ - test/unit/auto_instrument/controller-ast.prism.txt
429
434
  - test/unit/auto_instrument/controller-instrumented.rb
430
435
  - test/unit/auto_instrument/controller.rb
431
436
  - test/unit/auto_instrument/hanging_method.rb
@@ -460,6 +465,7 @@ files:
460
465
  - test/unit/instruments/fixtures/test_view.html.erb
461
466
  - test/unit/instruments/http_client_test.rb
462
467
  - test/unit/instruments/http_test.rb
468
+ - test/unit/instruments/httpx_test.rb
463
469
  - test/unit/instruments/moped_test.rb
464
470
  - test/unit/instruments/net_http_test.rb
465
471
  - test/unit/instruments/percentile_sampler_test.rb