appsignal 3.9.2-java → 3.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3138 -0
  3. data/.rubocop.yml +28 -20
  4. data/.rubocop_todo.yml +7 -33
  5. data/CHANGELOG.md +130 -0
  6. data/README.md +0 -1
  7. data/Rakefile +80 -65
  8. data/appsignal.gemspec +1 -1
  9. data/build_matrix.yml +112 -184
  10. data/ext/base.rb +1 -1
  11. data/gemfiles/hanami-2.1.gemfile +7 -0
  12. data/gemfiles/webmachine1.gemfile +5 -4
  13. data/lib/appsignal/cli/diagnose.rb +1 -1
  14. data/lib/appsignal/config.rb +5 -1
  15. data/lib/appsignal/demo.rb +0 -1
  16. data/lib/appsignal/environment.rb +11 -2
  17. data/lib/appsignal/extension/jruby.rb +1 -1
  18. data/lib/appsignal/helpers/instrumentation.rb +164 -2
  19. data/lib/appsignal/hooks/active_job.rb +1 -6
  20. data/lib/appsignal/integrations/grape.rb +19 -47
  21. data/lib/appsignal/integrations/hanami.rb +8 -7
  22. data/lib/appsignal/integrations/padrino.rb +51 -52
  23. data/lib/appsignal/integrations/railtie.rb +0 -3
  24. data/lib/appsignal/integrations/rake.rb +46 -12
  25. data/lib/appsignal/integrations/sidekiq.rb +1 -11
  26. data/lib/appsignal/integrations/sinatra.rb +0 -1
  27. data/lib/appsignal/integrations/webmachine.rb +15 -9
  28. data/lib/appsignal/probes/gvl.rb +24 -2
  29. data/lib/appsignal/probes/sidekiq.rb +1 -1
  30. data/lib/appsignal/probes.rb +1 -1
  31. data/lib/appsignal/rack/abstract_middleware.rb +104 -33
  32. data/lib/appsignal/rack/body_wrapper.rb +143 -0
  33. data/lib/appsignal/rack/event_handler.rb +12 -3
  34. data/lib/appsignal/rack/generic_instrumentation.rb +5 -4
  35. data/lib/appsignal/rack/grape_middleware.rb +40 -0
  36. data/lib/appsignal/rack/hanami_middleware.rb +2 -12
  37. data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
  38. data/lib/appsignal/rack/rails_instrumentation.rb +14 -57
  39. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
  40. data/lib/appsignal/rack/streaming_listener.rb +13 -59
  41. data/lib/appsignal/rack.rb +31 -0
  42. data/lib/appsignal/transaction.rb +50 -8
  43. data/lib/appsignal/utils/integration_memory_logger.rb +78 -0
  44. data/lib/appsignal/utils.rb +1 -0
  45. data/lib/appsignal/version.rb +1 -1
  46. data/lib/appsignal.rb +36 -33
  47. data/spec/.rubocop.yml +1 -1
  48. data/spec/lib/appsignal/cli/diagnose_spec.rb +1 -1
  49. data/spec/lib/appsignal/cli/install_spec.rb +3 -3
  50. data/spec/lib/appsignal/config_spec.rb +8 -5
  51. data/spec/lib/appsignal/demo_spec.rb +38 -41
  52. data/spec/lib/appsignal/hooks/action_cable_spec.rb +86 -167
  53. data/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +8 -20
  54. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +38 -84
  55. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +16 -37
  56. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +4 -4
  57. data/spec/lib/appsignal/hooks/activejob_spec.rb +111 -200
  58. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +54 -91
  59. data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +14 -32
  60. data/spec/lib/appsignal/hooks/excon_spec.rb +8 -12
  61. data/spec/lib/appsignal/hooks/net_http_spec.rb +7 -42
  62. data/spec/lib/appsignal/hooks/rake_spec.rb +107 -34
  63. data/spec/lib/appsignal/hooks/redis_client_spec.rb +18 -30
  64. data/spec/lib/appsignal/hooks/redis_spec.rb +10 -16
  65. data/spec/lib/appsignal/hooks/resque_spec.rb +42 -62
  66. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +33 -74
  67. data/spec/lib/appsignal/integrations/hanami_spec.rb +79 -21
  68. data/spec/lib/appsignal/integrations/http_spec.rb +12 -20
  69. data/spec/lib/appsignal/integrations/net_http_spec.rb +33 -0
  70. data/spec/lib/appsignal/integrations/object_spec.rb +29 -36
  71. data/spec/lib/appsignal/integrations/padrino_spec.rb +190 -163
  72. data/spec/lib/appsignal/integrations/que_spec.rb +43 -70
  73. data/spec/lib/appsignal/integrations/railtie_spec.rb +26 -67
  74. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +86 -160
  75. data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -3
  76. data/spec/lib/appsignal/integrations/webmachine_spec.rb +77 -40
  77. data/spec/lib/appsignal/probes/gvl_spec.rb +80 -3
  78. data/spec/lib/appsignal/probes_spec.rb +7 -4
  79. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +302 -105
  80. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
  81. data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -78
  82. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -27
  83. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +234 -0
  84. data/spec/lib/appsignal/rack/hanami_middleware_spec.rb +2 -16
  85. data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
  86. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +67 -131
  87. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +36 -44
  88. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +44 -139
  89. data/spec/lib/appsignal/transaction_spec.rb +239 -94
  90. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +163 -0
  91. data/spec/lib/appsignal_spec.rb +556 -344
  92. data/spec/support/helpers/dependency_helper.rb +6 -1
  93. data/spec/support/helpers/std_streams_helper.rb +1 -1
  94. data/spec/support/helpers/transaction_helpers.rb +8 -0
  95. data/spec/support/matchers/transaction.rb +185 -0
  96. data/spec/support/mocks/dummy_app.rb +20 -0
  97. data/spec/support/shared_examples/instrument.rb +17 -12
  98. data/spec/support/testing.rb +18 -9
  99. metadata +20 -11
  100. data/.semaphore/semaphore.yml +0 -2347
  101. data/script/lint_git +0 -22
  102. data/spec/lib/appsignal/integrations/grape_spec.rb +0 -239
  103. data/spec/support/matchers/be_completed.rb +0 -5
  104. data/support/check_versions +0 -22
  105. /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
- @instrument_span_name = options.fetch(:instrument_span_name, "process.abstract")
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
- add_transaction_metadata_before(transaction, request)
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
- instrument_request(request, transaction)
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 {GenericInstrumentation} or {EventHandler} is higher in
57
- # 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.
58
82
  #
59
- # @see {#instrument_request}
60
- def instrument_wrapped_request(request, transaction)
61
- instrument_app_call(request.env)
62
- ensure
63
- add_transaction_metadata_after(transaction, request)
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
- # {#instrument_wrapped_request} this will report any exceptions being
108
+ # {#instrument_app_call} this will report any exceptions being
69
109
  # raised.
70
110
  #
71
- # @see {#instrument_wrapped_request}
72
- def instrument_request(request, transaction)
73
- instrument_app_call(request.env)
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
- transaction.set_error(error)
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.env["appsignal.route"] || request.env["appsignal.action"]
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
- transaction.set_metadata("method", request.request_method)
105
- transaction.set_params_if_nil(params_for(request))
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
- if response
87
- transaction.set_tags(:response_status => response.status)
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 => response.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[: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
@@ -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[:instrument_span_name] ||= "process_action.hanami"
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
- super&.to_h
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
- class RailsInstrumentation
5
+ # @api private
6
+ class RailsInstrumentation < Appsignal::Rack::AbstractMiddleware
9
7
  def initialize(app, options = {})
10
- Appsignal.internal_logger.debug "Initializing Appsignal::Rack::RailsInstrumentation"
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
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
- def fetch_request_id(env)
50
- env["action_dispatch.request_id"]
51
- end
15
+ private
52
16
 
53
- def fetch_params(request)
54
- return unless request.respond_to?(:filtered_parameters)
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.filtered_parameters
57
- rescue => error
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
- def fetch_request_method(request)
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[: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