appsignal 2.10.8-java → 2.11.0.beta.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 +3 -0
- data/.semaphore/semaphore.yml +75 -61
- data/CHANGELOG.md +21 -0
- data/build_matrix.yml +13 -7
- data/ext/agent.yml +19 -19
- data/ext/appsignal_extension.c +10 -1
- data/ext/base.rb +11 -2
- 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.rb +21 -1
- data/lib/appsignal/capistrano.rb +2 -0
- data/lib/appsignal/config.rb +6 -2
- data/lib/appsignal/environment.rb +126 -0
- data/lib/appsignal/extension/jruby.rb +10 -0
- data/lib/appsignal/hooks.rb +2 -0
- data/lib/appsignal/hooks/active_job.rb +89 -0
- data/lib/appsignal/hooks/net_http.rb +2 -0
- data/lib/appsignal/hooks/puma.rb +2 -58
- data/lib/appsignal/hooks/redis.rb +2 -0
- data/lib/appsignal/hooks/resque.rb +60 -0
- data/lib/appsignal/hooks/sequel.rb +2 -0
- data/lib/appsignal/hooks/sidekiq.rb +18 -191
- data/lib/appsignal/integrations/object.rb +4 -0
- 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 -24
- 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 +32 -7
- 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/cli/diagnose_spec.rb +2 -1
- data/spec/lib/appsignal/config_spec.rb +6 -1
- data/spec/lib/appsignal/environment_spec.rb +167 -0
- data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
- 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 +292 -546
- 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 -137
- 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 +35 -20
- data/spec/lib/appsignal_spec.rb +22 -0
- 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 +12 -0
- data/spec/support/helpers/env_helpers.rb +1 -1
- data/spec/support/helpers/environment_metdata_helper.rb +16 -0
- data/spec/support/helpers/transaction_helpers.rb +6 -0
- data/spec/support/stubs/sidekiq/api.rb +2 -2
- metadata +25 -5
@@ -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
|
|
@@ -48,369 +99,132 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
48
99
|
expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
|
49
100
|
end
|
50
101
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
perform_job
|
102
|
+
describe "internal Sidekiq job values" do
|
103
|
+
it "does not save internal Sidekiq values as metadata on transaction" do
|
104
|
+
perform_job
|
55
105
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
106
|
+
transaction_hash = transaction.to_h
|
107
|
+
expect(transaction_hash["metadata"].keys)
|
108
|
+
.to_not include(*Appsignal::Hooks::SidekiqPlugin::EXCLUDED_JOB_KEYS)
|
60
109
|
end
|
110
|
+
end
|
61
111
|
|
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
|
112
|
+
context "with parameter filtering" do
|
113
|
+
before do
|
114
|
+
Appsignal.config = project_fixture_config("production")
|
115
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
83
116
|
end
|
84
117
|
|
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
|
118
|
+
it "filters selected arguments" do
|
119
|
+
perform_job
|
93
120
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
121
|
+
transaction_hash = transaction.to_h
|
122
|
+
expect(transaction_hash["sample_data"]).to include(
|
123
|
+
"params" => [
|
124
|
+
"foo",
|
125
|
+
{
|
126
|
+
"foo" => "[FILTERED]",
|
127
|
+
"bar" => "Bar",
|
128
|
+
"baz" => { "1" => "foo" }
|
129
|
+
}
|
130
|
+
]
|
131
|
+
)
|
99
132
|
end
|
133
|
+
end
|
100
134
|
|
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
|
135
|
+
context "with encrypted arguments" do
|
136
|
+
before do
|
137
|
+
item["encrypt"] = true
|
138
|
+
item["args"] << "super secret value" # Last argument will be replaced
|
139
139
|
end
|
140
140
|
|
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
|
141
|
+
it "replaces the last argument (the secret bag) with an [encrypted data] string" do
|
142
|
+
perform_job
|
159
143
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
144
|
+
transaction_hash = transaction.to_h
|
145
|
+
expect(transaction_hash["sample_data"]).to include(
|
146
|
+
"params" => expected_args << "[encrypted data]"
|
147
|
+
)
|
148
|
+
end
|
149
|
+
end
|
166
150
|
|
167
|
-
|
168
|
-
|
151
|
+
context "when using the Sidekiq delayed extension" do
|
152
|
+
let(:item) do
|
153
|
+
{
|
154
|
+
"jid" => "efb140489485999d32b5504c",
|
155
|
+
"class" => "Sidekiq::Extensions::DelayedClass",
|
156
|
+
"queue" => "default",
|
157
|
+
"args" => [
|
158
|
+
"---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
|
159
|
+
],
|
160
|
+
"retry" => true,
|
161
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
162
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
163
|
+
"extra" => "data"
|
164
|
+
}
|
165
|
+
end
|
169
166
|
|
170
|
-
|
171
|
-
|
167
|
+
it "uses the delayed class and method name for the action" do
|
168
|
+
perform_job
|
172
169
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end
|
170
|
+
transaction_hash = transaction.to_h
|
171
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
|
172
|
+
expect(transaction_hash["sample_data"]).to include(
|
173
|
+
"params" => ["bar" => "baz"]
|
174
|
+
)
|
179
175
|
end
|
180
176
|
|
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
|
177
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
178
|
+
before { item["args"] = [] }
|
201
179
|
|
202
|
-
it "
|
180
|
+
it "logs a warning and uses the default argument" do
|
203
181
|
perform_job
|
204
182
|
|
205
183
|
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
|
184
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
|
185
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
186
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
389
187
|
end
|
390
188
|
end
|
391
189
|
end
|
392
190
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
191
|
+
context "when using the Sidekiq ActiveRecord instance delayed extension" do
|
192
|
+
let(:item) do
|
193
|
+
{
|
194
|
+
"jid" => "efb140489485999d32b5504c",
|
195
|
+
"class" => "Sidekiq::Extensions::DelayedModel",
|
196
|
+
"queue" => "default",
|
197
|
+
"args" => [
|
198
|
+
"---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
|
199
|
+
],
|
200
|
+
"retry" => true,
|
201
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
202
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
203
|
+
"extra" => "data"
|
204
|
+
}
|
397
205
|
end
|
398
206
|
|
399
|
-
it "
|
207
|
+
it "uses the delayed class and method name for the action" do
|
208
|
+
perform_job
|
209
|
+
|
400
210
|
transaction_hash = transaction.to_h
|
401
|
-
expect(transaction_hash).to
|
402
|
-
|
403
|
-
|
404
|
-
"params" => [],
|
405
|
-
"tags" => {}
|
406
|
-
}
|
211
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
|
212
|
+
expect(transaction_hash["sample_data"]).to include(
|
213
|
+
"params" => ["bar" => "baz"]
|
407
214
|
)
|
408
215
|
end
|
409
216
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
217
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
218
|
+
before { item["args"] = [] }
|
219
|
+
|
220
|
+
it "logs a warning and uses the default argument" do
|
221
|
+
perform_job
|
222
|
+
|
223
|
+
transaction_hash = transaction.to_h
|
224
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
|
225
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
226
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
227
|
+
end
|
414
228
|
end
|
415
229
|
end
|
416
230
|
|
@@ -452,8 +266,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
452
266
|
)
|
453
267
|
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
454
268
|
end
|
455
|
-
|
456
|
-
include_examples "sidekiq metadata"
|
457
269
|
end
|
458
270
|
|
459
271
|
context "without an error" do
|
@@ -487,8 +299,6 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
487
299
|
)
|
488
300
|
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
489
301
|
end
|
490
|
-
|
491
|
-
include_examples "sidekiq metadata"
|
492
302
|
end
|
493
303
|
|
494
304
|
def perform_job
|
@@ -516,247 +326,183 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
516
326
|
end
|
517
327
|
end
|
518
328
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
329
|
+
if DependencyHelper.active_job_present?
|
330
|
+
require "active_job"
|
331
|
+
require "action_mailer"
|
332
|
+
require "sidekiq/testing"
|
333
|
+
|
334
|
+
describe "Sidekiq ActiveJob integration" do
|
335
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
336
|
+
let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
|
337
|
+
let(:log) { StringIO.new }
|
338
|
+
let(:given_args) do
|
339
|
+
[
|
340
|
+
"foo",
|
341
|
+
{
|
342
|
+
:foo => "Foo",
|
343
|
+
"bar" => "Bar",
|
344
|
+
"baz" => { "1" => "foo" }
|
345
|
+
}
|
346
|
+
]
|
528
347
|
end
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
348
|
+
let(:expected_args) do
|
349
|
+
[
|
350
|
+
"foo",
|
351
|
+
{
|
352
|
+
"_aj_symbol_keys" => ["foo"],
|
353
|
+
"foo" => "Foo",
|
354
|
+
"bar" => "Bar",
|
355
|
+
"baz" => {
|
356
|
+
"_aj_symbol_keys" => [],
|
357
|
+
"1" => "foo"
|
358
|
+
}
|
359
|
+
}
|
360
|
+
]
|
534
361
|
end
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
Appsignal.config = project_fixture_config
|
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
|
362
|
+
let(:expected_tags) do
|
363
|
+
{}.tap do |hash|
|
364
|
+
if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
|
365
|
+
hash["provider_job_id"] = kind_of(String)
|
551
366
|
end
|
552
367
|
end
|
553
368
|
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
369
|
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
|
370
|
+
start_agent
|
371
|
+
Appsignal.logger = test_logger(log)
|
372
|
+
ActiveJob::Base.queue_adapter = :sidekiq
|
621
373
|
|
622
|
-
|
623
|
-
|
624
|
-
end
|
374
|
+
class ActiveJobSidekiqTestJob < ActiveJob::Base
|
375
|
+
self.queue_adapter = :sidekiq
|
625
376
|
|
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
|
377
|
+
def perform(*_args)
|
639
378
|
end
|
379
|
+
end
|
640
380
|
|
641
|
-
|
642
|
-
|
381
|
+
class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
|
382
|
+
self.queue_adapter = :sidekiq
|
643
383
|
|
644
|
-
|
645
|
-
|
646
|
-
Queue.new("default", 10, 12),
|
647
|
-
Queue.new("critical", 1, 2)
|
648
|
-
]
|
649
|
-
end
|
384
|
+
def perform(*_args)
|
385
|
+
raise "uh oh"
|
650
386
|
end
|
651
387
|
end
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
class Redis; end
|
658
|
-
Redis.const_set(:VERSION, version)
|
388
|
+
# Manually add the AppSignal middleware for the Testing environment.
|
389
|
+
# It doesn't use configured middlewares by default looks like.
|
390
|
+
# We test somewhere else if the middleware is installed properly.
|
391
|
+
Sidekiq::Testing.server_middleware do |chain|
|
392
|
+
chain.add Appsignal::Hooks::SidekiqPlugin
|
659
393
|
end
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
it "does not start probe" do
|
666
|
-
expect(described_class.dependencies_present?).to be_falsy
|
394
|
+
end
|
395
|
+
around do |example|
|
396
|
+
keep_transactions do
|
397
|
+
Sidekiq::Testing.fake! do
|
398
|
+
example.run
|
667
399
|
end
|
668
400
|
end
|
401
|
+
end
|
402
|
+
after do
|
403
|
+
Object.send(:remove_const, :ActiveJobSidekiqTestJob)
|
404
|
+
Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
|
405
|
+
end
|
669
406
|
|
670
|
-
|
671
|
-
|
407
|
+
it "reports the transaction from the ActiveJob integration" do
|
408
|
+
perform_job(ActiveJobSidekiqTestJob, given_args)
|
672
409
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
410
|
+
transaction = last_transaction
|
411
|
+
transaction_hash = transaction.to_h
|
412
|
+
expect(transaction_hash).to include(
|
413
|
+
"action" => "ActiveJobSidekiqTestJob#perform",
|
414
|
+
"error" => nil,
|
415
|
+
"namespace" => namespace,
|
416
|
+
"metadata" => hash_including(
|
417
|
+
"queue" => "default"
|
418
|
+
),
|
419
|
+
"sample_data" => hash_including(
|
420
|
+
"environment" => {},
|
421
|
+
"params" => [expected_args],
|
422
|
+
"tags" => expected_tags.merge("queue" => "default")
|
423
|
+
)
|
424
|
+
)
|
425
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
426
|
+
events = transaction_hash["events"]
|
427
|
+
.sort_by { |e| e["start"] }
|
428
|
+
.map { |event| event["name"] }
|
429
|
+
expect(events)
|
430
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
677
431
|
end
|
678
432
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
433
|
+
context "with error" do
|
434
|
+
it "reports the error on the transaction from the ActiveRecord integration" do
|
435
|
+
expect do
|
436
|
+
perform_job(ActiveJobSidekiqErrorTestJob, given_args)
|
437
|
+
end.to raise_error(RuntimeError, "uh oh")
|
684
438
|
|
685
|
-
|
686
|
-
|
687
|
-
|
439
|
+
transaction = last_transaction
|
440
|
+
transaction_hash = transaction.to_h
|
441
|
+
expect(transaction_hash).to include(
|
442
|
+
"action" => "ActiveJobSidekiqErrorTestJob#perform",
|
443
|
+
"error" => {
|
444
|
+
"name" => "RuntimeError",
|
445
|
+
"message" => "uh oh",
|
446
|
+
"backtrace" => kind_of(String)
|
447
|
+
},
|
448
|
+
"namespace" => namespace,
|
449
|
+
"metadata" => hash_including(
|
450
|
+
"queue" => "default"
|
451
|
+
),
|
452
|
+
"sample_data" => hash_including(
|
453
|
+
"environment" => {},
|
454
|
+
"params" => [expected_args],
|
455
|
+
"tags" => expected_tags.merge("queue" => "default")
|
456
|
+
)
|
457
|
+
)
|
458
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
459
|
+
events = transaction_hash["events"]
|
460
|
+
.sort_by { |e| e["start"] }
|
461
|
+
.map { |event| event["name"] }
|
462
|
+
expect(events)
|
463
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
464
|
+
end
|
688
465
|
end
|
689
466
|
|
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
|
467
|
+
context "with ActionMailer" do
|
468
|
+
include ActionMailerHelpers
|
469
|
+
|
723
470
|
before do
|
724
|
-
|
471
|
+
class ActionMailerSidekiqTestJob < ActionMailer::Base
|
472
|
+
def welcome(*args)
|
473
|
+
end
|
474
|
+
end
|
725
475
|
end
|
726
476
|
|
727
|
-
it "
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
477
|
+
it "reports ActionMailer data on the transaction" do
|
478
|
+
perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
|
479
|
+
|
480
|
+
transaction = last_transaction
|
481
|
+
transaction_hash = transaction.to_h
|
482
|
+
expect(transaction_hash).to include(
|
483
|
+
"action" => "ActionMailerSidekiqTestJob#welcome",
|
484
|
+
"sample_data" => hash_including(
|
485
|
+
"params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
|
486
|
+
)
|
487
|
+
)
|
732
488
|
end
|
733
489
|
end
|
734
490
|
|
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)
|
491
|
+
def perform_sidekiq
|
492
|
+
Timecop.freeze(time) do
|
493
|
+
yield
|
494
|
+
# Combined with Sidekiq::Testing.fake! and drain_all we get a
|
495
|
+
# enqueue_at in the job data.
|
496
|
+
Sidekiq::Worker.drain_all
|
753
497
|
end
|
754
498
|
end
|
755
499
|
|
756
|
-
def
|
757
|
-
|
758
|
-
|
759
|
-
|
500
|
+
def perform_job(job_class, args)
|
501
|
+
perform_sidekiq { job_class.perform_later(args) }
|
502
|
+
end
|
503
|
+
|
504
|
+
def perform_mailer(mailer, method, args = nil)
|
505
|
+
perform_sidekiq { perform_action_mailer(mailer, method, args) }
|
760
506
|
end
|
761
507
|
end
|
762
508
|
end
|