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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -19
  3. data/CHANGELOG.md +92 -0
  4. data/README.md +0 -1
  5. data/Rakefile +1 -1
  6. data/build_matrix.yml +10 -12
  7. data/gemfiles/webmachine1.gemfile +5 -4
  8. data/lib/appsignal/config.rb +4 -0
  9. data/lib/appsignal/environment.rb +6 -1
  10. data/lib/appsignal/helpers/instrumentation.rb +163 -1
  11. data/lib/appsignal/hooks/active_job.rb +1 -6
  12. data/lib/appsignal/integrations/padrino.rb +21 -25
  13. data/lib/appsignal/integrations/rake.rb +46 -12
  14. data/lib/appsignal/integrations/sidekiq.rb +1 -11
  15. data/lib/appsignal/integrations/webmachine.rb +15 -9
  16. data/lib/appsignal/rack/abstract_middleware.rb +49 -12
  17. data/lib/appsignal/rack/body_wrapper.rb +143 -0
  18. data/lib/appsignal/rack/generic_instrumentation.rb +5 -4
  19. data/lib/appsignal/rack/grape_middleware.rb +1 -1
  20. data/lib/appsignal/rack/hanami_middleware.rb +1 -1
  21. data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
  22. data/lib/appsignal/rack/rails_instrumentation.rb +1 -3
  23. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
  24. data/lib/appsignal/rack/streaming_listener.rb +13 -59
  25. data/lib/appsignal/rack.rb +31 -0
  26. data/lib/appsignal/transaction.rb +50 -8
  27. data/lib/appsignal/version.rb +1 -1
  28. data/lib/appsignal.rb +3 -1
  29. data/spec/lib/appsignal/config_spec.rb +1 -0
  30. data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
  31. data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
  32. data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
  33. data/spec/lib/appsignal/integrations/webmachine_spec.rb +65 -17
  34. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +96 -8
  35. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
  36. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -17
  37. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +1 -1
  38. data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
  39. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +43 -120
  40. data/spec/lib/appsignal/transaction_spec.rb +163 -4
  41. data/spec/lib/appsignal_spec.rb +197 -6
  42. data/spec/support/mocks/dummy_app.rb +1 -1
  43. metadata +8 -4
  44. 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
- super
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
- # Format given arguments and cast to hash if possible
10
- params, _ = args
11
- params = params.to_hash if params.respond_to?(:to_hash)
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
- transaction = Appsignal::Transaction.create(
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
- transaction.set_action(name)
21
- transaction.set_error(error)
22
- transaction.complete
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(filtered_arguments(item))
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
- transaction = Appsignal::Transaction.create(
9
- SecureRandom.uuid,
10
- Appsignal::Transaction::HTTP_REQUEST,
11
- request,
12
- :params_method => :query
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(
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
- @instrument_span_name = options.fetch(:instrument_span_name, "process.abstract")
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 {GenericInstrumentation} or {EventHandler} is higher in
76
- # the stack and will report the exception and complete the transaction.
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 @instrument_span_name
81
- Appsignal.instrument(@instrument_span_name) do
82
- @app.call(env)
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
- @app.call(env)
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.env["appsignal.route"] || request.env["appsignal.action"]
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(params_for(request))
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[:instrument_span_name] ||= "process_action.generic"
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[:instrument_span_name] = "process_request.grape"
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[:instrument_span_name] ||= "process_action.hanami"
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[:instrument_span_name] = nil
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[:instrument_span_name] ||= "process_action.sinatra"
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
- # Appsignal module that tracks exceptions in Streaming rack responses.
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
- Appsignal.internal_logger.debug "Initializing Appsignal::Rack::StreamingListener"
11
- @app = app
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 call_with_appsignal_monitoring(env)
24
- request = ::Rack::Request.new(env)
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
- # Instrument a `process_action`, to set params/action name
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