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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -0
- data/CHANGELOG.markdown +14 -2
- data/README.markdown +20 -8
- data/gems/instruments.gemfile +1 -0
- data/lib/scout_apm/auto_instrument/instruction_sequence.rb +2 -1
- data/lib/scout_apm/auto_instrument/parser.rb +150 -2
- data/lib/scout_apm/auto_instrument/prism.rb +357 -0
- data/lib/scout_apm/auto_instrument/rails.rb +9 -155
- data/lib/scout_apm/auto_instrument/requirements.rb +11 -0
- data/lib/scout_apm/background_job_integrations/delayed_job.rb +15 -1
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +89 -1
- data/lib/scout_apm/config.rb +32 -7
- data/lib/scout_apm/context.rb +3 -1
- data/lib/scout_apm/error_service/error_record.rb +1 -1
- data/lib/scout_apm/instrument_manager.rb +2 -0
- data/lib/scout_apm/instruments/http_client.rb +10 -0
- data/lib/scout_apm/instruments/httpx.rb +119 -0
- data/lib/scout_apm/instruments/opensearch.rb +131 -0
- data/lib/scout_apm/sampling.rb +25 -13
- data/lib/scout_apm/server_integrations/puma.rb +21 -4
- data/lib/scout_apm/version.rb +1 -1
- data/lib/scout_apm.rb +9 -3
- data/test/unit/auto_instrument/controller-ast.prism.txt +1015 -0
- data/test/unit/auto_instrument/controller-instrumented.rb +36 -11
- data/test/unit/auto_instrument/controller.rb +25 -0
- data/test/unit/auto_instrument/hash_shorthand_controller-instrumented.rb +28 -10
- data/test/unit/auto_instrument/hash_shorthand_controller.rb +19 -1
- data/test/unit/auto_instrument_test.rb +7 -1
- data/test/unit/background_job_integrations/sidekiq_test.rb +38 -0
- data/test/unit/config_test.rb +14 -0
- data/test/unit/error_service/error_buffer_test.rb +31 -0
- data/test/unit/error_test.rb +1 -1
- data/test/unit/ignored_uris_test.rb +7 -0
- data/test/unit/instruments/http_client_test.rb +0 -2
- data/test/unit/instruments/httpx_test.rb +78 -0
- data/test/unit/sampling_test.rb +10 -10
- metadata +8 -2
- /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:
|
|
15
|
-
if ::ScoutApm::AutoInstrument("@client.save",["ROOT/test/unit/auto_instrument/controller.rb:
|
|
16
|
-
::ScoutApm::AutoInstrument("redirect_to @client",["ROOT/test/unit/auto_instrument/controller.rb:
|
|
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:
|
|
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:
|
|
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:
|
|
28
|
-
::ScoutApm::AutoInstrument("@client.transaction do...",["ROOT/test/unit/auto_instrument/controller.rb:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
7
|
-
longhand: ::ScoutApm::AutoInstrument("longhand: longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:
|
|
8
|
-
longhand_different_key: ::ScoutApm::AutoInstrument("longhand",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:
|
|
9
|
-
hash_rocket: ::ScoutApm::AutoInstrument(":hash_rocket => hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:
|
|
10
|
-
:hash_rocket_different_key => ::ScoutApm::AutoInstrument("hash_rocket",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:
|
|
11
|
-
non_nil_receiver: ::ScoutApm::AutoInstrument("non_nil_receiver.value",["ROOT/test/unit/auto_instrument/hash_shorthand_controller.rb:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
data/test/unit/config_test.rb
CHANGED
|
@@ -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
|
data/test/unit/error_test.rb
CHANGED
|
@@ -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
|
|
@@ -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
|
data/test/unit/sampling_test.rb
CHANGED
|
@@ -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' =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
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.
|
|
69
|
+
assert_equal(false, sampling.downsample?(0.50))
|
|
70
70
|
end
|
|
71
71
|
sampling.stub(:rand, 0.99) do
|
|
72
|
-
assert_equal(true, sampling.
|
|
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:
|
|
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
|
|
File without changes
|