appsignal 4.8.6-java → 4.9.0-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/CHANGELOG.md +49 -0
- data/appsignal.gemspec +2 -2
- data/build_matrix.yml +25 -0
- data/ext/base.rb +3 -7
- data/ext/extconf.rb +1 -1
- data/lib/appsignal/check_in/event.rb +5 -4
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/cli/helpers.rb +2 -2
- data/lib/appsignal/cli/install.rb +3 -3
- data/lib/appsignal/config.rb +44 -1
- data/lib/appsignal/environment.rb +1 -0
- data/lib/appsignal/extension/jruby.rb +6 -5
- data/lib/appsignal/extension.rb +1 -1
- data/lib/appsignal/helpers/instrumentation.rb +12 -12
- data/lib/appsignal/hooks/action_cable.rb +2 -2
- data/lib/appsignal/hooks/active_job.rb +42 -3
- data/lib/appsignal/hooks/delayed_job.rb +2 -1
- data/lib/appsignal/hooks/excon.rb +1 -1
- data/lib/appsignal/hooks/faraday.rb +21 -0
- data/lib/appsignal/hooks/http.rb +9 -0
- data/lib/appsignal/hooks/mongo_ruby_driver.rb +2 -1
- data/lib/appsignal/hooks/que.rb +7 -1
- data/lib/appsignal/hooks/resque.rb +5 -1
- data/lib/appsignal/hooks/shoryuken.rb +15 -1
- data/lib/appsignal/hooks/sidekiq.rb +14 -1
- data/lib/appsignal/hooks.rb +1 -0
- data/lib/appsignal/integrations/action_cable.rb +1 -1
- data/lib/appsignal/integrations/active_support_notifications.rb +19 -6
- data/lib/appsignal/integrations/delayed_job_plugin.rb +38 -1
- data/lib/appsignal/integrations/excon.rb +8 -0
- data/lib/appsignal/integrations/faraday.rb +51 -0
- data/lib/appsignal/integrations/http.rb +9 -0
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +4 -2
- data/lib/appsignal/integrations/net_http.rb +7 -0
- data/lib/appsignal/integrations/object.rb +3 -3
- data/lib/appsignal/integrations/que.rb +68 -1
- data/lib/appsignal/integrations/rake.rb +1 -1
- data/lib/appsignal/integrations/resque.rb +20 -1
- data/lib/appsignal/integrations/shoryuken.rb +33 -1
- data/lib/appsignal/integrations/sidekiq.rb +73 -36
- data/lib/appsignal/integrations/webmachine.rb +1 -1
- data/lib/appsignal/logger.rb +4 -1
- data/lib/appsignal/rack/abstract_middleware.rb +1 -1
- data/lib/appsignal/rack/body_wrapper.rb +5 -5
- data/lib/appsignal/rack/hanami_middleware.rb +1 -0
- data/lib/appsignal/sample_data.rb +1 -0
- data/lib/appsignal/transaction.rb +59 -11
- data/lib/appsignal/utils/query_params_sanitizer.rb +2 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +2 -2
- data/lib/puma/plugin/appsignal.rb +1 -1
- data/sig/appsignal.rbi +41 -1
- data/sig/appsignal.rbs +30 -0
- metadata +7 -6
- data/lib/appsignal/event_formatter/faraday/request_formatter.rb +0 -24
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
class Hooks
|
|
5
|
+
# @!visibility private
|
|
6
|
+
class FaradayHook < Appsignal::Hooks::Hook
|
|
7
|
+
register :faraday
|
|
8
|
+
|
|
9
|
+
def dependencies_present?
|
|
10
|
+
defined?(::Faraday) && Appsignal.config && Appsignal.config[:instrument_faraday]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def install
|
|
14
|
+
require "appsignal/integrations/faraday"
|
|
15
|
+
::Faraday::RackBuilder.prepend(Appsignal::Integrations::FaradayRackBuilderPatch)
|
|
16
|
+
|
|
17
|
+
Appsignal::Environment.report_enabled("faraday")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/appsignal/hooks/http.rb
CHANGED
|
@@ -16,6 +16,8 @@ module Appsignal
|
|
|
16
16
|
|
|
17
17
|
def install
|
|
18
18
|
require "appsignal/integrations/http"
|
|
19
|
+
# `Client#request` takes positional options in http5 and keyword options
|
|
20
|
+
# in http6.
|
|
19
21
|
integration =
|
|
20
22
|
if self.class.http6_or_higher?
|
|
21
23
|
Appsignal::Integrations::HttpIntegration::KeywordOptions
|
|
@@ -23,6 +25,13 @@ module Appsignal
|
|
|
23
25
|
Appsignal::Integrations::HttpIntegration::HashOptions
|
|
24
26
|
end
|
|
25
27
|
HTTP::Client.prepend integration
|
|
28
|
+
# In http6 a chained request (`.follow`, `.headers`, ...) goes through
|
|
29
|
+
# `HTTP::Session#request` instead of `HTTP::Client#request`, so
|
|
30
|
+
# instrument it too (keyword options). http5 has no Session; chained
|
|
31
|
+
# requests run through `Client#request` there.
|
|
32
|
+
if defined?(HTTP::Session)
|
|
33
|
+
HTTP::Session.prepend Appsignal::Integrations::HttpIntegration::KeywordOptions
|
|
34
|
+
end
|
|
26
35
|
|
|
27
36
|
Appsignal::Environment.report_enabled("http_rb")
|
|
28
37
|
end
|
data/lib/appsignal/hooks/que.rb
CHANGED
|
@@ -7,12 +7,18 @@ module Appsignal
|
|
|
7
7
|
register :que
|
|
8
8
|
|
|
9
9
|
def dependencies_present?
|
|
10
|
-
defined?(::Que::Job)
|
|
10
|
+
defined?(::Que::Job) && Appsignal.config && Appsignal.config[:instrument_que]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def install
|
|
14
14
|
require "appsignal/integrations/que"
|
|
15
15
|
::Que::Job.prepend Appsignal::Integrations::QuePlugin
|
|
16
|
+
::Que::Job.singleton_class.prepend Appsignal::Integrations::QueClientPlugin
|
|
17
|
+
|
|
18
|
+
# `bulk_enqueue` exists only on Que 2+; don't define one where it's absent.
|
|
19
|
+
if ::Que::Job.respond_to?(:bulk_enqueue)
|
|
20
|
+
::Que::Job.singleton_class.prepend Appsignal::Integrations::QueBulkClientPlugin
|
|
21
|
+
end
|
|
16
22
|
|
|
17
23
|
::Que.error_notifier = proc do |error, _job|
|
|
18
24
|
Appsignal::Transaction.current.set_error(error)
|
|
@@ -7,12 +7,16 @@ module Appsignal
|
|
|
7
7
|
register :resque
|
|
8
8
|
|
|
9
9
|
def dependencies_present?
|
|
10
|
-
defined?(::Resque)
|
|
10
|
+
defined?(::Resque) && Appsignal.config && Appsignal.config[:instrument_resque]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def install
|
|
14
14
|
require "appsignal/integrations/resque"
|
|
15
15
|
Resque::Job.prepend Appsignal::Integrations::ResqueIntegration
|
|
16
|
+
|
|
17
|
+
# Resque enqueues through the `Resque.push` singleton method, so prepend
|
|
18
|
+
# onto its singleton class to record the enqueue event.
|
|
19
|
+
Resque.singleton_class.prepend Appsignal::Integrations::ResquePushIntegration
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
@@ -7,7 +7,7 @@ module Appsignal
|
|
|
7
7
|
register :shoryuken
|
|
8
8
|
|
|
9
9
|
def dependencies_present?
|
|
10
|
-
defined?(::Shoryuken)
|
|
10
|
+
defined?(::Shoryuken) && Appsignal.config && Appsignal.config[:instrument_shoryuken]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def install
|
|
@@ -17,6 +17,20 @@ module Appsignal
|
|
|
17
17
|
config.server_middleware do |chain|
|
|
18
18
|
chain.add Appsignal::Integrations::ShoryukenMiddleware
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
# Servers enqueue jobs too, so they need the client middleware that
|
|
22
|
+
# records the enqueue event. Shoryuken only yields `configure_client`
|
|
23
|
+
# outside the server, so register it here as well for enqueues from
|
|
24
|
+
# within a worker.
|
|
25
|
+
config.client_middleware do |chain|
|
|
26
|
+
chain.add Appsignal::Integrations::ShoryukenClientMiddleware
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
::Shoryuken.configure_client do |config|
|
|
31
|
+
config.client_middleware do |chain|
|
|
32
|
+
chain.add Appsignal::Integrations::ShoryukenClientMiddleware
|
|
33
|
+
end
|
|
20
34
|
end
|
|
21
35
|
end
|
|
22
36
|
end
|
|
@@ -20,7 +20,8 @@ module Appsignal
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def dependencies_present?
|
|
23
|
-
self.class.dependencies_present?
|
|
23
|
+
self.class.dependencies_present? && Appsignal.config &&
|
|
24
|
+
Appsignal.config[:instrument_sidekiq]
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def install
|
|
@@ -42,6 +43,18 @@ module Appsignal
|
|
|
42
43
|
chain.add Appsignal::Integrations::SidekiqMiddleware
|
|
43
44
|
end
|
|
44
45
|
end
|
|
46
|
+
|
|
47
|
+
# Servers enqueue jobs too, so they need the client middleware that
|
|
48
|
+
# records the enqueue event.
|
|
49
|
+
config.client_middleware do |chain|
|
|
50
|
+
chain.add Appsignal::Integrations::SidekiqClientMiddleware
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
::Sidekiq.configure_client do |config|
|
|
55
|
+
config.client_middleware do |chain|
|
|
56
|
+
chain.add Appsignal::Integrations::SidekiqClientMiddleware
|
|
57
|
+
end
|
|
45
58
|
end
|
|
46
59
|
end
|
|
47
60
|
end
|
data/lib/appsignal/hooks.rb
CHANGED
|
@@ -85,6 +85,7 @@ require "appsignal/hooks/code_ownership"
|
|
|
85
85
|
require "appsignal/hooks/delayed_job"
|
|
86
86
|
require "appsignal/hooks/gvl"
|
|
87
87
|
require "appsignal/hooks/dry_monitor"
|
|
88
|
+
require "appsignal/hooks/faraday"
|
|
88
89
|
require "appsignal/hooks/http"
|
|
89
90
|
require "appsignal/hooks/mri"
|
|
90
91
|
require "appsignal/hooks/net_http"
|
|
@@ -7,16 +7,21 @@ module Appsignal
|
|
|
7
7
|
class << self
|
|
8
8
|
BANG = "!"
|
|
9
9
|
|
|
10
|
+
# Events a dedicated AppSignal integration already records, so the
|
|
11
|
+
# generic notifications path must not record them a second time. The
|
|
12
|
+
# ActiveJob hook owns `enqueue.active_job` (it wraps the enqueue in its
|
|
13
|
+
# own event, with Rails' native notification nested inside), and the
|
|
14
|
+
# Faraday integration owns `request.faraday`.
|
|
15
|
+
SUPPRESSED_EVENT_NAMES = ["enqueue.active_job", "request.faraday"].freeze
|
|
16
|
+
|
|
10
17
|
def start_event(name)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Appsignal::Transaction.current.start_event
|
|
18
|
+
return unless record_event?(name)
|
|
19
|
+
|
|
20
|
+
Appsignal::Transaction.current.start_event
|
|
14
21
|
end
|
|
15
22
|
|
|
16
23
|
def finish_event(name, payload = {})
|
|
17
|
-
|
|
18
|
-
instrument_this = name[0] != BANG
|
|
19
|
-
return unless instrument_this
|
|
24
|
+
return unless record_event?(name)
|
|
20
25
|
|
|
21
26
|
title, body, body_format = Appsignal::EventFormatter.format(name, payload)
|
|
22
27
|
Appsignal::Transaction.current.finish_event(
|
|
@@ -26,6 +31,14 @@ module Appsignal
|
|
|
26
31
|
body_format
|
|
27
32
|
)
|
|
28
33
|
end
|
|
34
|
+
|
|
35
|
+
# Events starting with a bang are internal to Rails; suppressed events
|
|
36
|
+
# are recorded by a dedicated integration instead. Both `start_event`
|
|
37
|
+
# and `finish_event` gate on this so the event stack stays balanced.
|
|
38
|
+
def record_event?(name)
|
|
39
|
+
name = name.to_s
|
|
40
|
+
name[0] != BANG && !SUPPRESSED_EVENT_NAMES.include?(name)
|
|
41
|
+
end
|
|
29
42
|
end
|
|
30
43
|
|
|
31
44
|
module InstrumentIntegration
|
|
@@ -7,6 +7,10 @@ module Appsignal
|
|
|
7
7
|
extend Appsignal::Hooks::Helpers
|
|
8
8
|
|
|
9
9
|
callbacks do |lifecycle|
|
|
10
|
+
lifecycle.around(:enqueue) do |job, &block|
|
|
11
|
+
enqueue_with_instrumentation(job, block)
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
lifecycle.around(:invoke_job) do |job, &block|
|
|
11
15
|
invoke_with_instrumentation(job, block)
|
|
12
16
|
end
|
|
@@ -16,6 +20,37 @@ module Appsignal
|
|
|
16
20
|
end
|
|
17
21
|
end
|
|
18
22
|
|
|
23
|
+
# Records an `enqueue.delayed_job` event so the enqueue shows up under the
|
|
24
|
+
# active transaction (e.g. when enqueuing from within a web request or
|
|
25
|
+
# another job). An enqueue with no active transaction is a transparent
|
|
26
|
+
# pass-through.
|
|
27
|
+
def self.enqueue_with_instrumentation(job, block)
|
|
28
|
+
# Under Active Job the enqueue is already recorded as an
|
|
29
|
+
# `enqueue.active_job` event, so skip recording it again here.
|
|
30
|
+
if Appsignal::Transaction.current? &&
|
|
31
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
32
|
+
return block.call(job)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Appsignal.instrument("enqueue.delayed_job", "enqueue #{enqueue_name(job)} job") do
|
|
36
|
+
block.call(job)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Titles the enqueue event after the job. The `appsignal_name` override is
|
|
41
|
+
# honored verbatim, as it is when naming the perform action. That override
|
|
42
|
+
# is a full action name, so an enqueue that uses it reads as
|
|
43
|
+
# `enqueue Class#method job` rather than the bare `enqueue Class job`. We
|
|
44
|
+
# accept that inconsistency so the enqueue and perform events stay tied to
|
|
45
|
+
# the same name for the rare job that sets it.
|
|
46
|
+
def self.enqueue_name(job)
|
|
47
|
+
payload = job.payload_object
|
|
48
|
+
appsignal_name = extract_value(payload, :appsignal_name, nil)
|
|
49
|
+
return appsignal_name if appsignal_name.is_a?(String)
|
|
50
|
+
|
|
51
|
+
job.name
|
|
52
|
+
end
|
|
53
|
+
|
|
19
54
|
def self.invoke_with_instrumentation(job, block)
|
|
20
55
|
transaction =
|
|
21
56
|
Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
|
|
@@ -24,7 +59,7 @@ module Appsignal
|
|
|
24
59
|
Appsignal.instrument("perform_job.delayed_job") do
|
|
25
60
|
block.call(job)
|
|
26
61
|
end
|
|
27
|
-
rescue Exception => error
|
|
62
|
+
rescue Exception => error
|
|
28
63
|
transaction.set_error(error)
|
|
29
64
|
raise
|
|
30
65
|
ensure
|
|
@@ -63,6 +98,7 @@ module Appsignal
|
|
|
63
98
|
"#{default_name}#perform"
|
|
64
99
|
end
|
|
65
100
|
|
|
101
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
66
102
|
def self.extract_value(object_or_hash, field, default_value = nil, convert_to_s = false)
|
|
67
103
|
value = nil
|
|
68
104
|
|
|
@@ -87,6 +123,7 @@ module Appsignal
|
|
|
87
123
|
value
|
|
88
124
|
end
|
|
89
125
|
end
|
|
126
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
90
127
|
end
|
|
91
128
|
end
|
|
92
129
|
end
|
|
@@ -5,6 +5,14 @@ module Appsignal
|
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module ExconIntegration
|
|
7
7
|
def self.instrument(name, data, &block)
|
|
8
|
+
# Skip when an outer HTTP client integration (Faraday) already records
|
|
9
|
+
# this request, so it isn't instrumented twice. Excon calls the
|
|
10
|
+
# instrumentor for block-less notifications too, hence the `block_given?`.
|
|
11
|
+
if Appsignal::Transaction.current? &&
|
|
12
|
+
Appsignal::Transaction.current.http_client_events_suppressed?
|
|
13
|
+
return block_given? ? yield : nil
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
namespace, *event = name.split(".")
|
|
9
17
|
rails_name = [event, namespace].flatten.join(".")
|
|
10
18
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
module Integrations
|
|
5
|
+
# Faraday middleware that records each request as a `request.faraday` event
|
|
6
|
+
# and suppresses the downstream HTTP client's own instrumentation, so the
|
|
7
|
+
# request is recorded once rather than as nested Faraday + Net::HTTP (or
|
|
8
|
+
# Excon) client events.
|
|
9
|
+
#
|
|
10
|
+
# @!visibility private
|
|
11
|
+
class FaradayMiddleware < ::Faraday::Middleware
|
|
12
|
+
def call(env)
|
|
13
|
+
http_method = env[:method].to_s.upcase
|
|
14
|
+
uri = env[:url]
|
|
15
|
+
# Title only, no body: the path is left out so the event matches
|
|
16
|
+
# Net::HTTP's (scheme and host only), keeping paths out of event titles.
|
|
17
|
+
Appsignal.instrument(
|
|
18
|
+
"request.faraday",
|
|
19
|
+
"#{http_method} #{uri.scheme}://#{uri.host}"
|
|
20
|
+
) do
|
|
21
|
+
# Faraday's default adapter is Net::HTTP, which AppSignal also
|
|
22
|
+
# instruments. Suppress the adapter's own instrumentation so the
|
|
23
|
+
# request appears once (as the Faraday event) rather than as nested
|
|
24
|
+
# Faraday + Net::HTTP client events.
|
|
25
|
+
if Appsignal::Transaction.current?
|
|
26
|
+
Appsignal::Transaction.current.suppress_http_client_events { @app.call(env) }
|
|
27
|
+
else
|
|
28
|
+
@app.call(env)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Prepended to `Faraday::RackBuilder#adapter`, the single point every
|
|
35
|
+
# connection passes through as it finishes building its middleware stack.
|
|
36
|
+
# Faraday has no global default middleware stack (unlike Excon), so patching
|
|
37
|
+
# the build path is the only way to instrument every connection automatically.
|
|
38
|
+
#
|
|
39
|
+
# Just before the adapter (the innermost handler, where the request is sent)
|
|
40
|
+
# it inserts `FaradayMiddleware`, which records the `request.faraday` event
|
|
41
|
+
# and suppresses the downstream client. Skipped if it's already present.
|
|
42
|
+
#
|
|
43
|
+
# @!visibility private
|
|
44
|
+
module FaradayRackBuilderPatch
|
|
45
|
+
def adapter(*)
|
|
46
|
+
use(FaradayMiddleware) unless handlers.any? { |handler| handler.klass == FaradayMiddleware }
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -12,6 +12,15 @@ module Appsignal
|
|
|
12
12
|
Appsignal.instrument("request.http_rb", "#{verb.upcase} #{request_uri}", &block)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# The event is recorded at the request boundary, so a redirected request
|
|
16
|
+
# stays a single `request.http_rb` event spanning every hop. That boundary
|
|
17
|
+
# lives in more than one place: a bare request runs through
|
|
18
|
+
# `HTTP::Client#request`, but in http6 a chained request (`.follow`,
|
|
19
|
+
# `.headers`, `.timeout`, ...) runs through `HTTP::Session#request`
|
|
20
|
+
# instead, which never touches `Client#request`. The hook prepends one of
|
|
21
|
+
# these onto each. `Client#request` takes positional options in http5 and
|
|
22
|
+
# keyword options in http6; `Session#request` (http6 only) takes keyword
|
|
23
|
+
# options.
|
|
15
24
|
module HashOptions
|
|
16
25
|
def request(verb, uri, opts = {})
|
|
17
26
|
HttpIntegration.instrument(verb, uri) { super }
|
|
@@ -46,11 +46,13 @@ module Appsignal
|
|
|
46
46
|
store = transaction.store("mongo_driver")
|
|
47
47
|
command = store.delete(event.request_id) || {}
|
|
48
48
|
|
|
49
|
-
# Finish the event
|
|
49
|
+
# Finish the event. The sanitized command is a (nested) Hash; emit it
|
|
50
|
+
# as a JSON string. The agent serializes structured bodies to JSON
|
|
51
|
+
# anyway, so this is equivalent output.
|
|
50
52
|
transaction.finish_event(
|
|
51
53
|
"query.mongodb",
|
|
52
54
|
"#{event.command_name} | #{event.database_name} | #{result}",
|
|
53
|
-
Appsignal::Utils::
|
|
55
|
+
Appsignal::Utils::JSON.generate(command),
|
|
54
56
|
Appsignal::EventFormatter::DEFAULT
|
|
55
57
|
)
|
|
56
58
|
|
|
@@ -5,6 +5,13 @@ module Appsignal
|
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module NetHttpIntegration
|
|
7
7
|
def request(request, body = nil, &block)
|
|
8
|
+
# Skip when an outer HTTP client integration (Faraday) already records
|
|
9
|
+
# this request, so it isn't instrumented twice.
|
|
10
|
+
if Appsignal::Transaction.current? &&
|
|
11
|
+
Appsignal::Transaction.current.http_client_events_suppressed?
|
|
12
|
+
return super
|
|
13
|
+
end
|
|
14
|
+
|
|
8
15
|
Appsignal.instrument(
|
|
9
16
|
"request.net_http",
|
|
10
17
|
"#{request.method} #{use_ssl? ? "https" : "http"}://#{request["host"] || address}"
|
|
@@ -27,9 +27,9 @@ class Object
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
return unless singleton_class.respond_to?(:ruby2_keywords, true)
|
|
31
|
+
|
|
32
|
+
singleton_class.send(:ruby2_keywords, method_name)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# Instruments an instance method with AppSignal monitoring.
|
|
@@ -10,7 +10,7 @@ module Appsignal
|
|
|
10
10
|
|
|
11
11
|
begin
|
|
12
12
|
Appsignal.instrument("perform_job.que") { super }
|
|
13
|
-
rescue Exception => error
|
|
13
|
+
rescue Exception => error
|
|
14
14
|
transaction.set_error(error)
|
|
15
15
|
raise error
|
|
16
16
|
ensure
|
|
@@ -34,5 +34,72 @@ module Appsignal
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
# @!visibility private
|
|
39
|
+
#
|
|
40
|
+
# Prepended to `Que::Job`'s singleton so it records each enqueue as an
|
|
41
|
+
# `enqueue.que` event under the active transaction. Like all AppSignal
|
|
42
|
+
# events, it only records when there's an active transaction (e.g. enqueuing
|
|
43
|
+
# from within a web request or another job); otherwise it's a transparent
|
|
44
|
+
# pass-through.
|
|
45
|
+
module QueClientPlugin
|
|
46
|
+
def enqueue(*_args, job_options: {}, **_rest)
|
|
47
|
+
# Inside a `bulk_enqueue` block the batch is recorded once by the
|
|
48
|
+
# `bulk_enqueue` wrapper, so each inner enqueue is a pass-through to
|
|
49
|
+
# avoid recording an event per job.
|
|
50
|
+
return super if Thread.current[:appsignal_que_bulk_enqueue]
|
|
51
|
+
|
|
52
|
+
# Under Active Job the enqueue is already recorded as an
|
|
53
|
+
# `enqueue.active_job` event, so skip recording it again here.
|
|
54
|
+
return super if Appsignal::Transaction.current? &&
|
|
55
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
56
|
+
|
|
57
|
+
# Resolve the job class the way Que does: an explicit `:job_class`, else
|
|
58
|
+
# the class `enqueue` was called on.
|
|
59
|
+
title = "enqueue #{job_options[:job_class] || name} job"
|
|
60
|
+
Appsignal.instrument("enqueue.que", title) { super }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @!visibility private
|
|
65
|
+
#
|
|
66
|
+
# `bulk_enqueue` exists only on Que 2+, so this lives in its own module that
|
|
67
|
+
# the hook prepends only when Que has the method -- otherwise we'd define a
|
|
68
|
+
# `bulk_enqueue` on Que versions that have none. The whole batch records a
|
|
69
|
+
# single `bulk_enqueue.que` event; the inner enqueues are pass-throughs.
|
|
70
|
+
module QueBulkClientPlugin
|
|
71
|
+
def bulk_enqueue(*_args, job_options: {}, **_rest)
|
|
72
|
+
# Under Active Job the enqueue is already recorded as an
|
|
73
|
+
# `enqueue.active_job` event, so skip recording it again here.
|
|
74
|
+
return super if Appsignal::Transaction.current? &&
|
|
75
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
76
|
+
|
|
77
|
+
Appsignal.instrument("bulk_enqueue.que", bulk_enqueue_title(job_options)) do
|
|
78
|
+
# Flag the batch so the enqueues this block triggers pass through
|
|
79
|
+
# without recording, without reading Que's internal bulk state.
|
|
80
|
+
was_bulk = Thread.current[:appsignal_que_bulk_enqueue]
|
|
81
|
+
Thread.current[:appsignal_que_bulk_enqueue] = true
|
|
82
|
+
begin
|
|
83
|
+
super
|
|
84
|
+
ensure
|
|
85
|
+
Thread.current[:appsignal_que_bulk_enqueue] = was_bulk
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# The batch's job class is known up front only from an explicit
|
|
93
|
+
# `:job_class` or when `bulk_enqueue` is called on a concrete subclass;
|
|
94
|
+
# called on `Que::Job` itself the class isn't known until the inner
|
|
95
|
+
# enqueues run, so the title is left class-less.
|
|
96
|
+
def bulk_enqueue_title(job_options)
|
|
97
|
+
job_class = job_options[:job_class]
|
|
98
|
+
job_class ||= name unless equal?(::Que::Job)
|
|
99
|
+
return "bulk enqueue jobs" unless job_class
|
|
100
|
+
|
|
101
|
+
"bulk enqueue #{job_class} jobs"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
37
104
|
end
|
|
38
105
|
end
|
|
@@ -25,7 +25,7 @@ module Appsignal
|
|
|
25
25
|
Appsignal.instrument "task.rake" do
|
|
26
26
|
super
|
|
27
27
|
end
|
|
28
|
-
rescue Exception => error
|
|
28
|
+
rescue Exception => error
|
|
29
29
|
Appsignal::Integrations::RakeIntegrationHelper.register_at_exit_hook
|
|
30
30
|
unless RakeIntegration.ignored_error?(error)
|
|
31
31
|
transaction ||= _appsignal_create_transaction
|
|
@@ -10,7 +10,7 @@ module Appsignal
|
|
|
10
10
|
Appsignal.instrument "perform.resque" do
|
|
11
11
|
super
|
|
12
12
|
end
|
|
13
|
-
rescue Exception => exception
|
|
13
|
+
rescue Exception => exception
|
|
14
14
|
transaction.set_error(exception)
|
|
15
15
|
raise exception
|
|
16
16
|
ensure
|
|
@@ -25,6 +25,25 @@ module Appsignal
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# Wraps `Resque.push` to record an `enqueue.resque` event so the enqueue
|
|
29
|
+
# shows up under the active transaction.
|
|
30
|
+
#
|
|
31
|
+
# Like all AppSignal events, this only records when there's an active
|
|
32
|
+
# transaction (e.g. enqueuing from within a web request or another job).
|
|
33
|
+
# An enqueue with no transaction is a transparent pass-through.
|
|
34
|
+
#
|
|
35
|
+
# @!visibility private
|
|
36
|
+
module ResquePushIntegration
|
|
37
|
+
def push(_queue, item)
|
|
38
|
+
# Under Active Job the enqueue is already recorded as an
|
|
39
|
+
# `enqueue.active_job` event, so skip recording it again here.
|
|
40
|
+
return super if Appsignal::Transaction.current? &&
|
|
41
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
42
|
+
|
|
43
|
+
Appsignal.instrument("enqueue.resque", "enqueue #{item["class"]} job") { super }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
28
47
|
# @!visibility private
|
|
29
48
|
class ResqueHelpers
|
|
30
49
|
def self.arguments(payload)
|
|
@@ -8,7 +8,7 @@ module Appsignal
|
|
|
8
8
|
transaction = Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
|
|
9
9
|
|
|
10
10
|
Appsignal.instrument("perform_job.shoryuken", &block)
|
|
11
|
-
rescue Exception => error
|
|
11
|
+
rescue Exception => error
|
|
12
12
|
transaction.set_error(error)
|
|
13
13
|
raise
|
|
14
14
|
ensure
|
|
@@ -71,5 +71,37 @@ module Appsignal
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
# Shoryuken client middleware that records an `enqueue.shoryuken` event so
|
|
76
|
+
# the enqueue shows up under the active transaction.
|
|
77
|
+
#
|
|
78
|
+
# Like all AppSignal events, this only records when there's an active
|
|
79
|
+
# transaction (e.g. enqueuing from within a web request or another job). An
|
|
80
|
+
# enqueue with no transaction is a transparent pass-through.
|
|
81
|
+
#
|
|
82
|
+
# @!visibility private
|
|
83
|
+
class ShoryukenClientMiddleware
|
|
84
|
+
def call(options, &block)
|
|
85
|
+
# Under Active Job the enqueue is already recorded as an
|
|
86
|
+
# `enqueue.active_job` event, so skip recording it again here.
|
|
87
|
+
return yield if Appsignal::Transaction.current? &&
|
|
88
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
89
|
+
|
|
90
|
+
Appsignal.instrument("enqueue.shoryuken", enqueue_title(options), &block)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Enqueues through a Shoryuken worker carry the worker class in the
|
|
96
|
+
# `shoryuken_class` message attribute. Raw `send_message` enqueues don't,
|
|
97
|
+
# so there's no worker class to name -- fall back to the queue instead.
|
|
98
|
+
def enqueue_title(options)
|
|
99
|
+
worker_class = options.dig(:message_attributes, "shoryuken_class", :string_value)
|
|
100
|
+
return "enqueue #{worker_class} job" if worker_class
|
|
101
|
+
|
|
102
|
+
queue = options[:queue_url].to_s.split("/").last
|
|
103
|
+
"enqueue on #{queue}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
74
106
|
end
|
|
75
107
|
end
|