appsignal 3.9.3-java → 3.11.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/.github/workflows/ci.yml +22 -19
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +180 -0
- data/Gemfile +1 -0
- data/README.md +0 -1
- data/Rakefile +1 -1
- data/benchmark.rake +99 -42
- data/build_matrix.yml +10 -12
- data/gemfiles/webmachine1.gemfile +5 -4
- data/lib/appsignal/cli/demo.rb +0 -1
- data/lib/appsignal/config.rb +57 -97
- data/lib/appsignal/demo.rb +15 -20
- data/lib/appsignal/environment.rb +6 -1
- data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
- data/lib/appsignal/event_formatter.rb +3 -2
- data/lib/appsignal/helpers/instrumentation.rb +490 -16
- data/lib/appsignal/hooks/action_cable.rb +21 -16
- data/lib/appsignal/hooks/active_job.rb +15 -14
- data/lib/appsignal/hooks/delayed_job.rb +1 -1
- data/lib/appsignal/hooks/shoryuken.rb +3 -63
- data/lib/appsignal/integrations/action_cable.rb +5 -7
- data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
- data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
- data/lib/appsignal/integrations/data_mapper.rb +1 -0
- data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
- data/lib/appsignal/integrations/dry_monitor.rb +1 -0
- data/lib/appsignal/integrations/excon.rb +1 -0
- data/lib/appsignal/integrations/http.rb +1 -0
- data/lib/appsignal/integrations/net_http.rb +1 -0
- data/lib/appsignal/integrations/object.rb +6 -0
- data/lib/appsignal/integrations/padrino.rb +21 -25
- data/lib/appsignal/integrations/que.rb +13 -20
- data/lib/appsignal/integrations/railtie.rb +1 -1
- data/lib/appsignal/integrations/rake.rb +45 -15
- data/lib/appsignal/integrations/redis.rb +1 -0
- data/lib/appsignal/integrations/redis_client.rb +1 -0
- data/lib/appsignal/integrations/resque.rb +2 -5
- data/lib/appsignal/integrations/shoryuken.rb +75 -0
- data/lib/appsignal/integrations/sidekiq.rb +7 -25
- data/lib/appsignal/integrations/unicorn.rb +1 -0
- data/lib/appsignal/integrations/webmachine.rb +12 -9
- data/lib/appsignal/logger.rb +7 -3
- data/lib/appsignal/probes/helpers.rb +1 -0
- data/lib/appsignal/probes/mri.rb +1 -0
- data/lib/appsignal/probes/sidekiq.rb +1 -0
- data/lib/appsignal/probes.rb +3 -0
- data/lib/appsignal/rack/abstract_middleware.rb +67 -24
- data/lib/appsignal/rack/body_wrapper.rb +143 -0
- data/lib/appsignal/rack/event_handler.rb +39 -8
- data/lib/appsignal/rack/generic_instrumentation.rb +6 -4
- data/lib/appsignal/rack/grape_middleware.rb +3 -2
- data/lib/appsignal/rack/hanami_middleware.rb +1 -1
- data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
- data/lib/appsignal/rack/rails_instrumentation.rb +1 -3
- data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
- data/lib/appsignal/rack/streaming_listener.rb +14 -59
- data/lib/appsignal/rack.rb +60 -0
- data/lib/appsignal/span.rb +1 -0
- data/lib/appsignal/transaction.rb +353 -104
- data/lib/appsignal/utils/data.rb +0 -1
- data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
- data/lib/appsignal/utils/integration_logger.rb +0 -13
- data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
- data/lib/appsignal/utils/json.rb +0 -1
- data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
- data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
- data/lib/appsignal/utils.rb +6 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +9 -6
- data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
- data/spec/lib/appsignal/config_spec.rb +139 -43
- data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
- data/spec/lib/appsignal/hooks/activejob_spec.rb +9 -0
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
- data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
- data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
- data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
- data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
- data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
- data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -4
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
- data/spec/lib/appsignal/integrations/webmachine_spec.rb +77 -17
- data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +144 -11
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
- data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -10
- data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -17
- data/spec/lib/appsignal/rack/grape_middleware_spec.rb +1 -1
- data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
- data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
- data/spec/lib/appsignal/rack/streaming_listener_spec.rb +43 -120
- data/spec/lib/appsignal/rack_spec.rb +63 -0
- data/spec/lib/appsignal/transaction_spec.rb +1675 -953
- data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
- data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
- data/spec/lib/appsignal_spec.rb +517 -13
- data/spec/support/helpers/transaction_helpers.rb +44 -20
- data/spec/support/matchers/transaction.rb +15 -1
- data/spec/support/mocks/dummy_app.rb +1 -1
- data/spec/support/testing.rb +1 -1
- metadata +12 -4
- data/support/check_versions +0 -22
|
@@ -5,11 +5,7 @@ module Appsignal
|
|
|
5
5
|
# @api private
|
|
6
6
|
module ResqueIntegration
|
|
7
7
|
def perform
|
|
8
|
-
transaction = Appsignal::Transaction.create(
|
|
9
|
-
SecureRandom.uuid,
|
|
10
|
-
Appsignal::Transaction::BACKGROUND_JOB,
|
|
11
|
-
Appsignal::Transaction::GenericRequest.new({})
|
|
12
|
-
)
|
|
8
|
+
transaction = Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
|
|
13
9
|
|
|
14
10
|
Appsignal.instrument "perform.resque" do
|
|
15
11
|
super
|
|
@@ -34,6 +30,7 @@ module Appsignal
|
|
|
34
30
|
end
|
|
35
31
|
end
|
|
36
32
|
|
|
33
|
+
# @api private
|
|
37
34
|
class ResqueHelpers
|
|
38
35
|
def self.arguments(payload)
|
|
39
36
|
case payload["class"]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
module Integrations
|
|
5
|
+
# @api private
|
|
6
|
+
class ShoryukenMiddleware
|
|
7
|
+
def call(worker_instance, queue, sqs_msg, body, &block)
|
|
8
|
+
transaction = Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
|
|
9
|
+
|
|
10
|
+
Appsignal.instrument("perform_job.shoryuken", &block)
|
|
11
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
12
|
+
transaction.set_error(error)
|
|
13
|
+
raise
|
|
14
|
+
ensure
|
|
15
|
+
batch = sqs_msg.is_a?(Array)
|
|
16
|
+
attributes = fetch_attributes(batch, sqs_msg)
|
|
17
|
+
transaction.set_action_if_nil("#{worker_instance.class.name}#perform")
|
|
18
|
+
transaction.set_params_if_nil { fetch_args(batch, sqs_msg, body) }
|
|
19
|
+
transaction.set_tags(attributes)
|
|
20
|
+
transaction.set_tags("queue" => queue)
|
|
21
|
+
transaction.set_tags("batch" => true) if batch
|
|
22
|
+
|
|
23
|
+
if attributes.key?("SentTimestamp")
|
|
24
|
+
transaction.set_queue_start(Time.at(attributes["SentTimestamp"].to_i).to_i)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Appsignal::Transaction.complete_current!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def fetch_attributes(batch, sqs_msg)
|
|
33
|
+
if batch
|
|
34
|
+
# We can't instrument batched message separately, the `yield` will
|
|
35
|
+
# perform all the batched messages.
|
|
36
|
+
# To provide somewhat useful metadata, Get first message based on
|
|
37
|
+
# SentTimestamp, and use its attributes as metadata for the
|
|
38
|
+
# transaction. We can't combine them all because then they would
|
|
39
|
+
# overwrite each other and the last message (in an sorted order)
|
|
40
|
+
# would be used as the source of the metadata. With the
|
|
41
|
+
# oldest/first message at least some useful information is stored
|
|
42
|
+
# such as the first received time and the number of retries for the
|
|
43
|
+
# first message. The newer message should have lower values and
|
|
44
|
+
# timestamps in their metadata.
|
|
45
|
+
first_msg =
|
|
46
|
+
sqs_msg.min do |a, b|
|
|
47
|
+
a.attributes["SentTimestamp"].to_i <=> b.attributes["SentTimestamp"].to_i
|
|
48
|
+
end
|
|
49
|
+
first_msg.attributes
|
|
50
|
+
else
|
|
51
|
+
sqs_msg.attributes.merge(:message_id => sqs_msg.message_id)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def fetch_args(batch, sqs_msg, body)
|
|
56
|
+
if batch
|
|
57
|
+
bodies = {}
|
|
58
|
+
sqs_msg.each_with_index do |msg, index|
|
|
59
|
+
# Store all separate bodies on a hash with the key being the
|
|
60
|
+
# message_id
|
|
61
|
+
bodies[msg.message_id] = body[index]
|
|
62
|
+
end
|
|
63
|
+
bodies
|
|
64
|
+
else
|
|
65
|
+
case body
|
|
66
|
+
when Hash
|
|
67
|
+
body
|
|
68
|
+
else
|
|
69
|
+
{ :params => body }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -11,6 +11,7 @@ module Appsignal
|
|
|
11
11
|
# about completing the transaction.
|
|
12
12
|
#
|
|
13
13
|
# Introduced in Sidekiq 5.1.
|
|
14
|
+
# @api private
|
|
14
15
|
class SidekiqDeathHandler
|
|
15
16
|
def call(_job_context, exception)
|
|
16
17
|
return unless Appsignal.config[:sidekiq_report_errors] == "discard"
|
|
@@ -37,12 +38,7 @@ module Appsignal
|
|
|
37
38
|
# Sidekiq error outside of the middleware scope.
|
|
38
39
|
# Can be a job JSON parse error or some other error happening in
|
|
39
40
|
# Sidekiq.
|
|
40
|
-
transaction =
|
|
41
|
-
Appsignal::Transaction.create(
|
|
42
|
-
SecureRandom.uuid, # Newly generated job id
|
|
43
|
-
Appsignal::Transaction::BACKGROUND_JOB,
|
|
44
|
-
Appsignal::Transaction::GenericRequest.new({})
|
|
45
|
-
)
|
|
41
|
+
transaction = Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
|
|
46
42
|
transaction.set_action_if_nil("SidekiqInternal")
|
|
47
43
|
transaction.set_metadata("sidekiq_error", sidekiq_context[:context])
|
|
48
44
|
transaction.set_params_if_nil(:jobstr => sidekiq_context[:jobstr])
|
|
@@ -64,13 +60,7 @@ module Appsignal
|
|
|
64
60
|
|
|
65
61
|
def call(_worker, item, _queue, &block)
|
|
66
62
|
job_status = nil
|
|
67
|
-
transaction = Appsignal::Transaction.create(
|
|
68
|
-
item["jid"],
|
|
69
|
-
Appsignal::Transaction::BACKGROUND_JOB,
|
|
70
|
-
Appsignal::Transaction::GenericRequest.new(
|
|
71
|
-
:queue_start => item["enqueued_at"]
|
|
72
|
-
)
|
|
73
|
-
)
|
|
63
|
+
transaction = Appsignal::Transaction.create(Appsignal::Transaction::BACKGROUND_JOB)
|
|
74
64
|
transaction.set_action_if_nil(formatted_action_name(item))
|
|
75
65
|
|
|
76
66
|
formatted_metadata(item).each do |key, value|
|
|
@@ -83,8 +73,10 @@ module Appsignal
|
|
|
83
73
|
raise exception
|
|
84
74
|
ensure
|
|
85
75
|
if transaction
|
|
86
|
-
transaction.set_params_if_nil(
|
|
87
|
-
|
|
76
|
+
transaction.set_params_if_nil { parse_arguments(item) }
|
|
77
|
+
queue_start = (item["enqueued_at"].to_f * 1000.0).to_i # Convert seconds to milliseconds
|
|
78
|
+
transaction.set_queue_start(queue_start)
|
|
79
|
+
transaction.set_tags(:request_id => item["jid"])
|
|
88
80
|
Appsignal::Transaction.complete_current! unless exception
|
|
89
81
|
|
|
90
82
|
queue = item["queue"] || "unknown"
|
|
@@ -115,16 +107,6 @@ module Appsignal
|
|
|
115
107
|
"#{sidekiq_action_name}#perform"
|
|
116
108
|
end
|
|
117
109
|
|
|
118
|
-
def filtered_arguments(job)
|
|
119
|
-
arguments = parse_arguments(job)
|
|
120
|
-
return unless arguments
|
|
121
|
-
|
|
122
|
-
Appsignal::Utils::HashSanitizer.sanitize(
|
|
123
|
-
arguments,
|
|
124
|
-
Appsignal.config[:filter_parameters]
|
|
125
|
-
)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
110
|
def formatted_metadata(item)
|
|
129
111
|
{}.tap do |hash|
|
|
130
112
|
(item || {}).each do |key, value|
|
|
@@ -5,20 +5,23 @@ module Appsignal
|
|
|
5
5
|
# @api private
|
|
6
6
|
module WebmachineIntegration
|
|
7
7
|
def run
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
transaction.set_action_if_nil("#{resource.class.name}##{request.method}")
|
|
8
|
+
has_parent_transaction = Appsignal::Transaction.current?
|
|
9
|
+
transaction =
|
|
10
|
+
if has_parent_transaction
|
|
11
|
+
Appsignal::Transaction.current
|
|
12
|
+
else
|
|
13
|
+
Appsignal::Transaction.create(Appsignal::Transaction::HTTP_REQUEST)
|
|
14
|
+
end
|
|
16
15
|
|
|
17
16
|
Appsignal.instrument("process_action.webmachine") do
|
|
18
17
|
super
|
|
19
18
|
end
|
|
19
|
+
ensure
|
|
20
|
+
transaction.set_action_if_nil("#{resource.class.name}##{request.method}")
|
|
21
|
+
transaction.set_params_if_nil(request.query)
|
|
22
|
+
transaction.set_headers_if_nil { request.headers if request.respond_to?(:headers) }
|
|
20
23
|
|
|
21
|
-
Appsignal::Transaction.complete_current!
|
|
24
|
+
Appsignal::Transaction.complete_current! unless has_parent_transaction
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
private
|
data/lib/appsignal/logger.rb
CHANGED
|
@@ -4,7 +4,10 @@ require "logger"
|
|
|
4
4
|
require "set"
|
|
5
5
|
|
|
6
6
|
module Appsignal
|
|
7
|
-
# Logger that flushes logs to the AppSignal logging service
|
|
7
|
+
# Logger that flushes logs to the AppSignal logging service.
|
|
8
|
+
#
|
|
9
|
+
# @see https://docs.appsignal.com/logging/platforms/integrations/ruby.html
|
|
10
|
+
# AppSignal Ruby logging documentation.
|
|
8
11
|
class Logger < ::Logger
|
|
9
12
|
PLAINTEXT = 0
|
|
10
13
|
LOGFMT = 1
|
|
@@ -144,8 +147,9 @@ module Appsignal
|
|
|
144
147
|
# as our logger directly inherits from Ruby base logger.
|
|
145
148
|
#
|
|
146
149
|
# Links:
|
|
147
|
-
#
|
|
148
|
-
# https://github.com/rails/rails/blob/
|
|
150
|
+
#
|
|
151
|
+
# - https://github.com/rails/rails/blob/e11ebc04cfbe41c06cdfb70ee5a9fdbbd98bb263/activesupport/lib/active_support/logger.rb#L60-L76
|
|
152
|
+
# - https://github.com/rails/rails/blob/main/activesupport/e11ebc04cfbe41c06cdfb70ee5a9fdbbd98bb263/active_support/logger_silence.rb
|
|
149
153
|
def silence(_severity = ERROR, &block)
|
|
150
154
|
block.call
|
|
151
155
|
end
|
data/lib/appsignal/probes/mri.rb
CHANGED
data/lib/appsignal/probes.rb
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Appsignal
|
|
4
4
|
module Probes
|
|
5
|
+
# @api private
|
|
5
6
|
ITERATION_IN_SECONDS = 60
|
|
6
7
|
|
|
8
|
+
# @api private
|
|
7
9
|
class ProbeCollection
|
|
8
10
|
def initialize
|
|
9
11
|
@probes = {}
|
|
@@ -72,6 +74,7 @@ module Appsignal
|
|
|
72
74
|
|
|
73
75
|
# @see ProbeCollection
|
|
74
76
|
# @return [ProbeCollection] Returns list of probes.
|
|
77
|
+
# @api private
|
|
75
78
|
def probes
|
|
76
79
|
@probes ||= ProbeCollection.new
|
|
77
80
|
end
|
|
@@ -4,6 +4,12 @@ require "rack"
|
|
|
4
4
|
|
|
5
5
|
module Appsignal
|
|
6
6
|
module Rack
|
|
7
|
+
# Base instrumentation middleware.
|
|
8
|
+
#
|
|
9
|
+
# Do not use this middleware directly. Instead use
|
|
10
|
+
# {InstrumentationMiddleware}.
|
|
11
|
+
#
|
|
12
|
+
# @abstract
|
|
7
13
|
# @api private
|
|
8
14
|
class AbstractMiddleware
|
|
9
15
|
DEFAULT_ERROR_REPORTING = :default
|
|
@@ -14,7 +20,7 @@ module Appsignal
|
|
|
14
20
|
@options = options
|
|
15
21
|
@request_class = options.fetch(:request_class, ::Rack::Request)
|
|
16
22
|
@params_method = options.fetch(:params_method, :params)
|
|
17
|
-
@
|
|
23
|
+
@instrument_event_name = options.fetch(:instrument_event_name, nil)
|
|
18
24
|
@report_errors = options.fetch(:report_errors, DEFAULT_ERROR_REPORTING)
|
|
19
25
|
end
|
|
20
26
|
|
|
@@ -28,11 +34,7 @@ module Appsignal
|
|
|
28
34
|
if wrapped_instrumentation
|
|
29
35
|
env[Appsignal::Rack::APPSIGNAL_TRANSACTION]
|
|
30
36
|
else
|
|
31
|
-
Appsignal::Transaction.create(
|
|
32
|
-
SecureRandom.uuid,
|
|
33
|
-
Appsignal::Transaction::HTTP_REQUEST,
|
|
34
|
-
request
|
|
35
|
-
)
|
|
37
|
+
Appsignal::Transaction.create(Appsignal::Transaction::HTTP_REQUEST)
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
unless wrapped_instrumentation
|
|
@@ -53,7 +55,7 @@ module Appsignal
|
|
|
53
55
|
wrapped_instrumentation
|
|
54
56
|
)
|
|
55
57
|
else
|
|
56
|
-
instrument_app_call(request.env)
|
|
58
|
+
instrument_app_call(request.env, transaction)
|
|
57
59
|
end
|
|
58
60
|
ensure
|
|
59
61
|
add_transaction_metadata_after(transaction, request)
|
|
@@ -72,28 +74,40 @@ module Appsignal
|
|
|
72
74
|
# don't report any exceptions here, the top instrumentation middleware
|
|
73
75
|
# will be the one reporting the exception.
|
|
74
76
|
#
|
|
75
|
-
# Either another {
|
|
76
|
-
#
|
|
77
|
+
# Either another {AbstractMiddleware} or {EventHandler} is higher in the
|
|
78
|
+
# stack and will report the exception and complete the transaction.
|
|
77
79
|
#
|
|
78
|
-
# @see
|
|
79
|
-
def instrument_app_call(env)
|
|
80
|
-
if @
|
|
81
|
-
Appsignal.instrument(@
|
|
82
|
-
|
|
80
|
+
# @see #instrument_app_call_with_exception_handling
|
|
81
|
+
def instrument_app_call(env, transaction)
|
|
82
|
+
if @instrument_event_name
|
|
83
|
+
Appsignal.instrument(@instrument_event_name) do
|
|
84
|
+
call_app(env, transaction)
|
|
83
85
|
end
|
|
84
86
|
else
|
|
85
|
-
|
|
87
|
+
call_app(env, transaction)
|
|
86
88
|
end
|
|
87
89
|
end
|
|
88
90
|
|
|
91
|
+
def call_app(env, transaction)
|
|
92
|
+
status, headers, obody = @app.call(env)
|
|
93
|
+
body =
|
|
94
|
+
if obody.is_a? Appsignal::Rack::BodyWrapper
|
|
95
|
+
obody
|
|
96
|
+
else
|
|
97
|
+
# Instrument response body and closing of the response body
|
|
98
|
+
Appsignal::Rack::BodyWrapper.wrap(obody, transaction)
|
|
99
|
+
end
|
|
100
|
+
[status, headers, body]
|
|
101
|
+
end
|
|
102
|
+
|
|
89
103
|
# Instrument the request fully. This is used by the top instrumentation
|
|
90
104
|
# middleware in the middleware stack. Unlike
|
|
91
105
|
# {#instrument_app_call} this will report any exceptions being
|
|
92
106
|
# raised.
|
|
93
107
|
#
|
|
94
|
-
# @see
|
|
108
|
+
# @see #instrument_app_call
|
|
95
109
|
def instrument_app_call_with_exception_handling(env, transaction, wrapped_instrumentation)
|
|
96
|
-
instrument_app_call(env)
|
|
110
|
+
instrument_app_call(env, transaction)
|
|
97
111
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
98
112
|
report_errors =
|
|
99
113
|
if @report_errors == DEFAULT_ERROR_REPORTING
|
|
@@ -122,15 +136,22 @@ module Appsignal
|
|
|
122
136
|
# Call `super` to also include the default set metadata.
|
|
123
137
|
def add_transaction_metadata_after(transaction, request)
|
|
124
138
|
default_action =
|
|
125
|
-
request
|
|
139
|
+
appsignal_route_env_value(request) || appsignal_action_env_value(request)
|
|
126
140
|
transaction.set_action_if_nil(default_action)
|
|
127
141
|
transaction.set_metadata("path", request.path)
|
|
128
142
|
|
|
129
143
|
request_method = request_method_for(request)
|
|
130
144
|
transaction.set_metadata("method", request_method) if request_method
|
|
131
145
|
|
|
132
|
-
transaction.set_params_if_nil
|
|
133
|
-
transaction.
|
|
146
|
+
transaction.set_params_if_nil { params_for(request) }
|
|
147
|
+
transaction.set_session_data_if_nil do
|
|
148
|
+
request.session if request.respond_to?(:session)
|
|
149
|
+
end
|
|
150
|
+
transaction.set_headers_if_nil do
|
|
151
|
+
request.env if request.respond_to?(:env)
|
|
152
|
+
end
|
|
153
|
+
queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
|
|
154
|
+
transaction.set_queue_start(queue_start) if queue_start
|
|
134
155
|
end
|
|
135
156
|
|
|
136
157
|
def params_for(request)
|
|
@@ -138,9 +159,9 @@ module Appsignal
|
|
|
138
159
|
|
|
139
160
|
request.send(@params_method)
|
|
140
161
|
rescue => error
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
162
|
+
Appsignal.internal_logger.error(
|
|
163
|
+
"Exception while fetching params from '#{@request_class}##{@params_method}': " \
|
|
164
|
+
"#{error.class} #{error}"
|
|
144
165
|
)
|
|
145
166
|
nil
|
|
146
167
|
end
|
|
@@ -148,13 +169,35 @@ module Appsignal
|
|
|
148
169
|
def request_method_for(request)
|
|
149
170
|
request.request_method
|
|
150
171
|
rescue => error
|
|
151
|
-
Appsignal.internal_logger.error(
|
|
172
|
+
Appsignal.internal_logger.error(
|
|
173
|
+
"Exception while fetching the HTTP request method: #{error.class}: #{error}"
|
|
174
|
+
)
|
|
152
175
|
nil
|
|
153
176
|
end
|
|
154
177
|
|
|
155
178
|
def request_for(env)
|
|
156
179
|
@request_class.new(env)
|
|
157
180
|
end
|
|
181
|
+
|
|
182
|
+
def appsignal_route_env_value(request)
|
|
183
|
+
request.env["appsignal.route"].tap do |value|
|
|
184
|
+
next unless value
|
|
185
|
+
|
|
186
|
+
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
|
187
|
+
"Setting the action name with the request env 'appsignal.route' is deprecated. " \
|
|
188
|
+
"Please use `Appsignal.set_action` instead. "
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def appsignal_action_env_value(request)
|
|
193
|
+
request.env["appsignal.action"].tap do |value|
|
|
194
|
+
next unless value
|
|
195
|
+
|
|
196
|
+
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
|
197
|
+
"Setting the action name with the request env 'appsignal.action' is deprecated. " \
|
|
198
|
+
"Please use `Appsignal.set_action` instead. "
|
|
199
|
+
end
|
|
200
|
+
end
|
|
158
201
|
end
|
|
159
202
|
end
|
|
160
203
|
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
module Rack
|
|
5
|
+
# @api private
|
|
6
|
+
class BodyWrapper
|
|
7
|
+
def self.wrap(original_body, appsignal_transaction)
|
|
8
|
+
# The logic of how Rack treats a response body differs based on which methods
|
|
9
|
+
# the body responds to. This means that to support the Rack 3.x spec in full
|
|
10
|
+
# we need to return a wrapper which matches the API of the wrapped body as closely
|
|
11
|
+
# as possible. Pick the wrapper from the most specific to the least specific.
|
|
12
|
+
# See https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body-
|
|
13
|
+
#
|
|
14
|
+
# What is important is that our Body wrapper responds to the same methods Rack
|
|
15
|
+
# (or a webserver) would be checking and calling, and passes through that functionality
|
|
16
|
+
# to the original body.
|
|
17
|
+
#
|
|
18
|
+
# This comment https://github.com/rails/rails/pull/49627#issuecomment-1769802573
|
|
19
|
+
# is of particular interest to understand why this has to be somewhat complicated.
|
|
20
|
+
if original_body.respond_to?(:to_path)
|
|
21
|
+
PathableBodyWrapper.new(original_body, appsignal_transaction)
|
|
22
|
+
elsif original_body.respond_to?(:to_ary)
|
|
23
|
+
ArrayableBodyWrapper.new(original_body, appsignal_transaction)
|
|
24
|
+
elsif !original_body.respond_to?(:each) && original_body.respond_to?(:call)
|
|
25
|
+
# This body only supports #call, so we must be running a Rack 3 application
|
|
26
|
+
# It is possible that a body exposes both `each` and `call` in the hopes of
|
|
27
|
+
# being backwards-compatible with both Rack 3.x and Rack 2.x, however
|
|
28
|
+
# this is not going to work since the SPEC says that if both are available,
|
|
29
|
+
# `each` should be used and `call` should be ignored.
|
|
30
|
+
# So for that case we can drop to our default EnumerableBodyWrapper
|
|
31
|
+
CallableBodyWrapper.new(original_body, appsignal_transaction)
|
|
32
|
+
else
|
|
33
|
+
EnumerableBodyWrapper.new(original_body, appsignal_transaction)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(body, appsignal_transaction)
|
|
38
|
+
@body_already_closed = false
|
|
39
|
+
@body = body
|
|
40
|
+
@transaction = appsignal_transaction
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# This must be present in all Rack bodies and will be called by the serving adapter
|
|
44
|
+
def close
|
|
45
|
+
# The @body_already_closed check is needed so that if `to_ary`
|
|
46
|
+
# of the body has already closed itself (as prescribed) we do not
|
|
47
|
+
# attempt to close it twice
|
|
48
|
+
if !@body_already_closed && @body.respond_to?(:close)
|
|
49
|
+
Appsignal.instrument("close_response_body.rack") { @body.close }
|
|
50
|
+
end
|
|
51
|
+
@body_already_closed = true
|
|
52
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
53
|
+
@transaction.set_error(error)
|
|
54
|
+
raise error
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The standard Rack body wrapper which exposes "each" for iterating
|
|
59
|
+
# over the response body. This is supported across all 3 major Rack
|
|
60
|
+
# versions.
|
|
61
|
+
#
|
|
62
|
+
# @api private
|
|
63
|
+
class EnumerableBodyWrapper < BodyWrapper
|
|
64
|
+
def each(&blk)
|
|
65
|
+
# This is a workaround for the Rails bug when there was a bit too much
|
|
66
|
+
# eagerness in implementing to_ary, see:
|
|
67
|
+
# https://github.com/rails/rails/pull/44953
|
|
68
|
+
# https://github.com/rails/rails/pull/47092
|
|
69
|
+
# https://github.com/rails/rails/pull/49627
|
|
70
|
+
# https://github.com/rails/rails/issues/49588
|
|
71
|
+
# While the Rack SPEC does not mandate `each` to be callable
|
|
72
|
+
# in a blockless way it is still a good idea to have it in place.
|
|
73
|
+
return enum_for(:each) unless block_given?
|
|
74
|
+
|
|
75
|
+
Appsignal.instrument("process_response_body.rack", "Process Rack response body (#each)") do
|
|
76
|
+
@body.each(&blk)
|
|
77
|
+
end
|
|
78
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
79
|
+
@transaction.set_error(error)
|
|
80
|
+
raise error
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The callable response bodies are a new Rack 3.x feature, and would not work
|
|
85
|
+
# with older Rack versions. They must not respond to `each` because
|
|
86
|
+
# "If it responds to each, you must call each and not call". This is why
|
|
87
|
+
# it inherits from BodyWrapper directly and not from EnumerableBodyWrapper
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
class CallableBodyWrapper < BodyWrapper
|
|
91
|
+
def call(stream)
|
|
92
|
+
# `stream` will be closed by the app we are calling, no need for us
|
|
93
|
+
# to close it ourselves
|
|
94
|
+
Appsignal.instrument("process_response_body.rack", "Process Rack response body (#call)") do
|
|
95
|
+
@body.call(stream)
|
|
96
|
+
end
|
|
97
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
98
|
+
@transaction.set_error(error)
|
|
99
|
+
raise error
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# "to_ary" takes precedence over "each" and allows the response body
|
|
104
|
+
# to be read eagerly. If the body supports that method, it takes precedence
|
|
105
|
+
# over "each":
|
|
106
|
+
# "Middleware may call to_ary directly on the Body and return a new Body in its place"
|
|
107
|
+
# One could "fold" both the to_ary API and the each() API into one Body object, but
|
|
108
|
+
# to_ary must also call "close" after it executes - and in the Rails implementation
|
|
109
|
+
# this pecularity was not handled properly.
|
|
110
|
+
#
|
|
111
|
+
# @api private
|
|
112
|
+
class ArrayableBodyWrapper < EnumerableBodyWrapper
|
|
113
|
+
def to_ary
|
|
114
|
+
@body_already_closed = true
|
|
115
|
+
Appsignal.instrument(
|
|
116
|
+
"process_response_body.rack",
|
|
117
|
+
"Process Rack response body (#to_ary)"
|
|
118
|
+
) do
|
|
119
|
+
@body.to_ary
|
|
120
|
+
end
|
|
121
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
122
|
+
@transaction.set_error(error)
|
|
123
|
+
raise error
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Having "to_path" on a body allows Rack to serve out a static file, or to
|
|
128
|
+
# pass that file to the downstream webserver for sending using X-Sendfile
|
|
129
|
+
class PathableBodyWrapper < EnumerableBodyWrapper
|
|
130
|
+
def to_path
|
|
131
|
+
Appsignal.instrument(
|
|
132
|
+
"process_response_body.rack",
|
|
133
|
+
"Process Rack response body (#to_path)"
|
|
134
|
+
) do
|
|
135
|
+
@body.to_path
|
|
136
|
+
end
|
|
137
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
138
|
+
@transaction.set_error(error)
|
|
139
|
+
raise error
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|