appsignal 3.9.3-java → 3.10.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/CHANGELOG.md +92 -0
- data/README.md +0 -1
- data/Rakefile +1 -1
- data/build_matrix.yml +10 -12
- data/gemfiles/webmachine1.gemfile +5 -4
- data/lib/appsignal/config.rb +4 -0
- data/lib/appsignal/environment.rb +6 -1
- data/lib/appsignal/helpers/instrumentation.rb +163 -1
- data/lib/appsignal/hooks/active_job.rb +1 -6
- data/lib/appsignal/integrations/padrino.rb +21 -25
- data/lib/appsignal/integrations/rake.rb +46 -12
- data/lib/appsignal/integrations/sidekiq.rb +1 -11
- data/lib/appsignal/integrations/webmachine.rb +15 -9
- data/lib/appsignal/rack/abstract_middleware.rb +49 -12
- data/lib/appsignal/rack/body_wrapper.rb +143 -0
- data/lib/appsignal/rack/generic_instrumentation.rb +5 -4
- data/lib/appsignal/rack/grape_middleware.rb +1 -1
- 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 +13 -59
- data/lib/appsignal/rack.rb +31 -0
- data/lib/appsignal/transaction.rb +50 -8
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +3 -1
- data/spec/lib/appsignal/config_spec.rb +1 -0
- data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
- data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
- data/spec/lib/appsignal/integrations/webmachine_spec.rb +65 -17
- data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +96 -8
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
- 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/streaming_listener_spec.rb +43 -120
- data/spec/lib/appsignal/transaction_spec.rb +163 -4
- data/spec/lib/appsignal_spec.rb +197 -6
- data/spec/support/mocks/dummy_app.rb +1 -1
- metadata +8 -4
- data/support/check_versions +0 -22
@@ -2,26 +2,60 @@
|
|
2
2
|
|
3
3
|
module Appsignal
|
4
4
|
module Integrations
|
5
|
+
# @api private
|
5
6
|
module RakeIntegration
|
6
7
|
def execute(*args)
|
7
|
-
|
8
|
+
transaction =
|
9
|
+
if Appsignal.config[:enable_rake_performance_instrumentation]
|
10
|
+
Appsignal::Integrations::RakeIntegrationHelper.register_at_exit_hook
|
11
|
+
_appsignal_create_transaction
|
12
|
+
end
|
13
|
+
|
14
|
+
Appsignal.instrument "task.rake" do
|
15
|
+
super
|
16
|
+
end
|
8
17
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
9
|
-
|
10
|
-
|
11
|
-
|
18
|
+
Appsignal::Integrations::RakeIntegrationHelper.register_at_exit_hook
|
19
|
+
transaction ||= _appsignal_create_transaction
|
20
|
+
transaction.set_error(error)
|
21
|
+
raise error
|
22
|
+
ensure
|
23
|
+
if transaction
|
24
|
+
# Format given arguments and cast to hash if possible
|
25
|
+
params, _ = args
|
26
|
+
params = params.to_hash if params.respond_to?(:to_hash)
|
27
|
+
transaction.set_params_if_nil(params)
|
28
|
+
transaction.set_action(name)
|
29
|
+
transaction.complete
|
30
|
+
end
|
31
|
+
end
|
12
32
|
|
13
|
-
|
33
|
+
private
|
34
|
+
|
35
|
+
def _appsignal_create_transaction
|
36
|
+
Appsignal::Transaction.create(
|
14
37
|
SecureRandom.uuid,
|
15
38
|
Appsignal::Transaction::BACKGROUND_JOB,
|
16
|
-
Appsignal::Transaction::GenericRequest.new(
|
17
|
-
:params => params
|
18
|
-
)
|
39
|
+
Appsignal::Transaction::GenericRequest.new({})
|
19
40
|
)
|
20
|
-
|
21
|
-
|
22
|
-
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
module RakeIntegrationHelper
|
46
|
+
# Register an `at_exit` hook when a task is executed. This will stop
|
47
|
+
# AppSignal when _all_ tasks are executed and Rake exits.
|
48
|
+
def self.register_at_exit_hook
|
49
|
+
return if @register_at_exit_hook
|
50
|
+
|
51
|
+
Kernel.at_exit(&method(:at_exit_hook))
|
52
|
+
|
53
|
+
@register_at_exit_hook = true
|
54
|
+
end
|
55
|
+
|
56
|
+
# The at_exit hook itself
|
57
|
+
def self.at_exit_hook
|
23
58
|
Appsignal.stop("rake")
|
24
|
-
raise error
|
25
59
|
end
|
26
60
|
end
|
27
61
|
end
|
@@ -83,7 +83,7 @@ module Appsignal
|
|
83
83
|
raise exception
|
84
84
|
ensure
|
85
85
|
if transaction
|
86
|
-
transaction.set_params_if_nil(
|
86
|
+
transaction.set_params_if_nil(parse_arguments(item))
|
87
87
|
transaction.set_http_or_background_queue_start
|
88
88
|
Appsignal::Transaction.complete_current! unless exception
|
89
89
|
|
@@ -115,16 +115,6 @@ module Appsignal
|
|
115
115
|
"#{sidekiq_action_name}#perform"
|
116
116
|
end
|
117
117
|
|
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
118
|
def formatted_metadata(item)
|
129
119
|
{}.tap do |hash|
|
130
120
|
(item || {}).each do |key, value|
|
@@ -5,20 +5,26 @@ module Appsignal
|
|
5
5
|
# @api private
|
6
6
|
module WebmachineIntegration
|
7
7
|
def run
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
has_parent_transaction = Appsignal::Transaction.current?
|
9
|
+
transaction =
|
10
|
+
if has_parent_transaction
|
11
|
+
Appsignal::Transaction.current
|
12
|
+
else
|
13
|
+
Appsignal::Transaction.create(
|
14
|
+
SecureRandom.uuid,
|
15
|
+
Appsignal::Transaction::HTTP_REQUEST,
|
16
|
+
request
|
17
|
+
)
|
18
|
+
end
|
16
19
|
|
17
20
|
Appsignal.instrument("process_action.webmachine") do
|
18
21
|
super
|
19
22
|
end
|
23
|
+
ensure
|
24
|
+
transaction.set_action_if_nil("#{resource.class.name}##{request.method}")
|
25
|
+
transaction.set_params_if_nil(request.query)
|
20
26
|
|
21
|
-
Appsignal::Transaction.complete_current!
|
27
|
+
Appsignal::Transaction.complete_current! unless has_parent_transaction
|
22
28
|
end
|
23
29
|
|
24
30
|
private
|
@@ -4,6 +4,11 @@ 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
|
+
#
|
7
12
|
# @api private
|
8
13
|
class AbstractMiddleware
|
9
14
|
DEFAULT_ERROR_REPORTING = :default
|
@@ -14,7 +19,7 @@ module Appsignal
|
|
14
19
|
@options = options
|
15
20
|
@request_class = options.fetch(:request_class, ::Rack::Request)
|
16
21
|
@params_method = options.fetch(:params_method, :params)
|
17
|
-
@
|
22
|
+
@instrument_event_name = options.fetch(:instrument_event_name, nil)
|
18
23
|
@report_errors = options.fetch(:report_errors, DEFAULT_ERROR_REPORTING)
|
19
24
|
end
|
20
25
|
|
@@ -53,7 +58,7 @@ module Appsignal
|
|
53
58
|
wrapped_instrumentation
|
54
59
|
)
|
55
60
|
else
|
56
|
-
instrument_app_call(request.env)
|
61
|
+
instrument_app_call(request.env, transaction)
|
57
62
|
end
|
58
63
|
ensure
|
59
64
|
add_transaction_metadata_after(transaction, request)
|
@@ -72,20 +77,32 @@ module Appsignal
|
|
72
77
|
# don't report any exceptions here, the top instrumentation middleware
|
73
78
|
# will be the one reporting the exception.
|
74
79
|
#
|
75
|
-
# Either another {
|
76
|
-
#
|
80
|
+
# Either another {AbstractMiddleware} or {EventHandler} is higher in the
|
81
|
+
# stack and will report the exception and complete the transaction.
|
77
82
|
#
|
78
83
|
# @see {#instrument_app_call_with_exception_handling}
|
79
|
-
def instrument_app_call(env)
|
80
|
-
if @
|
81
|
-
Appsignal.instrument(@
|
82
|
-
|
84
|
+
def instrument_app_call(env, transaction)
|
85
|
+
if @instrument_event_name
|
86
|
+
Appsignal.instrument(@instrument_event_name) do
|
87
|
+
call_app(env, transaction)
|
83
88
|
end
|
84
89
|
else
|
85
|
-
|
90
|
+
call_app(env, transaction)
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
94
|
+
def call_app(env, transaction)
|
95
|
+
status, headers, obody = @app.call(env)
|
96
|
+
body =
|
97
|
+
if obody.is_a? Appsignal::Rack::BodyWrapper
|
98
|
+
obody
|
99
|
+
else
|
100
|
+
# Instrument response body and closing of the response body
|
101
|
+
Appsignal::Rack::BodyWrapper.wrap(obody, transaction)
|
102
|
+
end
|
103
|
+
[status, headers, body]
|
104
|
+
end
|
105
|
+
|
89
106
|
# Instrument the request fully. This is used by the top instrumentation
|
90
107
|
# middleware in the middleware stack. Unlike
|
91
108
|
# {#instrument_app_call} this will report any exceptions being
|
@@ -93,7 +110,7 @@ module Appsignal
|
|
93
110
|
#
|
94
111
|
# @see {#instrument_app_call}
|
95
112
|
def instrument_app_call_with_exception_handling(env, transaction, wrapped_instrumentation)
|
96
|
-
instrument_app_call(env)
|
113
|
+
instrument_app_call(env, transaction)
|
97
114
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
98
115
|
report_errors =
|
99
116
|
if @report_errors == DEFAULT_ERROR_REPORTING
|
@@ -122,14 +139,14 @@ module Appsignal
|
|
122
139
|
# Call `super` to also include the default set metadata.
|
123
140
|
def add_transaction_metadata_after(transaction, request)
|
124
141
|
default_action =
|
125
|
-
request
|
142
|
+
appsignal_route_env_value(request) || appsignal_action_env_value(request)
|
126
143
|
transaction.set_action_if_nil(default_action)
|
127
144
|
transaction.set_metadata("path", request.path)
|
128
145
|
|
129
146
|
request_method = request_method_for(request)
|
130
147
|
transaction.set_metadata("method", request_method) if request_method
|
131
148
|
|
132
|
-
transaction.set_params_if_nil
|
149
|
+
transaction.set_params_if_nil { params_for(request) }
|
133
150
|
transaction.set_http_or_background_queue_start
|
134
151
|
end
|
135
152
|
|
@@ -155,6 +172,26 @@ module Appsignal
|
|
155
172
|
def request_for(env)
|
156
173
|
@request_class.new(env)
|
157
174
|
end
|
175
|
+
|
176
|
+
def appsignal_route_env_value(request)
|
177
|
+
request.env["appsignal.route"].tap do |value|
|
178
|
+
next unless value
|
179
|
+
|
180
|
+
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
181
|
+
"Setting the action name with the request env 'appsignal.route' is deprecated. " \
|
182
|
+
"Please use `Appsignal.set_action` instead. "
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def appsignal_action_env_value(request)
|
187
|
+
request.env["appsignal.action"].tap do |value|
|
188
|
+
next unless value
|
189
|
+
|
190
|
+
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
191
|
+
"Setting the action name with the request env 'appsignal.action' is deprecated. " \
|
192
|
+
"Please use `Appsignal.set_action` instead. "
|
193
|
+
end
|
194
|
+
end
|
158
195
|
end
|
159
196
|
end
|
160
197
|
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
|
@@ -1,13 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack"
|
4
|
-
|
5
3
|
module Appsignal
|
6
|
-
# @api private
|
7
4
|
module Rack
|
5
|
+
# @api private
|
8
6
|
class GenericInstrumentation < AbstractMiddleware
|
9
7
|
def initialize(app, options = {})
|
10
|
-
options[:
|
8
|
+
options[:instrument_event_name] ||= "process_action.generic"
|
11
9
|
super
|
12
10
|
end
|
13
11
|
|
@@ -16,5 +14,8 @@ module Appsignal
|
|
16
14
|
transaction.set_action_if_nil("unknown")
|
17
15
|
end
|
18
16
|
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
class GenericInstrumentationAlias < GenericInstrumentation; end
|
19
20
|
end
|
20
21
|
end
|
@@ -5,7 +5,7 @@ module Appsignal
|
|
5
5
|
# @api private
|
6
6
|
class GrapeMiddleware < Appsignal::Rack::AbstractMiddleware
|
7
7
|
def initialize(app, options = {})
|
8
|
-
options[:
|
8
|
+
options[:instrument_event_name] = "process_request.grape"
|
9
9
|
options[:report_errors] = lambda { |env| !env["grape.skip_appsignal_error"] }
|
10
10
|
super
|
11
11
|
end
|
@@ -6,7 +6,7 @@ module Appsignal
|
|
6
6
|
class HanamiMiddleware < AbstractMiddleware
|
7
7
|
def initialize(app, options = {})
|
8
8
|
options[:params_method] ||= :params
|
9
|
-
options[:
|
9
|
+
options[:instrument_event_name] ||= "process_action.hanami"
|
10
10
|
super
|
11
11
|
end
|
12
12
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
module Rack
|
5
|
+
# Rack instrumentation middleware.
|
6
|
+
#
|
7
|
+
# This Ruby gem automatically instruments several Rack based libraries,
|
8
|
+
# like Rails and Sinatra. This middleware does not need to be added
|
9
|
+
# manually to these frameworks.
|
10
|
+
#
|
11
|
+
# This instrumentation middleware will wrap an app and report how long the
|
12
|
+
# request and response took, report errors that occurred in the app, and
|
13
|
+
# report metadata about the request method and path.
|
14
|
+
#
|
15
|
+
# The action name for the endpoint is not set by default, which is required
|
16
|
+
# for performance monitoring. Set the action name in each endpoint using
|
17
|
+
# the {Appsignal::Helpers::Instrumentation#set_action} helper.
|
18
|
+
#
|
19
|
+
# If multiple of these middlewares, or
|
20
|
+
# {AbstractMiddleware} subclasses are present in an app, only the top
|
21
|
+
# middleware will report errors from apps and other middleware.
|
22
|
+
#
|
23
|
+
# This middleware is best used in combination with the {EventHandler}.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# # config.ru
|
27
|
+
# require "appsignal"
|
28
|
+
# # Configure and start AppSignal
|
29
|
+
#
|
30
|
+
# # Add the EventHandler first
|
31
|
+
# use ::Rack::Events, [Appsignal::Rack::EventHandler.new]
|
32
|
+
# # Add the instrumentation middleware second
|
33
|
+
# use Appsignal::Rack::InstrumentationMiddleware
|
34
|
+
#
|
35
|
+
# # Other middleware
|
36
|
+
#
|
37
|
+
# # Start app
|
38
|
+
#
|
39
|
+
# @example Customize instrumentation event category
|
40
|
+
# use Appsignal::Rack::InstrumentationMiddleware,
|
41
|
+
# :instrument_event_name => "custom.goup"
|
42
|
+
#
|
43
|
+
# @example Disable error reporting for this middleware
|
44
|
+
# use Appsignal::Rack::InstrumentationMiddleware, :report_errors => false
|
45
|
+
#
|
46
|
+
# @example Always report errors, even when wrapped by other instrumentation middleware
|
47
|
+
# use Appsignal::Rack::InstrumentationMiddleware, :report_errors => true
|
48
|
+
#
|
49
|
+
# @example Disable error reporting for this middleware based on the request env
|
50
|
+
# use Appsignal::Rack::InstrumentationMiddleware,
|
51
|
+
# :report_errors => lambda { |env| env["some_key"] == "some value" }
|
52
|
+
#
|
53
|
+
# @see https://docs.appsignal.com/ruby/integrations/rack.html
|
54
|
+
# @api public
|
55
|
+
class InstrumentationMiddleware < AbstractMiddleware
|
56
|
+
def initialize(app, options = {})
|
57
|
+
options[:instrument_event_name] ||= "process_request_middleware.rack"
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack"
|
4
|
-
|
5
3
|
module Appsignal
|
6
4
|
module Rack
|
7
5
|
# @api private
|
@@ -9,7 +7,7 @@ module Appsignal
|
|
9
7
|
def initialize(app, options = {})
|
10
8
|
options[:request_class] ||= ActionDispatch::Request
|
11
9
|
options[:params_method] ||= :filtered_parameters
|
12
|
-
options[:
|
10
|
+
options[:instrument_event_name] = nil
|
13
11
|
options[:report_errors] = true
|
14
12
|
super
|
15
13
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack"
|
4
|
-
|
5
3
|
module Appsignal
|
6
4
|
module Rack
|
7
5
|
# Stub old middleware. Prevents Sinatra middleware being loaded twice.
|
@@ -34,7 +32,7 @@ module Appsignal
|
|
34
32
|
def initialize(app, options = {})
|
35
33
|
options[:request_class] ||= Sinatra::Request
|
36
34
|
options[:params_method] ||= :params
|
37
|
-
options[:
|
35
|
+
options[:instrument_event_name] ||= "process_action.sinatra"
|
38
36
|
super
|
39
37
|
@raise_errors_on = raise_errors?(app)
|
40
38
|
end
|
@@ -1,73 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
4
|
+
"The constant Appsignal::Rack::StreamingListener has been deprecated. " \
|
5
|
+
"Please update the constant name to " \
|
6
|
+
"Appsignal::Rack::InstrumentationMiddleware."
|
7
|
+
|
3
8
|
module Appsignal
|
4
9
|
module Rack
|
5
|
-
#
|
10
|
+
# Instrumentation middleware that tracks exceptions in streaming Rack
|
11
|
+
# responses.
|
6
12
|
#
|
7
13
|
# @api private
|
8
|
-
class StreamingListener
|
14
|
+
class StreamingListener < AbstractMiddleware
|
9
15
|
def initialize(app, options = {})
|
10
|
-
|
11
|
-
|
12
|
-
@options = options
|
13
|
-
end
|
14
|
-
|
15
|
-
def call(env)
|
16
|
-
if Appsignal.active?
|
17
|
-
call_with_appsignal_monitoring(env)
|
18
|
-
else
|
19
|
-
@app.call(env)
|
20
|
-
end
|
16
|
+
options[:instrument_event_name] ||= "process_streaming_request.rack"
|
17
|
+
super
|
21
18
|
end
|
22
19
|
|
23
|
-
def
|
24
|
-
request
|
25
|
-
transaction = Appsignal::Transaction.create(
|
26
|
-
SecureRandom.uuid,
|
27
|
-
Appsignal::Transaction::HTTP_REQUEST,
|
28
|
-
request
|
29
|
-
)
|
20
|
+
def add_transaction_metadata_after(transaction, request)
|
21
|
+
transaction.set_action_if_nil(request.env["appsignal.action"])
|
30
22
|
|
31
|
-
|
32
|
-
status, headers, body =
|
33
|
-
Appsignal.instrument("process_action.rack") do
|
34
|
-
@app.call(env)
|
35
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
36
|
-
transaction.set_error(e)
|
37
|
-
raise e
|
38
|
-
ensure
|
39
|
-
transaction.set_action_if_nil(env["appsignal.action"])
|
40
|
-
transaction.set_metadata("path", request.path)
|
41
|
-
transaction.set_metadata("method", request.request_method)
|
42
|
-
transaction.set_http_or_background_queue_start
|
43
|
-
end
|
44
|
-
|
45
|
-
# Wrap the result body with our StreamWrapper
|
46
|
-
[status, headers, StreamWrapper.new(body, transaction)]
|
23
|
+
super
|
47
24
|
end
|
48
25
|
end
|
49
26
|
end
|
50
|
-
|
51
|
-
class StreamWrapper
|
52
|
-
def initialize(stream, transaction)
|
53
|
-
@stream = stream
|
54
|
-
@transaction = transaction
|
55
|
-
end
|
56
|
-
|
57
|
-
def each(&block)
|
58
|
-
@stream.each(&block)
|
59
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
60
|
-
@transaction.set_error(e)
|
61
|
-
raise e
|
62
|
-
end
|
63
|
-
|
64
|
-
def close
|
65
|
-
@stream.close if @stream.respond_to?(:close)
|
66
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
67
|
-
@transaction.set_error(e)
|
68
|
-
raise e
|
69
|
-
ensure
|
70
|
-
Appsignal::Transaction.complete_current!
|
71
|
-
end
|
72
|
-
end
|
73
27
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
# @api private
|
5
|
+
module Rack
|
6
|
+
# Alias constants that have moved with a warning message that points to the
|
7
|
+
# place to update the reference.
|
8
|
+
def self.const_missing(name)
|
9
|
+
case name
|
10
|
+
when :GenericInstrumentation
|
11
|
+
require "appsignal/rack/generic_instrumentation"
|
12
|
+
|
13
|
+
callers = caller
|
14
|
+
Appsignal::Utils::StdoutAndLoggerMessage.warning \
|
15
|
+
"The constant Appsignal::Rack::GenericInstrumentation has been deprecated. " \
|
16
|
+
"Please use the new Appsignal::Rack::InstrumentationMiddleware middleware. " \
|
17
|
+
"This new middleware does not default the action name to 'unknown'. " \
|
18
|
+
"Set the action name for the endpoint using the Appsignal.set_action helper. " \
|
19
|
+
"Read our Rack docs for more information " \
|
20
|
+
"https://docs.appsignal.com/ruby/integrations/rack.html " \
|
21
|
+
"Update the constant name to " \
|
22
|
+
"Appsignal::Rack::InstrumentationMiddleware in the following file to " \
|
23
|
+
"remove this message.\n#{callers.first}"
|
24
|
+
# Return the alias so it can't ever get stuck in a recursive loop
|
25
|
+
Appsignal::Rack::GenericInstrumentationAlias
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|