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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +75 -61
  4. data/CHANGELOG.md +21 -0
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/ext/appsignal_extension.c +10 -1
  8. data/ext/base.rb +11 -2
  9. data/gemfiles/padrino.gemfile +2 -2
  10. data/gemfiles/rails-4.2.gemfile +9 -2
  11. data/gemfiles/rails-5.0.gemfile +1 -0
  12. data/gemfiles/rails-5.1.gemfile +1 -0
  13. data/gemfiles/rails-5.2.gemfile +1 -0
  14. data/gemfiles/rails-6.0.gemfile +1 -0
  15. data/gemfiles/resque-1.gemfile +7 -0
  16. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  17. data/lib/appsignal.rb +21 -1
  18. data/lib/appsignal/capistrano.rb +2 -0
  19. data/lib/appsignal/config.rb +6 -2
  20. data/lib/appsignal/environment.rb +126 -0
  21. data/lib/appsignal/extension/jruby.rb +10 -0
  22. data/lib/appsignal/hooks.rb +2 -0
  23. data/lib/appsignal/hooks/active_job.rb +89 -0
  24. data/lib/appsignal/hooks/net_http.rb +2 -0
  25. data/lib/appsignal/hooks/puma.rb +2 -58
  26. data/lib/appsignal/hooks/redis.rb +2 -0
  27. data/lib/appsignal/hooks/resque.rb +60 -0
  28. data/lib/appsignal/hooks/sequel.rb +2 -0
  29. data/lib/appsignal/hooks/sidekiq.rb +18 -191
  30. data/lib/appsignal/integrations/object.rb +4 -0
  31. data/lib/appsignal/integrations/que.rb +1 -1
  32. data/lib/appsignal/integrations/resque.rb +9 -12
  33. data/lib/appsignal/integrations/resque_active_job.rb +9 -24
  34. data/lib/appsignal/probes/puma.rb +61 -0
  35. data/lib/appsignal/probes/sidekiq.rb +102 -0
  36. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  37. data/lib/appsignal/transaction.rb +32 -7
  38. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  39. data/lib/appsignal/version.rb +1 -1
  40. data/lib/puma/plugin/appsignal.rb +2 -1
  41. data/spec/lib/appsignal/cli/diagnose_spec.rb +2 -1
  42. data/spec/lib/appsignal/config_spec.rb +6 -1
  43. data/spec/lib/appsignal/environment_spec.rb +167 -0
  44. data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
  45. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  46. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  47. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
  48. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  49. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  50. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -137
  51. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  52. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  53. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  54. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  55. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  56. data/spec/lib/appsignal_spec.rb +22 -0
  57. data/spec/lib/puma/appsignal_spec.rb +1 -1
  58. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  59. data/spec/support/helpers/dependency_helper.rb +12 -0
  60. data/spec/support/helpers/env_helpers.rb +1 -1
  61. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  62. data/spec/support/helpers/transaction_helpers.rb +6 -0
  63. data/spec/support/stubs/sidekiq/api.rb +2 -2
  64. metadata +25 -5
@@ -0,0 +1,204 @@
1
+ require "appsignal/probes/sidekiq"
2
+
3
+ describe Appsignal::Probes::SidekiqProbe do
4
+ describe "#call" do
5
+ let(:probe) { described_class.new }
6
+ let(:redis_hostname) { "localhost" }
7
+ let(:expected_default_tags) { { :hostname => "localhost" } }
8
+ before do
9
+ Appsignal.config = project_fixture_config
10
+ module SidekiqMock
11
+ def self.redis_info
12
+ {
13
+ "connected_clients" => 2,
14
+ "used_memory" => 1024,
15
+ "used_memory_rss" => 512
16
+ }
17
+ end
18
+
19
+ def self.redis
20
+ yield Client.new
21
+ end
22
+
23
+ class Client
24
+ def connection
25
+ { :host => "localhost" }
26
+ end
27
+ end
28
+
29
+ class Stats
30
+ class << self
31
+ attr_reader :calls
32
+
33
+ def count_call
34
+ @calls ||= -1
35
+ @calls += 1
36
+ end
37
+ end
38
+
39
+ def workers_size
40
+ # First method called, so count it towards a call
41
+ self.class.count_call
42
+ 24
43
+ end
44
+
45
+ def processes_size
46
+ 25
47
+ end
48
+
49
+ # Return two different values for two separate calls.
50
+ # This allows us to test the delta of the value send as a gauge.
51
+ def processed
52
+ [10, 15][self.class.calls]
53
+ end
54
+
55
+ # Return two different values for two separate calls.
56
+ # This allows us to test the delta of the value send as a gauge.
57
+ def failed
58
+ [10, 13][self.class.calls]
59
+ end
60
+
61
+ def retry_size
62
+ 12
63
+ end
64
+
65
+ # Return two different values for two separate calls.
66
+ # This allows us to test the delta of the value send as a gauge.
67
+ def dead_size
68
+ [10, 12][self.class.calls]
69
+ end
70
+
71
+ def scheduled_size
72
+ 14
73
+ end
74
+
75
+ def enqueued
76
+ 15
77
+ end
78
+ end
79
+
80
+ class Queue
81
+ Queue = Struct.new(:name, :size, :latency)
82
+
83
+ def self.all
84
+ [
85
+ Queue.new("default", 10, 12),
86
+ Queue.new("critical", 1, 2)
87
+ ]
88
+ end
89
+ end
90
+ end
91
+ stub_const("Sidekiq", SidekiqMock)
92
+ end
93
+ after { Object.send(:remove_const, :SidekiqMock) }
94
+
95
+ describe ".dependencies_present?" do
96
+ before do
97
+ stub_const("Redis::VERSION", version)
98
+ end
99
+
100
+ context "when Redis version is < 3.3.5" do
101
+ let(:version) { "3.3.4" }
102
+
103
+ it "does not start probe" do
104
+ expect(described_class.dependencies_present?).to be_falsy
105
+ end
106
+ end
107
+
108
+ context "when Redis version is >= 3.3.5" do
109
+ let(:version) { "3.3.5" }
110
+
111
+ it "does not start probe" do
112
+ expect(described_class.dependencies_present?).to be_truthy
113
+ end
114
+ end
115
+ end
116
+
117
+ it "loads Sidekiq::API" do
118
+ # Hide the Sidekiq constant if it was already loaded. It will be
119
+ # redefined by loading "sidekiq/api" in the probe.
120
+ hide_const "Sidekiq::Stats"
121
+
122
+ expect(defined?(Sidekiq::Stats)).to be_falsy
123
+ probe
124
+ expect(defined?(Sidekiq::Stats)).to be_truthy
125
+ end
126
+
127
+ it "logs config on initialize" do
128
+ log = capture_logs { probe }
129
+ expect(log).to contains_log(:debug, "Initializing Sidekiq probe\n")
130
+ end
131
+
132
+ it "logs used hostname on call once" do
133
+ log = capture_logs { probe.call }
134
+ expect(log).to contains_log(
135
+ :debug,
136
+ %(Sidekiq probe: Using Redis server hostname "localhost" as hostname)
137
+ )
138
+ log = capture_logs { probe.call }
139
+ # Match more logs with incompelete message
140
+ expect(log).to_not contains_log(:debug, %(Sidekiq probe: ))
141
+ end
142
+
143
+ it "collects custom metrics" do
144
+ expect_gauge("worker_count", 24).twice
145
+ expect_gauge("process_count", 25).twice
146
+ expect_gauge("connection_count", 2).twice
147
+ expect_gauge("memory_usage", 1024).twice
148
+ expect_gauge("memory_usage_rss", 512).twice
149
+ expect_gauge("job_count", 5, :status => :processed) # Gauge delta
150
+ expect_gauge("job_count", 3, :status => :failed) # Gauge delta
151
+ expect_gauge("job_count", 12, :status => :retry_queue).twice
152
+ expect_gauge("job_count", 2, :status => :died) # Gauge delta
153
+ expect_gauge("job_count", 14, :status => :scheduled).twice
154
+ expect_gauge("job_count", 15, :status => :enqueued).twice
155
+ expect_gauge("queue_length", 10, :queue => "default").twice
156
+ expect_gauge("queue_latency", 12_000, :queue => "default").twice
157
+ expect_gauge("queue_length", 1, :queue => "critical").twice
158
+ expect_gauge("queue_latency", 2_000, :queue => "critical").twice
159
+ # Call probe twice so we can calculate the delta for some gauge values
160
+ probe.call
161
+ probe.call
162
+ end
163
+
164
+ context "when `redis_info` is not defined" do
165
+ before do
166
+ allow(Sidekiq).to receive(:respond_to?).with(:redis_info).and_return(false)
167
+ end
168
+
169
+ it "does not collect redis metrics" do
170
+ expect_gauge("connection_count", 2).never
171
+ expect_gauge("memory_usage", 1024).never
172
+ expect_gauge("memory_usage_rss", 512).never
173
+ probe.call
174
+ end
175
+ end
176
+
177
+ context "when hostname is configured for probe" do
178
+ let(:redis_hostname) { "my_redis_server" }
179
+ let(:probe) { described_class.new(:hostname => redis_hostname) }
180
+
181
+ it "uses the redis hostname for the hostname tag" do
182
+ allow(Appsignal).to receive(:set_gauge).and_call_original
183
+ log = capture_logs { probe }
184
+ expect(log).to contains_log(
185
+ :debug,
186
+ %(Initializing Sidekiq probe with config: {:hostname=>"#{redis_hostname}"})
187
+ )
188
+ log = capture_logs { probe.call }
189
+ expect(log).to contains_log(
190
+ :debug,
191
+ "Sidekiq probe: Using hostname config option #{redis_hostname.inspect} as hostname"
192
+ )
193
+ expect(Appsignal).to have_received(:set_gauge)
194
+ .with(anything, anything, :hostname => redis_hostname).at_least(:once)
195
+ end
196
+ end
197
+
198
+ def expect_gauge(key, value, tags = {})
199
+ expect(Appsignal).to receive(:set_gauge)
200
+ .with("sidekiq_#{key}", value, expected_default_tags.merge(tags))
201
+ .and_call_original
202
+ end
203
+ end
204
+ end
@@ -4,9 +4,12 @@ describe Appsignal::Rack::JSExceptionCatcher do
4
4
  let(:config_options) { { :enable_frontend_error_catching => true } }
5
5
  let(:config) { project_fixture_config("production", config_options) }
6
6
  let(:deprecation_message) do
7
- "The Appsignal::Rack::JSExceptionCatcher is deprecated. " \
8
- "Please use the official AppSignal JavaScript integration instead. " \
9
- "https://docs.appsignal.com/front-end/"
7
+ "The Appsignal::Rack::JSExceptionCatcher is " \
8
+ "deprecated and will be removed in a future version. Please use " \
9
+ "the official AppSignal JavaScript integration by disabling " \
10
+ "`enable_frontend_error_catching` in your configuration and " \
11
+ "installing AppSignal for JavaScript instead. " \
12
+ "(https://docs.appsignal.com/front-end/)"
10
13
  end
11
14
  before { Appsignal.config = config }
12
15
 
@@ -32,7 +35,9 @@ describe Appsignal::Rack::JSExceptionCatcher do
32
35
 
33
36
  describe "#call" do
34
37
  let(:catcher) do
35
- silence { Appsignal::Rack::JSExceptionCatcher.new(app, options) }
38
+ silence :allowed => ["enable_frontend_error_catching"] do
39
+ Appsignal::Rack::JSExceptionCatcher.new(app, options)
40
+ end
36
41
  end
37
42
  after { catcher.call(env) }
38
43
 
@@ -473,22 +473,20 @@ describe Appsignal::Transaction do
473
473
  end
474
474
  end
475
475
 
476
- describe "set_queue_start" do
477
- it "should set the queue start in extension" do
478
- expect(transaction.ext).to receive(:set_queue_start).with(
479
- 10.0
480
- ).once
476
+ describe "#set_queue_start" do
477
+ it "sets the queue start in extension" do
478
+ expect(transaction.ext).to receive(:set_queue_start).with(10.0).once
481
479
 
482
480
  transaction.set_queue_start(10.0)
483
481
  end
484
482
 
485
- it "should not set the queue start in extension when value is nil" do
483
+ it "does not set the queue start in extension when value is nil" do
486
484
  expect(transaction.ext).to_not receive(:set_queue_start)
487
485
 
488
486
  transaction.set_queue_start(nil)
489
487
  end
490
488
 
491
- it "should not raise an error when the queue start is too big" do
489
+ it "does not raise an error when the queue start is too big" do
492
490
  expect(transaction.ext).to receive(:set_queue_start).and_raise(RangeError)
493
491
 
494
492
  expect(Appsignal.logger).to receive(:warn).with("Queue start value 10 is too big")
@@ -500,23 +498,40 @@ describe Appsignal::Transaction do
500
498
  end
501
499
 
502
500
  describe "#set_http_or_background_queue_start" do
503
- context "for a http transaction" do
504
- let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
505
- let(:env) { { "HTTP_X_REQUEST_START" => (fixed_time * 1000).to_s } }
501
+ let(:header_factor) { 1_000 }
502
+ let(:env_queue_start) { fixed_time + 20 } # in seconds
503
+
504
+ context "when a queue time is found in a request header" do
505
+ let(:header_time) { ((fixed_time + 10) * header_factor).to_i } # in milliseconds
506
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=#{header_time}" } }
506
507
 
507
- it "should set the queue start on the transaction" do
508
- expect(transaction).to receive(:set_queue_start).with(13_897_836_000)
508
+ it "sets the http header value in milliseconds on the transaction" do
509
+ expect(transaction).to receive(:set_queue_start).with(1_389_783_610_000)
509
510
 
510
511
  transaction.set_http_or_background_queue_start
511
512
  end
513
+
514
+ context "when a :queue_start key is found in the transaction environment" do
515
+ let(:env) do
516
+ {
517
+ "HTTP_X_REQUEST_START" => "t=#{header_time}",
518
+ :queue_start => env_queue_start
519
+ }
520
+ end
521
+
522
+ it "sets the http header value in milliseconds on the transaction" do
523
+ expect(transaction).to receive(:set_queue_start).with(1_389_783_610_000)
524
+
525
+ transaction.set_http_or_background_queue_start
526
+ end
527
+ end
512
528
  end
513
529
 
514
- context "for a background transaction" do
515
- let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
516
- let(:env) { { :queue_start => fixed_time } }
530
+ context "when a :queue_start key is found in the transaction environment" do
531
+ let(:env) { { :queue_start => env_queue_start } } # in seconds
517
532
 
518
- it "should set the queue start on the transaction" do
519
- expect(transaction).to receive(:set_queue_start).with(1_389_783_600_000)
533
+ it "sets the :queue_start value in milliseconds on the transaction" do
534
+ expect(transaction).to receive(:set_queue_start).with(1_389_783_620_000)
520
535
 
521
536
  transaction.set_http_or_background_queue_start
522
537
  end
@@ -910,7 +925,7 @@ describe Appsignal::Transaction do
910
925
  context "when queue start is set" do
911
926
  let(:env) { background_env_with_data }
912
927
 
913
- it { is_expected.to eq 1_389_783_590_000 }
928
+ it { is_expected.to eq 1_389_783_600_000 }
914
929
  end
915
930
  end
916
931
 
@@ -949,7 +964,7 @@ describe Appsignal::Transaction do
949
964
  it { is_expected.to be_nil }
950
965
  end
951
966
 
952
- context "with some cruft" do
967
+ context "with unparsable content at the end" do
953
968
  let(:env) { { "HTTP_X_REQUEST_START" => "t=#{slightly_earlier_time_value}aaaa" } }
954
969
 
955
970
  it { is_expected.to eq 1_389_783_599_600 }
@@ -969,7 +984,7 @@ describe Appsignal::Transaction do
969
984
  end
970
985
  end
971
986
 
972
- context "time in miliseconds" do
987
+ context "time in milliseconds" do
973
988
  let(:factor) { 1_000 }
974
989
 
975
990
  it_should_behave_like "http queue start"
@@ -1,4 +1,6 @@
1
1
  describe Appsignal do
2
+ include EnvironmentMetadataHelper
3
+
2
4
  before do
3
5
  # Make sure we have a clean state because we want to test
4
6
  # initialization here.
@@ -80,18 +82,22 @@ describe Appsignal do
80
82
  allow(GC::Profiler).to receive(:enable)
81
83
  Appsignal.config.config_hash[:enable_allocation_tracking] = true
82
84
  Appsignal.config.config_hash[:enable_gc_instrumentation] = true
85
+ capture_environment_metadata_report_calls
83
86
  end
84
87
 
85
88
  it "should enable Ruby's GC::Profiler" do
86
89
  expect(GC::Profiler).to receive(:enable)
87
90
  Appsignal.start
91
+ expect_environment_metadata("ruby_gc_instrumentation_enabled", "true")
88
92
  end
89
93
 
90
94
  unless Appsignal::System.jruby?
95
+
91
96
  it "installs the allocation event hook" do
92
97
  expect(Appsignal::Extension).to receive(:install_allocation_event_hook)
93
98
  .and_call_original
94
99
  Appsignal.start
100
+ expect_environment_metadata("ruby_allocation_tracking_enabled", "true")
95
101
  end
96
102
  end
97
103
  end
@@ -100,6 +106,7 @@ describe Appsignal do
100
106
  before do
101
107
  Appsignal.config.config_hash[:enable_allocation_tracking] = false
102
108
  Appsignal.config.config_hash[:enable_gc_instrumentation] = false
109
+ capture_environment_metadata_report_calls
103
110
  end
104
111
 
105
112
  it "should not enable Ruby's GC::Profiler" do
@@ -110,11 +117,13 @@ describe Appsignal do
110
117
  it "should not install the allocation event hook" do
111
118
  expect(Appsignal::Minutely).not_to receive(:install_allocation_event_hook)
112
119
  Appsignal.start
120
+ expect_not_environment_metadata("ruby_allocation_tracking_enabled")
113
121
  end
114
122
 
115
123
  it "should not add the gc probe to minutely" do
116
124
  expect(Appsignal::Minutely).not_to receive(:register_garbage_collection_probe)
117
125
  Appsignal.start
126
+ expect_not_environment_metadata("ruby_gc_instrumentation_enabled")
118
127
  end
119
128
  end
120
129
 
@@ -139,6 +148,19 @@ describe Appsignal do
139
148
  Appsignal.start
140
149
  end
141
150
  end
151
+
152
+ describe "environment metadata" do
153
+ before { capture_environment_metadata_report_calls }
154
+
155
+ it "collects and reports environment metadata" do
156
+ Appsignal.start
157
+ expect_environment_metadata("ruby_version", "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}")
158
+ expect_environment_metadata("ruby_engine", RUBY_ENGINE)
159
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0")
160
+ expect_environment_metadata("ruby_engine_version", RUBY_ENGINE_VERSION)
161
+ end
162
+ end
163
+ end
142
164
  end
143
165
 
144
166
  context "with debug logging" do
@@ -62,7 +62,7 @@ RSpec.describe "Puma plugin" do
62
62
  expect(launcher.events.on_booted).to_not be_nil
63
63
 
64
64
  launcher.events.on_booted.call
65
- expect(Appsignal::Minutely.probes[:puma]).to eql(Appsignal::Hooks::PumaProbe)
65
+ expect(Appsignal::Minutely.probes[:puma]).to eql(Appsignal::Probes::PumaProbe)
66
66
 
67
67
  # Minutely probes started and called
68
68
  wait_for("enough probe calls") { probe.calls >= 2 }
@@ -0,0 +1,25 @@
1
+ module ActionMailerHelpers
2
+ def perform_action_mailer(mailer, method, args = nil)
3
+ if DependencyHelper.rails_version >= Gem::Version.new("5.2.0")
4
+ case args
5
+ when Array
6
+ mailer.send(method, *args).deliver_later
7
+ when Hash
8
+ mailer.with(args).send(method).deliver_later
9
+ when NilClass
10
+ mailer.send(method).deliver_later
11
+ else
12
+ raise "Unknown scenario for arguments: #{args}"
13
+ end
14
+ else
15
+ # Rails 5.1 and lower
16
+ mailer_object =
17
+ if args
18
+ mailer.send(method, *args)
19
+ else
20
+ mailer.send(method)
21
+ end
22
+ mailer_object.deliver_later
23
+ end
24
+ end
25
+ end
@@ -1,6 +1,10 @@
1
1
  module DependencyHelper
2
2
  module_function
3
3
 
4
+ def ruby_version
5
+ Gem::Version.new(RUBY_VERSION)
6
+ end
7
+
4
8
  def running_jruby?
5
9
  defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
6
10
  end
@@ -9,6 +13,14 @@ module DependencyHelper
9
13
  dependency_present? "rails"
10
14
  end
11
15
 
16
+ def rails6_present?
17
+ rails_present? && rails_version >= Gem::Version.new("6.0.0")
18
+ end
19
+
20
+ def rails_version
21
+ Gem.loaded_specs["rails"].version
22
+ end
23
+
12
24
  def sequel_present?
13
25
  dependency_present? "sequel"
14
26
  end