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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.semaphore/semaphore.yml +75 -61
- data/CHANGELOG.md +21 -0
- data/build_matrix.yml +13 -7
- data/ext/agent.yml +19 -19
- data/ext/appsignal_extension.c +10 -1
- data/ext/base.rb +11 -2
- data/gemfiles/padrino.gemfile +2 -2
- data/gemfiles/rails-4.2.gemfile +9 -2
- data/gemfiles/rails-5.0.gemfile +1 -0
- data/gemfiles/rails-5.1.gemfile +1 -0
- data/gemfiles/rails-5.2.gemfile +1 -0
- data/gemfiles/rails-6.0.gemfile +1 -0
- data/gemfiles/resque-1.gemfile +7 -0
- data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
- data/lib/appsignal.rb +21 -1
- data/lib/appsignal/capistrano.rb +2 -0
- data/lib/appsignal/config.rb +6 -2
- data/lib/appsignal/environment.rb +126 -0
- data/lib/appsignal/extension/jruby.rb +10 -0
- data/lib/appsignal/hooks.rb +2 -0
- data/lib/appsignal/hooks/active_job.rb +89 -0
- data/lib/appsignal/hooks/net_http.rb +2 -0
- data/lib/appsignal/hooks/puma.rb +2 -58
- data/lib/appsignal/hooks/redis.rb +2 -0
- data/lib/appsignal/hooks/resque.rb +60 -0
- data/lib/appsignal/hooks/sequel.rb +2 -0
- data/lib/appsignal/hooks/sidekiq.rb +18 -191
- data/lib/appsignal/integrations/object.rb +4 -0
- data/lib/appsignal/integrations/que.rb +1 -1
- data/lib/appsignal/integrations/resque.rb +9 -12
- data/lib/appsignal/integrations/resque_active_job.rb +9 -24
- data/lib/appsignal/probes/puma.rb +61 -0
- data/lib/appsignal/probes/sidekiq.rb +102 -0
- data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
- data/lib/appsignal/transaction.rb +32 -7
- data/lib/appsignal/utils/deprecation_message.rb +5 -1
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +2 -1
- data/spec/lib/appsignal/cli/diagnose_spec.rb +2 -1
- data/spec/lib/appsignal/config_spec.rb +6 -1
- data/spec/lib/appsignal/environment_spec.rb +167 -0
- data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
- data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
- data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
- data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
- data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
- data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -137
- data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
- data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
- data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
- data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
- data/spec/lib/appsignal/transaction_spec.rb +35 -20
- data/spec/lib/appsignal_spec.rb +22 -0
- data/spec/lib/puma/appsignal_spec.rb +1 -1
- data/spec/support/helpers/action_mailer_helpers.rb +25 -0
- data/spec/support/helpers/dependency_helper.rb +12 -0
- data/spec/support/helpers/env_helpers.rb +1 -1
- data/spec/support/helpers/environment_metdata_helper.rb +16 -0
- data/spec/support/helpers/transaction_helpers.rb +6 -0
- data/spec/support/stubs/sidekiq/api.rb +2 -2
- metadata +25 -5
data/lib/appsignal/hooks/puma.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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
|
@@ -12,7 +12,8 @@ module Appsignal
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def install
|
15
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
183
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|