appsignal 2.11.8-java → 3.0.1-java
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/.rubocop.yml +4 -1
- data/.rubocop_todo.yml +1 -1
- data/.semaphore/semaphore.yml +88 -111
- data/CHANGELOG.md +24 -0
- data/appsignal.gemspec +1 -1
- data/build_matrix.yml +11 -15
- data/lib/appsignal.rb +2 -29
- data/lib/appsignal/auth_check.rb +2 -8
- data/lib/appsignal/cli.rb +1 -23
- data/lib/appsignal/config.rb +1 -25
- data/lib/appsignal/event_formatter.rb +0 -25
- data/lib/appsignal/helpers/instrumentation.rb +69 -5
- data/lib/appsignal/hooks.rb +6 -13
- data/lib/appsignal/hooks/action_cable.rb +3 -34
- data/lib/appsignal/hooks/active_support_notifications.rb +7 -86
- data/lib/appsignal/hooks/celluloid.rb +5 -9
- data/lib/appsignal/hooks/net_http.rb +2 -12
- data/lib/appsignal/hooks/puma.rb +3 -5
- data/lib/appsignal/hooks/que.rb +1 -1
- data/lib/appsignal/hooks/rake.rb +2 -24
- data/lib/appsignal/hooks/redis.rb +2 -13
- data/lib/appsignal/hooks/resque.rb +2 -43
- data/lib/appsignal/hooks/sidekiq.rb +6 -143
- data/lib/appsignal/hooks/unicorn.rb +3 -24
- data/lib/appsignal/hooks/webmachine.rb +1 -7
- data/lib/appsignal/integrations/action_cable.rb +34 -0
- data/lib/appsignal/integrations/active_support_notifications.rb +77 -0
- data/lib/appsignal/integrations/net_http.rb +16 -0
- data/lib/appsignal/integrations/object.rb +39 -4
- data/lib/appsignal/integrations/padrino.rb +5 -7
- data/lib/appsignal/integrations/que.rb +26 -33
- data/lib/appsignal/integrations/railtie.rb +1 -4
- data/lib/appsignal/integrations/rake.rb +26 -2
- data/lib/appsignal/integrations/redis.rb +17 -0
- data/lib/appsignal/integrations/resque.rb +39 -10
- data/lib/appsignal/integrations/sidekiq.rb +171 -0
- data/lib/appsignal/integrations/unicorn.rb +28 -0
- data/lib/appsignal/integrations/webmachine.rb +22 -24
- data/lib/appsignal/minutely.rb +0 -12
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/auth_check_spec.rb +1 -24
- data/spec/lib/appsignal/cli_spec.rb +1 -1
- data/spec/lib/appsignal/config_spec.rb +2 -66
- data/spec/lib/appsignal/event_formatter_spec.rb +0 -37
- data/spec/lib/appsignal/hooks/celluloid_spec.rb +6 -1
- data/spec/lib/appsignal/hooks/rake_spec.rb +1 -2
- data/spec/lib/appsignal/hooks/redis_spec.rb +50 -15
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +12 -464
- data/spec/lib/appsignal/hooks/unicorn_spec.rb +14 -3
- data/spec/lib/appsignal/hooks/webmachine_spec.rb +2 -13
- data/spec/lib/appsignal/hooks_spec.rb +6 -22
- data/spec/lib/appsignal/integrations/object_spec.rb +91 -8
- data/spec/lib/appsignal/integrations/padrino_spec.rb +2 -3
- data/spec/lib/appsignal/integrations/railtie_spec.rb +0 -45
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +524 -0
- data/spec/lib/appsignal/integrations/webmachine_spec.rb +26 -8
- data/spec/lib/appsignal/minutely_spec.rb +0 -19
- data/spec/lib/appsignal/transaction_spec.rb +1 -14
- data/spec/lib/appsignal/transmitter_spec.rb +1 -1
- data/spec/lib/appsignal_spec.rb +162 -116
- data/spec/spec_helper.rb +1 -15
- metadata +11 -21
- data/lib/appsignal/cli/notify_of_deploy.rb +0 -131
- data/lib/appsignal/integrations/object_ruby_19.rb +0 -37
- data/lib/appsignal/integrations/object_ruby_modern.rb +0 -64
- data/lib/appsignal/integrations/resque_active_job.rb +0 -19
- data/lib/appsignal/js_exception_transaction.rb +0 -56
- data/lib/appsignal/rack/js_exception_catcher.rb +0 -80
- data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +0 -180
- data/spec/lib/appsignal/integrations/object_19_spec.rb +0 -266
- data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +0 -28
- data/spec/lib/appsignal/integrations/resque_spec.rb +0 -28
- data/spec/lib/appsignal/js_exception_transaction_spec.rb +0 -128
- data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +0 -170
@@ -3,12 +3,22 @@ describe Appsignal::Hooks::UnicornHook do
|
|
3
3
|
before :context do
|
4
4
|
module Unicorn
|
5
5
|
class HttpServer
|
6
|
-
def worker_loop(
|
6
|
+
def worker_loop(_worker)
|
7
|
+
@worker_loop = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def worker_loop?
|
11
|
+
@worker_loop == true
|
7
12
|
end
|
8
13
|
end
|
9
14
|
|
10
15
|
class Worker
|
11
16
|
def close
|
17
|
+
@close = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def close?
|
21
|
+
@close == true
|
12
22
|
end
|
13
23
|
end
|
14
24
|
end
|
@@ -27,18 +37,19 @@ describe Appsignal::Hooks::UnicornHook do
|
|
27
37
|
worker = double
|
28
38
|
|
29
39
|
expect(Appsignal).to receive(:forked)
|
30
|
-
expect(server).to receive(:worker_loop_without_appsignal).with(worker)
|
31
40
|
|
32
41
|
server.worker_loop(worker)
|
42
|
+
|
43
|
+
expect(server.worker_loop?).to be true
|
33
44
|
end
|
34
45
|
|
35
46
|
it "adds behavior to Unicorn::Worker#close" do
|
36
47
|
worker = Unicorn::Worker.new
|
37
48
|
|
38
49
|
expect(Appsignal).to receive(:stop)
|
39
|
-
expect(worker).to receive(:close_without_appsignal)
|
40
50
|
|
41
51
|
worker.close
|
52
|
+
expect(worker.close?).to be true
|
42
53
|
end
|
43
54
|
end
|
44
55
|
|
@@ -10,19 +10,8 @@ describe Appsignal::Hooks::WebmachineHook do
|
|
10
10
|
it { is_expected.to be_truthy }
|
11
11
|
end
|
12
12
|
|
13
|
-
it "
|
14
|
-
expect(fsm).to
|
15
|
-
expect(fsm).to respond_to(:run_without_appsignal)
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should include the handle_exceptions alias methods" do
|
19
|
-
expect(
|
20
|
-
fsm.respond_to?(:handle_exceptions_with_appsignal, true)
|
21
|
-
).to be_truthy
|
22
|
-
|
23
|
-
expect(
|
24
|
-
fsm.respond_to?(:handle_exceptions_without_appsignal, true)
|
25
|
-
).to be_truthy
|
13
|
+
it "adds behavior to Webmachine::Decision::FSM" do
|
14
|
+
expect(fsm.class.ancestors.first).to eq(Appsignal::Integrations::WebmachineIntegration)
|
26
15
|
end
|
27
16
|
end
|
28
17
|
else
|
@@ -92,32 +92,16 @@ describe Appsignal::Hooks do
|
|
92
92
|
capture_std_streams(std_stream, err_stream, &block)
|
93
93
|
end
|
94
94
|
|
95
|
-
describe "
|
95
|
+
describe "SidekiqPlugin" do
|
96
96
|
it "logs a deprecation message and returns the new constant" do
|
97
|
-
constant = call_constant { Appsignal::Hooks::
|
97
|
+
constant = call_constant { Appsignal::Hooks::SidekiqPlugin }
|
98
98
|
|
99
|
-
expect(constant).to eql(Appsignal::
|
100
|
-
expect(constant.name).to eql("Appsignal::
|
99
|
+
expect(constant).to eql(Appsignal::Integrations::SidekiqMiddleware)
|
100
|
+
expect(constant.name).to eql("Appsignal::Integrations::SidekiqMiddleware")
|
101
101
|
|
102
102
|
deprecation_message =
|
103
|
-
"The constant Appsignal::Hooks::
|
104
|
-
"Please update the constant name to Appsignal::
|
105
|
-
"in the following file to remove this message.\n#{__FILE__}:"
|
106
|
-
expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
|
107
|
-
expect(log).to contains_log :warn, deprecation_message
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
describe "PumaProbe" do
|
112
|
-
it "logs a deprecation message and returns the new constant" do
|
113
|
-
constant = call_constant { Appsignal::Hooks::PumaProbe }
|
114
|
-
|
115
|
-
expect(constant).to eql(Appsignal::Probes::PumaProbe)
|
116
|
-
expect(constant.name).to eql("Appsignal::Probes::PumaProbe")
|
117
|
-
|
118
|
-
deprecation_message =
|
119
|
-
"The constant Appsignal::Hooks::PumaProbe has been deprecated. " \
|
120
|
-
"Please update the constant name to Appsignal::Probes::PumaProbe " \
|
103
|
+
"The constant Appsignal::Hooks::SidekiqPlugin has been deprecated. " \
|
104
|
+
"Please update the constant name to Appsignal::Integrations::SidekiqMiddleware " \
|
121
105
|
"in the following file to remove this message.\n#{__FILE__}:"
|
122
106
|
expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
|
123
107
|
expect(log).to contains_log :warn, deprecation_message
|
@@ -1,9 +1,5 @@
|
|
1
1
|
require "appsignal/integrations/object"
|
2
2
|
|
3
|
-
def is_ruby_19
|
4
|
-
RUBY_VERSION < "2.0"
|
5
|
-
end
|
6
|
-
|
7
3
|
describe Object do
|
8
4
|
describe "#instrument_method" do
|
9
5
|
context "with instance method" do
|
@@ -30,12 +26,57 @@ describe Object do
|
|
30
26
|
before do
|
31
27
|
Appsignal.config = project_fixture_config
|
32
28
|
expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
|
29
|
+
expect(Appsignal.active?).to be_truthy
|
33
30
|
end
|
34
31
|
after { Appsignal.config = nil }
|
35
32
|
|
33
|
+
context "with different kind of arguments" do
|
34
|
+
let(:klass) do
|
35
|
+
Class.new do
|
36
|
+
def positional_arguments(param1, param2)
|
37
|
+
[param1, param2]
|
38
|
+
end
|
39
|
+
appsignal_instrument_method :positional_arguments
|
40
|
+
|
41
|
+
def positional_arguments_splat(*params)
|
42
|
+
params
|
43
|
+
end
|
44
|
+
appsignal_instrument_method :positional_arguments_splat
|
45
|
+
|
46
|
+
def keyword_arguments(a: nil, b: nil)
|
47
|
+
[a, b]
|
48
|
+
end
|
49
|
+
appsignal_instrument_method :keyword_arguments
|
50
|
+
|
51
|
+
def keyword_arguments_splat(**kwargs)
|
52
|
+
kwargs
|
53
|
+
end
|
54
|
+
appsignal_instrument_method :keyword_arguments_splat
|
55
|
+
|
56
|
+
def splat(*args, **kwargs)
|
57
|
+
[args, kwargs]
|
58
|
+
end
|
59
|
+
appsignal_instrument_method :splat
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "instruments the method and calls it" do
|
64
|
+
expect(instance.positional_arguments("abc", "def")).to eq(["abc", "def"])
|
65
|
+
expect(instance.positional_arguments_splat("abc", "def")).to eq(["abc", "def"])
|
66
|
+
expect(instance.keyword_arguments(:a => "a", :b => "b")).to eq(["a", "b"])
|
67
|
+
expect(instance.keyword_arguments_splat(:a => "a", :b => "b"))
|
68
|
+
.to eq(:a => "a", :b => "b")
|
69
|
+
|
70
|
+
expect(instance.splat).to eq([[], {}])
|
71
|
+
expect(instance.splat(:a => "a", :b => "b")).to eq([[], { :a => "a", :b => "b" }])
|
72
|
+
expect(instance.splat("abc", "def")).to eq([["abc", "def"], {}])
|
73
|
+
expect(instance.splat("abc", "def", :a => "a", :b => "b"))
|
74
|
+
.to eq([["abc", "def"], { :a => "a", :b => "b" }])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
36
78
|
context "with anonymous class" do
|
37
79
|
it "instruments the method and calls it" do
|
38
|
-
expect(Appsignal.active?).to be_truthy
|
39
80
|
expect(transaction).to receive(:start_event)
|
40
81
|
expect(transaction).to receive(:finish_event).with \
|
41
82
|
"foo.AnonymousClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
|
@@ -56,7 +97,6 @@ describe Object do
|
|
56
97
|
let(:klass) { NamedClass }
|
57
98
|
|
58
99
|
it "instruments the method and calls it" do
|
59
|
-
expect(Appsignal.active?).to be_truthy
|
60
100
|
expect(transaction).to receive(:start_event)
|
61
101
|
expect(transaction).to receive(:finish_event).with \
|
62
102
|
"foo.NamedClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
|
@@ -81,7 +121,6 @@ describe Object do
|
|
81
121
|
let(:klass) { MyModule::NestedModule::NamedClass }
|
82
122
|
|
83
123
|
it "instruments the method and calls it" do
|
84
|
-
expect(Appsignal.active?).to be_truthy
|
85
124
|
expect(transaction).to receive(:start_event)
|
86
125
|
expect(transaction).to receive(:finish_event).with \
|
87
126
|
"bar.NamedClass.NestedModule.MyModule.other", nil, nil,
|
@@ -101,7 +140,6 @@ describe Object do
|
|
101
140
|
end
|
102
141
|
|
103
142
|
it "instruments with custom name" do
|
104
|
-
expect(Appsignal.active?).to be_truthy
|
105
143
|
expect(transaction).to receive(:start_event)
|
106
144
|
expect(transaction).to receive(:finish_event).with \
|
107
145
|
"my_method.group", nil, nil, Appsignal::EventFormatter::DEFAULT
|
@@ -162,6 +200,51 @@ describe Object do
|
|
162
200
|
end
|
163
201
|
after { Appsignal.config = nil }
|
164
202
|
|
203
|
+
context "with different kind of arguments" do
|
204
|
+
let(:klass) do
|
205
|
+
Class.new do
|
206
|
+
def self.positional_arguments(param1, param2)
|
207
|
+
[param1, param2]
|
208
|
+
end
|
209
|
+
appsignal_instrument_class_method :positional_arguments
|
210
|
+
|
211
|
+
def self.positional_arguments_splat(*params)
|
212
|
+
params
|
213
|
+
end
|
214
|
+
appsignal_instrument_class_method :positional_arguments_splat
|
215
|
+
|
216
|
+
def self.keyword_arguments(a: nil, b: nil)
|
217
|
+
[a, b]
|
218
|
+
end
|
219
|
+
appsignal_instrument_class_method :keyword_arguments
|
220
|
+
|
221
|
+
def self.keyword_arguments_splat(**kwargs)
|
222
|
+
kwargs
|
223
|
+
end
|
224
|
+
appsignal_instrument_class_method :keyword_arguments_splat
|
225
|
+
|
226
|
+
def self.splat(*args, **kwargs)
|
227
|
+
[args, kwargs]
|
228
|
+
end
|
229
|
+
appsignal_instrument_class_method :splat
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it "instruments the method and calls it" do
|
234
|
+
expect(klass.positional_arguments("abc", "def")).to eq(["abc", "def"])
|
235
|
+
expect(klass.positional_arguments_splat("abc", "def")).to eq(["abc", "def"])
|
236
|
+
expect(klass.keyword_arguments(:a => "a", :b => "b")).to eq(["a", "b"])
|
237
|
+
expect(klass.keyword_arguments_splat(:a => "a", :b => "b"))
|
238
|
+
.to eq(:a => "a", :b => "b")
|
239
|
+
|
240
|
+
expect(klass.splat).to eq([[], {}])
|
241
|
+
expect(klass.splat(:a => "a", :b => "b")).to eq([[], { :a => "a", :b => "b" }])
|
242
|
+
expect(klass.splat("abc", "def")).to eq([["abc", "def"], {}])
|
243
|
+
expect(klass.splat("abc", "def", :a => "a", :b => "b"))
|
244
|
+
.to eq([["abc", "def"], { :a => "a", :b => "b" }])
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
165
248
|
context "with anonymous class" do
|
166
249
|
it "instruments the method and calls it" do
|
167
250
|
expect(Appsignal.active?).to be_truthy
|
@@ -150,10 +150,9 @@ if DependencyHelper.padrino_present?
|
|
150
150
|
let(:path) { "/static" }
|
151
151
|
before do
|
152
152
|
env["sinatra.static_file"] = true
|
153
|
-
|
154
|
-
.to receive(:route_without_appsignal).and_return([200, {}, ["foo"]])
|
153
|
+
app.controllers { get(:static) { "Static!" } }
|
155
154
|
end
|
156
|
-
after { expect(response).to match_response(200, "
|
155
|
+
after { expect(response).to match_response(200, "Static!") }
|
157
156
|
|
158
157
|
it "does not instrument the request" do
|
159
158
|
expect_no_transaction_to_be_created
|
@@ -79,51 +79,6 @@ if DependencyHelper.rails_present?
|
|
79
79
|
|
80
80
|
after { Appsignal::Integrations::Railtie.initialize_appsignal(app) }
|
81
81
|
end
|
82
|
-
|
83
|
-
describe "frontend_error_catching middleware" do
|
84
|
-
let(:config) do
|
85
|
-
Appsignal::Config.new(
|
86
|
-
project_fixture_path,
|
87
|
-
"test",
|
88
|
-
:name => "MyApp",
|
89
|
-
:enable_frontend_error_catching => enable_frontend_error_catching
|
90
|
-
)
|
91
|
-
end
|
92
|
-
before { allow(Appsignal::Config).to receive(:new).and_return(config) }
|
93
|
-
after { Appsignal::Integrations::Railtie.initialize_appsignal(app) }
|
94
|
-
|
95
|
-
context "when enabled" do
|
96
|
-
let(:enable_frontend_error_catching) { true }
|
97
|
-
|
98
|
-
it "adds the Rails and JSExceptionCatcher middleware" do
|
99
|
-
expect(app.middleware).to receive(:insert_after).with(
|
100
|
-
ActionDispatch::DebugExceptions,
|
101
|
-
Appsignal::Rack::RailsInstrumentation
|
102
|
-
)
|
103
|
-
|
104
|
-
expect(app.middleware).to receive(:insert_before).with(
|
105
|
-
Appsignal::Rack::RailsInstrumentation,
|
106
|
-
Appsignal::Rack::JSExceptionCatcher
|
107
|
-
)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
context "when not enabled" do
|
112
|
-
let(:enable_frontend_error_catching) { false }
|
113
|
-
|
114
|
-
it "adds the Rails middleware, but not the JSExceptionCatcher middleware" do
|
115
|
-
expect(app.middleware).to receive(:insert_after).with(
|
116
|
-
ActionDispatch::DebugExceptions,
|
117
|
-
Appsignal::Rack::RailsInstrumentation
|
118
|
-
)
|
119
|
-
|
120
|
-
expect(app.middleware).to_not receive(:insert_before).with(
|
121
|
-
Appsignal::Rack::RailsInstrumentation,
|
122
|
-
Appsignal::Rack::JSExceptionCatcher
|
123
|
-
)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
82
|
end
|
128
83
|
end
|
129
84
|
end
|
@@ -0,0 +1,524 @@
|
|
1
|
+
require "appsignal/integrations/sidekiq"
|
2
|
+
|
3
|
+
describe Appsignal::Integrations::SidekiqErrorHandler do
|
4
|
+
let(:log) { StringIO.new }
|
5
|
+
before do
|
6
|
+
start_agent
|
7
|
+
Appsignal.logger = test_logger(log)
|
8
|
+
end
|
9
|
+
around { |example| keep_transactions { example.run } }
|
10
|
+
|
11
|
+
context "without a current transction" do
|
12
|
+
let(:exception) do
|
13
|
+
begin
|
14
|
+
raise ExampleStandardError, "uh oh"
|
15
|
+
rescue => error
|
16
|
+
error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
let(:job_context) do
|
20
|
+
{
|
21
|
+
:context => "Sidekiq internal error!",
|
22
|
+
:jobstr => "{ bad json }"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "tracks error on a new transaction" do
|
27
|
+
described_class.new.call(exception, job_context)
|
28
|
+
|
29
|
+
transaction_hash = last_transaction.to_h
|
30
|
+
expect(transaction_hash["error"]).to include(
|
31
|
+
"name" => "ExampleStandardError",
|
32
|
+
"message" => "uh oh",
|
33
|
+
"backtrace" => kind_of(String)
|
34
|
+
)
|
35
|
+
expect(transaction_hash["sample_data"]).to include(
|
36
|
+
"params" => {
|
37
|
+
"jobstr" => "{ bad json }"
|
38
|
+
}
|
39
|
+
)
|
40
|
+
expect(transaction_hash["metadata"]).to include(
|
41
|
+
"sidekiq_error" => "Sidekiq internal error!"
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => false do
|
48
|
+
class DelayedTestClass; end
|
49
|
+
|
50
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
51
|
+
let(:worker) { anything }
|
52
|
+
let(:queue) { anything }
|
53
|
+
let(:given_args) do
|
54
|
+
[
|
55
|
+
"foo",
|
56
|
+
{
|
57
|
+
:foo => "Foo",
|
58
|
+
:bar => "Bar",
|
59
|
+
"baz" => { 1 => :foo }
|
60
|
+
}
|
61
|
+
]
|
62
|
+
end
|
63
|
+
let(:expected_args) do
|
64
|
+
[
|
65
|
+
"foo",
|
66
|
+
{
|
67
|
+
"foo" => "Foo",
|
68
|
+
"bar" => "Bar",
|
69
|
+
"baz" => { "1" => "foo" }
|
70
|
+
}
|
71
|
+
]
|
72
|
+
end
|
73
|
+
let(:job_class) { "TestClass" }
|
74
|
+
let(:jid) { "b4a577edbccf1d805744efa9" }
|
75
|
+
let(:item) do
|
76
|
+
{
|
77
|
+
"jid" => jid,
|
78
|
+
"class" => job_class,
|
79
|
+
"retry_count" => 0,
|
80
|
+
"queue" => "default",
|
81
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
82
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
83
|
+
"args" => given_args,
|
84
|
+
"extra" => "data"
|
85
|
+
}
|
86
|
+
end
|
87
|
+
let(:plugin) { Appsignal::Integrations::SidekiqMiddleware.new }
|
88
|
+
let(:log) { StringIO.new }
|
89
|
+
before do
|
90
|
+
start_agent
|
91
|
+
Appsignal.logger = test_logger(log)
|
92
|
+
end
|
93
|
+
around { |example| keep_transactions { example.run } }
|
94
|
+
after :with_yaml_parse_error => false do
|
95
|
+
expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "internal Sidekiq job values" do
|
99
|
+
it "does not save internal Sidekiq values as metadata on transaction" do
|
100
|
+
perform_job
|
101
|
+
|
102
|
+
transaction_hash = transaction.to_h
|
103
|
+
expect(transaction_hash["metadata"].keys)
|
104
|
+
.to_not include(*Appsignal::Integrations::SidekiqMiddleware::EXCLUDED_JOB_KEYS)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "with parameter filtering" do
|
109
|
+
before do
|
110
|
+
Appsignal.config = project_fixture_config("production")
|
111
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "filters selected arguments" do
|
115
|
+
perform_job
|
116
|
+
|
117
|
+
transaction_hash = transaction.to_h
|
118
|
+
expect(transaction_hash["sample_data"]).to include(
|
119
|
+
"params" => [
|
120
|
+
"foo",
|
121
|
+
{
|
122
|
+
"foo" => "[FILTERED]",
|
123
|
+
"bar" => "Bar",
|
124
|
+
"baz" => { "1" => "foo" }
|
125
|
+
}
|
126
|
+
]
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "with encrypted arguments" do
|
132
|
+
before do
|
133
|
+
item["encrypt"] = true
|
134
|
+
item["args"] << "super secret value" # Last argument will be replaced
|
135
|
+
end
|
136
|
+
|
137
|
+
it "replaces the last argument (the secret bag) with an [encrypted data] string" do
|
138
|
+
perform_job
|
139
|
+
|
140
|
+
transaction_hash = transaction.to_h
|
141
|
+
expect(transaction_hash["sample_data"]).to include(
|
142
|
+
"params" => expected_args << "[encrypted data]"
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "when using the Sidekiq delayed extension" do
|
148
|
+
let(:item) do
|
149
|
+
{
|
150
|
+
"jid" => jid,
|
151
|
+
"class" => "Sidekiq::Extensions::DelayedClass",
|
152
|
+
"queue" => "default",
|
153
|
+
"args" => [
|
154
|
+
"---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
|
155
|
+
],
|
156
|
+
"retry" => true,
|
157
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
158
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
159
|
+
"extra" => "data"
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
it "uses the delayed class and method name for the action" do
|
164
|
+
perform_job
|
165
|
+
|
166
|
+
transaction_hash = transaction.to_h
|
167
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
|
168
|
+
expect(transaction_hash["sample_data"]).to include(
|
169
|
+
"params" => ["bar" => "baz"]
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
174
|
+
before { item["args"] = [] }
|
175
|
+
|
176
|
+
it "logs a warning and uses the default argument" do
|
177
|
+
perform_job
|
178
|
+
|
179
|
+
transaction_hash = transaction.to_h
|
180
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
|
181
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
182
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "when using the Sidekiq ActiveRecord instance delayed extension" do
|
188
|
+
let(:item) do
|
189
|
+
{
|
190
|
+
"jid" => jid,
|
191
|
+
"class" => "Sidekiq::Extensions::DelayedModel",
|
192
|
+
"queue" => "default",
|
193
|
+
"args" => [
|
194
|
+
"---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
|
195
|
+
],
|
196
|
+
"retry" => true,
|
197
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
198
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
199
|
+
"extra" => "data"
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
it "uses the delayed class and method name for the action" do
|
204
|
+
perform_job
|
205
|
+
|
206
|
+
transaction_hash = transaction.to_h
|
207
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
|
208
|
+
expect(transaction_hash["sample_data"]).to include(
|
209
|
+
"params" => ["bar" => "baz"]
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
214
|
+
before { item["args"] = [] }
|
215
|
+
|
216
|
+
it "logs a warning and uses the default argument" do
|
217
|
+
perform_job
|
218
|
+
|
219
|
+
transaction_hash = transaction.to_h
|
220
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
|
221
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
222
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "with an error" do
|
228
|
+
let(:error) { ExampleException }
|
229
|
+
|
230
|
+
it "creates a transaction and adds the error" do
|
231
|
+
expect(Appsignal).to receive(:increment_counter)
|
232
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :failed)
|
233
|
+
expect(Appsignal).to receive(:increment_counter)
|
234
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
235
|
+
|
236
|
+
expect do
|
237
|
+
perform_job { raise error, "uh oh" }
|
238
|
+
end.to raise_error(error)
|
239
|
+
|
240
|
+
transaction_hash = transaction.to_h
|
241
|
+
expect(transaction_hash).to include(
|
242
|
+
"id" => jid,
|
243
|
+
"action" => "TestClass#perform",
|
244
|
+
"error" => {
|
245
|
+
"name" => "ExampleException",
|
246
|
+
"message" => "uh oh",
|
247
|
+
# TODO: backtrace should be an Array of Strings
|
248
|
+
# https://github.com/appsignal/appsignal-agent/issues/294
|
249
|
+
"backtrace" => kind_of(String)
|
250
|
+
},
|
251
|
+
"metadata" => {
|
252
|
+
"extra" => "data",
|
253
|
+
"queue" => "default",
|
254
|
+
"retry_count" => "0"
|
255
|
+
},
|
256
|
+
"namespace" => namespace,
|
257
|
+
"sample_data" => {
|
258
|
+
"environment" => {},
|
259
|
+
"params" => expected_args,
|
260
|
+
"tags" => {},
|
261
|
+
"breadcrumbs" => []
|
262
|
+
}
|
263
|
+
)
|
264
|
+
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
context "without an error" do
|
269
|
+
it "creates a transaction with events" do
|
270
|
+
expect(Appsignal).to receive(:increment_counter)
|
271
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
272
|
+
|
273
|
+
perform_job
|
274
|
+
|
275
|
+
transaction_hash = transaction.to_h
|
276
|
+
expect(transaction_hash).to include(
|
277
|
+
"id" => jid,
|
278
|
+
"action" => "TestClass#perform",
|
279
|
+
"error" => nil,
|
280
|
+
"metadata" => {
|
281
|
+
"extra" => "data",
|
282
|
+
"queue" => "default",
|
283
|
+
"retry_count" => "0"
|
284
|
+
},
|
285
|
+
"namespace" => namespace,
|
286
|
+
"sample_data" => {
|
287
|
+
"environment" => {},
|
288
|
+
"params" => expected_args,
|
289
|
+
"tags" => {},
|
290
|
+
"breadcrumbs" => []
|
291
|
+
}
|
292
|
+
)
|
293
|
+
# TODO: Not available in transaction.to_h yet.
|
294
|
+
# https://github.com/appsignal/appsignal-agent/issues/293
|
295
|
+
expect(transaction.request.env).to eq(
|
296
|
+
:queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
|
297
|
+
)
|
298
|
+
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def perform_job
|
303
|
+
Timecop.freeze(Time.parse("2001-01-01 10:01:00UTC")) do
|
304
|
+
begin
|
305
|
+
exception = nil
|
306
|
+
plugin.call(worker, item, queue) do
|
307
|
+
yield if block_given?
|
308
|
+
end
|
309
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
310
|
+
raise exception
|
311
|
+
ensure
|
312
|
+
if exception
|
313
|
+
Appsignal::Integrations::SidekiqErrorHandler.new.call(exception, :job => item)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def transaction
|
320
|
+
last_transaction
|
321
|
+
end
|
322
|
+
|
323
|
+
def expect_transaction_to_have_sidekiq_event(transaction_hash)
|
324
|
+
events = transaction_hash["events"]
|
325
|
+
expect(events.count).to eq(1)
|
326
|
+
expect(events.first).to include(
|
327
|
+
"name" => "perform_job.sidekiq",
|
328
|
+
"title" => "",
|
329
|
+
"count" => 1,
|
330
|
+
"body" => "",
|
331
|
+
"body_format" => Appsignal::EventFormatter::DEFAULT
|
332
|
+
)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
if DependencyHelper.active_job_present?
|
337
|
+
require "active_job"
|
338
|
+
require "action_mailer"
|
339
|
+
require "sidekiq/testing"
|
340
|
+
|
341
|
+
describe "Sidekiq ActiveJob integration" do
|
342
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
343
|
+
let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
|
344
|
+
let(:log) { StringIO.new }
|
345
|
+
let(:given_args) do
|
346
|
+
[
|
347
|
+
"foo",
|
348
|
+
{
|
349
|
+
:foo => "Foo",
|
350
|
+
"bar" => "Bar",
|
351
|
+
"baz" => { "1" => "foo" }
|
352
|
+
}
|
353
|
+
]
|
354
|
+
end
|
355
|
+
let(:expected_args) do
|
356
|
+
[
|
357
|
+
"foo",
|
358
|
+
{
|
359
|
+
"_aj_symbol_keys" => ["foo"],
|
360
|
+
"foo" => "Foo",
|
361
|
+
"bar" => "Bar",
|
362
|
+
"baz" => {
|
363
|
+
"_aj_symbol_keys" => [],
|
364
|
+
"1" => "foo"
|
365
|
+
}
|
366
|
+
}
|
367
|
+
]
|
368
|
+
end
|
369
|
+
let(:expected_tags) do
|
370
|
+
{}.tap do |hash|
|
371
|
+
hash["active_job_id"] = kind_of(String)
|
372
|
+
if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
|
373
|
+
hash["provider_job_id"] = kind_of(String)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
before do
|
378
|
+
start_agent
|
379
|
+
Appsignal.logger = test_logger(log)
|
380
|
+
ActiveJob::Base.queue_adapter = :sidekiq
|
381
|
+
|
382
|
+
class ActiveJobSidekiqTestJob < ActiveJob::Base
|
383
|
+
self.queue_adapter = :sidekiq
|
384
|
+
|
385
|
+
def perform(*_args)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
|
390
|
+
self.queue_adapter = :sidekiq
|
391
|
+
|
392
|
+
def perform(*_args)
|
393
|
+
raise "uh oh"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
# Manually add the AppSignal middleware for the Testing environment.
|
397
|
+
# It doesn't use configured middlewares by default looks like.
|
398
|
+
# We test somewhere else if the middleware is installed properly.
|
399
|
+
Sidekiq::Testing.server_middleware do |chain|
|
400
|
+
chain.add Appsignal::Integrations::SidekiqMiddleware
|
401
|
+
end
|
402
|
+
end
|
403
|
+
around do |example|
|
404
|
+
keep_transactions do
|
405
|
+
Sidekiq::Testing.fake! do
|
406
|
+
example.run
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
after do
|
411
|
+
Object.send(:remove_const, :ActiveJobSidekiqTestJob)
|
412
|
+
Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
|
413
|
+
end
|
414
|
+
|
415
|
+
it "reports the transaction from the ActiveJob integration" do
|
416
|
+
perform_job(ActiveJobSidekiqTestJob, given_args)
|
417
|
+
|
418
|
+
transaction = last_transaction
|
419
|
+
transaction_hash = transaction.to_h
|
420
|
+
expect(transaction_hash).to include(
|
421
|
+
"action" => "ActiveJobSidekiqTestJob#perform",
|
422
|
+
"error" => nil,
|
423
|
+
"namespace" => namespace,
|
424
|
+
"metadata" => hash_including(
|
425
|
+
"queue" => "default"
|
426
|
+
),
|
427
|
+
"sample_data" => hash_including(
|
428
|
+
"environment" => {},
|
429
|
+
"params" => [expected_args],
|
430
|
+
"tags" => expected_tags.merge("queue" => "default")
|
431
|
+
)
|
432
|
+
)
|
433
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
434
|
+
events = transaction_hash["events"]
|
435
|
+
.sort_by { |e| e["start"] }
|
436
|
+
.map { |event| event["name"] }
|
437
|
+
expect(events)
|
438
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
439
|
+
end
|
440
|
+
|
441
|
+
context "with error" do
|
442
|
+
it "reports the error on the transaction from the ActiveRecord integration" do
|
443
|
+
expect do
|
444
|
+
perform_job(ActiveJobSidekiqErrorTestJob, given_args)
|
445
|
+
end.to raise_error(RuntimeError, "uh oh")
|
446
|
+
|
447
|
+
transaction = last_transaction
|
448
|
+
transaction_hash = transaction.to_h
|
449
|
+
expect(transaction_hash).to include(
|
450
|
+
"action" => "ActiveJobSidekiqErrorTestJob#perform",
|
451
|
+
"error" => {
|
452
|
+
"name" => "RuntimeError",
|
453
|
+
"message" => "uh oh",
|
454
|
+
"backtrace" => kind_of(String)
|
455
|
+
},
|
456
|
+
"namespace" => namespace,
|
457
|
+
"metadata" => hash_including(
|
458
|
+
"queue" => "default"
|
459
|
+
),
|
460
|
+
"sample_data" => hash_including(
|
461
|
+
"environment" => {},
|
462
|
+
"params" => [expected_args],
|
463
|
+
"tags" => expected_tags.merge("queue" => "default")
|
464
|
+
)
|
465
|
+
)
|
466
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
467
|
+
events = transaction_hash["events"]
|
468
|
+
.sort_by { |e| e["start"] }
|
469
|
+
.map { |event| event["name"] }
|
470
|
+
expect(events)
|
471
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context "with ActionMailer" do
|
476
|
+
include ActionMailerHelpers
|
477
|
+
|
478
|
+
before do
|
479
|
+
class ActionMailerSidekiqTestJob < ActionMailer::Base
|
480
|
+
def welcome(*args)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
it "reports ActionMailer data on the transaction" do
|
486
|
+
perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
|
487
|
+
|
488
|
+
transaction = last_transaction
|
489
|
+
transaction_hash = transaction.to_h
|
490
|
+
expect(transaction_hash).to include(
|
491
|
+
"action" => "ActionMailerSidekiqTestJob#welcome",
|
492
|
+
"sample_data" => hash_including(
|
493
|
+
"params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
|
494
|
+
)
|
495
|
+
)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def perform_sidekiq
|
500
|
+
Timecop.freeze(time) do
|
501
|
+
begin
|
502
|
+
yield
|
503
|
+
# Combined with Sidekiq::Testing.fake! and drain_all we get a
|
504
|
+
# enqueue_at in the job data.
|
505
|
+
Sidekiq::Worker.drain_all
|
506
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
507
|
+
raise exception
|
508
|
+
ensure
|
509
|
+
if exception
|
510
|
+
Appsignal::Integrations::SidekiqErrorHandler.new.call(exception, {})
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
def perform_job(job_class, args)
|
517
|
+
perform_sidekiq { job_class.perform_later(args) }
|
518
|
+
end
|
519
|
+
|
520
|
+
def perform_mailer(mailer, method, args = nil)
|
521
|
+
perform_sidekiq { perform_action_mailer(mailer, method, args) }
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|