scout_apm 5.6.5 → 5.7.1
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/CHANGELOG.markdown +8 -0
- data/ext/allocations/allocations.c +20 -33
- data/ext/allocations/extconf.rb +1 -2
- data/lib/scout_apm/config.rb +17 -0
- data/lib/scout_apm/error.rb +16 -5
- data/lib/scout_apm/error_service/error_record.rb +14 -42
- data/lib/scout_apm/error_service/payload.rb +0 -1
- data/lib/scout_apm/sampling.rb +1 -1
- data/lib/scout_apm/version.rb +1 -1
- data/test/test_helper.rb +5 -0
- data/test/unit/error_service/error_buffer_test.rb +46 -1
- data/test/unit/error_test.rb +32 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e96ebe1b7d9a1fb4bd1427ba0564e645cb9bed376da56bc92f9d86f0515adf7
|
4
|
+
data.tar.gz: d1c4692af9e37bb6107f14064ce89f94626d987bf55d845dea19b522fa910ce1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 958530e614d6ad7ac0ef8805d40843883fae446d5dbfdee7f982a504eff838e0f6abe9fc70e7ead42f0e3a7075540291954264704da73694f132674ef2edc48b
|
7
|
+
data.tar.gz: d35f05a9f2c6094b87e1df04a95fa54138c901b25b479ce5962c616d54806fea1a25ab5e89feb5d98c0ecf0d5e13163e97f990c3e74229544751f3b6e0d4fe9f
|
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 5.7.1
|
4
|
+
- Update error capture API to use context (#560)
|
5
|
+
- Filter env in error records (#559)
|
6
|
+
|
7
|
+
# 5.7.0
|
8
|
+
- Fix native extension compilation with GCC 15 (#552)
|
9
|
+
- Handle non Rails/ActiveSupport applications w/ sampling (#557)
|
10
|
+
|
3
11
|
# 5.6.5
|
4
12
|
- Improve error capture API (#555)
|
5
13
|
- Add git sha and agent time tracking to error payloads (#554)
|
@@ -1,26 +1,38 @@
|
|
1
|
-
#ifdef HAVE_RUBY_RUBY_H
|
2
|
-
#include <ruby/ruby.h>
|
3
|
-
#else // Ruby <= 1.8.7
|
4
1
|
#include <ruby.h>
|
5
|
-
#endif
|
6
2
|
|
7
3
|
VALUE mScoutApm;
|
8
4
|
VALUE mInstruments;
|
9
5
|
VALUE cAllocations;
|
10
6
|
|
11
|
-
#
|
7
|
+
#ifdef _WIN32
|
8
|
+
|
9
|
+
#define ALLOCATIONS_ENABLED Qfalse
|
10
|
+
|
11
|
+
static VALUE
|
12
|
+
get_allocation_count(VALUE klass) {
|
13
|
+
return ULL2NUM(0);
|
14
|
+
}
|
15
|
+
|
16
|
+
void
|
17
|
+
Init_hooks(VALUE module)
|
18
|
+
{
|
19
|
+
}
|
20
|
+
|
21
|
+
#else // _WIN32
|
12
22
|
|
13
23
|
#include <sys/resource.h> // is this needed?
|
14
24
|
#include <sys/time.h>
|
15
25
|
#include <ruby/debug.h>
|
16
26
|
|
27
|
+
#define ALLOCATIONS_ENABLED Qtrue
|
28
|
+
|
17
29
|
static __thread uint64_t endpoint_allocations;
|
18
30
|
void increment_allocations() {
|
19
31
|
endpoint_allocations++;
|
20
32
|
}
|
21
33
|
|
22
34
|
static VALUE
|
23
|
-
get_allocation_count() {
|
35
|
+
get_allocation_count(VALUE klass) {
|
24
36
|
return ULL2NUM(endpoint_allocations);
|
25
37
|
}
|
26
38
|
|
@@ -50,28 +62,7 @@ Init_hooks(VALUE module)
|
|
50
62
|
set_gc_hook(RUBY_INTERNAL_EVENT_NEWOBJ);
|
51
63
|
}
|
52
64
|
|
53
|
-
|
54
|
-
{
|
55
|
-
mScoutApm = rb_define_module("ScoutApm");
|
56
|
-
mInstruments = rb_define_module_under(mScoutApm, "Instruments");
|
57
|
-
cAllocations = rb_define_class_under(mInstruments, "Allocations", rb_cObject);
|
58
|
-
rb_define_singleton_method(cAllocations, "count", get_allocation_count, 0);
|
59
|
-
rb_define_singleton_method(cAllocations, "count", get_allocation_count, 0);
|
60
|
-
rb_define_const(cAllocations, "ENABLED", Qtrue);
|
61
|
-
Init_hooks(mScoutApm);
|
62
|
-
}
|
63
|
-
|
64
|
-
#else
|
65
|
-
|
66
|
-
static VALUE
|
67
|
-
get_allocation_count() {
|
68
|
-
return ULL2NUM(0);
|
69
|
-
}
|
70
|
-
|
71
|
-
void
|
72
|
-
Init_hooks(VALUE module)
|
73
|
-
{
|
74
|
-
}
|
65
|
+
#endif // _WIN32
|
75
66
|
|
76
67
|
void Init_allocations()
|
77
68
|
{
|
@@ -79,10 +70,6 @@ void Init_allocations()
|
|
79
70
|
mInstruments = rb_define_module_under(mScoutApm, "Instruments");
|
80
71
|
cAllocations = rb_define_class_under(mInstruments, "Allocations", rb_cObject);
|
81
72
|
rb_define_singleton_method(cAllocations, "count", get_allocation_count, 0);
|
82
|
-
|
83
|
-
rb_define_const(cAllocations, "ENABLED", Qfalse);
|
73
|
+
rb_define_const(cAllocations, "ENABLED", ALLOCATIONS_ENABLED);
|
84
74
|
Init_hooks(mScoutApm);
|
85
75
|
}
|
86
|
-
|
87
|
-
#endif //#ifdef RUBY_INTERNAL_EVENT_NEWOBJ
|
88
|
-
|
data/ext/allocations/extconf.rb
CHANGED
data/lib/scout_apm/config.rb
CHANGED
@@ -50,6 +50,14 @@ require 'scout_apm/environment'
|
|
50
50
|
# endpoint_sample_rate - Rate to sample all endpoints. An integer between 0 and 100. 0 means no traces are sent, 100 means all traces are sent. (supercedes 'sample_rate')
|
51
51
|
# job_sample_rate - Rate to sample all jobs. An integer between 0 and 100. 0 means no traces are sent, 100 means all traces are sent. (supercedes 'sample_rate')
|
52
52
|
#
|
53
|
+
#
|
54
|
+
# Errors Service Configuration
|
55
|
+
# errors_enabled - true/false to enable the errors service
|
56
|
+
# errors_ignored_exceptions - An array of exception classes to ignore.
|
57
|
+
# errors_filtered_params - An array of parameter names to filter/redact out of error reports.
|
58
|
+
# errors_env_capture - An array of environment variables to capture in error reports.
|
59
|
+
# errors_host - The host to send error reports to.
|
60
|
+
#
|
53
61
|
# Any of these config settings can be set with an environment variable prefixed
|
54
62
|
# by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
|
55
63
|
|
@@ -113,6 +121,7 @@ module ScoutApm
|
|
113
121
|
'errors_enabled',
|
114
122
|
'errors_ignored_exceptions',
|
115
123
|
'errors_filtered_params',
|
124
|
+
'errors_env_capture',
|
116
125
|
'errors_host',
|
117
126
|
]
|
118
127
|
|
@@ -232,6 +241,7 @@ module ScoutApm
|
|
232
241
|
'errors_enabled' => BooleanCoercion.new,
|
233
242
|
'errors_ignored_exceptions' => JsonCoercion.new,
|
234
243
|
'errors_filtered_params' => JsonCoercion.new,
|
244
|
+
'errors_env_capture' => JsonCoercion.new,
|
235
245
|
}
|
236
246
|
|
237
247
|
|
@@ -290,6 +300,12 @@ module ScoutApm
|
|
290
300
|
coercion.coerce(raw_value)
|
291
301
|
end
|
292
302
|
|
303
|
+
# https://github.com/rails/rails/blob/aca037411eab504a48c41472e696547da1543a19/activesupport/lib/active_support/core_ext/object/blank.rb#L25
|
304
|
+
def value_present?(key)
|
305
|
+
val = value(key)
|
306
|
+
val.respond_to?(:empty?) ? !val.empty? : false
|
307
|
+
end
|
308
|
+
|
293
309
|
# Did we load anything for configuration?
|
294
310
|
def any_keys_found?
|
295
311
|
@overlays.any? { |overlay| overlay.any_keys_found? }
|
@@ -359,6 +375,7 @@ module ScoutApm
|
|
359
375
|
'errors_enabled' => false,
|
360
376
|
'errors_ignored_exceptions' => %w(ActiveRecord::RecordNotFound ActionController::RoutingError),
|
361
377
|
'errors_filtered_params' => %w(password s3-key),
|
378
|
+
'errors_env_capture' => %w(),
|
362
379
|
'errors_host' => 'https://errors.scoutapm.com',
|
363
380
|
}.freeze
|
364
381
|
|
data/lib/scout_apm/error.rb
CHANGED
@@ -9,11 +9,11 @@ module ScoutApm
|
|
9
9
|
class Custom < ScoutDefined; end
|
10
10
|
|
11
11
|
class << self
|
12
|
-
def capture(exception,
|
13
|
-
|
12
|
+
def capture(exception, context={}, env: {}, name: "ScoutApm::Error::Custom")
|
13
|
+
agent_context = ScoutApm::Agent.instance.context
|
14
14
|
|
15
15
|
# Skip if error monitoring isn't enabled at all
|
16
|
-
if !
|
16
|
+
if ! agent_context.config.value("errors_enabled")
|
17
17
|
return false
|
18
18
|
end
|
19
19
|
|
@@ -21,12 +21,23 @@ module ScoutApm
|
|
21
21
|
return false unless exception
|
22
22
|
|
23
23
|
# Skip if this one error is ignored
|
24
|
-
if
|
24
|
+
if agent_context.ignored_exceptions.ignored?(exception)
|
25
25
|
return false
|
26
26
|
end
|
27
27
|
|
28
|
+
unless env.is_a?(Hash)
|
29
|
+
log_warning("Expected env to be a Hash, got #{env.class}")
|
30
|
+
env = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
unless context.is_a?(Hash)
|
34
|
+
log_warning("Expected context to be a Hash, got #{context.class}")
|
35
|
+
context = {}
|
36
|
+
end
|
37
|
+
ScoutApm::Context.add(context)
|
38
|
+
|
28
39
|
# Capture the error for further processing and shipping
|
29
|
-
|
40
|
+
agent_context.error_buffer.capture(exception, env)
|
30
41
|
|
31
42
|
return true
|
32
43
|
end
|
@@ -7,7 +7,6 @@ module ScoutApm
|
|
7
7
|
attr_reader :message
|
8
8
|
attr_reader :request_uri
|
9
9
|
attr_reader :request_params
|
10
|
-
attr_reader :request_session
|
11
10
|
attr_reader :environment
|
12
11
|
attr_reader :trace
|
13
12
|
attr_reader :request_components
|
@@ -28,8 +27,7 @@ module ScoutApm
|
|
28
27
|
@message = LengthLimit.new(exception.message, 100).to_s
|
29
28
|
@request_uri = LengthLimit.new(rack_request_url(env), 200).to_s
|
30
29
|
@request_params = clean_params(env["action_dispatch.request.parameters"])
|
31
|
-
@
|
32
|
-
@environment = clean_params(strip_env(env))
|
30
|
+
@environment = clean_params(env_captured(env))
|
33
31
|
@trace = clean_backtrace(exception.backtrace)
|
34
32
|
@request_components = components(env)
|
35
33
|
@agent_time = Time.now.iso8601
|
@@ -92,46 +90,20 @@ module ScoutApm
|
|
92
90
|
end
|
93
91
|
end
|
94
92
|
|
95
|
-
#
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
"rack.request.form_hash",
|
102
|
-
"rack.request.form_vars",
|
103
|
-
"async.callback",
|
104
|
-
|
105
|
-
# Security related items
|
106
|
-
"action_dispatch.secret_key_base",
|
107
|
-
"action_dispatch.http_auth_salt",
|
108
|
-
"action_dispatch.signed_cookie_salt",
|
109
|
-
"action_dispatch.encrypted_cookie_salt",
|
110
|
-
"action_dispatch.encrypted_signed_cookie_salt",
|
111
|
-
"action_dispatch.authenticated_encrypted_cookie_salt",
|
112
|
-
|
113
|
-
# Raw data from the URL & parameters. Would bypass our normal params filtering
|
114
|
-
"QUERY_STRING",
|
115
|
-
"REQUEST_URI",
|
116
|
-
"REQUEST_PATH",
|
117
|
-
"ORIGINAL_FULLPATH",
|
118
|
-
"action_dispatch.request.query_parameters",
|
119
|
-
"action_dispatch.request.parameters",
|
120
|
-
"rack.request.query_string",
|
121
|
-
"rack.request.query_hash",
|
93
|
+
# Capture params from env
|
94
|
+
KEYS_TO_KEEP = [
|
95
|
+
"HTTP_USER_AGENT",
|
96
|
+
"HTTP_REFERER",
|
97
|
+
"HTTP_ACCEPT_ENCODING",
|
98
|
+
"HTTP_ORIGIN",
|
122
99
|
]
|
123
|
-
def
|
124
|
-
env.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
if session.respond_to?(:to_hash)
|
132
|
-
session.to_hash
|
133
|
-
else
|
134
|
-
session.data
|
100
|
+
def env_captured(env)
|
101
|
+
env.select { |k, v| KEYS_TO_KEEP.include?(k) }.tap do |filtered_env|
|
102
|
+
filtered_env["HTTP_X_FORWARDED_FOR"] = env["HTTP_X_FORWARDED_FOR"] if @agent_context.config.value('collect_remote_ip') && env["HTTP_X_FORWARDED_FOR"]
|
103
|
+
|
104
|
+
@agent_context.config.value('errors_env_capture').each do |key|
|
105
|
+
filtered_env[key] = env[key] if env[key]
|
106
|
+
end
|
135
107
|
end
|
136
108
|
end
|
137
109
|
|
@@ -35,7 +35,6 @@ module ScoutApm
|
|
35
35
|
:message => error_record.message,
|
36
36
|
:request_uri => error_record.request_uri,
|
37
37
|
:request_params => error_record.request_params,
|
38
|
-
:request_session => error_record.request_session,
|
39
38
|
:environment => error_record.environment,
|
40
39
|
:trace => error_record.trace,
|
41
40
|
:request_components => error_record.request_components,
|
data/lib/scout_apm/sampling.rb
CHANGED
@@ -8,7 +8,7 @@ module ScoutApm
|
|
8
8
|
# jobs matched explicitly by name
|
9
9
|
|
10
10
|
# for now still support old config key ('ignore') for backwards compatibility
|
11
|
-
@ignore_endpoints = config.
|
11
|
+
@ignore_endpoints = config.value_present?('ignore') ? config.value('ignore') : config.value('ignore_endpoints')
|
12
12
|
@sample_endpoints = individual_sample_to_hash(config.value('sample_endpoints'))
|
13
13
|
@endpoint_sample_rate = config.value('endpoint_sample_rate')
|
14
14
|
|
data/lib/scout_apm/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -9,6 +9,34 @@ class ErrorBufferTest < Minitest::Test
|
|
9
9
|
eb.capture(ex, env)
|
10
10
|
end
|
11
11
|
|
12
|
+
def test_only_captures_relevant_environment
|
13
|
+
config = make_fake_config(
|
14
|
+
'errors_enabled' => true,
|
15
|
+
'errors_env_capture' => %w(ANOTHER_HEADER),
|
16
|
+
'collect_remote_ip' => true
|
17
|
+
)
|
18
|
+
|
19
|
+
test_context = ScoutApm::AgentContext.new().tap { |c| c.config = config }
|
20
|
+
ScoutApm::Agent.instance.stub(:context, test_context) do
|
21
|
+
eb = ScoutApm::ErrorService::ErrorBuffer.new(test_context)
|
22
|
+
eb.capture(ex, env)
|
23
|
+
exceptions = eb.instance_variable_get(:@error_records)
|
24
|
+
assert_equal 1, exceptions.length
|
25
|
+
|
26
|
+
exception = exceptions[0]
|
27
|
+
expected_env_keys = [
|
28
|
+
"ANOTHER_HEADER",
|
29
|
+
"HTTP_X_FORWARDED_FOR",
|
30
|
+
"HTTP_USER_AGENT",
|
31
|
+
"HTTP_REFERER",
|
32
|
+
"HTTP_ACCEPT_ENCODING",
|
33
|
+
"HTTP_ORIGIN",
|
34
|
+
].to_set
|
35
|
+
|
36
|
+
assert_equal expected_env_keys, exception.environment.keys.to_set
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
12
40
|
#### Helpers
|
13
41
|
|
14
42
|
def context
|
@@ -16,7 +44,24 @@ class ErrorBufferTest < Minitest::Test
|
|
16
44
|
end
|
17
45
|
|
18
46
|
def env
|
19
|
-
{
|
47
|
+
{
|
48
|
+
"REQUEST_METHOD" => "GET",
|
49
|
+
"SERVER_NAME" => "localhost",
|
50
|
+
"SERVER_PORT" => "3000",
|
51
|
+
"PATH_INFO" => "/test",
|
52
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
53
|
+
"HTTP_USER_AGENT" => "TestAgent",
|
54
|
+
"HTTP_ACCEPT" => "text/html",
|
55
|
+
"HTTP_HOST" => "localhost:3000",
|
56
|
+
"HTTP_X_FORWARDED_FOR" => "123.345.67.89",
|
57
|
+
"HTTP_X_FORWARDED_PROTO" => "http",
|
58
|
+
"rack.url_scheme" => "http",
|
59
|
+
"REMOTE_ADDR" => "123.345.67.89",
|
60
|
+
"ANOTHER_HEADER" => "value",
|
61
|
+
"HTTP_REFERER" => "http://example.com",
|
62
|
+
"HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
63
|
+
"HTTP_ORIGIN" => "http://example.com",
|
64
|
+
}
|
20
65
|
end
|
21
66
|
|
22
67
|
def ex(msg="Whoops")
|
data/test/unit/error_test.rb
CHANGED
@@ -7,6 +7,7 @@ class ErrorTest < Minitest::Test
|
|
7
7
|
def test_captures_and_stores_exceptions_and_env
|
8
8
|
config = make_fake_config(
|
9
9
|
'errors_enabled' => true,
|
10
|
+
'errors_env_capture' => %w(),
|
10
11
|
)
|
11
12
|
test_context = ScoutApm::AgentContext.new().tap{|c| c.config = config }
|
12
13
|
ScoutApm::Agent.instance.stub(:context, test_context) do
|
@@ -15,12 +16,19 @@ class ErrorTest < Minitest::Test
|
|
15
16
|
end
|
16
17
|
assert_equal true, ScoutApm::Error.capture("Oh no an error") # Will be of type ScoutApm::Error::Custom
|
17
18
|
assert_equal true, ScoutApm::Error.capture("Oh no another error") # Will be of type ScoutApm::Error::Custom
|
19
|
+
|
18
20
|
assert_equal true, ScoutApm::Error.capture("Something went boom", {"boom" => "yes"}, name: "boom_error")
|
19
|
-
|
20
|
-
|
21
|
-
assert_equal true, ScoutApm::Error.capture(
|
22
|
-
|
23
|
-
assert_equal true, ScoutApm::Error.capture(
|
21
|
+
ScoutApm::Context.current.instance_variable_set(:@extra, {}) # Need to reset as the context/request won't change.
|
22
|
+
|
23
|
+
assert_equal true, ScoutApm::Error.capture("No context or env", name: "another error")
|
24
|
+
|
25
|
+
assert_equal true, ScoutApm::Error.capture(ex, context)
|
26
|
+
ScoutApm::Context.current.instance_variable_set(:@extra, {})
|
27
|
+
|
28
|
+
assert_equal true, ScoutApm::Error.capture(exwbt, env: env)
|
29
|
+
|
30
|
+
assert_equal false, ScoutApm::Error.capture(Class, env: env)
|
31
|
+
assert_equal true, ScoutApm::Error.capture("Name collision", context, env: env, name: "ScoutApm")
|
24
32
|
|
25
33
|
begin
|
26
34
|
raise StandardError, "Whoops"
|
@@ -43,24 +51,41 @@ class ErrorTest < Minitest::Test
|
|
43
51
|
|
44
52
|
assert_equal "Something went boom", exceptions[2].message
|
45
53
|
assert_equal "BoomError", exceptions[2].exception_class
|
54
|
+
assert_equal "yes", exceptions[2].context["boom"]
|
46
55
|
|
47
|
-
assert_equal "No env", exceptions[3].message
|
56
|
+
assert_equal "No context or env", exceptions[3].message
|
48
57
|
assert_equal "AnotherError", exceptions[3].exception_class
|
58
|
+
assert_equal assert_empty_context, exceptions[3].context
|
49
59
|
|
50
60
|
assert_equal "Whoops", exceptions[4].message
|
51
61
|
assert_equal "ErrorTest::FakeError", exceptions[4].exception_class
|
62
|
+
assert_equal 123, exceptions[4].context["user_id"]
|
52
63
|
|
53
64
|
assert_equal "/path/to/file.rb:10:in `method_name'", exceptions[5].trace.first
|
54
65
|
|
55
66
|
assert_equal "ScoutApm::Error::Custom", exceptions[6].exception_class
|
67
|
+
assert_equal 123, exceptions[6].context["user_id"]
|
68
|
+
assert_equal "TestAgent", exceptions[6].environment["HTTP_USER_AGENT"]
|
56
69
|
|
57
70
|
assert_equal "StandardError", exceptions[7].exception_class
|
58
71
|
end
|
59
72
|
end
|
60
73
|
|
61
74
|
#### Helpers
|
75
|
+
def context
|
76
|
+
{
|
77
|
+
"user_id" => 123,
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
62
81
|
def env
|
63
|
-
{
|
82
|
+
{
|
83
|
+
"HTTP_USER_AGENT" => "TestAgent",
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def assert_empty_context
|
88
|
+
{user: {}}
|
64
89
|
end
|
65
90
|
|
66
91
|
def ex(msg="Whoops")
|