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.
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