appsignal 2.10.6 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +138 -62
  4. data/CHANGELOG.md +56 -0
  5. data/README.md +4 -4
  6. data/Rakefile +16 -4
  7. data/appsignal.gemspec +1 -1
  8. data/build_matrix.yml +20 -9
  9. data/ext/Rakefile +2 -0
  10. data/ext/agent.yml +19 -19
  11. data/ext/appsignal_extension.c +10 -1
  12. data/ext/base.rb +22 -4
  13. data/ext/extconf.rb +2 -0
  14. data/gemfiles/padrino.gemfile +2 -2
  15. data/gemfiles/rails-4.2.gemfile +9 -2
  16. data/gemfiles/rails-5.0.gemfile +1 -0
  17. data/gemfiles/rails-5.1.gemfile +1 -0
  18. data/gemfiles/rails-5.2.gemfile +1 -0
  19. data/gemfiles/rails-6.0.gemfile +1 -0
  20. data/gemfiles/resque-1.gemfile +7 -0
  21. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  22. data/lib/appsignal.rb +22 -1
  23. data/lib/appsignal/auth_check.rb +4 -2
  24. data/lib/appsignal/capistrano.rb +2 -0
  25. data/lib/appsignal/cli/diagnose.rb +1 -1
  26. data/lib/appsignal/config.rb +85 -16
  27. data/lib/appsignal/environment.rb +126 -0
  28. data/lib/appsignal/extension.rb +6 -5
  29. data/lib/appsignal/extension/jruby.rb +16 -5
  30. data/lib/appsignal/hooks.rb +25 -0
  31. data/lib/appsignal/hooks/active_job.rb +137 -0
  32. data/lib/appsignal/hooks/net_http.rb +10 -13
  33. data/lib/appsignal/hooks/puma.rb +1 -58
  34. data/lib/appsignal/hooks/redis.rb +2 -0
  35. data/lib/appsignal/hooks/resque.rb +60 -0
  36. data/lib/appsignal/hooks/sequel.rb +2 -0
  37. data/lib/appsignal/hooks/sidekiq.rb +18 -192
  38. data/lib/appsignal/integrations/delayed_job_plugin.rb +16 -3
  39. data/lib/appsignal/integrations/object.rb +4 -0
  40. data/lib/appsignal/integrations/que.rb +1 -1
  41. data/lib/appsignal/integrations/resque.rb +9 -12
  42. data/lib/appsignal/integrations/resque_active_job.rb +9 -24
  43. data/lib/appsignal/probes.rb +7 -0
  44. data/lib/appsignal/probes/puma.rb +61 -0
  45. data/lib/appsignal/probes/sidekiq.rb +104 -0
  46. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  47. data/lib/appsignal/system.rb +0 -6
  48. data/lib/appsignal/transaction.rb +32 -7
  49. data/lib/appsignal/utils/deprecation_message.rb +6 -2
  50. data/lib/appsignal/version.rb +1 -1
  51. data/lib/puma/plugin/appsignal.rb +2 -1
  52. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  53. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  54. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  55. data/spec/lib/appsignal/cli/diagnose_spec.rb +44 -1
  56. data/spec/lib/appsignal/config_spec.rb +44 -1
  57. data/spec/lib/appsignal/environment_spec.rb +167 -0
  58. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  59. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  60. data/spec/lib/appsignal/hooks/activejob_spec.rb +591 -0
  61. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +187 -166
  62. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  63. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  64. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +297 -549
  65. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  66. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  67. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  68. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -137
  69. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  70. data/spec/lib/appsignal/marker_spec.rb +1 -1
  71. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  72. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  73. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  74. data/spec/lib/appsignal/system_spec.rb +0 -36
  75. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  76. data/spec/lib/appsignal_spec.rb +22 -0
  77. data/spec/lib/puma/appsignal_spec.rb +1 -1
  78. data/spec/spec_helper.rb +5 -0
  79. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  80. data/spec/support/helpers/config_helpers.rb +3 -2
  81. data/spec/support/helpers/dependency_helper.rb +12 -0
  82. data/spec/support/helpers/env_helpers.rb +1 -1
  83. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  84. data/spec/support/helpers/transaction_helpers.rb +6 -0
  85. data/spec/support/stubs/sidekiq/api.rb +2 -2
  86. data/spec/support/testing.rb +19 -19
  87. metadata +31 -9
  88. data/lib/appsignal/integrations/net_http.rb +0 -16
@@ -69,10 +69,34 @@ module Appsignal
69
69
  text.size > 200 ? "#{text[0...197]}..." : text
70
70
  end
71
71
  end
72
+
73
+ # Alias Probes constants that have moved to their own module in version
74
+ # 2.11.0.
75
+ def self.const_missing(name)
76
+ case name
77
+ when :SidekiqProbe
78
+ callers = caller
79
+ Appsignal::Utils::DeprecationMessage.message \
80
+ "The constant Appsignal::Hooks::SidekiqProbe has been deprecated. " \
81
+ "Please update the constant name to Appsignal::Probes::SidekiqProbe " \
82
+ "in the following file to remove this message.\n#{callers.first}"
83
+ Appsignal::Probes::SidekiqProbe
84
+ when :PumaProbe
85
+ callers = caller
86
+ Appsignal::Utils::DeprecationMessage.message \
87
+ "The constant Appsignal::Hooks::PumaProbe has been deprecated. " \
88
+ "Please update the constant name to Appsignal::Probes::PumaProbe " \
89
+ "in the following file to remove this message.\n#{callers.first}"
90
+ Appsignal::Probes::PumaProbe
91
+ else
92
+ super
93
+ end
94
+ end
72
95
  end
73
96
  end
74
97
 
75
98
  require "appsignal/hooks/action_cable"
99
+ require "appsignal/hooks/active_job"
76
100
  require "appsignal/hooks/active_support_notifications"
77
101
  require "appsignal/hooks/celluloid"
78
102
  require "appsignal/hooks/delayed_job"
@@ -81,6 +105,7 @@ require "appsignal/hooks/passenger"
81
105
  require "appsignal/hooks/puma"
82
106
  require "appsignal/hooks/rake"
83
107
  require "appsignal/hooks/redis"
108
+ require "appsignal/hooks/resque"
84
109
  require "appsignal/hooks/sequel"
85
110
  require "appsignal/hooks/shoryuken"
86
111
  require "appsignal/hooks/sidekiq"
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ class Hooks
5
+ # @api private
6
+ class ActiveJobHook < Appsignal::Hooks::Hook
7
+ register :active_job
8
+
9
+ def dependencies_present?
10
+ defined?(::ActiveJob)
11
+ end
12
+
13
+ def install
14
+ ::ActiveJob::Base
15
+ .extend ::Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation
16
+ end
17
+
18
+ module ActiveJobClassInstrumentation
19
+ def execute(job)
20
+ job_status = nil
21
+ current_transaction = Appsignal::Transaction.current
22
+ transaction =
23
+ if current_transaction.nil_transaction?
24
+ # No standalone integration started before ActiveJob integration.
25
+ # We don't have a separate integration for this QueueAdapter like
26
+ # we do for Sidekiq.
27
+ #
28
+ # Prefer job_id from provider, instead of ActiveJob's internal ID.
29
+ Appsignal::Transaction.create(
30
+ job["provider_job_id"] || job["job_id"],
31
+ Appsignal::Transaction::BACKGROUND_JOB,
32
+ Appsignal::Transaction::GenericRequest.new({})
33
+ )
34
+ else
35
+ current_transaction
36
+ end
37
+
38
+ super
39
+ rescue Exception => exception # rubocop:disable Lint/RescueException
40
+ job_status = :failed
41
+ transaction.set_error(exception)
42
+ raise exception
43
+ ensure
44
+ if transaction
45
+ transaction.params =
46
+ Appsignal::Utils::HashSanitizer.sanitize(
47
+ job["arguments"],
48
+ Appsignal.config[:filter_parameters]
49
+ )
50
+
51
+ transaction_tags = ActiveJobHelpers.transaction_tags_for(job)
52
+ transaction_tags["active_job_id"] = job["job_id"]
53
+ provider_job_id = job["provider_job_id"]
54
+ if provider_job_id
55
+ transaction_tags[:provider_job_id] = provider_job_id
56
+ end
57
+ transaction.set_tags(transaction_tags)
58
+
59
+ transaction.set_action_if_nil(ActiveJobHelpers.action_name(job))
60
+ enqueued_at = job["enqueued_at"]
61
+ if enqueued_at # Present in Rails 6 and up
62
+ transaction.set_queue_start((Time.parse(enqueued_at).to_f * 1_000).to_i)
63
+ end
64
+
65
+ if current_transaction.nil_transaction?
66
+ # Only complete transaction if ActiveJob is not wrapped in
67
+ # another supported integration, such as Sidekiq.
68
+ Appsignal::Transaction.complete_current!
69
+ end
70
+ end
71
+
72
+ metrics = ActiveJobHelpers.metrics_for(job)
73
+ metrics.each do |(metric_name, tags)|
74
+ if job_status
75
+ ActiveJobHelpers.increment_counter metric_name, 1,
76
+ tags.merge(:status => job_status)
77
+ end
78
+ ActiveJobHelpers.increment_counter metric_name, 1,
79
+ tags.merge(:status => :processed)
80
+ end
81
+ end
82
+ end
83
+
84
+ module ActiveJobHelpers
85
+ ACTION_MAILER_CLASSES = [
86
+ "ActionMailer::DeliveryJob",
87
+ "ActionMailer::Parameterized::DeliveryJob",
88
+ "ActionMailer::MailDeliveryJob"
89
+ ].freeze
90
+
91
+ def self.action_name(job)
92
+ case job["job_class"]
93
+ when *ACTION_MAILER_CLASSES
94
+ job["arguments"][0..1].join("#")
95
+ else
96
+ "#{job["job_class"]}#perform"
97
+ end
98
+ end
99
+
100
+ # Returns an array of metrics with tags used to report the job metrics
101
+ #
102
+ # If job ONLY has a queue, it will return `queue_job_count` with tags.
103
+ # If job has a queue AND priority, it will ALSO return
104
+ # `queue_priority_job_count` with tags.
105
+ #
106
+ # @return [Array] Array of metrics with tags to report.
107
+ def self.metrics_for(job)
108
+ tags = { :queue => job["queue_name"] }
109
+ metrics = [["queue_job_count", tags]]
110
+
111
+ priority = job["priority"]
112
+ if priority
113
+ metrics << [
114
+ "queue_priority_job_count",
115
+ tags.merge(:priority => priority)
116
+ ]
117
+ end
118
+
119
+ metrics
120
+ end
121
+
122
+ def self.transaction_tags_for(job)
123
+ tags = {}
124
+ queue = job["queue_name"]
125
+ tags[:queue] = queue if queue
126
+ priority = job["priority"]
127
+ tags[:priority] = priority if priority
128
+ tags
129
+ end
130
+
131
+ def self.increment_counter(key, value, tags = {})
132
+ Appsignal.increment_counter "active_job_#{key}", value, tags
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -13,23 +13,20 @@ module Appsignal
13
13
  end
14
14
 
15
15
  def install
16
- if Appsignal::System.ruby_2_or_up?
17
- require "appsignal/integrations/net_http"
18
- Net::HTTP.send(:prepend, Appsignal::Integrations::NetHttpIntegration)
19
- else
20
- Net::HTTP.class_eval do
21
- alias request_without_appsignal request
16
+ Net::HTTP.class_eval do
17
+ alias request_without_appsignal request
22
18
 
23
- def request(request, body = nil, &block)
24
- Appsignal.instrument(
25
- "request.net_http",
26
- "#{request.method} #{use_ssl? ? "https" : "http"}://#{request["host"] || address}"
27
- ) do
28
- request_without_appsignal(request, body, &block)
29
- end
19
+ def request(request, body = nil, &block)
20
+ Appsignal.instrument(
21
+ "request.net_http",
22
+ "#{request.method} #{use_ssl? ? "https" : "http"}://#{request["host"] || address}"
23
+ ) do
24
+ request_without_appsignal(request, body, &block)
30
25
  end
31
26
  end
32
27
  end
28
+
29
+ Appsignal::Environment.report_enabled("net_http")
33
30
  end
34
31
  end
35
32
  end
@@ -24,7 +24,7 @@ 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
+ Appsignal::Minutely.probes.register :puma, ::Appsignal::Probes::PumaProbe
28
28
  end
29
29
 
30
30
  return unless defined?(::Puma::Cluster)
@@ -39,62 +39,5 @@ module Appsignal
39
39
  end
40
40
  end
41
41
  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
42
  end
100
43
  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,7 @@ module Appsignal
12
12
  end
13
13
 
14
14
  def install
15
- Appsignal::Minutely.probes.register :sidekiq, SidekiqProbe
15
+ Appsignal::Minutely.probes.register :sidekiq, Appsignal::Probes::SidekiqProbe
16
16
 
17
17
  ::Sidekiq.configure_server do |config|
18
18
  config.server_middleware do |chain|
@@ -22,110 +22,11 @@ module Appsignal
22
22
  end
23
23
  end
24
24
 
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
25
  # @api private
124
26
  class SidekiqPlugin # rubocop:disable Metrics/ClassLength
125
27
  include Appsignal::Hooks::Helpers
126
28
 
127
- UNKNOWN_ACTION_NAME = "unknown".freeze
128
- JOB_KEYS = %w[
29
+ EXCLUDED_JOB_KEYS = %w[
129
30
  args backtrace class created_at enqueued_at error_backtrace error_class
130
31
  error_message failed_at jid retried_at retry wrapped
131
32
  ].freeze
@@ -133,7 +34,7 @@ module Appsignal
133
34
  def call(_worker, item, _queue)
134
35
  job_status = nil
135
36
  transaction = Appsignal::Transaction.create(
136
- SecureRandom.uuid,
37
+ item["jid"],
137
38
  Appsignal::Transaction::BACKGROUND_JOB,
138
39
  Appsignal::Transaction::GenericRequest.new(
139
40
  :queue_start => item["enqueued_at"]
@@ -152,7 +53,10 @@ module Appsignal
152
53
  ensure
153
54
  if transaction
154
55
  transaction.set_action_if_nil(formatted_action_name(item))
155
- transaction.params = filtered_arguments(item)
56
+
57
+ params = filtered_arguments(item)
58
+ transaction.params = params if params
59
+
156
60
  formatted_metadata(item).each do |key, value|
157
61
  transaction.set_metadata key, value
158
62
  end
@@ -178,16 +82,20 @@ module Appsignal
178
82
 
179
83
  def formatted_action_name(job)
180
84
  sidekiq_action_name = parse_action_name(job)
85
+ return unless sidekiq_action_name
86
+
181
87
  complete_action = sidekiq_action_name =~ /\.|#/
182
- if complete_action || sidekiq_action_name == UNKNOWN_ACTION_NAME
183
- return sidekiq_action_name
184
- end
88
+ return sidekiq_action_name if complete_action
89
+
185
90
  "#{sidekiq_action_name}#perform"
186
91
  end
187
92
 
188
93
  def filtered_arguments(job)
94
+ arguments = parse_arguments(job)
95
+ return unless arguments
96
+
189
97
  Appsignal::Utils::HashSanitizer.sanitize(
190
- parse_arguments(job),
98
+ arguments,
191
99
  Appsignal.config[:filter_parameters]
192
100
  )
193
101
  end
@@ -195,7 +103,8 @@ module Appsignal
195
103
  def formatted_metadata(item)
196
104
  {}.tap do |hash|
197
105
  (item || {}).each do |key, value|
198
- next if JOB_KEYS.include?(key)
106
+ next if EXCLUDED_JOB_KEYS.include?(key)
107
+
199
108
  hash[key] = truncate(string_or_inspect(value))
200
109
  end
201
110
  end
@@ -214,81 +123,11 @@ module Appsignal
214
123
  safe_load(args[0], job_class) do |target, method, _|
215
124
  "#{target}.#{method}"
216
125
  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
126
  else
225
127
  job_class
226
128
  end
227
129
  end
228
130
 
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
131
  # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L336-L358
293
132
  def parse_arguments(job)
294
133
  args = job.fetch("args", [])
@@ -298,20 +137,7 @@ module Appsignal
298
137
  arg
299
138
  end
300
139
  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
140
+ nil # Set in the ActiveJob integration
315
141
  else
316
142
  # Sidekiq Enterprise argument encryption.
317
143
  # More information: https://github.com/mperham/sidekiq/wiki/Ent-Encryption