scout_apm 5.6.2 → 5.6.5
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/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -2
- data/lib/scout_apm/error.rb +65 -11
- data/lib/scout_apm/error_service/error_record.rb +4 -0
- data/lib/scout_apm/error_service/payload.rb +2 -0
- data/lib/scout_apm/sampling.rb +1 -1
- data/lib/scout_apm/version.rb +1 -1
- data/test/unit/background_job_integrations/sidekiq_test.rb +10 -2
- data/test/unit/error_test.rb +69 -0
- data/test/unit/sampling_test.rb +2 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a45c15600501200c54adb79600c0ff8c2c7229c6f4c8c6292804f2f10c716e2
|
4
|
+
data.tar.gz: d068883fec42f88524e0ccac0d84da6941a8c981c6eecb5b7e525affddb3ea77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92c538b493815dd07fed71cf43bf610c109b988a8aa597647e5b0d5c854120c13c9d8f96b8c04740ece120b952327ac920d41d18589422cfd23fe84ee1cded38
|
7
|
+
data.tar.gz: f69b0f43b36cc020600f91c97aafe3ed523225ac87bb9b62b2764d797e8d40380381b08032303a2d1c54d5a2f02164a8086a1b55bc7ad20d12e5ccf189db2921
|
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 5.6.5
|
4
|
+
- Improve error capture API (#555)
|
5
|
+
- Add git sha and agent time tracking to error payloads (#554)
|
6
|
+
- Add support for named spaced jobs in sampling (#549)
|
7
|
+
|
8
|
+
# 5.6.4
|
9
|
+
- Use new ActiveJob class name and timestamp units for Sidekiq 8+ (#544)
|
10
|
+
|
3
11
|
# 5.6.2
|
4
12
|
- Fix deprecation warning for Sidekiq 7.1.5+ (#535)
|
5
13
|
- Add support for Mongoid 8 and 9. Remove support for old versions. (#538)
|
@@ -69,8 +69,21 @@ module ScoutApm
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
def self.sidekiq_version_8?
|
73
|
+
if defined?(::Sidekiq::VERSION)
|
74
|
+
::Sidekiq::VERSION.to_i >= 8
|
75
|
+
else
|
76
|
+
false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
72
80
|
UNKNOWN_CLASS_PLACEHOLDER = 'UnknownJob'.freeze
|
73
|
-
|
81
|
+
# This name was changed in Sidekiq 8
|
82
|
+
ACTIVE_JOB_KLASS = if sidekiq_version_8?
|
83
|
+
'Sidekiq::ActiveJob::Wrapper'.freeze
|
84
|
+
else
|
85
|
+
'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper'.freeze
|
86
|
+
end
|
74
87
|
DELAYED_WRAPPER_KLASS = 'Sidekiq::Extensions::DelayedClass'.freeze
|
75
88
|
|
76
89
|
|
@@ -119,7 +132,14 @@ module ScoutApm
|
|
119
132
|
def latency(msg, time = Time.now.to_f)
|
120
133
|
created_at = msg['enqueued_at'] || msg['created_at']
|
121
134
|
if created_at
|
122
|
-
|
135
|
+
# Sidekiq 8+ uses milliseconds, older versions use seconds.
|
136
|
+
# Do it this way because downstream expects seconds.
|
137
|
+
if self.class.sidekiq_version_8?
|
138
|
+
# Convert milliseconds to seconds for consistency.
|
139
|
+
(time - (created_at.to_f / 1000.0))
|
140
|
+
else
|
141
|
+
(time - created_at)
|
142
|
+
end
|
123
143
|
else
|
124
144
|
0
|
125
145
|
end
|
data/lib/scout_apm/error.rb
CHANGED
@@ -5,23 +5,77 @@ module ScoutApm
|
|
5
5
|
module Error
|
6
6
|
# Capture an exception, optionally with an environment hash. This may be a
|
7
7
|
# Rack environment, but is not required.
|
8
|
-
|
9
|
-
|
8
|
+
class ScoutDefined < Exception; end
|
9
|
+
class Custom < ScoutDefined; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def capture(exception, env={}, name: "ScoutApm::Error::Custom")
|
13
|
+
context = ScoutApm::Agent.instance.context
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
# Skip if error monitoring isn't enabled at all
|
16
|
+
if ! context.config.value("errors_enabled")
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
|
20
|
+
exception = validate_or_create_exception(exception, name)
|
21
|
+
return false unless exception
|
22
|
+
|
23
|
+
# Skip if this one error is ignored
|
24
|
+
if context.ignored_exceptions.ignored?(exception)
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
# Capture the error for further processing and shipping
|
29
|
+
context.error_buffer.capture(exception, env)
|
30
|
+
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def get_caller_location
|
37
|
+
caller_locations(0, 10)
|
38
|
+
.reject { |loc| loc.absolute_path == __FILE__ }
|
39
|
+
.map(&:to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
def define_error_class(name_str)
|
43
|
+
# e.g., "some_error" → "SomeError", "some error" → "SomeError"
|
44
|
+
class_name = name_str.gsub(/(?:^|[_\s])([a-zA-Z])/) { $1.upcase }
|
45
|
+
|
46
|
+
if Object.const_defined?(class_name)
|
47
|
+
klass = Object.const_get(class_name)
|
48
|
+
return klass if klass.ancestors.include?(ScoutApm::Error::ScoutDefined)
|
49
|
+
|
50
|
+
log_warning("Error class name '#{class_name}' is already defined. Falling back to ScoutApm::Error::Custom.")
|
51
|
+
return Custom
|
52
|
+
else
|
53
|
+
Object.const_set(class_name, Class.new(ScoutDefined))
|
54
|
+
end
|
14
55
|
end
|
15
56
|
|
16
|
-
|
17
|
-
|
18
|
-
return false
|
57
|
+
def log_warning(message)
|
58
|
+
ScoutApm::Agent.instance.context.logger.warn(message)
|
19
59
|
end
|
20
60
|
|
21
|
-
|
22
|
-
|
61
|
+
def validate_or_create_exception(exception, name)
|
62
|
+
return exception if exception.is_a?(Exception) && exception.backtrace
|
23
63
|
|
24
|
-
|
64
|
+
if exception.is_a?(Exception)
|
65
|
+
exception.tap do |e|
|
66
|
+
e.set_backtrace(get_caller_location)
|
67
|
+
end
|
68
|
+
|
69
|
+
elsif exception.is_a?(String)
|
70
|
+
define_error_class(name).new(exception).tap do |e|
|
71
|
+
e.set_backtrace(get_caller_location)
|
72
|
+
end
|
73
|
+
|
74
|
+
else
|
75
|
+
log_warning("Invalid exception type: #{exception.class}. Expected Exception or String.")
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
25
79
|
end
|
26
80
|
end
|
27
81
|
end
|
@@ -12,6 +12,8 @@ module ScoutApm
|
|
12
12
|
attr_reader :trace
|
13
13
|
attr_reader :request_components
|
14
14
|
attr_reader :context
|
15
|
+
attr_reader :agent_time
|
16
|
+
attr_reader :git_sha
|
15
17
|
|
16
18
|
def initialize(agent_context, exception, env, context=nil)
|
17
19
|
@agent_context = agent_context
|
@@ -30,6 +32,8 @@ module ScoutApm
|
|
30
32
|
@environment = clean_params(strip_env(env))
|
31
33
|
@trace = clean_backtrace(exception.backtrace)
|
32
34
|
@request_components = components(env)
|
35
|
+
@agent_time = Time.now.iso8601
|
36
|
+
@git_sha = @agent_context.environment.git_revision.sha.to_s
|
33
37
|
end
|
34
38
|
|
35
39
|
# TODO: This is rails specific
|
data/lib/scout_apm/sampling.rb
CHANGED
@@ -58,7 +58,7 @@ module ScoutApm
|
|
58
58
|
# config looks like ['/foo:50','/bar:100']. parse it into hash of string: integer
|
59
59
|
sample_hash = {}
|
60
60
|
sampling_config.each do |sample|
|
61
|
-
path, rate = sample.
|
61
|
+
path, _, rate = sample.rpartition(':')
|
62
62
|
sample_hash[path] = rate.to_i
|
63
63
|
end
|
64
64
|
sample_hash
|
data/lib/scout_apm/version.rb
CHANGED
@@ -102,13 +102,21 @@ class SidekiqTest < Minitest::Test
|
|
102
102
|
########################################
|
103
103
|
def test_latency_from_created_at
|
104
104
|
# Created at time 80, but now it is 200. Latency was 120
|
105
|
-
msg =
|
105
|
+
msg = if SidekiqMiddleware.sidekiq_version_8?
|
106
|
+
{ 'created_at' => 80000 } # milliseconds for Sidekiq 8+
|
107
|
+
else
|
108
|
+
{ 'created_at' => 80 }
|
109
|
+
end
|
106
110
|
assert_equal 120, SidekiqMiddleware.new.latency(msg, 200)
|
107
111
|
end
|
108
112
|
|
109
113
|
def test_latency_from_enqueued_at
|
110
114
|
# Created at time 80, but now it is 200. Latency was 120
|
111
|
-
msg =
|
115
|
+
msg = if SidekiqMiddleware.sidekiq_version_8?
|
116
|
+
{ 'enqueued_at' => 80000 } # milliseconds for Sidekiq 8+
|
117
|
+
else
|
118
|
+
{ 'enqueued_at' => 80 }
|
119
|
+
end
|
112
120
|
assert_equal 120, SidekiqMiddleware.new.latency(msg, 200)
|
113
121
|
end
|
114
122
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ErrorTest < Minitest::Test
|
4
|
+
class FakeError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_captures_and_stores_exceptions_and_env
|
8
|
+
config = make_fake_config(
|
9
|
+
'errors_enabled' => true,
|
10
|
+
)
|
11
|
+
test_context = ScoutApm::AgentContext.new().tap{|c| c.config = config }
|
12
|
+
ScoutApm::Agent.instance.stub(:context, test_context) do
|
13
|
+
exwbt = ex.tap do |e|
|
14
|
+
e.set_backtrace(["/path/to/file.rb:10:in `method_name'"])
|
15
|
+
end
|
16
|
+
assert_equal true, ScoutApm::Error.capture("Oh no an error") # Will be of type ScoutApm::Error::Custom
|
17
|
+
assert_equal true, ScoutApm::Error.capture("Oh no another error") # Will be of type ScoutApm::Error::Custom
|
18
|
+
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")
|
24
|
+
|
25
|
+
begin
|
26
|
+
raise StandardError, "Whoops"
|
27
|
+
rescue StandardError => e
|
28
|
+
assert_equal true, ScoutApm::Error.capture(e, env)
|
29
|
+
end
|
30
|
+
|
31
|
+
exceptions = ScoutApm::Agent.instance.context.error_buffer.instance_variable_get(:@error_records)
|
32
|
+
|
33
|
+
assert_equal 8, exceptions.length
|
34
|
+
assert_equal "Oh no an error", exceptions[0].message
|
35
|
+
assert_equal "ScoutApm::Error::Custom", exceptions[0].exception_class
|
36
|
+
|
37
|
+
# Ensure we capture time and git sha
|
38
|
+
refute_nil exceptions[0].git_sha
|
39
|
+
assert exceptions[0].agent_time.is_a?(String)
|
40
|
+
|
41
|
+
assert_equal "Oh no another error", exceptions[1].message
|
42
|
+
assert_equal "ScoutApm::Error::Custom", exceptions[1].exception_class
|
43
|
+
|
44
|
+
assert_equal "Something went boom", exceptions[2].message
|
45
|
+
assert_equal "BoomError", exceptions[2].exception_class
|
46
|
+
|
47
|
+
assert_equal "No env", exceptions[3].message
|
48
|
+
assert_equal "AnotherError", exceptions[3].exception_class
|
49
|
+
|
50
|
+
assert_equal "Whoops", exceptions[4].message
|
51
|
+
assert_equal "ErrorTest::FakeError", exceptions[4].exception_class
|
52
|
+
|
53
|
+
assert_equal "/path/to/file.rb:10:in `method_name'", exceptions[5].trace.first
|
54
|
+
|
55
|
+
assert_equal "ScoutApm::Error::Custom", exceptions[6].exception_class
|
56
|
+
|
57
|
+
assert_equal "StandardError", exceptions[7].exception_class
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#### Helpers
|
62
|
+
def env
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
|
66
|
+
def ex(msg="Whoops")
|
67
|
+
FakeError.new(msg)
|
68
|
+
end
|
69
|
+
end
|
data/test/unit/sampling_test.rb
CHANGED
@@ -15,7 +15,7 @@ class SamplingTest < Minitest::Test
|
|
15
15
|
{
|
16
16
|
'sample_endpoints' => ['/foo/bar:100', '/foo:50', '/bar/zap:80'],
|
17
17
|
'ignore_endpoints' => ['/baz'],
|
18
|
-
'sample_jobs' => ['joba:50'],
|
18
|
+
'sample_jobs' => ['joba:50', 'Foo::BarJob:95'],
|
19
19
|
'ignore_jobs' => 'jobb,jobc',
|
20
20
|
}
|
21
21
|
)
|
@@ -59,6 +59,7 @@ class SamplingTest < Minitest::Test
|
|
59
59
|
def test_job_sample
|
60
60
|
sampling = ScoutApm::Sampling.new(@individual_config)
|
61
61
|
assert_equal 50, sampling.job_sample_rate('joba')
|
62
|
+
assert_equal 95, sampling.job_sample_rate('Foo::BarJob')
|
62
63
|
assert_nil sampling.job_sample_rate('jobb')
|
63
64
|
end
|
64
65
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_apm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.6.
|
4
|
+
version: 5.6.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Haynes
|
8
8
|
- Andre Lewis
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -443,6 +443,7 @@ files:
|
|
443
443
|
- test/unit/environment_test.rb
|
444
444
|
- test/unit/error_service/error_buffer_test.rb
|
445
445
|
- test/unit/error_service/ignored_exceptions_test.rb
|
446
|
+
- test/unit/error_test.rb
|
446
447
|
- test/unit/extensions/periodic_callbacks_test.rb
|
447
448
|
- test/unit/extensions/transaction_callbacks_test.rb
|
448
449
|
- test/unit/external_service_metric_set_test.rb
|
@@ -508,7 +509,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
508
509
|
- !ruby/object:Gem::Version
|
509
510
|
version: '0'
|
510
511
|
requirements: []
|
511
|
-
rubygems_version: 3.6.
|
512
|
+
rubygems_version: 3.6.9
|
512
513
|
specification_version: 4
|
513
514
|
summary: Ruby application performance monitoring
|
514
515
|
test_files: []
|