appsignal 3.4.4 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +1 -1
  4. data/.semaphore/semaphore.yml +683 -52
  5. data/CHANGELOG.md +353 -4
  6. data/README.md +3 -0
  7. data/Rakefile +4 -2
  8. data/appsignal.gemspec +1 -1
  9. data/build_matrix.yml +27 -13
  10. data/ext/Rakefile +8 -1
  11. data/ext/agent.rb +27 -27
  12. data/ext/appsignal_extension.c +0 -24
  13. data/ext/base.rb +5 -2
  14. data/gemfiles/dry-monitor.gemfile +5 -0
  15. data/gemfiles/rails-7.1.gemfile +7 -0
  16. data/gemfiles/redis-4.gemfile +5 -0
  17. data/gemfiles/redis-5.gemfile +6 -0
  18. data/lib/appsignal/auth_check.rb +1 -1
  19. data/lib/appsignal/cli/diagnose/paths.rb +33 -10
  20. data/lib/appsignal/cli/diagnose.rb +15 -1
  21. data/lib/appsignal/config.rb +72 -7
  22. data/lib/appsignal/demo.rb +1 -1
  23. data/lib/appsignal/environment.rb +24 -13
  24. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +1 -1
  25. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +18 -0
  26. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +1 -1
  27. data/lib/appsignal/event_formatter.rb +2 -2
  28. data/lib/appsignal/extension/jruby.rb +4 -17
  29. data/lib/appsignal/extension.rb +1 -1
  30. data/lib/appsignal/heartbeat.rb +71 -0
  31. data/lib/appsignal/helpers/instrumentation.rb +10 -10
  32. data/lib/appsignal/helpers/metrics.rb +15 -13
  33. data/lib/appsignal/hooks/active_job.rb +9 -1
  34. data/lib/appsignal/hooks/active_support_notifications.rb +18 -9
  35. data/lib/appsignal/hooks/dry_monitor.rb +20 -0
  36. data/lib/appsignal/hooks/redis.rb +1 -0
  37. data/lib/appsignal/hooks/redis_client.rb +28 -0
  38. data/lib/appsignal/hooks.rb +4 -2
  39. data/lib/appsignal/integrations/active_support_notifications.rb +26 -0
  40. data/lib/appsignal/integrations/dry_monitor.rb +22 -0
  41. data/lib/appsignal/integrations/hanami.rb +1 -1
  42. data/lib/appsignal/integrations/padrino.rb +1 -1
  43. data/lib/appsignal/integrations/railtie.rb +28 -6
  44. data/lib/appsignal/integrations/redis_client.rb +20 -0
  45. data/lib/appsignal/integrations/sidekiq.rb +2 -2
  46. data/lib/appsignal/integrations/sinatra.rb +1 -1
  47. data/lib/appsignal/logger.rb +7 -5
  48. data/lib/appsignal/minutely.rb +4 -4
  49. data/lib/appsignal/probes/gvl.rb +1 -1
  50. data/lib/appsignal/probes/helpers.rb +1 -1
  51. data/lib/appsignal/probes/mri.rb +1 -1
  52. data/lib/appsignal/probes/sidekiq.rb +10 -8
  53. data/lib/appsignal/rack/generic_instrumentation.rb +1 -1
  54. data/lib/appsignal/rack/rails_instrumentation.rb +2 -2
  55. data/lib/appsignal/rack/sinatra_instrumentation.rb +5 -4
  56. data/lib/appsignal/rack/streaming_listener.rb +1 -1
  57. data/lib/appsignal/span.rb +2 -2
  58. data/lib/appsignal/transaction.rb +69 -14
  59. data/lib/appsignal/utils/deprecation_message.rb +2 -2
  60. data/lib/appsignal/utils/hash_sanitizer.rb +21 -9
  61. data/lib/appsignal/version.rb +1 -1
  62. data/lib/appsignal.rb +38 -31
  63. data/lib/puma/plugin/appsignal.rb +1 -1
  64. data/resources/cacert.pem +321 -159
  65. data/spec/lib/appsignal/capistrano2_spec.rb +2 -2
  66. data/spec/lib/appsignal/capistrano3_spec.rb +2 -2
  67. data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +11 -0
  68. data/spec/lib/appsignal/cli/diagnose_spec.rb +70 -13
  69. data/spec/lib/appsignal/config_spec.rb +75 -18
  70. data/spec/lib/appsignal/environment_spec.rb +3 -3
  71. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +1 -1
  72. data/spec/lib/appsignal/event_formatter/rom/sql_formatter_spec.rb +22 -0
  73. data/spec/lib/appsignal/heartbeat_spec.rb +89 -0
  74. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +6 -0
  75. data/spec/lib/appsignal/hooks/activejob_spec.rb +26 -1
  76. data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +104 -0
  77. data/spec/lib/appsignal/hooks/redis_client_spec.rb +238 -0
  78. data/spec/lib/appsignal/hooks/redis_spec.rb +98 -76
  79. data/spec/lib/appsignal/hooks/resque_spec.rb +1 -1
  80. data/spec/lib/appsignal/hooks_spec.rb +5 -5
  81. data/spec/lib/appsignal/integrations/railtie_spec.rb +128 -59
  82. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +20 -15
  83. data/spec/lib/appsignal/integrations/sinatra_spec.rb +2 -2
  84. data/spec/lib/appsignal/minutely_spec.rb +2 -2
  85. data/spec/lib/appsignal/probes/sidekiq_spec.rb +29 -6
  86. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +1 -1
  87. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +163 -71
  88. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +1 -0
  89. data/spec/lib/appsignal/transaction_spec.rb +139 -10
  90. data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +42 -4
  91. data/spec/lib/appsignal_spec.rb +63 -61
  92. data/spec/lib/puma/appsignal_spec.rb +1 -1
  93. data/spec/spec_helper.rb +7 -7
  94. data/spec/support/fixtures/projects/valid/config/appsignal.yml +3 -3
  95. data/spec/support/helpers/config_helpers.rb +6 -2
  96. data/spec/support/helpers/dependency_helper.rb +13 -1
  97. data/spec/support/helpers/log_helpers.rb +2 -2
  98. data/spec/support/helpers/rails_helper.rb +28 -0
  99. data/spec/support/matchers/have_colorized_text.rb +1 -1
  100. metadata +19 -5
  101. data/ext/._appsignal-agent +0 -0
@@ -2,6 +2,10 @@ if DependencyHelper.rails_present?
2
2
  require "action_mailer"
3
3
 
4
4
  describe Appsignal::Integrations::Railtie do
5
+ include RailsHelper
6
+
7
+ after { clear_rails_error_reporter! }
8
+
5
9
  context "after initializing the app" do
6
10
  it "should call initialize_appsignal" do
7
11
  expect(Appsignal::Integrations::Railtie).to receive(:initialize_appsignal)
@@ -14,9 +18,9 @@ if DependencyHelper.rails_present?
14
18
  describe "#initialize_appsignal" do
15
19
  let(:app) { MyApp::Application.new }
16
20
 
17
- describe ".logger" do
21
+ describe ".internal_logger" do
18
22
  before { Appsignal::Integrations::Railtie.initialize_appsignal(app) }
19
- subject { Appsignal.logger }
23
+ subject { Appsignal.internal_logger }
20
24
 
21
25
  it { is_expected.to be_a Logger }
22
26
  end
@@ -125,17 +129,16 @@ if DependencyHelper.rails_present?
125
129
 
126
130
  if Rails.respond_to?(:error)
127
131
  describe "Rails error reporter" do
128
- before do
129
- Appsignal::Integrations::Railtie.initialize_appsignal(app)
130
- start_agent
131
- end
132
+ before { start_agent }
132
133
  around { |example| keep_transactions { example.run } }
133
134
 
134
135
  context "when error is not handled (reraises the error)" do
135
136
  it "does nothing" do
136
- expect do
137
- Rails.error.record { raise ExampleStandardError }
138
- end.to raise_error(ExampleStandardError)
137
+ with_rails_error_reporter do
138
+ expect do
139
+ Rails.error.record { raise ExampleStandardError }
140
+ end.to raise_error(ExampleStandardError)
141
+ end
139
142
 
140
143
  expect(created_transactions).to be_empty
141
144
  end
@@ -151,26 +154,28 @@ if DependencyHelper.rails_present?
151
154
  :duplicated_tag => "duplicated value"
152
155
  )
153
156
 
154
- with_current_transaction current_transaction do
155
- Rails.error.handle { raise ExampleStandardError }
156
-
157
- transaction = last_transaction
158
- transaction_hash = transaction.to_h
159
- expect(transaction_hash).to include(
160
- "action" => "CustomAction",
161
- "namespace" => "custom",
162
- "error" => {
163
- "name" => "ExampleStandardError",
164
- "message" => "ExampleStandardError",
165
- "backtrace" => kind_of(String)
166
- },
167
- "sample_data" => hash_including(
168
- "tags" => {
169
- "duplicated_tag" => "duplicated value",
170
- "severity" => "warning"
171
- }
157
+ with_rails_error_reporter do
158
+ with_current_transaction current_transaction do
159
+ Rails.error.handle { raise ExampleStandardError }
160
+
161
+ transaction = last_transaction
162
+ transaction_hash = transaction.to_h
163
+ expect(transaction_hash).to include(
164
+ "action" => "CustomAction",
165
+ "namespace" => "custom",
166
+ "error" => {
167
+ "name" => "ExampleStandardError",
168
+ "message" => "ExampleStandardError",
169
+ "backtrace" => kind_of(String)
170
+ },
171
+ "sample_data" => hash_including(
172
+ "tags" => hash_including(
173
+ "duplicated_tag" => "duplicated value",
174
+ "severity" => "warning"
175
+ )
176
+ )
172
177
  )
173
- )
178
+ end
174
179
  end
175
180
  end
176
181
 
@@ -178,21 +183,52 @@ if DependencyHelper.rails_present?
178
183
  current_transaction = http_request_transaction
179
184
  current_transaction.set_tags(:tag1 => "duplicated value")
180
185
 
181
- with_current_transaction current_transaction do
182
- given_context = { :tag1 => "value1", :tag2 => "value2" }
183
- Rails.error.handle(:context => given_context) { raise ExampleStandardError }
186
+ with_rails_error_reporter do
187
+ with_current_transaction current_transaction do
188
+ given_context = { :tag1 => "value1", :tag2 => "value2" }
189
+ Rails.error.handle(:context => given_context) { raise ExampleStandardError }
190
+
191
+ transaction = last_transaction
192
+ transaction_hash = transaction.to_h
193
+ expect(transaction_hash).to include(
194
+ "sample_data" => hash_including(
195
+ "tags" => hash_including(
196
+ "tag1" => "value1",
197
+ "tag2" => "value2",
198
+ "severity" => "warning"
199
+ )
200
+ )
201
+ )
202
+ end
203
+ end
204
+ end
205
+
206
+ it "sends tags stored in :appsignal -> :custom_data as custom data" do
207
+ current_transaction = http_request_transaction
184
208
 
185
- transaction = last_transaction
186
- transaction_hash = transaction.to_h
187
- expect(transaction_hash).to include(
188
- "sample_data" => hash_including(
189
- "tags" => {
190
- "tag1" => "value1",
191
- "tag2" => "value2",
192
- "severity" => "warning"
209
+ with_rails_error_reporter do
210
+ with_current_transaction current_transaction do
211
+ given_context = {
212
+ :appsignal => {
213
+ :custom_data => {
214
+ :array => [1, 2],
215
+ :hash => { :one => 1, :two => 2 }
216
+ }
193
217
  }
218
+ }
219
+ Rails.error.handle(:context => given_context) { raise ExampleStandardError }
220
+
221
+ transaction = last_transaction
222
+ transaction_hash = transaction.to_h
223
+ expect(transaction_hash).to include(
224
+ "sample_data" => hash_including(
225
+ "custom_data" => {
226
+ "array" => [1, 2],
227
+ "hash" => { "one" => 1, "two" => 2 }
228
+ }
229
+ )
194
230
  )
195
- )
231
+ end
196
232
  end
197
233
  end
198
234
 
@@ -201,27 +237,47 @@ if DependencyHelper.rails_present?
201
237
  current_transaction.set_namespace "custom"
202
238
  current_transaction.set_action "CustomAction"
203
239
 
204
- with_current_transaction current_transaction do
205
- given_context = {
206
- :appsignal => { :namespace => "context", :action => "ContextAction" }
207
- }
208
- Rails.error.handle(:context => given_context) { raise ExampleStandardError }
209
-
210
- transaction = last_transaction
211
- transaction_hash = transaction.to_h
212
- expect(transaction_hash).to include(
213
- "namespace" => "context",
214
- "action" => "ContextAction"
215
- )
240
+ with_rails_error_reporter do
241
+ with_current_transaction current_transaction do
242
+ given_context = {
243
+ :appsignal => { :namespace => "context", :action => "ContextAction" }
244
+ }
245
+ Rails.error.handle(:context => given_context) { raise ExampleStandardError }
246
+
247
+ transaction = last_transaction
248
+ transaction_hash = transaction.to_h
249
+ expect(transaction_hash).to include(
250
+ "namespace" => "context",
251
+ "action" => "ContextAction"
252
+ )
253
+ end
216
254
  end
217
255
  end
218
256
  end
219
257
 
220
258
  context "when no transaction is active" do
259
+ class ExampleRailsRequestMock
260
+ def path
261
+ "path"
262
+ end
263
+
264
+ def method
265
+ "GET"
266
+ end
267
+
268
+ def filtered_parameters
269
+ { :user_id => 123, :password => "[FILTERED]" }
270
+ end
271
+ end
272
+
221
273
  class ExampleRailsControllerMock
222
274
  def action_name
223
275
  "index"
224
276
  end
277
+
278
+ def request
279
+ @request ||= ExampleRailsRequestMock.new
280
+ end
225
281
  end
226
282
 
227
283
  class ExampleRailsJobMock
@@ -237,21 +293,32 @@ if DependencyHelper.rails_present?
237
293
  clear_current_transaction!
238
294
  end
239
295
 
240
- it "fetches the action from the controller in the context" do
296
+ it "fetches the action, path and method from the controller in the context" do
241
297
  # The controller key is set by Rails when raised in a controller
242
298
  given_context = { :controller => ExampleRailsControllerMock.new }
243
- Rails.error.handle(:context => given_context) { raise ExampleStandardError }
299
+ with_rails_error_reporter do
300
+ Rails.error.handle(:context => given_context) { raise ExampleStandardError }
301
+ end
244
302
 
245
303
  transaction = last_transaction
246
304
  transaction_hash = transaction.to_h
247
305
  expect(transaction_hash).to include(
248
- "action" => "ExampleRailsControllerMock#index"
306
+ "action" => "ExampleRailsControllerMock#index",
307
+ "metadata" => hash_including(
308
+ "path" => "path",
309
+ "method" => "GET"
310
+ ),
311
+ "sample_data" => hash_including(
312
+ "params" => { "user_id" => 123, "password" => "[FILTERED]" }
313
+ )
249
314
  )
250
315
  end
251
316
 
252
317
  it "sets no action if no execution context is present" do
253
318
  # The controller key is set by Rails when raised in a controller
254
- Rails.error.handle { raise ExampleStandardError }
319
+ with_rails_error_reporter do
320
+ Rails.error.handle { raise ExampleStandardError }
321
+ end
255
322
 
256
323
  transaction = last_transaction
257
324
  transaction_hash = transaction.to_h
@@ -269,17 +336,19 @@ if DependencyHelper.rails_present?
269
336
  :tag1 => "value1",
270
337
  :tag2 => "value2"
271
338
  }
272
- Rails.error.handle(:context => given_context) { raise ExampleStandardError }
339
+ with_rails_error_reporter do
340
+ Rails.error.handle(:context => given_context) { raise ExampleStandardError }
341
+ end
273
342
 
274
343
  transaction = last_transaction
275
344
  transaction_hash = transaction.to_h
276
345
  expect(transaction_hash).to include(
277
346
  "sample_data" => hash_including(
278
- "tags" => {
347
+ "tags" => hash_including(
279
348
  "tag1" => "value1",
280
349
  "tag2" => "value2",
281
350
  "severity" => "warning"
282
- }
351
+ )
283
352
  )
284
353
  )
285
354
  end
@@ -4,11 +4,11 @@ describe Appsignal::Integrations::SidekiqErrorHandler do
4
4
  let(:log) { StringIO.new }
5
5
  before do
6
6
  start_agent
7
- Appsignal.logger = test_logger(log)
7
+ Appsignal.internal_logger = test_logger(log)
8
8
  end
9
9
  around { |example| keep_transactions { example.run } }
10
10
 
11
- context "without a current transction" do
11
+ context "without a current transaction" do
12
12
  let(:exception) do
13
13
  raise ExampleStandardError, "uh oh"
14
14
  rescue => error
@@ -86,7 +86,7 @@ describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => f
86
86
  let(:log) { StringIO.new }
87
87
  before do
88
88
  start_agent
89
- Appsignal.logger = test_logger(log)
89
+ Appsignal.internal_logger = test_logger(log)
90
90
  end
91
91
  around { |example| keep_transactions { example.run } }
92
92
  after :with_yaml_parse_error => false do
@@ -266,13 +266,16 @@ describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => f
266
266
 
267
267
  if DependencyHelper.rails7_present?
268
268
  context "with Rails error reporter" do
269
+ include RailsHelper
270
+
269
271
  it "reports the worker name as the action, copies the namespace and tags" do
270
- Appsignal::Integrations::Railtie.initialize_appsignal(MyApp::Application.new)
271
272
  Appsignal.config = project_fixture_config("production")
272
- perform_job do
273
- Appsignal.tag_job("test_tag" => "value")
274
- Rails.error.handle do
275
- raise error, "uh oh"
273
+ with_rails_error_reporter do
274
+ perform_job do
275
+ Appsignal.tag_job("test_tag" => "value")
276
+ Rails.error.handle do
277
+ raise ExampleStandardError, "uh oh"
278
+ end
276
279
  end
277
280
  end
278
281
 
@@ -363,7 +366,9 @@ if DependencyHelper.active_job_present?
363
366
  require "sidekiq/testing"
364
367
 
365
368
  describe "Sidekiq ActiveJob integration" do
369
+ include RailsHelper
366
370
  include ActiveJobHelpers
371
+
367
372
  let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
368
373
  let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
369
374
  let(:log) { StringIO.new }
@@ -416,9 +421,9 @@ if DependencyHelper.active_job_present?
416
421
  ["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"]
417
422
  end
418
423
  end
419
- before do
424
+ around do |example|
420
425
  start_agent
421
- Appsignal.logger = test_logger(log)
426
+ Appsignal.internal_logger = test_logger(log)
422
427
  ActiveJob::Base.queue_adapter = :sidekiq
423
428
 
424
429
  class ActiveJobSidekiqTestJob < ActiveJob::Base
@@ -441,11 +446,11 @@ if DependencyHelper.active_job_present?
441
446
  Sidekiq::Testing.server_middleware do |chain|
442
447
  chain.add Appsignal::Integrations::SidekiqMiddleware
443
448
  end
444
- end
445
- around do |example|
446
- keep_transactions do
447
- Sidekiq::Testing.fake! do
448
- example.run
449
+ with_rails_error_reporter do
450
+ keep_transactions do
451
+ Sidekiq::Testing.fake! do
452
+ example.run
453
+ end
449
454
  end
450
455
  end
451
456
  end
@@ -16,8 +16,8 @@ if DependencyHelper.sinatra_present?
16
16
  before { allow(Appsignal).to receive(:active?).and_return(true) }
17
17
  after { uninstall_sinatra_integration }
18
18
 
19
- context "Appsignal.logger" do
20
- subject { Appsignal.logger }
19
+ context "Appsignal.internal_logger" do
20
+ subject { Appsignal.internal_logger }
21
21
 
22
22
  it "sets a logger" do
23
23
  install_sinatra_integration
@@ -42,7 +42,7 @@ describe Appsignal::Minutely do
42
42
  let(:log_stream) { StringIO.new }
43
43
  let(:log) { log_contents(log_stream) }
44
44
  before do
45
- Appsignal.logger = test_logger(log_stream)
45
+ Appsignal.internal_logger = test_logger(log_stream)
46
46
  # Speed up test time
47
47
  allow(Appsignal::Minutely).to receive(:initial_wait_time).and_return(0.001)
48
48
  allow(Appsignal::Minutely).to receive(:wait_time).and_return(0.001)
@@ -287,7 +287,7 @@ describe Appsignal::Minutely do
287
287
  describe "#register" do
288
288
  let(:log_stream) { std_stream }
289
289
  let(:log) { log_contents(log_stream) }
290
- before { Appsignal.logger = test_logger(log_stream) }
290
+ before { Appsignal.internal_logger = test_logger(log_stream) }
291
291
 
292
292
  it "adds the by key probe" do
293
293
  probe = lambda {}
@@ -73,6 +73,20 @@ describe Appsignal::Probes::SidekiqProbe do
73
73
  module Sidekiq7Mock
74
74
  VERSION = "7.0.0".freeze
75
75
 
76
+ def self.redis_info_data=(info)
77
+ @redis_info_data = info
78
+ end
79
+
80
+ def self.redis_info_data
81
+ return @redis_info_data if defined?(@redis_info_data)
82
+
83
+ {
84
+ "connected_clients" => 2,
85
+ "used_memory" => 1024,
86
+ "used_memory_rss" => 512
87
+ }
88
+ end
89
+
76
90
  def self.redis
77
91
  yield Client.new
78
92
  end
@@ -83,11 +97,7 @@ describe Appsignal::Probes::SidekiqProbe do
83
97
  end
84
98
 
85
99
  def info
86
- {
87
- "connected_clients" => 2,
88
- "used_memory" => 1024,
89
- "used_memory_rss" => 512
90
- }
100
+ Sidekiq7Mock.redis_info_data
91
101
  end
92
102
  end
93
103
 
@@ -227,6 +237,19 @@ describe Appsignal::Probes::SidekiqProbe do
227
237
  probe.call
228
238
  probe.call
229
239
  end
240
+
241
+ context "when redis info doesn't contain requested keys" do
242
+ before { Sidekiq7Mock.redis_info_data = {} }
243
+
244
+ it "doesn't create metrics for nil values" do
245
+ expect_gauge("connection_count").never
246
+ expect_gauge("memory_usage").never
247
+ expect_gauge("memory_usage_rss").never
248
+ # Call probe twice so we can calculate the delta for some gauge values
249
+ probe.call
250
+ probe.call
251
+ end
252
+ end
230
253
  end
231
254
 
232
255
  context "with Sidekiq 6" do
@@ -301,7 +324,7 @@ describe Appsignal::Probes::SidekiqProbe do
301
324
  end
302
325
  end
303
326
 
304
- def expect_gauge(key, value, tags = {})
327
+ def expect_gauge(key, value = anything, tags = {})
305
328
  expect(Appsignal).to receive(:set_gauge)
306
329
  .with("sidekiq_#{key}", value, expected_default_tags.merge(tags))
307
330
  .and_call_original
@@ -6,7 +6,7 @@ if DependencyHelper.rails_present?
6
6
  let(:log) { StringIO.new }
7
7
  before do
8
8
  start_agent
9
- Appsignal.logger = test_logger(log)
9
+ Appsignal.internal_logger = test_logger(log)
10
10
  end
11
11
 
12
12
  let(:params) do