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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a45c15600501200c54adb79600c0ff8c2c7229c6f4c8c6292804f2f10c716e2
4
- data.tar.gz: d068883fec42f88524e0ccac0d84da6941a8c981c6eecb5b7e525affddb3ea77
3
+ metadata.gz: 2e96ebe1b7d9a1fb4bd1427ba0564e645cb9bed376da56bc92f9d86f0515adf7
4
+ data.tar.gz: d1c4692af9e37bb6107f14064ce89f94626d987bf55d845dea19b522fa910ce1
5
5
  SHA512:
6
- metadata.gz: 92c538b493815dd07fed71cf43bf610c109b988a8aa597647e5b0d5c854120c13c9d8f96b8c04740ece120b952327ac920d41d18589422cfd23fe84ee1cded38
7
- data.tar.gz: f69b0f43b36cc020600f91c97aafe3ed523225ac87bb9b62b2764d797e8d40380381b08032303a2d1c54d5a2f02164a8086a1b55bc7ad20d12e5ccf189db2921
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
- #if defined(RUBY_INTERNAL_EVENT_NEWOBJ) && !defined(_WIN32)
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
- void Init_allocations()
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
- rb_define_singleton_method(cAllocations, "count", get_allocation_count, 0);
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
-
@@ -1,4 +1,3 @@
1
1
  require 'mkmf'
2
2
 
3
- have_header("ruby/ruby.h") # Needed to check for Ruby <= 1.8.7
4
- create_makefile('allocations')
3
+ create_makefile('allocations')
@@ -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
 
@@ -9,11 +9,11 @@ module ScoutApm
9
9
  class Custom < ScoutDefined; end
10
10
 
11
11
  class << self
12
- def capture(exception, env={}, name: "ScoutApm::Error::Custom")
13
- context = ScoutApm::Agent.instance.context
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 ! context.config.value("errors_enabled")
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 context.ignored_exceptions.ignored?(exception)
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
- context.error_buffer.capture(exception, env)
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
- @request_session = clean_params(session_data(env))
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
- # Deletes params from env
96
- #
97
- # These are not configurable, and will leak PII info up to Scout if
98
- # allowed through. Things like specific parameters can be exposed with
99
- # the ScoutApm::Context interface.
100
- KEYS_TO_REMOVE = [
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 strip_env(env)
124
- env.reject { |k, v| KEYS_TO_REMOVE.include?(k) }
125
- end
126
-
127
- def session_data(env)
128
- session = env["action_dispatch.request.session"]
129
- return if session.nil?
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,
@@ -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.value('ignore').present? ? config.value('ignore') : config.value('ignore_endpoints')
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
 
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "5.6.5"
2
+ VERSION = "5.7.1"
3
3
  end
data/test/test_helper.rb CHANGED
@@ -37,6 +37,11 @@ class FakeConfigOverlay
37
37
  def value(key)
38
38
  @values[key]
39
39
  end
40
+
41
+ def value_present?(key)
42
+ val = value(key)
43
+ val.respond_to?(:empty?) ? !val.empty? : false
44
+ end
40
45
 
41
46
  def values
42
47
  @values
@@ -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")
@@ -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
- assert_equal true, ScoutApm::Error.capture("No env", name: "another error")
20
- assert_equal true, ScoutApm::Error.capture(ex, env)
21
- assert_equal true, ScoutApm::Error.capture(exwbt, env)
22
- assert_equal false, ScoutApm::Error.capture(Class, env)
23
- assert_equal true, ScoutApm::Error.capture("Name collision, but", env, name: "ScoutApm")
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")
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.6.5
4
+ version: 5.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes