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
@@ -25,6 +25,8 @@ module Appsignal
25
25
  end
26
26
  end
27
27
  end
28
+
29
+ Appsignal::Environment.report_enabled("net_http")
28
30
  end
29
31
  end
30
32
  end
@@ -24,7 +24,8 @@ module Appsignal
24
24
  # runs in the Puma main process.
25
25
  # For more information:
26
26
  # https://docs.appsignal.com/ruby/integrations/puma.html
27
- Appsignal::Minutely.probes.register :puma, PumaProbe
27
+ require "appsignal/probes/puma"
28
+ Appsignal::Minutely.probes.register :puma, ::Appsignal::Probes::PumaProbe
28
29
  end
29
30
 
30
31
  return unless defined?(::Puma::Cluster)
@@ -39,62 +40,5 @@ module Appsignal
39
40
  end
40
41
  end
41
42
  end
42
-
43
- class PumaProbe
44
- def initialize
45
- @hostname = Appsignal.config[:hostname] || Socket.gethostname
46
- end
47
-
48
- def call
49
- puma_stats = fetch_puma_stats
50
- return unless puma_stats
51
-
52
- stats = JSON.parse puma_stats, :symbolize_names => true
53
- counts = {}
54
- count_keys = [:backlog, :running, :pool_capacity, :max_threads]
55
-
56
- if stats[:worker_status] # Multiple workers
57
- stats[:worker_status].each do |worker|
58
- stat = worker[:last_status]
59
- count_keys.each do |key|
60
- count_if_present counts, key, stat
61
- end
62
- end
63
-
64
- gauge(:workers, stats[:workers], :type => :count)
65
- gauge(:workers, stats[:booted_workers], :type => :booted)
66
- gauge(:workers, stats[:old_workers], :type => :old)
67
- else # Single worker
68
- count_keys.each do |key|
69
- count_if_present counts, key, stats
70
- end
71
- end
72
-
73
- gauge(:connection_backlog, counts[:backlog]) if counts[:backlog]
74
- gauge(:pool_capacity, counts[:pool_capacity]) if counts[:pool_capacity]
75
- gauge(:threads, counts[:running], :type => :running) if counts[:running]
76
- gauge(:threads, counts[:max_threads], :type => :max) if counts[:max_threads]
77
- end
78
-
79
- private
80
-
81
- attr_reader :hostname
82
-
83
- def gauge(field, count, tags = {})
84
- Appsignal.set_gauge("puma_#{field}", count, tags.merge(:hostname => hostname))
85
- end
86
-
87
- def count_if_present(counts, key, stats)
88
- stat_value = stats[key]
89
- return unless stat_value
90
- counts[key] ||= 0
91
- counts[key] += stat_value
92
- end
93
-
94
- def fetch_puma_stats
95
- ::Puma.stats
96
- rescue NoMethodError # rubocop:disable Lint/HandleExceptions
97
- end
98
- end
99
43
  end
100
44
  end
@@ -26,6 +26,8 @@ module Appsignal
26
26
  end
27
27
  end
28
28
  end
29
+
30
+ Appsignal::Environment.report_enabled("redis")
29
31
  end
30
32
  end
31
33
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ class Hooks
5
+ # @api private
6
+ class ResqueHook < Appsignal::Hooks::Hook
7
+ register :resque
8
+
9
+ def dependencies_present?
10
+ defined?(::Resque)
11
+ end
12
+
13
+ def install
14
+ Resque::Job.class_eval do
15
+ alias_method :perform_without_appsignal, :perform
16
+
17
+ def perform
18
+ transaction = Appsignal::Transaction.create(
19
+ SecureRandom.uuid,
20
+ Appsignal::Transaction::BACKGROUND_JOB,
21
+ Appsignal::Transaction::GenericRequest.new({})
22
+ )
23
+
24
+ Appsignal.instrument "perform.resque" do
25
+ perform_without_appsignal
26
+ end
27
+ rescue Exception => exception # rubocop:disable Lint/RescueException
28
+ transaction.set_error(exception)
29
+ raise exception
30
+ ensure
31
+ if transaction
32
+ transaction.set_action_if_nil("#{payload["class"]}#perform")
33
+ args =
34
+ Appsignal::Utils::HashSanitizer.sanitize(
35
+ ResqueHelpers.arguments(payload),
36
+ Appsignal.config[:filter_parameters]
37
+ )
38
+ transaction.params = args if args
39
+ transaction.set_tags("queue" => queue)
40
+
41
+ Appsignal::Transaction.complete_current!
42
+ end
43
+ Appsignal.stop("resque")
44
+ end
45
+ end
46
+ end
47
+
48
+ class ResqueHelpers
49
+ def self.arguments(payload)
50
+ case payload["class"]
51
+ when "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper"
52
+ nil # Set in the ActiveJob integration
53
+ else
54
+ payload["args"]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -56,6 +56,8 @@ module Appsignal
56
56
 
57
57
  # ... and automatically add it to future instances.
58
58
  ::Sequel::Database.extension(:appsignal_integration)
59
+
60
+ Appsignal::Environment.report_enabled("sequel")
59
61
  end
60
62
  end
61
63
  end
@@ -12,7 +12,8 @@ module Appsignal
12
12
  end
13
13
 
14
14
  def install
15
- Appsignal::Minutely.probes.register :sidekiq, SidekiqProbe
15
+ require "appsignal/probes/sidekiq"
16
+ Appsignal::Minutely.probes.register :sidekiq, Appsignal::Probes::SidekiqProbe
16
17
 
17
18
  ::Sidekiq.configure_server do |config|
18
19
  config.server_middleware do |chain|
@@ -22,110 +23,11 @@ module Appsignal
22
23
  end
23
24
  end
24
25
 
25
- class SidekiqProbe
26
- attr_reader :config
27
-
28
- def self.dependencies_present?
29
- Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
30
- end
31
-
32
- def initialize(config = {})
33
- @config = config
34
- @cache = {}
35
- config_string = " with config: #{config}" unless config.empty?
36
- Appsignal.logger.debug("Initializing Sidekiq probe#{config_string}")
37
- require "sidekiq/api"
38
- end
39
-
40
- def call
41
- track_redis_info
42
- track_stats
43
- track_queues
44
- end
45
-
46
- private
47
-
48
- attr_reader :cache
49
-
50
- def track_redis_info
51
- return unless ::Sidekiq.respond_to?(:redis_info)
52
- redis_info = ::Sidekiq.redis_info
53
-
54
- gauge "connection_count", redis_info.fetch("connected_clients")
55
- gauge "memory_usage", redis_info.fetch("used_memory")
56
- gauge "memory_usage_rss", redis_info.fetch("used_memory_rss")
57
- end
58
-
59
- def track_stats
60
- stats = ::Sidekiq::Stats.new
61
-
62
- gauge "worker_count", stats.workers_size
63
- gauge "process_count", stats.processes_size
64
- gauge_delta :jobs_processed, "job_count", stats.processed,
65
- :status => :processed
66
- gauge_delta :jobs_failed, "job_count", stats.failed, :status => :failed
67
- gauge "job_count", stats.retry_size, :status => :retry_queue
68
- gauge_delta :jobs_dead, "job_count", stats.dead_size, :status => :died
69
- gauge "job_count", stats.scheduled_size, :status => :scheduled
70
- gauge "job_count", stats.enqueued, :status => :enqueued
71
- end
72
-
73
- def track_queues
74
- ::Sidekiq::Queue.all.each do |queue|
75
- gauge "queue_length", queue.size, :queue => queue.name
76
- # Convert latency from seconds to milliseconds
77
- gauge "queue_latency", queue.latency * 1_000.0, :queue => queue.name
78
- end
79
- end
80
-
81
- # Track a gauge metric with the `sidekiq_` prefix
82
- def gauge(key, value, tags = {})
83
- tags[:hostname] = hostname if hostname
84
- Appsignal.set_gauge "sidekiq_#{key}", value, tags
85
- end
86
-
87
- # Track the delta of two values for a gauge metric
88
- #
89
- # First call will store the data for the metric and the second call will
90
- # set a gauge metric with the difference. This is used for absolute
91
- # counter values which we want to track as gauges.
92
- #
93
- # @example
94
- # gauge_delta :my_cache_key, "my_gauge", 10
95
- # gauge_delta :my_cache_key, "my_gauge", 15
96
- # # Creates a gauge with the value `5`
97
- # @see #gauge
98
- def gauge_delta(cache_key, key, value, tags = {})
99
- previous_value = cache[cache_key]
100
- cache[cache_key] = value
101
- return unless previous_value
102
- new_value = value - previous_value
103
- gauge key, new_value, tags
104
- end
105
-
106
- def hostname
107
- return @hostname if defined?(@hostname)
108
- if config.key?(:hostname)
109
- @hostname = config[:hostname]
110
- Appsignal.logger.debug "Sidekiq probe: Using hostname config " \
111
- "option #{@hostname.inspect} as hostname"
112
- return @hostname
113
- end
114
-
115
- host = nil
116
- ::Sidekiq.redis { |c| host = c.connection[:host] }
117
- Appsignal.logger.debug "Sidekiq probe: Using Redis server hostname " \
118
- "#{host.inspect} as hostname"
119
- @hostname = host
120
- end
121
- end
122
-
123
26
  # @api private
124
27
  class SidekiqPlugin # rubocop:disable Metrics/ClassLength
125
28
  include Appsignal::Hooks::Helpers
126
29
 
127
- UNKNOWN_ACTION_NAME = "unknown".freeze
128
- JOB_KEYS = %w[
30
+ EXCLUDED_JOB_KEYS = %w[
129
31
  args backtrace class created_at enqueued_at error_backtrace error_class
130
32
  error_message failed_at jid retried_at retry wrapped
131
33
  ].freeze
@@ -152,7 +54,10 @@ module Appsignal
152
54
  ensure
153
55
  if transaction
154
56
  transaction.set_action_if_nil(formatted_action_name(item))
155
- transaction.params = filtered_arguments(item)
57
+
58
+ params = filtered_arguments(item)
59
+ transaction.params = params if params
60
+
156
61
  formatted_metadata(item).each do |key, value|
157
62
  transaction.set_metadata key, value
158
63
  end
@@ -178,16 +83,20 @@ module Appsignal
178
83
 
179
84
  def formatted_action_name(job)
180
85
  sidekiq_action_name = parse_action_name(job)
86
+ return unless sidekiq_action_name
87
+
181
88
  complete_action = sidekiq_action_name =~ /\.|#/
182
- if complete_action || sidekiq_action_name == UNKNOWN_ACTION_NAME
183
- return sidekiq_action_name
184
- end
89
+ return sidekiq_action_name if complete_action
90
+
185
91
  "#{sidekiq_action_name}#perform"
186
92
  end
187
93
 
188
94
  def filtered_arguments(job)
95
+ arguments = parse_arguments(job)
96
+ return unless arguments
97
+
189
98
  Appsignal::Utils::HashSanitizer.sanitize(
190
- parse_arguments(job),
99
+ arguments,
191
100
  Appsignal.config[:filter_parameters]
192
101
  )
193
102
  end
@@ -195,7 +104,8 @@ module Appsignal
195
104
  def formatted_metadata(item)
196
105
  {}.tap do |hash|
197
106
  (item || {}).each do |key, value|
198
- next if JOB_KEYS.include?(key)
107
+ next if EXCLUDED_JOB_KEYS.include?(key)
108
+
199
109
  hash[key] = truncate(string_or_inspect(value))
200
110
  end
201
111
  end
@@ -214,81 +124,11 @@ module Appsignal
214
124
  safe_load(args[0], job_class) do |target, method, _|
215
125
  "#{target}.#{method}"
216
126
  end
217
- when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
218
- wrapped_job = job["wrapped"]
219
- if wrapped_job
220
- parse_active_job_action_name_from_wrapped job
221
- else
222
- parse_active_job_action_name_from_arguments job
223
- end
224
127
  else
225
128
  job_class
226
129
  end
227
130
  end
228
131
 
229
- # Return the ActiveJob wrapped job name.
230
- #
231
- # Returns "unknown" if no acceptable job class name could be found.
232
- #
233
- # @example Payload with "wrapped" value
234
- # {
235
- # "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
236
- # "wrapped" => "MyWrappedJob",
237
- # # ...
238
- # }
239
- def parse_active_job_action_name_from_wrapped(job)
240
- job_class = job["wrapped"]
241
- case job_class
242
- when "ActionMailer::DeliveryJob"
243
- extract_action_mailer_name job["args"]
244
- when String
245
- job_class
246
- else
247
- unknown_action_name_for job
248
- end
249
- end
250
-
251
- # Return the ActiveJob job name based on the job's arguments.
252
- #
253
- # Returns "unknown" if no acceptable job class name could be found.
254
- #
255
- # @example Payload without "wrapped" value
256
- # {
257
- # "class" => "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper",
258
- # "args" => [{
259
- # "job_class" => "MyWrappedJob",
260
- # # ...
261
- # }]
262
- # # ...
263
- # }
264
- def parse_active_job_action_name_from_arguments(job)
265
- args = job.fetch("args", [])
266
- first_arg = args[0]
267
- if first_arg == "ActionMailer::DeliveryJob"
268
- extract_action_mailer_name args
269
- elsif active_job_payload?(first_arg)
270
- first_arg["job_class"]
271
- else
272
- unknown_action_name_for job
273
- end
274
- end
275
-
276
- # Checks if the first argument in the job payload is an ActiveJob payload.
277
- def active_job_payload?(arg)
278
- arg.is_a?(Hash) && arg["job_class"].is_a?(String)
279
- end
280
-
281
- def unknown_action_name_for(job)
282
- Appsignal.logger.debug \
283
- "Unable to determine an action name from Sidekiq payload: #{job}"
284
- UNKNOWN_ACTION_NAME
285
- end
286
-
287
- def extract_action_mailer_name(args)
288
- # Returns in format: MailerClass#mailer_method
289
- args[0]["arguments"][0..1].join("#")
290
- end
291
-
292
132
  # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L336-L358
293
133
  def parse_arguments(job)
294
134
  args = job.fetch("args", [])
@@ -298,20 +138,7 @@ module Appsignal
298
138
  arg
299
139
  end
300
140
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
301
- is_wrapped = job["wrapped"]
302
- first_arg = args[0]
303
- job_args =
304
- if is_wrapped || active_job_payload?(first_arg)
305
- first_arg["arguments"]
306
- else
307
- []
308
- end
309
- if (is_wrapped || first_arg) == "ActionMailer::DeliveryJob"
310
- # Remove MailerClass, mailer_method and "deliver_now"
311
- job_args.drop(3)
312
- else
313
- job_args
314
- end
141
+ nil # Set in the ActiveJob integration
315
142
  else
316
143
  # Sidekiq Enterprise argument encryption.
317
144
  # More information: https://github.com/mperham/sidekiq/wiki/Ent-Encryption
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if defined?(Appsignal)
4
+ Appsignal::Environment.report_enabled("object_instrumentation")
5
+ end
6
+
3
7
  class Object
4
8
  def self.appsignal_instrument_class_method(method_name, options = {})
5
9
  singleton_class.send \
@@ -32,7 +32,7 @@ module Appsignal
32
32
  transaction.set_error(error)
33
33
  raise error
34
34
  ensure
35
- transaction.set_action "#{local_attrs[:job_class]}#run"
35
+ transaction.set_action_if_nil "#{local_attrs[:job_class]}#run"
36
36
  Appsignal::Transaction.complete_current!
37
37
  end
38
38
  end