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
@@ -4,18 +4,15 @@ module Appsignal
4
4
  module Integrations
5
5
  # @api private
6
6
  module ResquePlugin
7
- # Do not use this file as a template for your own background processor
8
- # Resque is an exception to the rule and the code below causes the
9
- # extension to shut itself down after a single job.
10
- # see http://docs.appsignal.com/background-monitoring/custom.html
11
- def around_perform_resque_plugin(*_args)
12
- Appsignal.monitor_single_transaction(
13
- "perform_job.resque",
14
- :class => to_s,
15
- :method => "perform"
16
- ) do
17
- yield
18
- end
7
+ def self.extended(_)
8
+ callers = caller
9
+ Appsignal::Utils::DeprecationMessage.message \
10
+ "The AppSignal ResquePlugin is deprecated and does " \
11
+ "nothing on extend. In this version of the AppSignal Ruby gem " \
12
+ "the integration with Resque is automatic on all Resque workers. " \
13
+ "Please remove the following line from this file to remove this " \
14
+ "message: extend Appsignal::Integrations::ResquePlugin\n" \
15
+ "#{callers.first}"
19
16
  end
20
17
  end
21
18
  end
@@ -4,30 +4,15 @@ module Appsignal
4
4
  module Integrations
5
5
  # @api private
6
6
  module ResqueActiveJobPlugin
7
- include Appsignal::Hooks::Helpers
8
-
9
- def self.included(base)
10
- base.class_eval do
11
- around_perform do |job, block|
12
- params = Appsignal::Utils::HashSanitizer.sanitize(
13
- job.arguments,
14
- Appsignal.config[:filter_parameters]
15
- )
16
-
17
- Appsignal.monitor_single_transaction(
18
- "perform_job.resque",
19
- :class => job.class.to_s,
20
- :method => "perform",
21
- :params => params,
22
- :metadata => {
23
- :id => job.job_id,
24
- :queue => job.queue_name
25
- }
26
- ) do
27
- block.call
28
- end
29
- end
30
- end
7
+ def self.included(_)
8
+ callers = caller
9
+ Appsignal::Utils::DeprecationMessage.message \
10
+ "The AppSignal ResqueActiveJobPlugin is deprecated and does " \
11
+ "nothing on extend. In this version of the AppSignal Ruby gem " \
12
+ "the integration with Resque is automatic on all Resque workers. " \
13
+ "Please remove the following line from this file to remove this " \
14
+ "message: include Appsignal::Integrations::ResqueActiveJobPlugin\n" \
15
+ "#{callers.first}"
31
16
  end
32
17
  end
33
18
  end
@@ -0,0 +1,61 @@
1
+ module Appsignal
2
+ module Probes
3
+ # @api private
4
+ class PumaProbe
5
+ def initialize
6
+ @hostname = Appsignal.config[:hostname] || Socket.gethostname
7
+ end
8
+
9
+ def call
10
+ puma_stats = fetch_puma_stats
11
+ return unless puma_stats
12
+
13
+ stats = JSON.parse puma_stats, :symbolize_names => true
14
+ counts = {}
15
+ count_keys = [:backlog, :running, :pool_capacity, :max_threads]
16
+
17
+ if stats[:worker_status] # Multiple workers
18
+ stats[:worker_status].each do |worker|
19
+ stat = worker[:last_status]
20
+ count_keys.each do |key|
21
+ count_if_present counts, key, stat
22
+ end
23
+ end
24
+
25
+ gauge(:workers, stats[:workers], :type => :count)
26
+ gauge(:workers, stats[:booted_workers], :type => :booted)
27
+ gauge(:workers, stats[:old_workers], :type => :old)
28
+ else # Single worker
29
+ count_keys.each do |key|
30
+ count_if_present counts, key, stats
31
+ end
32
+ end
33
+
34
+ gauge(:connection_backlog, counts[:backlog]) if counts[:backlog]
35
+ gauge(:pool_capacity, counts[:pool_capacity]) if counts[:pool_capacity]
36
+ gauge(:threads, counts[:running], :type => :running) if counts[:running]
37
+ gauge(:threads, counts[:max_threads], :type => :max) if counts[:max_threads]
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :hostname
43
+
44
+ def gauge(field, count, tags = {})
45
+ Appsignal.set_gauge("puma_#{field}", count, tags.merge(:hostname => hostname))
46
+ end
47
+
48
+ def count_if_present(counts, key, stats)
49
+ stat_value = stats[key]
50
+ return unless stat_value
51
+ counts[key] ||= 0
52
+ counts[key] += stat_value
53
+ end
54
+
55
+ def fetch_puma_stats
56
+ ::Puma.stats
57
+ rescue NoMethodError # rubocop:disable Lint/HandleExceptions
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,102 @@
1
+ module Appsignal
2
+ module Probes
3
+ # @api private
4
+ class SidekiqProbe
5
+ attr_reader :config
6
+
7
+ def self.dependencies_present?
8
+ Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
9
+ end
10
+
11
+ def initialize(config = {})
12
+ @config = config
13
+ @cache = {}
14
+ config_string = " with config: #{config}" unless config.empty?
15
+ Appsignal.logger.debug("Initializing Sidekiq probe#{config_string}")
16
+ require "sidekiq/api"
17
+ end
18
+
19
+ def call
20
+ track_redis_info
21
+ track_stats
22
+ track_queues
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :cache
28
+
29
+ def track_redis_info
30
+ return unless ::Sidekiq.respond_to?(:redis_info)
31
+ redis_info = ::Sidekiq.redis_info
32
+
33
+ gauge "connection_count", redis_info.fetch("connected_clients")
34
+ gauge "memory_usage", redis_info.fetch("used_memory")
35
+ gauge "memory_usage_rss", redis_info.fetch("used_memory_rss")
36
+ end
37
+
38
+ def track_stats
39
+ stats = ::Sidekiq::Stats.new
40
+
41
+ gauge "worker_count", stats.workers_size
42
+ gauge "process_count", stats.processes_size
43
+ gauge_delta :jobs_processed, "job_count", stats.processed,
44
+ :status => :processed
45
+ gauge_delta :jobs_failed, "job_count", stats.failed, :status => :failed
46
+ gauge "job_count", stats.retry_size, :status => :retry_queue
47
+ gauge_delta :jobs_dead, "job_count", stats.dead_size, :status => :died
48
+ gauge "job_count", stats.scheduled_size, :status => :scheduled
49
+ gauge "job_count", stats.enqueued, :status => :enqueued
50
+ end
51
+
52
+ def track_queues
53
+ ::Sidekiq::Queue.all.each do |queue|
54
+ gauge "queue_length", queue.size, :queue => queue.name
55
+ # Convert latency from seconds to milliseconds
56
+ gauge "queue_latency", queue.latency * 1_000.0, :queue => queue.name
57
+ end
58
+ end
59
+
60
+ # Track a gauge metric with the `sidekiq_` prefix
61
+ def gauge(key, value, tags = {})
62
+ tags[:hostname] = hostname if hostname
63
+ Appsignal.set_gauge "sidekiq_#{key}", value, tags
64
+ end
65
+
66
+ # Track the delta of two values for a gauge metric
67
+ #
68
+ # First call will store the data for the metric and the second call will
69
+ # set a gauge metric with the difference. This is used for absolute
70
+ # counter values which we want to track as gauges.
71
+ #
72
+ # @example
73
+ # gauge_delta :my_cache_key, "my_gauge", 10
74
+ # gauge_delta :my_cache_key, "my_gauge", 15
75
+ # # Creates a gauge with the value `5`
76
+ # @see #gauge
77
+ def gauge_delta(cache_key, key, value, tags = {})
78
+ previous_value = cache[cache_key]
79
+ cache[cache_key] = value
80
+ return unless previous_value
81
+ new_value = value - previous_value
82
+ gauge key, new_value, tags
83
+ end
84
+
85
+ def hostname
86
+ return @hostname if defined?(@hostname)
87
+ if config.key?(:hostname)
88
+ @hostname = config[:hostname]
89
+ Appsignal.logger.debug "Sidekiq probe: Using hostname config " \
90
+ "option #{@hostname.inspect} as hostname"
91
+ return @hostname
92
+ end
93
+
94
+ host = nil
95
+ ::Sidekiq.redis { |c| host = c.connection[:host] }
96
+ Appsignal.logger.debug "Sidekiq probe: Using Redis server hostname " \
97
+ "#{host.inspect} as hostname"
98
+ @hostname = host
99
+ end
100
+ end
101
+ end
102
+ end
@@ -29,8 +29,11 @@ module Appsignal
29
29
  Appsignal.logger.debug \
30
30
  "Initializing Appsignal::Rack::JSExceptionCatcher"
31
31
  deprecation_message "The Appsignal::Rack::JSExceptionCatcher is " \
32
- "deprecated. Please use the official AppSignal JavaScript " \
33
- "integration instead. https://docs.appsignal.com/front-end/"
32
+ "deprecated and will be removed in a future version. Please use " \
33
+ "the official AppSignal JavaScript integration by disabling " \
34
+ "`enable_frontend_error_catching` in your configuration and " \
35
+ "installing AppSignal for JavaScript instead. " \
36
+ "(https://docs.appsignal.com/front-end/)"
34
37
  @app = app
35
38
  end
36
39
 
@@ -221,6 +221,16 @@ module Appsignal
221
221
  set_action_if_nil(group_and_action.compact.join("#"))
222
222
  end
223
223
 
224
+ # Set queue start time for transaction.
225
+ #
226
+ # Most commononly called by {set_http_or_background_queue_start}.
227
+ #
228
+ # @param start [Integer] Queue start time in milliseconds.
229
+ # @raise [RangeError] When the queue start time value is too big, this
230
+ # method raises a RangeError.
231
+ # @raise [TypeError] Raises a TypeError when the given `start` argument is
232
+ # not an Integer.
233
+ # @return [void]
224
234
  def set_queue_start(start)
225
235
  return unless start
226
236
  @ext.set_queue_start(start)
@@ -228,12 +238,27 @@ module Appsignal
228
238
  Appsignal.logger.warn("Queue start value #{start} is too big")
229
239
  end
230
240
 
241
+ # Set the queue time based on the HTTP header or `:queue_start` env key
242
+ # value.
243
+ #
244
+ # This method will first try to read the queue time from the HTTP headers
245
+ # `X-Request-Start` or `X-Queue-Start`. Which are parsed by Rack as
246
+ # `HTTP_X_QUEUE_START` and `HTTP_X_REQUEST_START`.
247
+ # The header value is parsed by AppSignal as either milliseconds or
248
+ # microseconds.
249
+ #
250
+ # If no headers are found, or the value could not be parsed, it falls back
251
+ # on the `:queue_start` env key on this Transaction's {request} environment
252
+ # (called like `request.env[:queue_start]`). This value is parsed by
253
+ # AppSignal as seconds.
254
+ #
255
+ # @see https://docs.appsignal.com/ruby/instrumentation/request-queue-time.html
256
+ # @return [void]
231
257
  def set_http_or_background_queue_start
232
- if namespace == HTTP_REQUEST
233
- set_queue_start(http_queue_start)
234
- elsif namespace == BACKGROUND_JOB
235
- set_queue_start(background_queue_start)
236
- end
258
+ start = http_queue_start || background_queue_start
259
+ return unless start
260
+
261
+ set_queue_start(start)
237
262
  end
238
263
 
239
264
  def set_metadata(key, value)
@@ -346,14 +371,14 @@ module Appsignal
346
371
  #
347
372
  # @return [nil] if no {#environment} is present.
348
373
  # @return [nil] if there is no `:queue_start` in the {#environment}.
349
- # @return [Integer]
374
+ # @return [Integer] `:queue_start` time (in seconds) converted to milliseconds
350
375
  def background_queue_start
351
376
  env = environment
352
377
  return unless env
353
378
  queue_start = env[:queue_start]
354
379
  return unless queue_start
355
380
 
356
- (queue_start.to_f * 1000.0).to_i
381
+ (queue_start.to_f * 1000.0).to_i # Convert seconds to milliseconds
357
382
  end
358
383
 
359
384
  # Returns HTTP queue start time in milliseconds.
@@ -1,10 +1,14 @@
1
1
  module Appsignal
2
2
  module Utils
3
3
  module DeprecationMessage
4
- def deprecation_message(message, logger = Appsignal.logger)
4
+ def self.message(message, logger = Appsignal.logger)
5
5
  $stderr.puts "appsignal WARNING: #{message}"
6
6
  logger.warn message
7
7
  end
8
+
9
+ def deprecation_message(message, logger = Appsignal.logger)
10
+ Appsignal::Utils::DeprecationMessage.message(message, logger)
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.10.8".freeze
4
+ VERSION = "2.11.0.beta.1".freeze
5
5
  end
@@ -17,7 +17,8 @@ Puma::Plugin.create do
17
17
  launcher.events.on_booted do
18
18
  require "appsignal"
19
19
  if ::Puma.respond_to?(:stats)
20
- Appsignal::Minutely.probes.register :puma, Appsignal::Hooks::PumaProbe
20
+ require "appsignal/probes/puma"
21
+ Appsignal::Minutely.probes.register :puma, Appsignal::Probes::PumaProbe
21
22
  end
22
23
  Appsignal.start
23
24
  Appsignal.start_logger
@@ -263,7 +263,8 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
263
263
  },
264
264
  "download" => {
265
265
  "download_url" => kind_of(String),
266
- "checksum" => "verified"
266
+ "checksum" => "verified",
267
+ "http_proxy" => nil
267
268
  },
268
269
  "build" => {
269
270
  "time" => kind_of(String),
@@ -148,6 +148,7 @@ describe Appsignal::Config do
148
148
  :instrument_redis => true,
149
149
  :instrument_sequel => true,
150
150
  :skip_session_data => false,
151
+ :send_environment_metadata => true,
151
152
  :send_params => true,
152
153
  :endpoint => "https://push.appsignal.com",
153
154
  :push_api_key => "abc",
@@ -411,7 +412,8 @@ describe Appsignal::Config do
411
412
  :instrument_sequel => false,
412
413
  :files_world_accessible => false,
413
414
  :request_headers => %w[accept accept-charset],
414
- :revision => "v2.5.1"
415
+ :revision => "v2.5.1",
416
+ :send_environment_metadata => false
415
417
  }
416
418
  end
417
419
  before do
@@ -428,6 +430,7 @@ describe Appsignal::Config do
428
430
  ENV["APPSIGNAL_INSTRUMENT_SEQUEL"] = "false"
429
431
  ENV["APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = "false"
430
432
  ENV["APPSIGNAL_REQUEST_HEADERS"] = "accept,accept-charset"
433
+ ENV["APPSIGNAL_SEND_ENVIRONMENT_METADATA"] = "false"
431
434
  ENV["APP_REVISION"] = "v2.5.1"
432
435
  end
433
436
 
@@ -527,6 +530,7 @@ describe Appsignal::Config do
527
530
  config[:running_in_container] = false
528
531
  config[:dns_servers] = ["8.8.8.8", "8.8.4.4"]
529
532
  config[:transaction_debug_mode] = true
533
+ config[:send_environment_metadata] = false
530
534
  config[:revision] = "v2.5.1"
531
535
  config.write_to_environment
532
536
  end
@@ -555,6 +559,7 @@ describe Appsignal::Config do
555
559
  expect(ENV["_APPSIGNAL_DNS_SERVERS"]).to eq "8.8.8.8,8.8.4.4"
556
560
  expect(ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"]).to eq "true"
557
561
  expect(ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"]).to eq "true"
562
+ expect(ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"]).to eq "false"
558
563
  expect(ENV["_APP_REVISION"]).to eq "v2.5.1"
559
564
  expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIR_PATH")
560
565
  expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIRECTORY_PATH")
@@ -0,0 +1,167 @@
1
+ describe Appsignal::Environment do
2
+ include EnvironmentMetadataHelper
3
+
4
+ before(:context) { start_agent }
5
+ before { capture_environment_metadata_report_calls }
6
+
7
+ def report(key, &value_block)
8
+ described_class.report(key, &value_block)
9
+ end
10
+
11
+ describe ".report" do
12
+ it "sends environment metadata to the extension" do
13
+ logs =
14
+ capture_logs do
15
+ report("_test_ruby_version") { "1.0.0" }
16
+ expect_environment_metadata("_test_ruby_version", "1.0.0")
17
+ end
18
+ expect(logs).to be_empty
19
+ end
20
+
21
+ context "when the key is a non String type" do
22
+ it "does not set the value" do
23
+ logs =
24
+ capture_logs do
25
+ report(:_test_symbol) { "1.0.0" }
26
+ expect_not_environment_metadata(:_test_symbol)
27
+ expect_not_environment_metadata("_test_symbol")
28
+ end
29
+ expect(logs).to contains_log(
30
+ :error,
31
+ "Unable to report on environment metadata: Unsupported value type for :_test_symbol"
32
+ )
33
+ end
34
+ end
35
+
36
+ context "when the key is nil" do
37
+ it "does not set the value" do
38
+ logs =
39
+ capture_logs do
40
+ report(nil) { "1" }
41
+ expect_not_environment_metadata(nil)
42
+ end
43
+ expect(logs).to contains_log(
44
+ :error,
45
+ "Unable to report on environment metadata: Unsupported value type for nil"
46
+ )
47
+ end
48
+ end
49
+
50
+ context "when the value is true or false" do
51
+ it "reports true or false as Strings" do
52
+ logs =
53
+ capture_logs do
54
+ report("_test_true") { true }
55
+ report("_test_false") { false }
56
+ expect_environment_metadata("_test_true", "true")
57
+ expect_environment_metadata("_test_false", "false")
58
+ end
59
+ expect(logs).to be_empty
60
+ end
61
+ end
62
+
63
+ context "when the value is nil" do
64
+ it "does not set the value" do
65
+ logs =
66
+ capture_logs do
67
+ report("_test_ruby_version") { nil }
68
+ expect_not_environment_metadata("_test_ruby_version")
69
+ end
70
+ expect(logs).to contains_log(
71
+ :error,
72
+ "Unable to report on environment metadata \"_test_ruby_version\": " \
73
+ "Unsupported value type for nil"
74
+ )
75
+ end
76
+ end
77
+
78
+ context "when the value block raises an error" do
79
+ it "does not re-raise the error and writes it to the log" do
80
+ logs =
81
+ capture_logs do
82
+ report("_test_error") { raise "uh oh" }
83
+ expect_not_environment_metadata("_test_error")
84
+ end
85
+ expect(logs).to contains_log(
86
+ :error,
87
+ "Unable to report on environment metadata \"_test_error\":\n" \
88
+ "RuntimeError: uh oh"
89
+ )
90
+ end
91
+ end
92
+
93
+ context "when something unforseen errors" do
94
+ it "does not re-raise the error and writes it to the log" do
95
+ klass = Class.new do
96
+ def inspect
97
+ raise "inspect error"
98
+ end
99
+ end
100
+
101
+ logs =
102
+ capture_logs do
103
+ report(klass.new) { raise "value error" }
104
+ expect(Appsignal::Extension).to_not have_received(:set_environment_metadata)
105
+ end
106
+ expect(logs).to contains_log(
107
+ :error,
108
+ "Unable to report on environment metadata:\n" \
109
+ "RuntimeError: inspect error"
110
+ )
111
+ end
112
+ end
113
+ end
114
+
115
+ describe ".report_supported_gems" do
116
+ it "reports about all AppSignal supported gems in the bundle" do
117
+ logs = capture_logs { described_class.report_supported_gems }
118
+
119
+ expect(logs).to be_empty
120
+
121
+ bundle_gem_specs = ::Bundler.rubygems.all_specs
122
+ rack_spec = bundle_gem_specs.find { |s| s.name == "rack" }
123
+ rake_spec = bundle_gem_specs.find { |s| s.name == "rake" }
124
+ expect_environment_metadata("ruby_rack_version", rack_spec.version.to_s)
125
+ expect_environment_metadata("ruby_rake_version", rake_spec.version.to_s)
126
+ expect(rack_spec.version.to_s).to_not be_empty
127
+ expect(rake_spec.version.to_s).to_not be_empty
128
+ end
129
+
130
+ context "when something unforseen errors" do
131
+ it "does not re-raise the error and writes it to the log" do
132
+ expect(Bundler).to receive(:rubygems).and_raise(RuntimeError, "bundler error")
133
+
134
+ logs = capture_logs { described_class.report_supported_gems }
135
+ expect(logs).to contains_log(
136
+ :error,
137
+ "Unable to report supported gems:\nRuntimeError: bundler error"
138
+ )
139
+ end
140
+ end
141
+ end
142
+
143
+ describe ".report_enabled" do
144
+ it "reports a feature being enabled" do
145
+ logs = capture_logs { described_class.report_enabled("a_test") }
146
+
147
+ expect(logs).to be_empty
148
+ expect_environment_metadata("ruby_a_test_enabled", "true")
149
+ end
150
+
151
+ context "when something unforseen errors" do
152
+ it "does not re-raise the error and writes it to the log" do
153
+ klass = Class.new do
154
+ def to_s
155
+ raise "to_s error"
156
+ end
157
+ end
158
+
159
+ logs = capture_logs { described_class.report_enabled(klass.new) }
160
+ expect(logs).to contains_log(
161
+ :error,
162
+ "Unable to report integration enabled:\nRuntimeError: to_s error"
163
+ )
164
+ end
165
+ end
166
+ end
167
+ end