appsignal 3.9.2-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 +3138 -0
- data/.rubocop.yml +28 -20
- data/.rubocop_todo.yml +7 -33
- data/CHANGELOG.md +130 -0
- data/README.md +0 -1
- data/Rakefile +80 -65
- data/appsignal.gemspec +1 -1
- data/build_matrix.yml +112 -184
- data/ext/base.rb +1 -1
- data/gemfiles/hanami-2.1.gemfile +7 -0
- data/gemfiles/webmachine1.gemfile +5 -4
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/config.rb +5 -1
- data/lib/appsignal/demo.rb +0 -1
- data/lib/appsignal/environment.rb +11 -2
- data/lib/appsignal/extension/jruby.rb +1 -1
- data/lib/appsignal/helpers/instrumentation.rb +164 -2
- data/lib/appsignal/hooks/active_job.rb +1 -6
- data/lib/appsignal/integrations/grape.rb +19 -47
- data/lib/appsignal/integrations/hanami.rb +8 -7
- data/lib/appsignal/integrations/padrino.rb +51 -52
- data/lib/appsignal/integrations/railtie.rb +0 -3
- data/lib/appsignal/integrations/rake.rb +46 -12
- data/lib/appsignal/integrations/sidekiq.rb +1 -11
- data/lib/appsignal/integrations/sinatra.rb +0 -1
- data/lib/appsignal/integrations/webmachine.rb +15 -9
- data/lib/appsignal/probes/gvl.rb +24 -2
- data/lib/appsignal/probes/sidekiq.rb +1 -1
- data/lib/appsignal/probes.rb +1 -1
- data/lib/appsignal/rack/abstract_middleware.rb +104 -33
- data/lib/appsignal/rack/body_wrapper.rb +143 -0
- data/lib/appsignal/rack/event_handler.rb +12 -3
- data/lib/appsignal/rack/generic_instrumentation.rb +5 -4
- data/lib/appsignal/rack/grape_middleware.rb +40 -0
- data/lib/appsignal/rack/hanami_middleware.rb +2 -12
- data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
- data/lib/appsignal/rack/rails_instrumentation.rb +14 -57
- 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/utils/integration_memory_logger.rb +78 -0
- data/lib/appsignal/utils.rb +1 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +36 -33
- data/spec/.rubocop.yml +1 -1
- data/spec/lib/appsignal/cli/diagnose_spec.rb +1 -1
- data/spec/lib/appsignal/cli/install_spec.rb +3 -3
- data/spec/lib/appsignal/config_spec.rb +8 -5
- data/spec/lib/appsignal/demo_spec.rb +38 -41
- data/spec/lib/appsignal/hooks/action_cable_spec.rb +86 -167
- data/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +8 -20
- data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +38 -84
- data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +16 -37
- data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +4 -4
- data/spec/lib/appsignal/hooks/activejob_spec.rb +111 -200
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +54 -91
- data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +14 -32
- data/spec/lib/appsignal/hooks/excon_spec.rb +8 -12
- data/spec/lib/appsignal/hooks/net_http_spec.rb +7 -42
- data/spec/lib/appsignal/hooks/rake_spec.rb +107 -34
- data/spec/lib/appsignal/hooks/redis_client_spec.rb +18 -30
- data/spec/lib/appsignal/hooks/redis_spec.rb +10 -16
- data/spec/lib/appsignal/hooks/resque_spec.rb +42 -62
- data/spec/lib/appsignal/hooks/shoryuken_spec.rb +33 -74
- data/spec/lib/appsignal/integrations/hanami_spec.rb +79 -21
- data/spec/lib/appsignal/integrations/http_spec.rb +12 -20
- data/spec/lib/appsignal/integrations/net_http_spec.rb +33 -0
- data/spec/lib/appsignal/integrations/object_spec.rb +29 -36
- data/spec/lib/appsignal/integrations/padrino_spec.rb +190 -163
- data/spec/lib/appsignal/integrations/que_spec.rb +43 -70
- data/spec/lib/appsignal/integrations/railtie_spec.rb +26 -67
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +86 -160
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -3
- data/spec/lib/appsignal/integrations/webmachine_spec.rb +77 -40
- data/spec/lib/appsignal/probes/gvl_spec.rb +80 -3
- data/spec/lib/appsignal/probes_spec.rb +7 -4
- data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +302 -105
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
- data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -78
- data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -27
- data/spec/lib/appsignal/rack/grape_middleware_spec.rb +234 -0
- data/spec/lib/appsignal/rack/hanami_middleware_spec.rb +2 -16
- data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
- data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +67 -131
- data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +36 -44
- data/spec/lib/appsignal/rack/streaming_listener_spec.rb +44 -139
- data/spec/lib/appsignal/transaction_spec.rb +239 -94
- data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +163 -0
- data/spec/lib/appsignal_spec.rb +556 -344
- data/spec/support/helpers/dependency_helper.rb +6 -1
- data/spec/support/helpers/std_streams_helper.rb +1 -1
- data/spec/support/helpers/transaction_helpers.rb +8 -0
- data/spec/support/matchers/transaction.rb +185 -0
- data/spec/support/mocks/dummy_app.rb +20 -0
- data/spec/support/shared_examples/instrument.rb +17 -12
- data/spec/support/testing.rb +18 -9
- metadata +20 -11
- data/.semaphore/semaphore.yml +0 -2347
- data/script/lint_git +0 -22
- data/spec/lib/appsignal/integrations/grape_spec.rb +0 -239
- data/spec/support/matchers/be_completed.rb +0 -5
- data/support/check_versions +0 -22
- /data/gemfiles/{hanami.gemfile → hanami-2.0.gemfile} +0 -0
|
@@ -3,16 +3,24 @@
|
|
|
3
3
|
require "rack"
|
|
4
4
|
|
|
5
5
|
module Appsignal
|
|
6
|
-
# @api private
|
|
7
6
|
module Rack
|
|
7
|
+
# Base instrumentation middleware.
|
|
8
|
+
#
|
|
9
|
+
# Do not use this middleware directly. Instead use
|
|
10
|
+
# {InstrumentationMiddleware}.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
8
13
|
class AbstractMiddleware
|
|
14
|
+
DEFAULT_ERROR_REPORTING = :default
|
|
15
|
+
|
|
9
16
|
def initialize(app, options = {})
|
|
10
17
|
Appsignal.internal_logger.debug "Initializing #{self.class}"
|
|
11
18
|
@app = app
|
|
12
19
|
@options = options
|
|
13
20
|
@request_class = options.fetch(:request_class, ::Rack::Request)
|
|
14
21
|
@params_method = options.fetch(:params_method, :params)
|
|
15
|
-
@
|
|
22
|
+
@instrument_event_name = options.fetch(:instrument_event_name, nil)
|
|
23
|
+
@report_errors = options.fetch(:report_errors, DEFAULT_ERROR_REPORTING)
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def call(env)
|
|
@@ -32,15 +40,31 @@ module Appsignal
|
|
|
32
40
|
)
|
|
33
41
|
end
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
if wrapped_instrumentation
|
|
37
|
-
instrument_wrapped_request(request, transaction)
|
|
38
|
-
else
|
|
43
|
+
unless wrapped_instrumentation
|
|
39
44
|
# Set transaction on the request environment so other nested
|
|
40
45
|
# middleware can detect if there is parent instrumentation
|
|
41
46
|
# middleware active.
|
|
42
47
|
env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = transaction
|
|
43
|
-
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
add_transaction_metadata_before(transaction, request)
|
|
52
|
+
# Report errors if the :report_errors option is set to true or when
|
|
53
|
+
# there is no parent instrumentation that can rescue and report the error.
|
|
54
|
+
if @report_errors || !wrapped_instrumentation
|
|
55
|
+
instrument_app_call_with_exception_handling(
|
|
56
|
+
request.env,
|
|
57
|
+
transaction,
|
|
58
|
+
wrapped_instrumentation
|
|
59
|
+
)
|
|
60
|
+
else
|
|
61
|
+
instrument_app_call(request.env, transaction)
|
|
62
|
+
end
|
|
63
|
+
ensure
|
|
64
|
+
add_transaction_metadata_after(transaction, request)
|
|
65
|
+
|
|
66
|
+
# Complete transaction because this is the top instrumentation middleware.
|
|
67
|
+
Appsignal::Transaction.complete_current! unless wrapped_instrumentation
|
|
44
68
|
end
|
|
45
69
|
else
|
|
46
70
|
@app.call(env)
|
|
@@ -53,38 +77,55 @@ module Appsignal
|
|
|
53
77
|
# don't report any exceptions here, the top instrumentation middleware
|
|
54
78
|
# will be the one reporting the exception.
|
|
55
79
|
#
|
|
56
|
-
# Either another {
|
|
57
|
-
#
|
|
80
|
+
# Either another {AbstractMiddleware} or {EventHandler} is higher in the
|
|
81
|
+
# stack and will report the exception and complete the transaction.
|
|
58
82
|
#
|
|
59
|
-
# @see {#
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
# @see {#instrument_app_call_with_exception_handling}
|
|
84
|
+
def instrument_app_call(env, transaction)
|
|
85
|
+
if @instrument_event_name
|
|
86
|
+
Appsignal.instrument(@instrument_event_name) do
|
|
87
|
+
call_app(env, transaction)
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
call_app(env, transaction)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
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]
|
|
64
104
|
end
|
|
65
105
|
|
|
66
106
|
# Instrument the request fully. This is used by the top instrumentation
|
|
67
107
|
# middleware in the middleware stack. Unlike
|
|
68
|
-
# {#
|
|
108
|
+
# {#instrument_app_call} this will report any exceptions being
|
|
69
109
|
# raised.
|
|
70
110
|
#
|
|
71
|
-
# @see {#
|
|
72
|
-
def
|
|
73
|
-
instrument_app_call(
|
|
111
|
+
# @see {#instrument_app_call}
|
|
112
|
+
def instrument_app_call_with_exception_handling(env, transaction, wrapped_instrumentation)
|
|
113
|
+
instrument_app_call(env, transaction)
|
|
74
114
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
75
|
-
|
|
115
|
+
report_errors =
|
|
116
|
+
if @report_errors == DEFAULT_ERROR_REPORTING
|
|
117
|
+
# If there's no parent transaction, report the error
|
|
118
|
+
!wrapped_instrumentation
|
|
119
|
+
elsif @report_errors.respond_to?(:call)
|
|
120
|
+
# If the @report_errors option is callable, call it with the
|
|
121
|
+
# request environment so it can determine if the error needs to be
|
|
122
|
+
# reported.
|
|
123
|
+
@report_errors.call(env)
|
|
124
|
+
else
|
|
125
|
+
@report_errors
|
|
126
|
+
end
|
|
127
|
+
transaction.set_error(error) if report_errors
|
|
76
128
|
raise error
|
|
77
|
-
ensure
|
|
78
|
-
add_transaction_metadata_after(transaction, request)
|
|
79
|
-
|
|
80
|
-
# Complete transaction because this is the top instrumentation middleware.
|
|
81
|
-
Appsignal::Transaction.complete_current!
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def instrument_app_call(env)
|
|
85
|
-
Appsignal.instrument(@instrument_span_name) do
|
|
86
|
-
@app.call(env)
|
|
87
|
-
end
|
|
88
129
|
end
|
|
89
130
|
|
|
90
131
|
# Add metadata to the transaction based on the request environment.
|
|
@@ -98,11 +139,14 @@ module Appsignal
|
|
|
98
139
|
# Call `super` to also include the default set metadata.
|
|
99
140
|
def add_transaction_metadata_after(transaction, request)
|
|
100
141
|
default_action =
|
|
101
|
-
request
|
|
142
|
+
appsignal_route_env_value(request) || appsignal_action_env_value(request)
|
|
102
143
|
transaction.set_action_if_nil(default_action)
|
|
103
144
|
transaction.set_metadata("path", request.path)
|
|
104
|
-
|
|
105
|
-
|
|
145
|
+
|
|
146
|
+
request_method = request_method_for(request)
|
|
147
|
+
transaction.set_metadata("method", request_method) if request_method
|
|
148
|
+
|
|
149
|
+
transaction.set_params_if_nil { params_for(request) }
|
|
106
150
|
transaction.set_http_or_background_queue_start
|
|
107
151
|
end
|
|
108
152
|
|
|
@@ -118,9 +162,36 @@ module Appsignal
|
|
|
118
162
|
nil
|
|
119
163
|
end
|
|
120
164
|
|
|
165
|
+
def request_method_for(request)
|
|
166
|
+
request.request_method
|
|
167
|
+
rescue => error
|
|
168
|
+
Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
121
172
|
def request_for(env)
|
|
122
173
|
@request_class.new(env)
|
|
123
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
|
|
124
195
|
end
|
|
125
196
|
end
|
|
126
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
|
|
@@ -4,8 +4,10 @@ module Appsignal
|
|
|
4
4
|
module Rack
|
|
5
5
|
APPSIGNAL_TRANSACTION = "appsignal.transaction"
|
|
6
6
|
APPSIGNAL_EVENT_HANDLER_ID = "appsignal.event_handler_id"
|
|
7
|
+
APPSIGNAL_EVENT_HANDLER_HAS_ERROR = "appsignal.event_handler.error"
|
|
7
8
|
RACK_AFTER_REPLY = "rack.after_reply"
|
|
8
9
|
|
|
10
|
+
# @api private
|
|
9
11
|
class EventHandler
|
|
10
12
|
include ::Rack::Events::Abstract
|
|
11
13
|
|
|
@@ -70,6 +72,7 @@ module Appsignal
|
|
|
70
72
|
transaction = request.env[APPSIGNAL_TRANSACTION]
|
|
71
73
|
return unless transaction
|
|
72
74
|
|
|
75
|
+
request.env[APPSIGNAL_EVENT_HANDLER_HAS_ERROR] = true
|
|
73
76
|
transaction.set_error(error)
|
|
74
77
|
end
|
|
75
78
|
end
|
|
@@ -83,12 +86,18 @@ module Appsignal
|
|
|
83
86
|
self.class.safe_execution("Appsignal::Rack::EventHandler#on_finish") do
|
|
84
87
|
transaction.finish_event("process_request.rack", "", "")
|
|
85
88
|
transaction.set_http_or_background_queue_start
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
response_status =
|
|
90
|
+
if response
|
|
91
|
+
response.status
|
|
92
|
+
elsif request.env[APPSIGNAL_EVENT_HANDLER_HAS_ERROR] == true
|
|
93
|
+
500
|
|
94
|
+
end
|
|
95
|
+
if response_status
|
|
96
|
+
transaction.set_tags(:response_status => response_status)
|
|
88
97
|
Appsignal.increment_counter(
|
|
89
98
|
:response_status,
|
|
90
99
|
1,
|
|
91
|
-
:status =>
|
|
100
|
+
:status => response_status,
|
|
92
101
|
:namespace => format_namespace(transaction.namespace)
|
|
93
102
|
)
|
|
94
103
|
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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
module Rack
|
|
5
|
+
# @api private
|
|
6
|
+
class GrapeMiddleware < Appsignal::Rack::AbstractMiddleware
|
|
7
|
+
def initialize(app, options = {})
|
|
8
|
+
options[:instrument_event_name] = "process_request.grape"
|
|
9
|
+
options[:report_errors] = lambda { |env| !env["grape.skip_appsignal_error"] }
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def add_transaction_metadata_after(transaction, request)
|
|
16
|
+
endpoint = request.env["api.endpoint"]
|
|
17
|
+
unless endpoint&.options
|
|
18
|
+
super
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
options = endpoint.options
|
|
23
|
+
request_method = options[:method].first.to_s.upcase
|
|
24
|
+
klass = options[:for]
|
|
25
|
+
namespace = endpoint.namespace
|
|
26
|
+
namespace = "" if namespace == "/"
|
|
27
|
+
|
|
28
|
+
path = options[:path].first.to_s
|
|
29
|
+
path = "/#{path}" if path[0] != "/"
|
|
30
|
+
path = "#{namespace}#{path}"
|
|
31
|
+
|
|
32
|
+
transaction.set_action_if_nil("#{request_method}::#{klass}##{path}")
|
|
33
|
+
|
|
34
|
+
super
|
|
35
|
+
|
|
36
|
+
transaction.set_metadata("path", path)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -5,25 +5,15 @@ module Appsignal
|
|
|
5
5
|
# @api private
|
|
6
6
|
class HanamiMiddleware < AbstractMiddleware
|
|
7
7
|
def initialize(app, options = {})
|
|
8
|
-
options[:request_class] ||= ::Hanami::Action::Request
|
|
9
8
|
options[:params_method] ||= :params
|
|
10
|
-
options[:
|
|
9
|
+
options[:instrument_event_name] ||= "process_action.hanami"
|
|
11
10
|
super
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
private
|
|
15
14
|
|
|
16
15
|
def params_for(request)
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def request_for(env)
|
|
21
|
-
params = ::Hanami::Action.params_class.new(env)
|
|
22
|
-
@request_class.new(
|
|
23
|
-
:env => env,
|
|
24
|
-
:params => params,
|
|
25
|
-
:sessions_enabled => true
|
|
26
|
-
)
|
|
16
|
+
::Hanami::Action.params_class.new(request.env).to_h
|
|
27
17
|
end
|
|
28
18
|
end
|
|
29
19
|
end
|
|
@@ -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,70 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "rack"
|
|
4
|
-
|
|
5
3
|
module Appsignal
|
|
6
|
-
# @api private
|
|
7
4
|
module Rack
|
|
8
|
-
|
|
5
|
+
# @api private
|
|
6
|
+
class RailsInstrumentation < Appsignal::Rack::AbstractMiddleware
|
|
9
7
|
def initialize(app, options = {})
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def call(env)
|
|
16
|
-
if Appsignal.active?
|
|
17
|
-
call_with_appsignal_monitoring(env)
|
|
18
|
-
else
|
|
19
|
-
@app.call(env)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def call_with_appsignal_monitoring(env)
|
|
24
|
-
request = ActionDispatch::Request.new(env)
|
|
25
|
-
transaction = env.fetch(
|
|
26
|
-
Appsignal::Rack::APPSIGNAL_TRANSACTION,
|
|
27
|
-
Appsignal::Transaction::NilTransaction.new
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
begin
|
|
31
|
-
@app.call(env)
|
|
32
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
33
|
-
transaction.set_error(error)
|
|
34
|
-
raise error
|
|
35
|
-
ensure
|
|
36
|
-
controller = env["action_controller.instance"]
|
|
37
|
-
if controller
|
|
38
|
-
transaction.set_action_if_nil("#{controller.class}##{controller.action_name}")
|
|
39
|
-
end
|
|
40
|
-
transaction.set_params_if_nil(fetch_params(request))
|
|
41
|
-
request_id = fetch_request_id(env)
|
|
42
|
-
transaction.set_tags(:request_id => request_id) if request_id
|
|
43
|
-
transaction.set_metadata("path", request.path)
|
|
44
|
-
request_method = fetch_request_method(request)
|
|
45
|
-
transaction.set_metadata("method", request_method) if request_method
|
|
46
|
-
end
|
|
8
|
+
options[:request_class] ||= ActionDispatch::Request
|
|
9
|
+
options[:params_method] ||= :filtered_parameters
|
|
10
|
+
options[:instrument_event_name] = nil
|
|
11
|
+
options[:report_errors] = true
|
|
12
|
+
super
|
|
47
13
|
end
|
|
48
14
|
|
|
49
|
-
|
|
50
|
-
env["action_dispatch.request_id"]
|
|
51
|
-
end
|
|
15
|
+
private
|
|
52
16
|
|
|
53
|
-
def
|
|
54
|
-
|
|
17
|
+
def add_transaction_metadata_after(transaction, request)
|
|
18
|
+
controller = request.env["action_controller.instance"]
|
|
19
|
+
transaction.set_action_if_nil("#{controller.class}##{controller.action_name}") if controller
|
|
55
20
|
|
|
56
|
-
request.
|
|
57
|
-
|
|
58
|
-
# Getting params from the request has been know to fail.
|
|
59
|
-
Appsignal.internal_logger.debug "Exception while getting Rails params: #{error}"
|
|
60
|
-
nil
|
|
61
|
-
end
|
|
21
|
+
request_id = request.env["action_dispatch.request_id"]
|
|
22
|
+
transaction.set_tags(:request_id => request_id) if request_id
|
|
62
23
|
|
|
63
|
-
|
|
64
|
-
request.request_method
|
|
65
|
-
rescue => error
|
|
66
|
-
Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
|
|
67
|
-
nil
|
|
24
|
+
super
|
|
68
25
|
end
|
|
69
26
|
end
|
|
70
27
|
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
|