appsignal 2.11.0.beta.2 → 2.11.1.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +57 -1
  3. data/CHANGELOG.md +28 -0
  4. data/README.md +11 -5
  5. data/Rakefile +27 -9
  6. data/appsignal.gemspec +1 -1
  7. data/build_matrix.yml +2 -2
  8. data/ext/Rakefile +2 -0
  9. data/ext/agent.yml +17 -25
  10. data/ext/appsignal_extension.c +1 -1
  11. data/ext/base.rb +7 -0
  12. data/ext/extconf.rb +2 -0
  13. data/lib/appsignal.rb +1 -0
  14. data/lib/appsignal/auth_check.rb +4 -2
  15. data/lib/appsignal/cli/diagnose.rb +1 -1
  16. data/lib/appsignal/config.rb +82 -17
  17. data/lib/appsignal/extension.rb +6 -5
  18. data/lib/appsignal/extension/jruby.rb +6 -5
  19. data/lib/appsignal/hooks.rb +24 -0
  20. data/lib/appsignal/hooks/action_mailer.rb +22 -0
  21. data/lib/appsignal/hooks/active_job.rb +53 -5
  22. data/lib/appsignal/hooks/active_support_notifications.rb +72 -0
  23. data/lib/appsignal/hooks/puma.rb +0 -1
  24. data/lib/appsignal/hooks/sidekiq.rb +1 -2
  25. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  26. data/lib/appsignal/probes.rb +7 -0
  27. data/lib/appsignal/probes/puma.rb +1 -1
  28. data/lib/appsignal/probes/sidekiq.rb +3 -1
  29. data/lib/appsignal/utils/deprecation_message.rb +1 -1
  30. data/lib/appsignal/version.rb +1 -1
  31. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  32. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  33. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  34. data/spec/lib/appsignal/cli/diagnose_spec.rb +42 -0
  35. data/spec/lib/appsignal/config_spec.rb +39 -1
  36. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  37. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  38. data/spec/lib/appsignal/hooks/action_mailer_spec.rb +54 -0
  39. data/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +35 -0
  40. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +145 -0
  41. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +69 -0
  42. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +9 -137
  43. data/spec/lib/appsignal/hooks/activejob_spec.rb +143 -10
  44. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  45. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +7 -5
  46. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  47. data/spec/lib/appsignal/marker_spec.rb +1 -1
  48. data/spec/spec_helper.rb +5 -0
  49. data/spec/support/helpers/config_helpers.rb +3 -2
  50. data/spec/support/helpers/dependency_helper.rb +4 -0
  51. data/spec/support/helpers/transaction_helpers.rb +1 -1
  52. data/spec/support/testing.rb +19 -19
  53. metadata +19 -7
@@ -1,3 +1,5 @@
1
+ require_relative "./active_support_notifications/instrument_shared_examples"
2
+
1
3
  describe Appsignal::Hooks::ActiveSupportNotificationsHook do
2
4
  if active_support_present?
3
5
  let(:notifier) { ActiveSupport::Notifications::Fanout.new }
@@ -18,148 +20,18 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
18
20
  it { is_expected.to be_truthy }
19
21
  end
20
22
 
21
- it "instruments an ActiveSupport::Notifications.instrument event" do
22
- return_value = as.instrument("sql.active_record", :sql => "SQL") do
23
- "value"
24
- end
25
-
26
- expect(return_value).to eq "value"
27
- expect(transaction.to_h["events"]).to match([
28
- {
29
- "allocation_count" => kind_of(Integer),
30
- "body" => "SQL",
31
- "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
32
- "child_allocation_count" => kind_of(Integer),
33
- "child_duration" => kind_of(Float),
34
- "child_gc_duration" => kind_of(Float),
35
- "count" => 1,
36
- "duration" => kind_of(Float),
37
- "gc_duration" => kind_of(Float),
38
- "name" => "sql.active_record",
39
- "start" => kind_of(Float),
40
- "title" => ""
41
- }
42
- ])
43
- end
44
-
45
- it "instruments an ActiveSupport::Notifications.instrument event with no registered formatter" do
46
- return_value = as.instrument("no-registered.formatter", :key => "something") do
47
- "value"
48
- end
49
-
50
- expect(return_value).to eq "value"
51
- expect(transaction.to_h["events"]).to match([
52
- {
53
- "allocation_count" => kind_of(Integer),
54
- "body" => "",
55
- "body_format" => Appsignal::EventFormatter::DEFAULT,
56
- "child_allocation_count" => kind_of(Integer),
57
- "child_duration" => kind_of(Float),
58
- "child_gc_duration" => kind_of(Float),
59
- "count" => 1,
60
- "duration" => kind_of(Float),
61
- "gc_duration" => kind_of(Float),
62
- "name" => "no-registered.formatter",
63
- "start" => kind_of(Float),
64
- "title" => ""
65
- }
66
- ])
67
- end
68
-
69
- it "converts non-string names to strings" do
70
- as.instrument(:not_a_string) {}
71
- expect(transaction.to_h["events"]).to match([
72
- {
73
- "allocation_count" => kind_of(Integer),
74
- "body" => "",
75
- "body_format" => Appsignal::EventFormatter::DEFAULT,
76
- "child_allocation_count" => kind_of(Integer),
77
- "child_duration" => kind_of(Float),
78
- "child_gc_duration" => kind_of(Float),
79
- "count" => 1,
80
- "duration" => kind_of(Float),
81
- "gc_duration" => kind_of(Float),
82
- "name" => "not_a_string",
83
- "start" => kind_of(Float),
84
- "title" => ""
85
- }
86
- ])
87
- end
88
-
89
- it "does not instrument events whose name starts with a bang" do
90
- expect(Appsignal::Transaction.current).not_to receive(:start_event)
91
- expect(Appsignal::Transaction.current).not_to receive(:finish_event)
92
-
93
- return_value = as.instrument("!sql.active_record", :sql => "SQL") do
94
- "value"
95
- end
96
-
97
- expect(return_value).to eq "value"
98
- end
99
-
100
- context "when an error is raised in an instrumented block" do
101
- it "instruments an ActiveSupport::Notifications.instrument event" do
102
- expect do
103
- as.instrument("sql.active_record", :sql => "SQL") do
104
- raise ExampleException, "foo"
105
- end
106
- end.to raise_error(ExampleException, "foo")
107
-
108
- expect(transaction.to_h["events"]).to match([
109
- {
110
- "allocation_count" => kind_of(Integer),
111
- "body" => "SQL",
112
- "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
113
- "child_allocation_count" => kind_of(Integer),
114
- "child_duration" => kind_of(Float),
115
- "child_gc_duration" => kind_of(Float),
116
- "count" => 1,
117
- "duration" => kind_of(Float),
118
- "gc_duration" => kind_of(Float),
119
- "name" => "sql.active_record",
120
- "start" => kind_of(Float),
121
- "title" => ""
122
- }
123
- ])
124
- end
125
- end
23
+ it_behaves_like "activesupport instrument override"
126
24
 
127
- context "when a message is thrown in an instrumented block" do
128
- it "instruments an ActiveSupport::Notifications.instrument event" do
129
- expect do
130
- as.instrument("sql.active_record", :sql => "SQL") do
131
- throw :foo
132
- end
133
- end.to throw_symbol(:foo)
25
+ if ::ActiveSupport::Notifications::Instrumenter.method_defined?(:start)
26
+ require_relative "./active_support_notifications/start_finish_shared_examples"
134
27
 
135
- expect(transaction.to_h["events"]).to match([
136
- {
137
- "allocation_count" => kind_of(Integer),
138
- "body" => "SQL",
139
- "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
140
- "child_allocation_count" => kind_of(Integer),
141
- "child_duration" => kind_of(Float),
142
- "child_gc_duration" => kind_of(Float),
143
- "count" => 1,
144
- "duration" => kind_of(Float),
145
- "gc_duration" => kind_of(Float),
146
- "name" => "sql.active_record",
147
- "start" => kind_of(Float),
148
- "title" => ""
149
- }
150
- ])
151
- end
28
+ it_behaves_like "activesupport start finish override"
152
29
  end
153
30
 
154
- context "when a transaction is completed in an instrumented block" do
155
- it "does not complete the ActiveSupport::Notifications.instrument event" do
156
- expect(transaction).to receive(:complete)
157
- as.instrument("sql.active_record", :sql => "SQL") do
158
- Appsignal::Transaction.complete_current!
159
- end
31
+ if ::ActiveSupport::Notifications::Instrumenter.method_defined?(:finish_with_state)
32
+ require_relative "./active_support_notifications/finish_with_state_shared_examples"
160
33
 
161
- expect(transaction.to_h["events"]).to match([])
162
- end
34
+ it_behaves_like "activesupport finish_with_state override"
163
35
  end
164
36
  else
165
37
  describe "#dependencies_present?" do
@@ -21,7 +21,7 @@ if DependencyHelper.active_job_present?
21
21
 
22
22
  describe "#install" do
23
23
  it "extends ActiveJob::Base with the AppSignal ActiveJob plugin" do
24
- described_class.new.install
24
+ start_agent
25
25
 
26
26
  path, _line_number = ActiveJob::Base.method(:execute).source_location
27
27
  expect(path).to end_with("/lib/appsignal/hooks/active_job.rb")
@@ -32,6 +32,7 @@ if DependencyHelper.active_job_present?
32
32
  describe Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation do
33
33
  let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
34
34
  let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
35
+ let(:queue) { "default" }
35
36
  let(:log) { StringIO.new }
36
37
  let(:parameterized_given_args) do
37
38
  {
@@ -78,14 +79,26 @@ if DependencyHelper.active_job_present?
78
79
  raise "uh oh"
79
80
  end
80
81
  end
82
+
83
+ class ActiveJobCustomQueueTestJob < ActiveJob::Base
84
+ queue_as :custom_queue
85
+
86
+ def perform(*_args)
87
+ end
88
+ end
81
89
  end
82
90
  around { |example| keep_transactions { example.run } }
83
91
  after do
84
92
  Object.send(:remove_const, :ActiveJobTestJob)
85
93
  Object.send(:remove_const, :ActiveJobErrorTestJob)
94
+ Object.send(:remove_const, :ActiveJobCustomQueueTestJob)
86
95
  end
87
96
 
88
97
  it "reports the name from the ActiveJob integration" do
98
+ tags = { :queue => queue }
99
+ expect(Appsignal).to receive(:increment_counter)
100
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
101
+
89
102
  perform_job(ActiveJobTestJob)
90
103
 
91
104
  transaction = last_transaction
@@ -98,7 +111,8 @@ if DependencyHelper.active_job_present?
98
111
  "sample_data" => hash_including(
99
112
  "params" => [],
100
113
  "tags" => {
101
- "queue" => "default"
114
+ "active_job_id" => kind_of(String),
115
+ "queue" => queue
102
116
  }
103
117
  )
104
118
  )
@@ -108,8 +122,66 @@ if DependencyHelper.active_job_present?
108
122
  expect(events).to eq(["perform_start.active_job", "perform.active_job"])
109
123
  end
110
124
 
125
+ context "with custom queue" do
126
+ it "reports the custom queue as tag on the transaction" do
127
+ tags = { :queue => "custom_queue" }
128
+ expect(Appsignal).to receive(:increment_counter)
129
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
130
+ perform_job(ActiveJobCustomQueueTestJob)
131
+
132
+ transaction = last_transaction
133
+ transaction_hash = transaction.to_h
134
+ expect(transaction_hash).to include(
135
+ "sample_data" => hash_including(
136
+ "tags" => hash_including("queue" => "custom_queue")
137
+ )
138
+ )
139
+ end
140
+ end
141
+
142
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
143
+ context "with priority" do
144
+ before do
145
+ class ActiveJobPriorityTestJob < ActiveJob::Base
146
+ queue_with_priority 10
147
+
148
+ def perform(*_args)
149
+ end
150
+ end
151
+ end
152
+ after do
153
+ Object.send(:remove_const, :ActiveJobPriorityTestJob)
154
+ end
155
+
156
+ it "reports the priority as tag on the transaction" do
157
+ tags = { :queue => queue }
158
+ expect(Appsignal).to receive(:increment_counter)
159
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
160
+ expect(Appsignal).to receive(:increment_counter)
161
+ .with("active_job_queue_priority_job_count", 1, tags.merge(:priority => 10, :status => :processed))
162
+
163
+ perform_job(ActiveJobPriorityTestJob)
164
+
165
+ transaction = last_transaction
166
+ transaction_hash = transaction.to_h
167
+ expect(transaction_hash).to include(
168
+ "sample_data" => hash_including(
169
+ "tags" => hash_including("queue" => queue, "priority" => 10)
170
+ )
171
+ )
172
+ end
173
+ end
174
+ end
175
+
111
176
  context "with error" do
112
177
  it "reports the error on the transaction from the ActiveRecord integration" do
178
+ allow(Appsignal).to receive(:increment_counter) # Other calls we're testing in another test
179
+ tags = { :queue => queue }
180
+ expect(Appsignal).to receive(:increment_counter)
181
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :failed))
182
+ expect(Appsignal).to receive(:increment_counter)
183
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
184
+
113
185
  expect do
114
186
  perform_job(ActiveJobErrorTestJob)
115
187
  end.to raise_error(RuntimeError, "uh oh")
@@ -128,7 +200,8 @@ if DependencyHelper.active_job_present?
128
200
  "sample_data" => hash_including(
129
201
  "params" => [],
130
202
  "tags" => {
131
- "queue" => "default"
203
+ "active_job_id" => kind_of(String),
204
+ "queue" => queue
132
205
  }
133
206
  )
134
207
  )
@@ -137,6 +210,47 @@ if DependencyHelper.active_job_present?
137
210
  .map { |event| event["name"] }
138
211
  expect(events).to eq(["perform_start.active_job", "perform.active_job"])
139
212
  end
213
+
214
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
215
+ context "with priority" do
216
+ before do
217
+ class ActiveJobErrorPriorityTestJob < ActiveJob::Base
218
+ queue_with_priority 10
219
+
220
+ def perform(*_args)
221
+ raise "uh oh"
222
+ end
223
+ end
224
+ end
225
+ after do
226
+ Object.send(:remove_const, :ActiveJobErrorPriorityTestJob)
227
+ end
228
+
229
+ it "reports the priority as tag on the transaction" do
230
+ tags = { :queue => queue }
231
+ expect(Appsignal).to receive(:increment_counter)
232
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
233
+ expect(Appsignal).to receive(:increment_counter)
234
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :failed))
235
+ expect(Appsignal).to receive(:increment_counter)
236
+ .with("active_job_queue_priority_job_count", 1, tags.merge(:priority => 10, :status => :processed))
237
+ expect(Appsignal).to receive(:increment_counter)
238
+ .with("active_job_queue_priority_job_count", 1, tags.merge(:priority => 10, :status => :failed))
239
+
240
+ expect do
241
+ perform_job(ActiveJobErrorPriorityTestJob)
242
+ end.to raise_error(RuntimeError, "uh oh")
243
+
244
+ transaction = last_transaction
245
+ transaction_hash = transaction.to_h
246
+ expect(transaction_hash).to include(
247
+ "sample_data" => hash_including(
248
+ "tags" => hash_including("queue" => queue, "priority" => 10)
249
+ )
250
+ )
251
+ end
252
+ end
253
+ end
140
254
  end
141
255
 
142
256
  context "when wrapped in another transaction" do
@@ -163,7 +277,8 @@ if DependencyHelper.active_job_present?
163
277
  "sample_data" => hash_including(
164
278
  "params" => [],
165
279
  "tags" => {
166
- "queue" => "default"
280
+ "active_job_id" => kind_of(String),
281
+ "queue" => queue
167
282
  }
168
283
  )
169
284
  )
@@ -300,7 +415,10 @@ if DependencyHelper.active_job_present?
300
415
  "action" => "ActionMailerTestJob#welcome",
301
416
  "sample_data" => hash_including(
302
417
  "params" => ["ActionMailerTestJob", "welcome", "deliver_now"],
303
- "tags" => { "queue" => "mailers" }
418
+ "tags" => {
419
+ "active_job_id" => kind_of(String),
420
+ "queue" => "mailers"
421
+ }
304
422
  )
305
423
  )
306
424
  end
@@ -316,7 +434,10 @@ if DependencyHelper.active_job_present?
316
434
  "action" => "ActionMailerTestJob#welcome",
317
435
  "sample_data" => hash_including(
318
436
  "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + method_expected_args,
319
- "tags" => { "queue" => "mailers" }
437
+ "tags" => {
438
+ "active_job_id" => kind_of(String),
439
+ "queue" => "mailers"
440
+ }
320
441
  )
321
442
  )
322
443
  end
@@ -333,7 +454,10 @@ if DependencyHelper.active_job_present?
333
454
  "action" => "ActionMailerTestJob#welcome",
334
455
  "sample_data" => hash_including(
335
456
  "params" => ["ActionMailerTestJob", "welcome", "deliver_now", parameterized_expected_args],
336
- "tags" => { "queue" => "mailers" }
457
+ "tags" => {
458
+ "active_job_id" => kind_of(String),
459
+ "queue" => "mailers"
460
+ }
337
461
  )
338
462
  )
339
463
  end
@@ -371,7 +495,10 @@ if DependencyHelper.active_job_present?
371
495
  "deliver_now",
372
496
  { active_job_internal_key => ["args"], "args" => [] }
373
497
  ],
374
- "tags" => { "queue" => "mailers" }
498
+ "tags" => {
499
+ "active_job_id" => kind_of(String),
500
+ "queue" => "mailers"
501
+ }
375
502
  )
376
503
  )
377
504
  end
@@ -394,7 +521,10 @@ if DependencyHelper.active_job_present?
394
521
  "args" => method_expected_args
395
522
  }
396
523
  ],
397
- "tags" => { "queue" => "mailers" }
524
+ "tags" => {
525
+ "active_job_id" => kind_of(String),
526
+ "queue" => "mailers"
527
+ }
398
528
  )
399
529
  )
400
530
  end
@@ -419,7 +549,10 @@ if DependencyHelper.active_job_present?
419
549
  "params" => parameterized_expected_args
420
550
  }
421
551
  ],
422
- "tags" => { "queue" => "mailers" }
552
+ "tags" => {
553
+ "active_job_id" => kind_of(String),
554
+ "queue" => "mailers"
555
+ }
423
556
  )
424
557
  )
425
558
  end
@@ -227,25 +227,14 @@ describe Appsignal::Hooks::DelayedJobHook do
227
227
  end
228
228
  end
229
229
 
230
- context "without job name" do
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 "wraps it in a transaction using the class method job name" do
235
+ it "appends #perform to the class name" do
247
236
  perform
248
- expect(last_transaction.to_h["action"]).to eql("unknown")
237
+ expect(last_transaction.to_h["action"]).to eql("Banana#perform")
249
238
  end
250
239
  end
251
240
 
@@ -76,9 +76,10 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
76
76
  ]
77
77
  end
78
78
  let(:job_class) { "TestClass" }
79
+ let(:jid) { "b4a577edbccf1d805744efa9" }
79
80
  let(:item) do
80
81
  {
81
- "jid" => "b4a577edbccf1d805744efa9",
82
+ "jid" => jid,
82
83
  "class" => job_class,
83
84
  "retry_count" => 0,
84
85
  "queue" => "default",
@@ -151,7 +152,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
151
152
  context "when using the Sidekiq delayed extension" do
152
153
  let(:item) do
153
154
  {
154
- "jid" => "efb140489485999d32b5504c",
155
+ "jid" => jid,
155
156
  "class" => "Sidekiq::Extensions::DelayedClass",
156
157
  "queue" => "default",
157
158
  "args" => [
@@ -191,7 +192,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
191
192
  context "when using the Sidekiq ActiveRecord instance delayed extension" do
192
193
  let(:item) do
193
194
  {
194
- "jid" => "efb140489485999d32b5504c",
195
+ "jid" => jid,
195
196
  "class" => "Sidekiq::Extensions::DelayedModel",
196
197
  "queue" => "default",
197
198
  "args" => [
@@ -243,7 +244,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
243
244
 
244
245
  transaction_hash = transaction.to_h
245
246
  expect(transaction_hash).to include(
246
- "id" => kind_of(String),
247
+ "id" => jid,
247
248
  "action" => "TestClass#perform",
248
249
  "error" => {
249
250
  "name" => "ExampleException",
@@ -277,7 +278,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
277
278
 
278
279
  transaction_hash = transaction.to_h
279
280
  expect(transaction_hash).to include(
280
- "id" => kind_of(String),
281
+ "id" => jid,
281
282
  "action" => "TestClass#perform",
282
283
  "error" => nil,
283
284
  "metadata" => {
@@ -361,6 +362,7 @@ if DependencyHelper.active_job_present?
361
362
  end
362
363
  let(:expected_tags) do
363
364
  {}.tap do |hash|
365
+ hash["active_job_id"] = kind_of(String)
364
366
  if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
365
367
  hash["provider_job_id"] = kind_of(String)
366
368
  end