appsignal 4.8.5 → 4.9.0
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 +61 -0
- data/README.md +2 -2
- data/SUPPORT.md +1 -1
- data/appsignal.gemspec +2 -2
- data/build_matrix.yml +35 -0
- data/ext/agent.rb +27 -27
- 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
data/lib/appsignal/extension.rb
CHANGED
|
@@ -84,7 +84,7 @@ module Appsignal
|
|
|
84
84
|
#
|
|
85
85
|
# This class inherits from the {Data} class so that it passes type checks.
|
|
86
86
|
class MockData < Data
|
|
87
|
-
def initialize(*_args)
|
|
87
|
+
def initialize(*_args) # rubocop:disable Lint/MissingSuper
|
|
88
88
|
# JRuby extension requirement, as it sends a pointer to the Data object
|
|
89
89
|
# when creating it
|
|
90
90
|
end
|
|
@@ -138,7 +138,7 @@ module Appsignal
|
|
|
138
138
|
|
|
139
139
|
begin
|
|
140
140
|
yield if block_given?
|
|
141
|
-
rescue Exception => error
|
|
141
|
+
rescue Exception => error
|
|
142
142
|
transaction.set_error(error)
|
|
143
143
|
raise error
|
|
144
144
|
ensure
|
|
@@ -242,7 +242,7 @@ module Appsignal
|
|
|
242
242
|
|
|
243
243
|
transaction.complete
|
|
244
244
|
end
|
|
245
|
-
alias
|
|
245
|
+
alias send_exception send_error
|
|
246
246
|
|
|
247
247
|
# Set an error on the current transaction.
|
|
248
248
|
#
|
|
@@ -305,8 +305,8 @@ module Appsignal
|
|
|
305
305
|
transaction.set_error(exception)
|
|
306
306
|
yield transaction if block_given?
|
|
307
307
|
end
|
|
308
|
-
alias
|
|
309
|
-
alias
|
|
308
|
+
alias set_exception set_error
|
|
309
|
+
alias add_exception set_error
|
|
310
310
|
|
|
311
311
|
# Report an error to AppSignal.
|
|
312
312
|
#
|
|
@@ -377,7 +377,7 @@ module Appsignal
|
|
|
377
377
|
|
|
378
378
|
transaction.complete unless has_parent_transaction
|
|
379
379
|
end
|
|
380
|
-
alias
|
|
380
|
+
alias report_exception report_error
|
|
381
381
|
|
|
382
382
|
# Set a custom action name for the current transaction.
|
|
383
383
|
#
|
|
@@ -497,7 +497,7 @@ module Appsignal
|
|
|
497
497
|
transaction = Appsignal::Transaction.current
|
|
498
498
|
transaction.add_custom_data(data)
|
|
499
499
|
end
|
|
500
|
-
alias
|
|
500
|
+
alias set_custom_data add_custom_data
|
|
501
501
|
|
|
502
502
|
# Add tags to the current transaction.
|
|
503
503
|
#
|
|
@@ -542,9 +542,9 @@ module Appsignal
|
|
|
542
542
|
transaction = Appsignal::Transaction.current
|
|
543
543
|
transaction.add_tags(tags)
|
|
544
544
|
end
|
|
545
|
-
alias
|
|
546
|
-
alias
|
|
547
|
-
alias
|
|
545
|
+
alias tag_request add_tags
|
|
546
|
+
alias tag_job add_tags
|
|
547
|
+
alias set_tags add_tags
|
|
548
548
|
|
|
549
549
|
# Add parameters to the current transaction.
|
|
550
550
|
#
|
|
@@ -600,7 +600,7 @@ module Appsignal
|
|
|
600
600
|
transaction = Appsignal::Transaction.current
|
|
601
601
|
transaction.add_params(params, &block)
|
|
602
602
|
end
|
|
603
|
-
alias
|
|
603
|
+
alias set_params add_params
|
|
604
604
|
|
|
605
605
|
# Mark the parameters sample data to be set as an empty value.
|
|
606
606
|
#
|
|
@@ -666,7 +666,7 @@ module Appsignal
|
|
|
666
666
|
transaction = Appsignal::Transaction.current
|
|
667
667
|
transaction.add_session_data(session_data, &block)
|
|
668
668
|
end
|
|
669
|
-
alias
|
|
669
|
+
alias set_session_data add_session_data
|
|
670
670
|
|
|
671
671
|
# Add request headers to the current transaction.
|
|
672
672
|
#
|
|
@@ -708,7 +708,7 @@ module Appsignal
|
|
|
708
708
|
transaction = Appsignal::Transaction.current
|
|
709
709
|
transaction.add_headers(headers, &block)
|
|
710
710
|
end
|
|
711
|
-
alias
|
|
711
|
+
alias set_headers add_headers
|
|
712
712
|
|
|
713
713
|
# Add breadcrumbs to the transaction.
|
|
714
714
|
#
|
|
@@ -43,7 +43,7 @@ module Appsignal
|
|
|
43
43
|
Appsignal.instrument "subscribed.action_cable" do
|
|
44
44
|
inner.call
|
|
45
45
|
end
|
|
46
|
-
rescue Exception => exception
|
|
46
|
+
rescue Exception => exception
|
|
47
47
|
transaction.set_error(exception)
|
|
48
48
|
raise exception
|
|
49
49
|
ensure
|
|
@@ -79,7 +79,7 @@ module Appsignal
|
|
|
79
79
|
Appsignal.instrument "unsubscribed.action_cable" do
|
|
80
80
|
inner.call
|
|
81
81
|
end
|
|
82
|
-
rescue Exception => exception
|
|
82
|
+
rescue Exception => exception
|
|
83
83
|
transaction.set_error(exception)
|
|
84
84
|
raise exception
|
|
85
85
|
ensure
|
|
@@ -22,13 +22,16 @@ module Appsignal
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def dependencies_present?
|
|
25
|
-
self.class.dependencies_present?
|
|
25
|
+
self.class.dependencies_present? && Appsignal.config &&
|
|
26
|
+
Appsignal.config[:instrument_active_job]
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def install
|
|
29
30
|
ActiveSupport.on_load(:active_job) do
|
|
30
31
|
::ActiveJob::Base
|
|
31
32
|
.extend ::Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation
|
|
33
|
+
::ActiveJob::Base
|
|
34
|
+
.prepend ::Appsignal::Hooks::ActiveJobHook::ActiveJobEnqueueInstrumentation
|
|
32
35
|
|
|
33
36
|
next unless Appsignal::Hooks::ActiveJobHook.version_7_1_or_higher?
|
|
34
37
|
|
|
@@ -41,8 +44,44 @@ module Appsignal
|
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
# Records an `enqueue.active_job` event when a job is enqueued, so the
|
|
48
|
+
# enqueue shows up on the active transaction's timeline (e.g. when
|
|
49
|
+
# enqueuing from within a web request or another job).
|
|
50
|
+
#
|
|
51
|
+
# Wrapping `enqueue` ourselves -- rather than relying on Rails' native
|
|
52
|
+
# `enqueue.active_job` notification, which the AppSignal notifications
|
|
53
|
+
# path now suppresses -- gives us a single event we own. Like all
|
|
54
|
+
# AppSignal events, this only records when there's an active transaction;
|
|
55
|
+
# an enqueue with no transaction is a transparent pass-through.
|
|
56
|
+
#
|
|
57
|
+
# @!visibility private
|
|
58
|
+
module ActiveJobEnqueueInstrumentation
|
|
59
|
+
def enqueue(*, **)
|
|
60
|
+
# Skip recording the event when enqueue events are suppressed. That is
|
|
61
|
+
# the case when enqueue instrumentation is disabled, and it keeps this
|
|
62
|
+
# integration consistent with the standalone adapters (Sidekiq, ...),
|
|
63
|
+
# which already gate their own enqueue event on this check.
|
|
64
|
+
if Appsignal::Transaction.current? &&
|
|
65
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
66
|
+
return super
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
Appsignal.instrument("enqueue.active_job", "enqueue #{self.class.name} job") do
|
|
70
|
+
# Active Job enqueues through an adapter (Sidekiq, Resque, ...) that
|
|
71
|
+
# has its own enqueue instrumentation. Suppress it so the enqueue is
|
|
72
|
+
# recorded once, as this event, rather than as nested Active Job +
|
|
73
|
+
# adapter events.
|
|
74
|
+
if Appsignal::Transaction.current?
|
|
75
|
+
Appsignal::Transaction.current.suppress_job_enqueue_events { super }
|
|
76
|
+
else
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
44
83
|
module ActiveJobClassInstrumentation
|
|
45
|
-
def execute(job)
|
|
84
|
+
def execute(job)
|
|
46
85
|
enqueued_at = job["enqueued_at"]
|
|
47
86
|
queue_start = Time.parse(enqueued_at) if enqueued_at
|
|
48
87
|
queue_time =
|
|
@@ -75,7 +114,7 @@ module Appsignal
|
|
|
75
114
|
transaction.set_action(ActiveJobHelpers.action_name(job))
|
|
76
115
|
|
|
77
116
|
super
|
|
78
|
-
rescue Exception => exception
|
|
117
|
+
rescue Exception => exception
|
|
79
118
|
job_status = :failed
|
|
80
119
|
transaction_set_error(transaction, exception)
|
|
81
120
|
raise exception
|
|
@@ -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.
|