appsignal 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  require "yaml"
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.4.0".freeze
4
+ VERSION = "2.4.1".freeze
5
5
  end
@@ -155,6 +155,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :report => true do
155
155
  expect(output).to include \
156
156
  "Gem version: #{Appsignal::VERSION}",
157
157
  "Agent version: #{Appsignal::Extension.agent_version}",
158
+ "Agent platform: #{Appsignal::System.installed_agent_platform}",
158
159
  "Gem install path: #{gem_path}"
159
160
  end
160
161
 
@@ -164,6 +165,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :report => true do
164
165
  "language" => "ruby",
165
166
  "package_version" => Appsignal::VERSION,
166
167
  "agent_version" => Appsignal::Extension.agent_version,
168
+ "agent_platform" => Appsignal::System.installed_agent_platform,
167
169
  "package_install_path" => gem_path,
168
170
  "extension_loaded" => true
169
171
  }
@@ -0,0 +1,19 @@
1
+ describe Appsignal::Hooks::QueHook do
2
+ if DependencyHelper.que_present?
3
+ describe "#dependencies_present?" do
4
+ subject { described_class.new.dependencies_present? }
5
+
6
+ it { is_expected.to be_truthy }
7
+ end
8
+
9
+ it "installs the QuePlugin" do
10
+ expect(Que::Job.included_modules).to include(Appsignal::Integrations::QuePlugin)
11
+ end
12
+ else
13
+ describe "#dependencies_present?" do
14
+ subject { described_class.new.dependencies_present? }
15
+
16
+ it { is_expected.to be_falsy }
17
+ end
18
+ end
19
+ end
@@ -1,259 +1,419 @@
1
- describe Appsignal::Hooks::SidekiqPlugin do
2
- let(:worker) { double }
3
- let(:queue) { double }
4
- let(:current_transaction) { background_job_transaction }
5
- let(:args) { ["Model", 1] }
1
+ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
2
+ class DelayedTestClass; end
3
+
4
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
5
+ let(:worker) { anything }
6
+ let(:queue) { anything }
7
+ let(:given_args) do
8
+ [
9
+ "foo",
10
+ {
11
+ :foo => "Foo",
12
+ :bar => "Bar",
13
+ "baz" => { 1 => :foo }
14
+ }
15
+ ]
16
+ end
17
+ let(:expected_args) do
18
+ [
19
+ "foo",
20
+ {
21
+ "foo" => "Foo",
22
+ "bar" => "Bar",
23
+ "baz" => { "1" => "foo" }
24
+ }
25
+ ]
26
+ end
27
+ let(:job_class) { "TestClass" }
6
28
  let(:item) do
7
29
  {
8
- "class" => "TestClass",
30
+ "jid" => "b4a577edbccf1d805744efa9",
31
+ "class" => job_class,
9
32
  "retry_count" => 0,
10
33
  "queue" => "default",
11
- "enqueued_at" => Time.parse("01-01-2001 10:00:00UTC"),
12
- "args" => args,
34
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
35
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
36
+ "args" => given_args,
13
37
  "extra" => "data"
14
38
  }
15
39
  end
16
40
  let(:plugin) { Appsignal::Hooks::SidekiqPlugin.new }
17
-
41
+ let(:test_store) { {} }
42
+ let(:log) { StringIO.new }
18
43
  before do
19
- allow(Appsignal::Transaction).to receive(:current).and_return(current_transaction)
20
44
  start_agent
45
+ Appsignal.logger = test_logger(log)
46
+
47
+ # Stub calls to extension, because that would remove the transaction
48
+ # from the extension.
49
+ allow_any_instance_of(Appsignal::Extension::Transaction).to receive(:finish).and_return(true)
50
+ allow_any_instance_of(Appsignal::Extension::Transaction).to receive(:complete)
51
+
52
+ # Stub removal of current transaction from current thread so we can fetch
53
+ # it later.
54
+ expect(Appsignal::Transaction).to receive(:clear_current_transaction!) do
55
+ transaction = Thread.current[:appsignal_transaction]
56
+ test_store[:transaction] = transaction if transaction
57
+ end
58
+ end
59
+ after :with_yaml_parse_error => false do
60
+ expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
21
61
  end
62
+ after { clear_current_transaction! }
22
63
 
23
- context "with a performance call" do
24
- after do
25
- Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
26
- Appsignal::Hooks::SidekiqPlugin.new.call(worker, item, queue) do
27
- # nothing
28
- end
64
+ shared_examples "sidekiq metadata" do
65
+ describe "internal Sidekiq job values" do
66
+ it "does not save internal Sidekiq values as metadata on transaction" do
67
+ perform_job
68
+
69
+ transaction_hash = transaction.to_h
70
+ expect(transaction_hash["metadata"].keys)
71
+ .to_not include(*Appsignal::Hooks::SidekiqPlugin::JOB_KEYS)
29
72
  end
30
73
  end
31
74
 
32
- it "wraps it in a transaction with the correct params" do
33
- expect(Appsignal).to receive(:monitor_transaction).with(
34
- "perform_job.sidekiq",
35
- :class => "TestClass",
36
- :method => "perform",
37
- :metadata => {
38
- "retry_count" => "0",
39
- "queue" => "default",
40
- "extra" => "data"
41
- },
42
- :params => ["Model", 1],
43
- :queue_start => Time.parse("01-01-2001 10:00:00UTC"),
44
- :queue_time => 60_000.to_f
45
- )
75
+ context "with parameter filtering" do
76
+ before do
77
+ Appsignal.config = project_fixture_config("production")
78
+ Appsignal.config[:filter_parameters] = ["foo"]
79
+ end
80
+
81
+ it "filters selected arguments" do
82
+ perform_job
83
+
84
+ transaction_hash = transaction.to_h
85
+ expect(transaction_hash["sample_data"]).to include(
86
+ "params" => [
87
+ "foo",
88
+ {
89
+ "foo" => "[FILTERED]",
90
+ "bar" => "Bar",
91
+ "baz" => { "1" => "foo" }
92
+ }
93
+ ]
94
+ )
95
+ end
46
96
  end
47
97
 
48
- context "with more complex arguments" do
49
- let(:default_params) do
50
- {
51
- :class => "TestClass",
52
- :method => "perform",
53
- :metadata => {
54
- "retry_count" => "0",
55
- "queue" => "default",
56
- "extra" => "data"
57
- },
58
- :params => {
59
- :foo => "Foo",
60
- :bar => "Bar"
61
- },
62
- :queue_start => Time.parse("01-01-2001 10:00:00UTC"),
63
- :queue_time => 60_000.to_f
64
- }
98
+ context "with encrypted arguments" do
99
+ before do
100
+ item["encrypt"] = true
101
+ item["args"] << "super secret value" # Last argument will be replaced
65
102
  end
66
- let(:args) do
103
+
104
+ it "replaces the last argument (the secret bag) with an [encrypted data] string" do
105
+ perform_job
106
+
107
+ transaction_hash = transaction.to_h
108
+ expect(transaction_hash["sample_data"]).to include(
109
+ "params" => expected_args << "[encrypted data]"
110
+ )
111
+ end
112
+ end
113
+
114
+ context "when using the Sidekiq delayed extension" do
115
+ let(:item) do
67
116
  {
68
- :foo => "Foo",
69
- :bar => "Bar"
117
+ "jid" => "efb140489485999d32b5504c",
118
+ "class" => "Sidekiq::Extensions::DelayedClass",
119
+ "queue" => "default",
120
+ "args" => [
121
+ "---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
122
+ ],
123
+ "retry" => true,
124
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
125
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
126
+ "extra" => "data"
70
127
  }
71
128
  end
72
129
 
73
- it "adds the more complex arguments" do
74
- expect(Appsignal).to receive(:monitor_transaction).with(
75
- "perform_job.sidekiq",
76
- default_params.merge(
77
- :params => {
78
- :foo => "Foo",
79
- :bar => "Bar"
80
- }
81
- )
130
+ it "uses the delayed class and method name for the action" do
131
+ perform_job
132
+
133
+ transaction_hash = transaction.to_h
134
+ expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
135
+ expect(transaction_hash["sample_data"]).to include(
136
+ "params" => ["bar" => "baz"]
82
137
  )
83
138
  end
84
139
 
85
- context "with parameter filtering" do
86
- before do
87
- Appsignal.config = project_fixture_config("production")
88
- Appsignal.config[:filter_parameters] = ["foo"]
89
- end
140
+ context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
141
+ before { item["args"] = [] }
90
142
 
91
- it "filters selected arguments" do
92
- expect(Appsignal).to receive(:monitor_transaction).with(
93
- "perform_job.sidekiq",
94
- default_params.merge(
95
- :params => {
96
- :foo => "[FILTERED]",
97
- :bar => "Bar"
98
- }
99
- )
100
- )
143
+ it "logs a warning and uses the default argument" do
144
+ perform_job
145
+
146
+ transaction_hash = transaction.to_h
147
+ expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
148
+ expect(transaction_hash["sample_data"]).to include("params" => [])
149
+ expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
101
150
  end
102
151
  end
103
152
  end
104
153
 
105
- context "when wrapped by ActiveJob" do
154
+ context "when using ActiveJob" do
106
155
  let(:item) do
107
156
  {
108
157
  "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
109
- "wrapped" => "TestClass",
158
+ "wrapped" => "ActiveJobTestClass",
110
159
  "queue" => "default",
111
160
  "args" => [{
112
- "job_class" => "TestJob",
161
+ "job_class" => "ActiveJobTestJob",
113
162
  "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
114
163
  "queue_name" => "default",
115
- "arguments" => args
164
+ "arguments" => [
165
+ "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
166
+ ]
116
167
  }],
117
168
  "retry" => true,
118
169
  "jid" => "efb140489485999d32b5504c",
119
- "created_at" => Time.parse("01-01-2001 10:00:00UTC").to_f,
120
- "enqueued_at" => Time.parse("01-01-2001 10:00:00UTC").to_f
170
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
171
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
121
172
  }
122
173
  end
123
- let(:default_params) do
124
- {
125
- :class => "TestClass",
126
- :method => "perform",
127
- :metadata => {
174
+
175
+ it "creates a transaction with events" do
176
+ perform_job
177
+
178
+ transaction_hash = transaction.to_h
179
+ expect(transaction_hash).to include(
180
+ "id" => kind_of(String),
181
+ "action" => "ActiveJobTestClass#perform",
182
+ "error" => nil,
183
+ "namespace" => namespace,
184
+ "metadata" => {
128
185
  "queue" => "default"
129
186
  },
130
- :queue_start => Time.parse("01-01-2001 10:00:00UTC").to_f,
131
- :queue_time => 60_000.to_f
132
- }
133
- end
134
-
135
- it "wraps it in a transaction with the correct params" do
136
- expect(Appsignal).to receive(:monitor_transaction).with(
137
- "perform_job.sidekiq",
138
- default_params.merge(:params => ["Model", 1])
187
+ "sample_data" => {
188
+ "environment" => {},
189
+ "params" => [
190
+ "foo",
191
+ {
192
+ "foo" => "Foo",
193
+ "bar" => "Bar",
194
+ "baz" => { "1" => "bar" }
195
+ }
196
+ ],
197
+ "tags" => {}
198
+ }
139
199
  )
200
+ # TODO: Not available in transaction.to_h yet.
201
+ # https://github.com/appsignal/appsignal-agent/issues/293
202
+ expect(transaction.request.env).to eq(
203
+ :queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
204
+ )
205
+ expect_transaction_to_have_sidekiq_event(transaction_hash)
140
206
  end
141
207
 
142
- context "with more complex arguments" do
143
- let(:args) do
208
+ context "with ActionMailer job" do
209
+ let(:item) do
144
210
  {
145
- :foo => "Foo",
146
- :bar => "Bar"
211
+ "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
212
+ "wrapped" => "ActionMailer::DeliveryJob",
213
+ "queue" => "default",
214
+ "args" => [{
215
+ "job_class" => "ActiveMailerTestJob",
216
+ "job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
217
+ "queue_name" => "default",
218
+ "arguments" => [
219
+ "MailerClass", "mailer_method", "deliver_now",
220
+ "foo", { "foo" => "Foo", "bar" => "Bar", "baz" => { 1 => :bar } }
221
+ ]
222
+ }],
223
+ "retry" => true,
224
+ "jid" => "efb140489485999d32b5504c",
225
+ "created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
226
+ "enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f
147
227
  }
148
228
  end
149
229
 
150
- it "adds the more complex arguments" do
151
- expect(Appsignal).to receive(:monitor_transaction).with(
152
- "perform_job.sidekiq",
153
- default_params.merge(
154
- :params => {
155
- :foo => "Foo",
156
- :bar => "Bar"
157
- }
158
- )
230
+ it "creates a transaction for the ActionMailer class" do
231
+ perform_job
232
+
233
+ transaction_hash = transaction.to_h
234
+ expect(transaction_hash).to include(
235
+ "id" => kind_of(String),
236
+ "action" => "MailerClass#mailer_method",
237
+ "error" => nil,
238
+ "namespace" => namespace,
239
+ "metadata" => {
240
+ "queue" => "default"
241
+ },
242
+ "sample_data" => {
243
+ "environment" => {},
244
+ "params" => [
245
+ "foo",
246
+ {
247
+ "foo" => "Foo",
248
+ "bar" => "Bar",
249
+ "baz" => { "1" => "bar" }
250
+ }
251
+ ],
252
+ "tags" => {}
253
+ }
159
254
  )
160
255
  end
256
+ end
161
257
 
162
- context "with parameter filtering" do
163
- before do
164
- Appsignal.config = project_fixture_config("production")
165
- Appsignal.config[:filter_parameters] = ["foo"]
166
- end
167
-
168
- it "filters selected arguments" do
169
- expect(Appsignal).to receive(:monitor_transaction).with(
170
- "perform_job.sidekiq",
171
- default_params.merge(
172
- :params => {
173
- :foo => "[FILTERED]",
174
- :bar => "Bar"
175
- }
176
- )
177
- )
178
- end
258
+ context "with parameter filtering" do
259
+ before do
260
+ Appsignal.config = project_fixture_config("production")
261
+ Appsignal.config[:filter_parameters] = ["foo"]
262
+ end
263
+
264
+ it "filters selected arguments" do
265
+ perform_job
266
+
267
+ transaction_hash = transaction.to_h
268
+ expect(transaction_hash["sample_data"]).to include(
269
+ "params" => [
270
+ "foo",
271
+ {
272
+ "foo" => "[FILTERED]",
273
+ "bar" => "Bar",
274
+ "baz" => { "1" => "bar" }
275
+ }
276
+ ]
277
+ )
179
278
  end
180
279
  end
181
280
  end
182
281
  end
183
282
 
184
- context "with an erroring call" do
283
+ context "with an error" do
185
284
  let(:error) { ExampleException }
186
- let(:transaction) do
187
- Appsignal::Transaction.new(
188
- SecureRandom.uuid,
189
- Appsignal::Transaction::BACKGROUND_JOB,
190
- Appsignal::Transaction::GenericRequest.new({})
191
- )
192
- end
193
- before do
194
- allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
195
- expect(Appsignal::Transaction).to receive(:create)
196
- .with(
197
- kind_of(String),
198
- Appsignal::Transaction::BACKGROUND_JOB,
199
- kind_of(Appsignal::Transaction::GenericRequest)
200
- ).and_return(transaction)
201
- end
202
-
203
- it "adds the error to the transaction" do
204
- expect(transaction).to receive(:set_error).with(error)
205
- expect(transaction).to receive(:complete)
206
- end
207
285
 
208
- after do
286
+ it "creates a transaction and adds the error" do
209
287
  expect do
210
- Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
211
- Appsignal::Hooks::SidekiqPlugin.new.call(worker, item, queue) do
212
- raise error
213
- end
214
- end
288
+ perform_job { raise error, "uh oh" }
215
289
  end.to raise_error(error)
290
+
291
+ transaction_hash = transaction.to_h
292
+ expect(transaction_hash).to include(
293
+ "id" => kind_of(String),
294
+ "action" => "TestClass#perform",
295
+ "error" => {
296
+ "name" => "ExampleException",
297
+ "message" => "uh oh",
298
+ # TODO: backtrace should be an Array of Strings
299
+ # https://github.com/appsignal/appsignal-agent/issues/294
300
+ "backtrace" => kind_of(String)
301
+ },
302
+ "metadata" => {
303
+ "extra" => "data",
304
+ "queue" => "default",
305
+ "retry_count" => "0"
306
+ },
307
+ "namespace" => namespace,
308
+ "sample_data" => {
309
+ "environment" => {},
310
+ "params" => expected_args,
311
+ "tags" => {}
312
+ }
313
+ )
314
+ expect_transaction_to_have_sidekiq_event(transaction_hash)
216
315
  end
316
+
317
+ include_examples "sidekiq metadata"
217
318
  end
218
319
 
219
- # TODO: Don't test (what are basically) private methods
220
- describe "#formatted_data" do
221
- let(:item) do
222
- {
223
- "foo" => "bar",
224
- "class" => "TestClass"
225
- }
320
+ context "without an error" do
321
+ it "creates a transaction with events" do
322
+ perform_job
323
+
324
+ transaction_hash = transaction.to_h
325
+ expect(transaction_hash).to include(
326
+ "id" => kind_of(String),
327
+ "action" => "TestClass#perform",
328
+ "error" => nil,
329
+ "metadata" => {
330
+ "extra" => "data",
331
+ "queue" => "default",
332
+ "retry_count" => "0"
333
+ },
334
+ "namespace" => namespace,
335
+ "sample_data" => {
336
+ "environment" => {},
337
+ "params" => expected_args,
338
+ "tags" => {}
339
+ }
340
+ )
341
+ # TODO: Not available in transaction.to_h yet.
342
+ # https://github.com/appsignal/appsignal-agent/issues/293
343
+ expect(transaction.request.env).to eq(
344
+ :queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
345
+ )
346
+ expect_transaction_to_have_sidekiq_event(transaction_hash)
226
347
  end
227
348
 
228
- it "only adds items to the hash that do not appear in JOB_KEYS" do
229
- expect(plugin.formatted_metadata(item)).to eq("foo" => "bar")
349
+ include_examples "sidekiq metadata"
350
+ end
351
+
352
+ def perform_job
353
+ Timecop.freeze(Time.parse("2001-01-01 10:01:00UTC")) do
354
+ plugin.call(worker, item, queue) do
355
+ yield if block_given?
356
+ end
230
357
  end
231
358
  end
359
+
360
+ def transaction
361
+ test_store[:transaction]
362
+ end
363
+
364
+ def expect_transaction_to_have_sidekiq_event(transaction_hash)
365
+ events = transaction_hash["events"]
366
+ expect(events.count).to eq(1)
367
+ expect(events.first).to include(
368
+ "name" => "perform_job.sidekiq",
369
+ "title" => "",
370
+ "count" => 1,
371
+ "body" => "",
372
+ "body_format" => Appsignal::EventFormatter::DEFAULT
373
+ )
374
+ end
232
375
  end
233
376
 
234
377
  describe Appsignal::Hooks::SidekiqHook do
235
- context "with sidekiq" do
236
- before :context do
237
- module Sidekiq
238
- def self.configure_server
239
- end
240
- end
241
- Appsignal::Hooks::SidekiqHook.new.install
242
- end
243
- after(:context) { Object.send(:remove_const, :Sidekiq) }
378
+ describe "#dependencies_present?" do
379
+ subject { described_class.new.dependencies_present? }
244
380
 
245
- describe "#dependencies_present?" do
246
- subject { described_class.new.dependencies_present? }
381
+ context "when Sidekiq constant is found" do
382
+ before { Object.const_set("Sidekiq", 1) }
383
+ after { Object.send(:remove_const, "Sidekiq") }
247
384
 
248
385
  it { is_expected.to be_truthy }
249
386
  end
250
- end
251
387
 
252
- context "without sidekiq" do
253
- describe "#dependencies_present?" do
254
- subject { described_class.new.dependencies_present? }
388
+ context "when Sidekiq constant is not found" do
389
+ before { Object.send(:remove_const, "Sidekiq") if defined?(Sidekiq) }
255
390
 
256
391
  it { is_expected.to be_falsy }
257
392
  end
258
393
  end
394
+
395
+ describe "#install" do
396
+ before do
397
+ class Sidekiq
398
+ def self.middlewares
399
+ @middlewares ||= Set.new
400
+ end
401
+
402
+ def self.configure_server
403
+ yield self
404
+ end
405
+
406
+ def self.server_middleware
407
+ yield middlewares
408
+ end
409
+ end
410
+ end
411
+ after { Object.send(:remove_const, "Sidekiq") }
412
+
413
+ it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
414
+ described_class.new.install
415
+
416
+ expect(Sidekiq.middlewares).to include(Appsignal::Hooks::SidekiqPlugin)
417
+ end
418
+ end
259
419
  end