appsignal 2.11.0.alpha.1-java → 2.11.0.beta.4-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 +3 -0
- data/.semaphore/semaphore.yml +75 -61
- data/CHANGELOG.md +21 -1
- data/build_matrix.yml +13 -7
- data/ext/agent.yml +19 -19
- data/gemfiles/padrino.gemfile +2 -2
- data/gemfiles/rails-4.2.gemfile +9 -2
- data/gemfiles/rails-5.0.gemfile +1 -0
- data/gemfiles/rails-5.1.gemfile +1 -0
- data/gemfiles/rails-5.2.gemfile +1 -0
- data/gemfiles/rails-6.0.gemfile +1 -0
- data/gemfiles/resque-1.gemfile +7 -0
- data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
- data/lib/appsignal/hooks.rb +2 -0
- data/lib/appsignal/hooks/active_job.rb +114 -0
- data/lib/appsignal/hooks/puma.rb +2 -58
- data/lib/appsignal/hooks/resque.rb +60 -0
- data/lib/appsignal/hooks/sidekiq.rb +19 -192
- data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
- data/lib/appsignal/integrations/que.rb +1 -1
- data/lib/appsignal/integrations/resque.rb +9 -12
- data/lib/appsignal/integrations/resque_active_job.rb +9 -32
- data/lib/appsignal/probes/puma.rb +61 -0
- data/lib/appsignal/probes/sidekiq.rb +102 -0
- data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
- data/lib/appsignal/transaction.rb +10 -0
- data/lib/appsignal/utils/deprecation_message.rb +5 -1
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +2 -1
- data/spec/lib/appsignal/hooks/activejob_spec.rb +548 -0
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
- data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
- data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +297 -549
- data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
- data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
- data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
- data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
- data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
- data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
- data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
- data/spec/lib/appsignal/transaction_spec.rb +5 -7
- data/spec/lib/puma/appsignal_spec.rb +1 -1
- data/spec/support/helpers/action_mailer_helpers.rb +25 -0
- data/spec/support/helpers/dependency_helper.rb +9 -2
- data/spec/support/helpers/transaction_helpers.rb +6 -0
- data/spec/support/stubs/sidekiq/api.rb +2 -2
- metadata +18 -3
@@ -227,25 +227,14 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
230
|
-
context "
|
231
|
-
let(:job_data) do
|
232
|
-
{ :name => "", :payload_object => payload_object }
|
233
|
-
end
|
234
|
-
|
235
|
-
it "wraps it in a transaction using the class method job name" do
|
236
|
-
perform
|
237
|
-
expect(last_transaction.to_h["action"]).to eql("unknown")
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
context "with invalid job name" do
|
230
|
+
context "with only job class name" do
|
242
231
|
let(:job_data) do
|
243
232
|
{ :name => "Banana", :payload_object => payload_object }
|
244
233
|
end
|
245
234
|
|
246
|
-
it "
|
235
|
+
it "appends #perform to the class name" do
|
247
236
|
perform
|
248
|
-
expect(last_transaction.to_h["action"]).to eql("
|
237
|
+
expect(last_transaction.to_h["action"]).to eql("Banana#perform")
|
249
238
|
end
|
250
239
|
end
|
251
240
|
|
@@ -55,7 +55,7 @@ describe Appsignal::Hooks::PumaHook do
|
|
55
55
|
|
56
56
|
Appsignal::Hooks::PumaHook.new.install
|
57
57
|
probe = Appsignal::Minutely.probes[:puma]
|
58
|
-
expect(probe).to eql(Appsignal::
|
58
|
+
expect(probe).to eql(Appsignal::Probes::PumaProbe)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
@@ -101,7 +101,7 @@ describe Appsignal::Hooks::PumaHook do
|
|
101
101
|
|
102
102
|
Appsignal::Hooks::PumaHook.new.install
|
103
103
|
probe = Appsignal::Minutely.probes[:puma]
|
104
|
-
expect(probe).to eql(Appsignal::
|
104
|
+
expect(probe).to eql(Appsignal::Probes::PumaProbe)
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
@@ -116,182 +116,3 @@ describe Appsignal::Hooks::PumaHook do
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
end
|
119
|
-
|
120
|
-
describe Appsignal::Hooks::PumaProbe do
|
121
|
-
before(:context) do
|
122
|
-
Appsignal.config = project_fixture_config
|
123
|
-
end
|
124
|
-
after(:context) do
|
125
|
-
Appsignal.config = nil
|
126
|
-
end
|
127
|
-
|
128
|
-
let(:probe) { Appsignal::Hooks::PumaProbe.new }
|
129
|
-
|
130
|
-
describe "hostname" do
|
131
|
-
it "returns the socket hostname" do
|
132
|
-
expect(probe.send(:hostname)).to eql(Socket.gethostname)
|
133
|
-
end
|
134
|
-
|
135
|
-
context "with overridden hostname" do
|
136
|
-
around do |sample|
|
137
|
-
Appsignal.config[:hostname] = "frontend1"
|
138
|
-
sample.run
|
139
|
-
Appsignal.config[:hostname] = nil
|
140
|
-
end
|
141
|
-
it "returns the configured host" do
|
142
|
-
expect(probe.send(:hostname)).to eql("frontend1")
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
describe "#call" do
|
148
|
-
let(:expected_default_tags) { { :hostname => Socket.gethostname } }
|
149
|
-
|
150
|
-
context "with multiple worker stats" do
|
151
|
-
before(:context) do
|
152
|
-
class Puma
|
153
|
-
def self.stats
|
154
|
-
{
|
155
|
-
"workers" => 2,
|
156
|
-
"booted_workers" => 2,
|
157
|
-
"old_workers" => 0,
|
158
|
-
"worker_status" => [
|
159
|
-
{
|
160
|
-
"last_status" => {
|
161
|
-
"backlog" => 0,
|
162
|
-
"running" => 5,
|
163
|
-
"pool_capacity" => 5,
|
164
|
-
"max_threads" => 5
|
165
|
-
}
|
166
|
-
},
|
167
|
-
{
|
168
|
-
"last_status" => {
|
169
|
-
"backlog" => 0,
|
170
|
-
"running" => 5,
|
171
|
-
"pool_capacity" => 5,
|
172
|
-
"max_threads" => 5
|
173
|
-
}
|
174
|
-
}
|
175
|
-
]
|
176
|
-
}.to_json
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
after(:context) { Object.send(:remove_const, :Puma) }
|
181
|
-
|
182
|
-
it "calls `puma_gauge` with the (summed) worker metrics" do
|
183
|
-
expect_gauge(:workers, 2, :type => :count)
|
184
|
-
expect_gauge(:workers, 2, :type => :booted)
|
185
|
-
expect_gauge(:workers, 0, :type => :old)
|
186
|
-
|
187
|
-
expect_gauge(:connection_backlog, 0)
|
188
|
-
expect_gauge(:pool_capacity, 10)
|
189
|
-
expect_gauge(:threads, 10, :type => :running)
|
190
|
-
expect_gauge(:threads, 10, :type => :max)
|
191
|
-
|
192
|
-
probe.call
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
context "with single worker stats" do
|
197
|
-
before(:context) do
|
198
|
-
class Puma
|
199
|
-
def self.stats
|
200
|
-
{
|
201
|
-
"backlog" => 0,
|
202
|
-
"running" => 5,
|
203
|
-
"pool_capacity" => 5,
|
204
|
-
"max_threads" => 5
|
205
|
-
}.to_json
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
after(:context) { Object.send(:remove_const, :Puma) }
|
210
|
-
|
211
|
-
it "calls `puma_gauge` with the (summed) worker metrics" do
|
212
|
-
expect_gauge(:connection_backlog, 0)
|
213
|
-
expect_gauge(:pool_capacity, 5)
|
214
|
-
expect_gauge(:threads, 5, :type => :running)
|
215
|
-
expect_gauge(:threads, 5, :type => :max)
|
216
|
-
probe.call
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
context "without stats" do
|
221
|
-
before(:context) do
|
222
|
-
class Puma
|
223
|
-
def self.stats
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
after(:context) { Object.send(:remove_const, :Puma) }
|
228
|
-
|
229
|
-
context "when it returns nil" do
|
230
|
-
it "does not track metrics" do
|
231
|
-
expect(probe).to_not receive(:puma_gauge)
|
232
|
-
probe.call
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
# Puma.stats raises a NoMethodError on a nil object on the first call.
|
237
|
-
context "when it returns a NoMethodError on the first call" do
|
238
|
-
let(:log) { StringIO.new }
|
239
|
-
|
240
|
-
it "ignores the first call and tracks the second call" do
|
241
|
-
use_logger_with log do
|
242
|
-
expect(Puma).to receive(:stats)
|
243
|
-
.and_raise(NoMethodError.new("undefined method `stats' for nil:NilClass"))
|
244
|
-
probe.call
|
245
|
-
|
246
|
-
expect(Puma).to receive(:stats).and_return({
|
247
|
-
"backlog" => 1,
|
248
|
-
"running" => 5,
|
249
|
-
"pool_capacity" => 4,
|
250
|
-
"max_threads" => 6
|
251
|
-
}.to_json)
|
252
|
-
|
253
|
-
expect_gauge(:connection_backlog, 1)
|
254
|
-
expect_gauge(:pool_capacity, 4)
|
255
|
-
expect_gauge(:threads, 5, :type => :running)
|
256
|
-
expect_gauge(:threads, 6, :type => :max)
|
257
|
-
probe.call
|
258
|
-
end
|
259
|
-
|
260
|
-
expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
context "when it does not have a complete stats payload" do
|
265
|
-
let(:log) { StringIO.new }
|
266
|
-
|
267
|
-
it "tracks whatever metrics we do have" do
|
268
|
-
use_logger_with log do
|
269
|
-
expect(Puma).to receive(:stats).and_return({
|
270
|
-
"backlog" => 1,
|
271
|
-
"running" => 5
|
272
|
-
}.to_json)
|
273
|
-
|
274
|
-
expect_gauge(:connection_backlog, 1)
|
275
|
-
expect_no_gauge(:pool_capacity)
|
276
|
-
expect_gauge(:threads, 5, :type => :running)
|
277
|
-
expect_no_gauge(:threads, :type => :max)
|
278
|
-
probe.call
|
279
|
-
end
|
280
|
-
|
281
|
-
expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
|
282
|
-
end
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def expect_gauge(key, value, tags = {})
|
287
|
-
expect(Appsignal).to receive(:set_gauge)
|
288
|
-
.with("puma_#{key}", value, expected_default_tags.merge(tags))
|
289
|
-
.and_call_original
|
290
|
-
end
|
291
|
-
|
292
|
-
def expect_no_gauge(key, tags = {})
|
293
|
-
expect(Appsignal).to_not receive(:set_gauge)
|
294
|
-
.with("puma_#{key}", anything, tags)
|
295
|
-
end
|
296
|
-
end
|
297
|
-
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
describe Appsignal::Hooks::ResqueHook do
|
2
|
+
describe "#dependency_present?" do
|
3
|
+
subject { described_class.new.dependencies_present? }
|
4
|
+
|
5
|
+
context "when Resque is loaded" do
|
6
|
+
before { stub_const "Resque", 1 }
|
7
|
+
|
8
|
+
it { is_expected.to be_truthy }
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when Resque is not loaded" do
|
12
|
+
before { hide_const "Resque" }
|
13
|
+
|
14
|
+
it { is_expected.to be_falsy }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if DependencyHelper.resque_present?
|
19
|
+
describe "#install" do
|
20
|
+
def perform_job(klass, options = {})
|
21
|
+
payload = { "class" => klass.to_s }.merge(options)
|
22
|
+
job = ::Resque::Job.new(queue, payload)
|
23
|
+
keep_transactions { job.perform }
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:queue) { "default" }
|
27
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
28
|
+
before do
|
29
|
+
start_agent
|
30
|
+
|
31
|
+
class ResqueTestJob
|
32
|
+
def self.perform(*_args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ResqueErrorTestJob
|
37
|
+
def self.perform
|
38
|
+
raise "resque job error"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
expect(Appsignal).to receive(:stop) # Resque calls stop after every job
|
43
|
+
end
|
44
|
+
around do |example|
|
45
|
+
keep_transactions { example.run }
|
46
|
+
end
|
47
|
+
after do
|
48
|
+
Object.send(:remove_const, :ResqueTestJob)
|
49
|
+
Object.send(:remove_const, :ResqueErrorTestJob)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "tracks a transaction on perform" do
|
53
|
+
perform_job(ResqueTestJob)
|
54
|
+
|
55
|
+
transaction = last_transaction
|
56
|
+
transaction_hash = transaction.to_h
|
57
|
+
expect(transaction_hash).to include(
|
58
|
+
"id" => kind_of(String),
|
59
|
+
"action" => "ResqueTestJob#perform",
|
60
|
+
"error" => nil,
|
61
|
+
"namespace" => namespace,
|
62
|
+
"metadata" => {},
|
63
|
+
"sample_data" => { "tags" => { "queue" => queue } }
|
64
|
+
)
|
65
|
+
expect(transaction_hash["events"].map { |e| e["name"] })
|
66
|
+
.to eql(["perform.resque"])
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with error" do
|
70
|
+
it "tracks the error on the transaction" do
|
71
|
+
expect do
|
72
|
+
perform_job(ResqueErrorTestJob)
|
73
|
+
end.to raise_error(RuntimeError, "resque job error")
|
74
|
+
|
75
|
+
transaction = last_transaction
|
76
|
+
transaction_hash = transaction.to_h
|
77
|
+
expect(transaction_hash).to include(
|
78
|
+
"id" => kind_of(String),
|
79
|
+
"action" => "ResqueErrorTestJob#perform",
|
80
|
+
"error" => {
|
81
|
+
"name" => "RuntimeError",
|
82
|
+
"message" => "resque job error",
|
83
|
+
"backtrace" => kind_of(String)
|
84
|
+
},
|
85
|
+
"namespace" => namespace,
|
86
|
+
"metadata" => {},
|
87
|
+
"sample_data" => { "tags" => { "queue" => queue } }
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with arguments" do
|
93
|
+
before do
|
94
|
+
Appsignal.config = project_fixture_config("production")
|
95
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
96
|
+
end
|
97
|
+
|
98
|
+
it "filters out configured arguments" do
|
99
|
+
perform_job(
|
100
|
+
ResqueTestJob,
|
101
|
+
"args" => [
|
102
|
+
"foo",
|
103
|
+
{
|
104
|
+
"foo" => "secret",
|
105
|
+
"bar" => "Bar",
|
106
|
+
"baz" => { "1" => "foo" }
|
107
|
+
}
|
108
|
+
]
|
109
|
+
)
|
110
|
+
|
111
|
+
transaction = last_transaction
|
112
|
+
transaction_hash = transaction.to_h
|
113
|
+
expect(transaction_hash).to include(
|
114
|
+
"id" => kind_of(String),
|
115
|
+
"action" => "ResqueTestJob#perform",
|
116
|
+
"error" => nil,
|
117
|
+
"namespace" => namespace,
|
118
|
+
"metadata" => {},
|
119
|
+
"sample_data" => {
|
120
|
+
"tags" => { "queue" => queue },
|
121
|
+
"params" => [
|
122
|
+
"foo",
|
123
|
+
{
|
124
|
+
"foo" => "[FILTERED]",
|
125
|
+
"bar" => "Bar",
|
126
|
+
"baz" => { "1" => "foo" }
|
127
|
+
}
|
128
|
+
]
|
129
|
+
}
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "with active job" do
|
135
|
+
before do
|
136
|
+
module ActiveJobMock
|
137
|
+
module QueueAdapters
|
138
|
+
module ResqueAdapter
|
139
|
+
module JobWrapper
|
140
|
+
class << self
|
141
|
+
def perform(job_data)
|
142
|
+
# Basic ActiveJob stub for this test.
|
143
|
+
# I haven't found a way to run Resque in a testing mode.
|
144
|
+
Appsignal.set_action(job_data["job_class"])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
stub_const "ActiveJob", ActiveJobMock
|
153
|
+
end
|
154
|
+
after { Object.send(:remove_const, :ActiveJobMock) }
|
155
|
+
|
156
|
+
it "does not set arguments but lets the ActiveJob intergration handle it" do
|
157
|
+
perform_job(
|
158
|
+
ResqueTestJob,
|
159
|
+
"class" => "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper",
|
160
|
+
"args" => [
|
161
|
+
{
|
162
|
+
"job_class" => "ResqueTestJobByActiveJob#perform",
|
163
|
+
"arguments" => ["an argument", "second argument"]
|
164
|
+
}
|
165
|
+
]
|
166
|
+
)
|
167
|
+
|
168
|
+
transaction = last_transaction
|
169
|
+
transaction_hash = transaction.to_h
|
170
|
+
expect(transaction_hash).to include(
|
171
|
+
"id" => kind_of(String),
|
172
|
+
"action" => "ResqueTestJobByActiveJob#perform",
|
173
|
+
"error" => nil,
|
174
|
+
"namespace" => namespace,
|
175
|
+
"metadata" => {},
|
176
|
+
"sample_data" => {
|
177
|
+
"tags" => { "queue" => queue }
|
178
|
+
# Params will be set by the ActiveJob integration
|
179
|
+
}
|
180
|
+
)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -1,3 +1,54 @@
|
|
1
|
+
describe Appsignal::Hooks::SidekiqHook do
|
2
|
+
describe "#dependencies_present?" do
|
3
|
+
subject { described_class.new.dependencies_present? }
|
4
|
+
|
5
|
+
context "when Sidekiq constant is found" do
|
6
|
+
before { stub_const "Sidekiq", Class.new }
|
7
|
+
|
8
|
+
it { is_expected.to be_truthy }
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when Sidekiq constant is not found" do
|
12
|
+
before { hide_const "Sidekiq" }
|
13
|
+
|
14
|
+
it { is_expected.to be_falsy }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#install" do
|
19
|
+
class SidekiqMiddlewareMock < Set
|
20
|
+
def exists?(middleware)
|
21
|
+
include?(middleware)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
module SidekiqMock
|
25
|
+
def self.middlewares
|
26
|
+
@middlewares ||= SidekiqMiddlewareMock.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.configure_server
|
30
|
+
yield self
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.server_middleware
|
34
|
+
yield middlewares if block_given?
|
35
|
+
middlewares
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
before do
|
40
|
+
Appsignal.config = project_fixture_config
|
41
|
+
stub_const "Sidekiq", SidekiqMock
|
42
|
+
end
|
43
|
+
|
44
|
+
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
|
45
|
+
described_class.new.install
|
46
|
+
|
47
|
+
expect(Sidekiq.server_middleware.exists?(Appsignal::Hooks::SidekiqPlugin)).to be(true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
1
52
|
describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
2
53
|
class DelayedTestClass; end
|
3
54
|
|
@@ -25,9 +76,10 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
25
76
|
]
|
26
77
|
end
|
27
78
|
let(:job_class) { "TestClass" }
|
79
|
+
let(:jid) { "b4a577edbccf1d805744efa9" }
|
28
80
|
let(:item) do
|
29
81
|
{
|
30
|
-
"jid" =>
|
82
|
+
"jid" => jid,
|
31
83
|
"class" => job_class,
|
32
84
|
"retry_count" => 0,
|
33
85
|
"queue" => "default",
|
@@ -48,369 +100,132 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
48
100
|
expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
|
49
101
|
end
|
50
102
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
perform_job
|
103
|
+
describe "internal Sidekiq job values" do
|
104
|
+
it "does not save internal Sidekiq values as metadata on transaction" do
|
105
|
+
perform_job
|
55
106
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
107
|
+
transaction_hash = transaction.to_h
|
108
|
+
expect(transaction_hash["metadata"].keys)
|
109
|
+
.to_not include(*Appsignal::Hooks::SidekiqPlugin::EXCLUDED_JOB_KEYS)
|
60
110
|
end
|
111
|
+
end
|
61
112
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
it "filters selected arguments" do
|
69
|
-
perform_job
|
70
|
-
|
71
|
-
transaction_hash = transaction.to_h
|
72
|
-
expect(transaction_hash["sample_data"]).to include(
|
73
|
-
"params" => [
|
74
|
-
"foo",
|
75
|
-
{
|
76
|
-
"foo" => "[FILTERED]",
|
77
|
-
"bar" => "Bar",
|
78
|
-
"baz" => { "1" => "foo" }
|
79
|
-
}
|
80
|
-
]
|
81
|
-
)
|
82
|
-
end
|
113
|
+
context "with parameter filtering" do
|
114
|
+
before do
|
115
|
+
Appsignal.config = project_fixture_config("production")
|
116
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
83
117
|
end
|
84
118
|
|
85
|
-
|
86
|
-
|
87
|
-
item["encrypt"] = true
|
88
|
-
item["args"] << "super secret value" # Last argument will be replaced
|
89
|
-
end
|
90
|
-
|
91
|
-
it "replaces the last argument (the secret bag) with an [encrypted data] string" do
|
92
|
-
perform_job
|
119
|
+
it "filters selected arguments" do
|
120
|
+
perform_job
|
93
121
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
122
|
+
transaction_hash = transaction.to_h
|
123
|
+
expect(transaction_hash["sample_data"]).to include(
|
124
|
+
"params" => [
|
125
|
+
"foo",
|
126
|
+
{
|
127
|
+
"foo" => "[FILTERED]",
|
128
|
+
"bar" => "Bar",
|
129
|
+
"baz" => { "1" => "foo" }
|
130
|
+
}
|
131
|
+
]
|
132
|
+
)
|
99
133
|
end
|
134
|
+
end
|
100
135
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
"class" => "Sidekiq::Extensions::DelayedClass",
|
106
|
-
"queue" => "default",
|
107
|
-
"args" => [
|
108
|
-
"---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
|
109
|
-
],
|
110
|
-
"retry" => true,
|
111
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
112
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
113
|
-
"extra" => "data"
|
114
|
-
}
|
115
|
-
end
|
116
|
-
|
117
|
-
it "uses the delayed class and method name for the action" do
|
118
|
-
perform_job
|
119
|
-
|
120
|
-
transaction_hash = transaction.to_h
|
121
|
-
expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
|
122
|
-
expect(transaction_hash["sample_data"]).to include(
|
123
|
-
"params" => ["bar" => "baz"]
|
124
|
-
)
|
125
|
-
end
|
126
|
-
|
127
|
-
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
128
|
-
before { item["args"] = [] }
|
129
|
-
|
130
|
-
it "logs a warning and uses the default argument" do
|
131
|
-
perform_job
|
132
|
-
|
133
|
-
transaction_hash = transaction.to_h
|
134
|
-
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
|
135
|
-
expect(transaction_hash["sample_data"]).to include("params" => [])
|
136
|
-
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
137
|
-
end
|
138
|
-
end
|
136
|
+
context "with encrypted arguments" do
|
137
|
+
before do
|
138
|
+
item["encrypt"] = true
|
139
|
+
item["args"] << "super secret value" # Last argument will be replaced
|
139
140
|
end
|
140
141
|
|
141
|
-
|
142
|
-
|
143
|
-
{
|
144
|
-
"jid" => "efb140489485999d32b5504c",
|
145
|
-
"class" => "Sidekiq::Extensions::DelayedModel",
|
146
|
-
"queue" => "default",
|
147
|
-
"args" => [
|
148
|
-
"---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
|
149
|
-
],
|
150
|
-
"retry" => true,
|
151
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
152
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
153
|
-
"extra" => "data"
|
154
|
-
}
|
155
|
-
end
|
156
|
-
|
157
|
-
it "uses the delayed class and method name for the action" do
|
158
|
-
perform_job
|
142
|
+
it "replaces the last argument (the secret bag) with an [encrypted data] string" do
|
143
|
+
perform_job
|
159
144
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
145
|
+
transaction_hash = transaction.to_h
|
146
|
+
expect(transaction_hash["sample_data"]).to include(
|
147
|
+
"params" => expected_args << "[encrypted data]"
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
166
151
|
|
167
|
-
|
168
|
-
|
152
|
+
context "when using the Sidekiq delayed extension" do
|
153
|
+
let(:item) do
|
154
|
+
{
|
155
|
+
"jid" => jid,
|
156
|
+
"class" => "Sidekiq::Extensions::DelayedClass",
|
157
|
+
"queue" => "default",
|
158
|
+
"args" => [
|
159
|
+
"---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
|
160
|
+
],
|
161
|
+
"retry" => true,
|
162
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
163
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
164
|
+
"extra" => "data"
|
165
|
+
}
|
166
|
+
end
|
169
167
|
|
170
|
-
|
171
|
-
|
168
|
+
it "uses the delayed class and method name for the action" do
|
169
|
+
perform_job
|
172
170
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end
|
171
|
+
transaction_hash = transaction.to_h
|
172
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
|
173
|
+
expect(transaction_hash["sample_data"]).to include(
|
174
|
+
"params" => ["bar" => "baz"]
|
175
|
+
)
|
179
176
|
end
|
180
177
|
|
181
|
-
context "when
|
182
|
-
|
183
|
-
{
|
184
|
-
"class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
|
185
|
-
"wrapped" => "ActiveJobTestClass",
|
186
|
-
"queue" => "default",
|
187
|
-
"args" => [{
|
188
|
-
"job_class" => "ActiveJobTestJob",
|
189
|
-
"job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
|
190
|
-
"queue_name" => "default",
|
191
|
-
"arguments" => [
|
192
|
-
"foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
|
193
|
-
]
|
194
|
-
}],
|
195
|
-
"retry" => true,
|
196
|
-
"jid" => "efb140489485999d32b5504c",
|
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
|
-
}
|
200
|
-
end
|
178
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
179
|
+
before { item["args"] = [] }
|
201
180
|
|
202
|
-
it "
|
181
|
+
it "logs a warning and uses the default argument" do
|
203
182
|
perform_job
|
204
183
|
|
205
184
|
transaction_hash = transaction.to_h
|
206
|
-
expect(transaction_hash).to
|
207
|
-
|
208
|
-
|
209
|
-
"error" => nil,
|
210
|
-
"namespace" => namespace,
|
211
|
-
"metadata" => {
|
212
|
-
"queue" => "default"
|
213
|
-
},
|
214
|
-
"sample_data" => {
|
215
|
-
"environment" => {},
|
216
|
-
"params" => [
|
217
|
-
"foo",
|
218
|
-
{
|
219
|
-
"foo" => "Foo",
|
220
|
-
"bar" => "Bar",
|
221
|
-
"baz" => { "1" => "bar" }
|
222
|
-
}
|
223
|
-
],
|
224
|
-
"tags" => {}
|
225
|
-
}
|
226
|
-
)
|
227
|
-
# TODO: Not available in transaction.to_h yet.
|
228
|
-
# https://github.com/appsignal/appsignal-agent/issues/293
|
229
|
-
expect(transaction.request.env).to eq(
|
230
|
-
:queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
|
231
|
-
)
|
232
|
-
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
233
|
-
end
|
234
|
-
|
235
|
-
context "with ActionMailer job" do
|
236
|
-
let(:item) do
|
237
|
-
{
|
238
|
-
"class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
|
239
|
-
"wrapped" => "ActionMailer::DeliveryJob",
|
240
|
-
"queue" => "default",
|
241
|
-
"args" => [{
|
242
|
-
"job_class" => "ActiveMailerTestJob",
|
243
|
-
"job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
|
244
|
-
"queue_name" => "default",
|
245
|
-
"arguments" => [
|
246
|
-
"MailerClass", "mailer_method", "deliver_now",
|
247
|
-
"foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
|
248
|
-
]
|
249
|
-
}],
|
250
|
-
"retry" => true,
|
251
|
-
"jid" => "efb140489485999d32b5504c",
|
252
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
253
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
|
254
|
-
}
|
255
|
-
end
|
256
|
-
|
257
|
-
it "creates a transaction for the ActionMailer class" do
|
258
|
-
perform_job
|
259
|
-
|
260
|
-
transaction_hash = transaction.to_h
|
261
|
-
expect(transaction_hash).to include(
|
262
|
-
"id" => kind_of(String),
|
263
|
-
"action" => "MailerClass#mailer_method",
|
264
|
-
"error" => nil,
|
265
|
-
"namespace" => namespace,
|
266
|
-
"metadata" => {
|
267
|
-
"queue" => "default"
|
268
|
-
},
|
269
|
-
"sample_data" => {
|
270
|
-
"environment" => {},
|
271
|
-
"params" => [
|
272
|
-
"foo",
|
273
|
-
{
|
274
|
-
"foo" => "Foo",
|
275
|
-
"bar" => "Bar",
|
276
|
-
"baz" => { "1" => "bar" }
|
277
|
-
}
|
278
|
-
],
|
279
|
-
"tags" => {}
|
280
|
-
}
|
281
|
-
)
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
context "with parameter filtering" do
|
286
|
-
before do
|
287
|
-
Appsignal.config = project_fixture_config("production")
|
288
|
-
Appsignal.config[:filter_parameters] = ["foo"]
|
289
|
-
end
|
290
|
-
|
291
|
-
it "filters selected arguments" do
|
292
|
-
perform_job
|
293
|
-
|
294
|
-
transaction_hash = transaction.to_h
|
295
|
-
expect(transaction_hash["sample_data"]).to include(
|
296
|
-
"params" => [
|
297
|
-
"foo",
|
298
|
-
{
|
299
|
-
"foo" => "[FILTERED]",
|
300
|
-
"bar" => "Bar",
|
301
|
-
"baz" => { "1" => "bar" }
|
302
|
-
}
|
303
|
-
]
|
304
|
-
)
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
context "when Sidekiq job payload is missing the 'wrapped' value" do
|
309
|
-
let(:item) do
|
310
|
-
{
|
311
|
-
"class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
|
312
|
-
"queue" => "default",
|
313
|
-
"args" => [first_argument],
|
314
|
-
"retry" => true,
|
315
|
-
"jid" => "efb140489485999d32b5504c",
|
316
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
317
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
|
318
|
-
}
|
319
|
-
end
|
320
|
-
before { perform_job }
|
321
|
-
|
322
|
-
context "when the first argument is not a Hash object" do
|
323
|
-
let(:first_argument) { "foo" }
|
324
|
-
|
325
|
-
include_examples "unknown job action name"
|
326
|
-
end
|
327
|
-
|
328
|
-
context "when the first argument is a Hash object not containing a job payload" do
|
329
|
-
let(:first_argument) { { "foo" => "bar" } }
|
330
|
-
|
331
|
-
include_examples "unknown job action name"
|
332
|
-
|
333
|
-
context "when the argument contains an invalid job_class value" do
|
334
|
-
let(:first_argument) { { "job_class" => :foo } }
|
335
|
-
|
336
|
-
include_examples "unknown job action name"
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
context "when the first argument is a Hash object containing a job payload" do
|
341
|
-
let(:first_argument) do
|
342
|
-
{
|
343
|
-
"job_class" => "ActiveMailerTestJob",
|
344
|
-
"job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
|
345
|
-
"queue_name" => "default",
|
346
|
-
"arguments" => [
|
347
|
-
"foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
|
348
|
-
]
|
349
|
-
}
|
350
|
-
end
|
351
|
-
|
352
|
-
it "sets the action name to the job class in the first argument" do
|
353
|
-
transaction_hash = transaction.to_h
|
354
|
-
expect(transaction_hash).to include(
|
355
|
-
"action" => "ActiveMailerTestJob#perform"
|
356
|
-
)
|
357
|
-
end
|
358
|
-
|
359
|
-
it "stores the job metadata on the transaction" do
|
360
|
-
transaction_hash = transaction.to_h
|
361
|
-
expect(transaction_hash).to include(
|
362
|
-
"id" => kind_of(String),
|
363
|
-
"error" => nil,
|
364
|
-
"namespace" => namespace,
|
365
|
-
"metadata" => {
|
366
|
-
"queue" => "default"
|
367
|
-
},
|
368
|
-
"sample_data" => {
|
369
|
-
"environment" => {},
|
370
|
-
"params" => [
|
371
|
-
"foo",
|
372
|
-
{
|
373
|
-
"foo" => "Foo",
|
374
|
-
"bar" => "Bar",
|
375
|
-
"baz" => { "1" => "bar" }
|
376
|
-
}
|
377
|
-
],
|
378
|
-
"tags" => {}
|
379
|
-
}
|
380
|
-
)
|
381
|
-
end
|
382
|
-
|
383
|
-
it "does not log a debug message" do
|
384
|
-
expect(log_contents(log)).to_not contains_log(
|
385
|
-
:debug, "Unable to determine an action name from Sidekiq payload"
|
386
|
-
)
|
387
|
-
end
|
388
|
-
end
|
185
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
|
186
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
187
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
389
188
|
end
|
390
189
|
end
|
391
190
|
end
|
392
191
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
192
|
+
context "when using the Sidekiq ActiveRecord instance delayed extension" do
|
193
|
+
let(:item) do
|
194
|
+
{
|
195
|
+
"jid" => jid,
|
196
|
+
"class" => "Sidekiq::Extensions::DelayedModel",
|
197
|
+
"queue" => "default",
|
198
|
+
"args" => [
|
199
|
+
"---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
|
200
|
+
],
|
201
|
+
"retry" => true,
|
202
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
203
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
204
|
+
"extra" => "data"
|
205
|
+
}
|
397
206
|
end
|
398
207
|
|
399
|
-
it "
|
208
|
+
it "uses the delayed class and method name for the action" do
|
209
|
+
perform_job
|
210
|
+
|
400
211
|
transaction_hash = transaction.to_h
|
401
|
-
expect(transaction_hash).to
|
402
|
-
|
403
|
-
|
404
|
-
"params" => [],
|
405
|
-
"tags" => {}
|
406
|
-
}
|
212
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
|
213
|
+
expect(transaction_hash["sample_data"]).to include(
|
214
|
+
"params" => ["bar" => "baz"]
|
407
215
|
)
|
408
216
|
end
|
409
217
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
218
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
219
|
+
before { item["args"] = [] }
|
220
|
+
|
221
|
+
it "logs a warning and uses the default argument" do
|
222
|
+
perform_job
|
223
|
+
|
224
|
+
transaction_hash = transaction.to_h
|
225
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
|
226
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
227
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
228
|
+
end
|
414
229
|
end
|
415
230
|
end
|
416
231
|
|
@@ -429,7 +244,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
429
244
|
|
430
245
|
transaction_hash = transaction.to_h
|
431
246
|
expect(transaction_hash).to include(
|
432
|
-
"id" =>
|
247
|
+
"id" => jid,
|
433
248
|
"action" => "TestClass#perform",
|
434
249
|
"error" => {
|
435
250
|
"name" => "ExampleException",
|
@@ -452,8 +267,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
452
267
|
)
|
453
268
|
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
454
269
|
end
|
455
|
-
|
456
|
-
include_examples "sidekiq metadata"
|
457
270
|
end
|
458
271
|
|
459
272
|
context "without an error" do
|
@@ -465,7 +278,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
465
278
|
|
466
279
|
transaction_hash = transaction.to_h
|
467
280
|
expect(transaction_hash).to include(
|
468
|
-
"id" =>
|
281
|
+
"id" => jid,
|
469
282
|
"action" => "TestClass#perform",
|
470
283
|
"error" => nil,
|
471
284
|
"metadata" => {
|
@@ -487,8 +300,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
487
300
|
)
|
488
301
|
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
489
302
|
end
|
490
|
-
|
491
|
-
include_examples "sidekiq metadata"
|
492
303
|
end
|
493
304
|
|
494
305
|
def perform_job
|
@@ -516,247 +327,184 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
516
327
|
end
|
517
328
|
end
|
518
329
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
330
|
+
if DependencyHelper.active_job_present?
|
331
|
+
require "active_job"
|
332
|
+
require "action_mailer"
|
333
|
+
require "sidekiq/testing"
|
334
|
+
|
335
|
+
describe "Sidekiq ActiveJob integration" do
|
336
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
337
|
+
let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
|
338
|
+
let(:log) { StringIO.new }
|
339
|
+
let(:given_args) do
|
340
|
+
[
|
341
|
+
"foo",
|
342
|
+
{
|
343
|
+
:foo => "Foo",
|
344
|
+
"bar" => "Bar",
|
345
|
+
"baz" => { "1" => "foo" }
|
346
|
+
}
|
347
|
+
]
|
528
348
|
end
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
349
|
+
let(:expected_args) do
|
350
|
+
[
|
351
|
+
"foo",
|
352
|
+
{
|
353
|
+
"_aj_symbol_keys" => ["foo"],
|
354
|
+
"foo" => "Foo",
|
355
|
+
"bar" => "Bar",
|
356
|
+
"baz" => {
|
357
|
+
"_aj_symbol_keys" => [],
|
358
|
+
"1" => "foo"
|
359
|
+
}
|
360
|
+
}
|
361
|
+
]
|
534
362
|
end
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
class Sidekiq
|
541
|
-
def self.middlewares
|
542
|
-
@middlewares ||= Set.new
|
543
|
-
end
|
544
|
-
|
545
|
-
def self.configure_server
|
546
|
-
yield self
|
547
|
-
end
|
548
|
-
|
549
|
-
def self.server_middleware
|
550
|
-
yield middlewares
|
363
|
+
let(:expected_tags) do
|
364
|
+
{}.tap do |hash|
|
365
|
+
hash["active_job_id"] = kind_of(String)
|
366
|
+
if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
|
367
|
+
hash["provider_job_id"] = kind_of(String)
|
551
368
|
end
|
552
369
|
end
|
553
370
|
end
|
554
|
-
after { Object.send(:remove_const, "Sidekiq") }
|
555
|
-
|
556
|
-
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
|
557
|
-
described_class.new.install
|
558
|
-
|
559
|
-
expect(Sidekiq.middlewares).to include(Appsignal::Hooks::SidekiqPlugin)
|
560
|
-
end
|
561
|
-
end
|
562
|
-
end
|
563
|
-
|
564
|
-
describe Appsignal::Hooks::SidekiqProbe do
|
565
|
-
describe "#call" do
|
566
|
-
let(:probe) { described_class.new }
|
567
|
-
let(:redis_hostname) { "localhost" }
|
568
|
-
let(:expected_default_tags) { { :hostname => "localhost" } }
|
569
371
|
before do
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
{
|
574
|
-
"connected_clients" => 2,
|
575
|
-
"used_memory" => 1024,
|
576
|
-
"used_memory_rss" => 512
|
577
|
-
}
|
578
|
-
end
|
579
|
-
|
580
|
-
def self.redis
|
581
|
-
yield Client.new
|
582
|
-
end
|
583
|
-
|
584
|
-
class Client
|
585
|
-
def connection
|
586
|
-
{ :host => "localhost" }
|
587
|
-
end
|
588
|
-
end
|
589
|
-
|
590
|
-
class Stats
|
591
|
-
class << self
|
592
|
-
attr_reader :calls
|
593
|
-
|
594
|
-
def count_call
|
595
|
-
@calls ||= -1
|
596
|
-
@calls += 1
|
597
|
-
end
|
598
|
-
end
|
599
|
-
|
600
|
-
def workers_size
|
601
|
-
# First method called, so count it towards a call
|
602
|
-
self.class.count_call
|
603
|
-
24
|
604
|
-
end
|
605
|
-
|
606
|
-
def processes_size
|
607
|
-
25
|
608
|
-
end
|
609
|
-
|
610
|
-
# Return two different values for two separate calls.
|
611
|
-
# This allows us to test the delta of the value send as a gauge.
|
612
|
-
def processed
|
613
|
-
[10, 15][self.class.calls]
|
614
|
-
end
|
615
|
-
|
616
|
-
# Return two different values for two separate calls.
|
617
|
-
# This allows us to test the delta of the value send as a gauge.
|
618
|
-
def failed
|
619
|
-
[10, 13][self.class.calls]
|
620
|
-
end
|
372
|
+
start_agent
|
373
|
+
Appsignal.logger = test_logger(log)
|
374
|
+
ActiveJob::Base.queue_adapter = :sidekiq
|
621
375
|
|
622
|
-
|
623
|
-
|
624
|
-
end
|
376
|
+
class ActiveJobSidekiqTestJob < ActiveJob::Base
|
377
|
+
self.queue_adapter = :sidekiq
|
625
378
|
|
626
|
-
|
627
|
-
# This allows us to test the delta of the value send as a gauge.
|
628
|
-
def dead_size
|
629
|
-
[10, 12][self.class.calls]
|
630
|
-
end
|
631
|
-
|
632
|
-
def scheduled_size
|
633
|
-
14
|
634
|
-
end
|
635
|
-
|
636
|
-
def enqueued
|
637
|
-
15
|
638
|
-
end
|
379
|
+
def perform(*_args)
|
639
380
|
end
|
381
|
+
end
|
640
382
|
|
641
|
-
|
642
|
-
|
383
|
+
class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
|
384
|
+
self.queue_adapter = :sidekiq
|
643
385
|
|
644
|
-
|
645
|
-
|
646
|
-
Queue.new("default", 10, 12),
|
647
|
-
Queue.new("critical", 1, 2)
|
648
|
-
]
|
649
|
-
end
|
386
|
+
def perform(*_args)
|
387
|
+
raise "uh oh"
|
650
388
|
end
|
651
389
|
end
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
class Redis; end
|
658
|
-
Redis.const_set(:VERSION, version)
|
390
|
+
# Manually add the AppSignal middleware for the Testing environment.
|
391
|
+
# It doesn't use configured middlewares by default looks like.
|
392
|
+
# We test somewhere else if the middleware is installed properly.
|
393
|
+
Sidekiq::Testing.server_middleware do |chain|
|
394
|
+
chain.add Appsignal::Hooks::SidekiqPlugin
|
659
395
|
end
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
it "does not start probe" do
|
666
|
-
expect(described_class.dependencies_present?).to be_falsy
|
396
|
+
end
|
397
|
+
around do |example|
|
398
|
+
keep_transactions do
|
399
|
+
Sidekiq::Testing.fake! do
|
400
|
+
example.run
|
667
401
|
end
|
668
402
|
end
|
403
|
+
end
|
404
|
+
after do
|
405
|
+
Object.send(:remove_const, :ActiveJobSidekiqTestJob)
|
406
|
+
Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
|
407
|
+
end
|
669
408
|
|
670
|
-
|
671
|
-
|
409
|
+
it "reports the transaction from the ActiveJob integration" do
|
410
|
+
perform_job(ActiveJobSidekiqTestJob, given_args)
|
672
411
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
412
|
+
transaction = last_transaction
|
413
|
+
transaction_hash = transaction.to_h
|
414
|
+
expect(transaction_hash).to include(
|
415
|
+
"action" => "ActiveJobSidekiqTestJob#perform",
|
416
|
+
"error" => nil,
|
417
|
+
"namespace" => namespace,
|
418
|
+
"metadata" => hash_including(
|
419
|
+
"queue" => "default"
|
420
|
+
),
|
421
|
+
"sample_data" => hash_including(
|
422
|
+
"environment" => {},
|
423
|
+
"params" => [expected_args],
|
424
|
+
"tags" => expected_tags.merge("queue" => "default")
|
425
|
+
)
|
426
|
+
)
|
427
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
428
|
+
events = transaction_hash["events"]
|
429
|
+
.sort_by { |e| e["start"] }
|
430
|
+
.map { |event| event["name"] }
|
431
|
+
expect(events)
|
432
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
677
433
|
end
|
678
434
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
435
|
+
context "with error" do
|
436
|
+
it "reports the error on the transaction from the ActiveRecord integration" do
|
437
|
+
expect do
|
438
|
+
perform_job(ActiveJobSidekiqErrorTestJob, given_args)
|
439
|
+
end.to raise_error(RuntimeError, "uh oh")
|
684
440
|
|
685
|
-
|
686
|
-
|
687
|
-
|
441
|
+
transaction = last_transaction
|
442
|
+
transaction_hash = transaction.to_h
|
443
|
+
expect(transaction_hash).to include(
|
444
|
+
"action" => "ActiveJobSidekiqErrorTestJob#perform",
|
445
|
+
"error" => {
|
446
|
+
"name" => "RuntimeError",
|
447
|
+
"message" => "uh oh",
|
448
|
+
"backtrace" => kind_of(String)
|
449
|
+
},
|
450
|
+
"namespace" => namespace,
|
451
|
+
"metadata" => hash_including(
|
452
|
+
"queue" => "default"
|
453
|
+
),
|
454
|
+
"sample_data" => hash_including(
|
455
|
+
"environment" => {},
|
456
|
+
"params" => [expected_args],
|
457
|
+
"tags" => expected_tags.merge("queue" => "default")
|
458
|
+
)
|
459
|
+
)
|
460
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
461
|
+
events = transaction_hash["events"]
|
462
|
+
.sort_by { |e| e["start"] }
|
463
|
+
.map { |event| event["name"] }
|
464
|
+
expect(events)
|
465
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
466
|
+
end
|
688
467
|
end
|
689
468
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
:debug,
|
694
|
-
%(Sidekiq probe: Using Redis server hostname "localhost" as hostname)
|
695
|
-
)
|
696
|
-
log = capture_logs { probe.call }
|
697
|
-
# Match more logs with incompelete message
|
698
|
-
expect(log).to_not contains_log(:debug, %(Sidekiq probe: ))
|
699
|
-
end
|
700
|
-
|
701
|
-
it "collects custom metrics" do
|
702
|
-
expect_gauge("worker_count", 24).twice
|
703
|
-
expect_gauge("process_count", 25).twice
|
704
|
-
expect_gauge("connection_count", 2).twice
|
705
|
-
expect_gauge("memory_usage", 1024).twice
|
706
|
-
expect_gauge("memory_usage_rss", 512).twice
|
707
|
-
expect_gauge("job_count", 5, :status => :processed) # Gauge delta
|
708
|
-
expect_gauge("job_count", 3, :status => :failed) # Gauge delta
|
709
|
-
expect_gauge("job_count", 12, :status => :retry_queue).twice
|
710
|
-
expect_gauge("job_count", 2, :status => :died) # Gauge delta
|
711
|
-
expect_gauge("job_count", 14, :status => :scheduled).twice
|
712
|
-
expect_gauge("job_count", 15, :status => :enqueued).twice
|
713
|
-
expect_gauge("queue_length", 10, :queue => "default").twice
|
714
|
-
expect_gauge("queue_latency", 12_000, :queue => "default").twice
|
715
|
-
expect_gauge("queue_length", 1, :queue => "critical").twice
|
716
|
-
expect_gauge("queue_latency", 2_000, :queue => "critical").twice
|
717
|
-
# Call probe twice so we can calculate the delta for some gauge values
|
718
|
-
probe.call
|
719
|
-
probe.call
|
720
|
-
end
|
721
|
-
|
722
|
-
context "when `redis_info` is not defined" do
|
469
|
+
context "with ActionMailer" do
|
470
|
+
include ActionMailerHelpers
|
471
|
+
|
723
472
|
before do
|
724
|
-
|
473
|
+
class ActionMailerSidekiqTestJob < ActionMailer::Base
|
474
|
+
def welcome(*args)
|
475
|
+
end
|
476
|
+
end
|
725
477
|
end
|
726
478
|
|
727
|
-
it "
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
479
|
+
it "reports ActionMailer data on the transaction" do
|
480
|
+
perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
|
481
|
+
|
482
|
+
transaction = last_transaction
|
483
|
+
transaction_hash = transaction.to_h
|
484
|
+
expect(transaction_hash).to include(
|
485
|
+
"action" => "ActionMailerSidekiqTestJob#welcome",
|
486
|
+
"sample_data" => hash_including(
|
487
|
+
"params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
|
488
|
+
)
|
489
|
+
)
|
732
490
|
end
|
733
491
|
end
|
734
492
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
log = capture_logs { probe }
|
742
|
-
expect(log).to contains_log(
|
743
|
-
:debug,
|
744
|
-
%(Initializing Sidekiq probe with config: {:hostname=>"#{redis_hostname}"})
|
745
|
-
)
|
746
|
-
log = capture_logs { probe.call }
|
747
|
-
expect(log).to contains_log(
|
748
|
-
:debug,
|
749
|
-
"Sidekiq probe: Using hostname config option #{redis_hostname.inspect} as hostname"
|
750
|
-
)
|
751
|
-
expect(Appsignal).to have_received(:set_gauge)
|
752
|
-
.with(anything, anything, :hostname => redis_hostname).at_least(:once)
|
493
|
+
def perform_sidekiq
|
494
|
+
Timecop.freeze(time) do
|
495
|
+
yield
|
496
|
+
# Combined with Sidekiq::Testing.fake! and drain_all we get a
|
497
|
+
# enqueue_at in the job data.
|
498
|
+
Sidekiq::Worker.drain_all
|
753
499
|
end
|
754
500
|
end
|
755
501
|
|
756
|
-
def
|
757
|
-
|
758
|
-
|
759
|
-
|
502
|
+
def perform_job(job_class, args)
|
503
|
+
perform_sidekiq { job_class.perform_later(args) }
|
504
|
+
end
|
505
|
+
|
506
|
+
def perform_mailer(mailer, method, args = nil)
|
507
|
+
perform_sidekiq { perform_action_mailer(mailer, method, args) }
|
760
508
|
end
|
761
509
|
end
|
762
510
|
end
|