appsignal 3.7.6 → 3.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2efeb1057d0b9afac2d97367e7cc531c5ae0b5781da74d612ac8f1d27b3c1441
4
- data.tar.gz: e55719783fdaad7abd0cf6fcdee6a27206e9b1f08ed0ef5ac577422e9258d5d4
3
+ metadata.gz: f68ce83624657d12b65c203950c7a92681d621bc9f5644fcc61d4123417e8e93
4
+ data.tar.gz: ecda706a29b6aea7d52d3620cebb984495c85937acf561a596826fd57c457095
5
5
  SHA512:
6
- metadata.gz: 3cc5ce97c7050bad66f043c92fee70c7e1ad215aa8eec8cf72ddff2edd0a34f875fe3fb63794d010f534eba32a8c15c12f81b6b0f03c61a62b9fd6e48562aed0
7
- data.tar.gz: a6a3da8e140002a3bb41b3135d4e23727aa77b21c33f533e926ea71b7d9fe96268e6a845837a0555a18e06eb7b443204c1814a6558a2622ef1efd38ed707458f
6
+ metadata.gz: 9ec9d7754da4808d85efd335d8e9c00bca8f0e2c16f7a4a9246b9bd9d679519c7550202c1d2f809c786e117cde31c76c8c3405664b9878b9b8c61724a05dca14
7
+ data.tar.gz: 955a45291f05e0df7456ae7bef0c7d9be55824b764429a6fffd72985ee252dcf83893e824a610d086c82c0050ae14bb6ddebc6baaf600946c1d50ce55a3ae4d7
@@ -15,6 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  env:
17
17
  PACKAGE_NAME: "Ruby gem"
18
+ CHANGELOG_CATEGORY: "Ruby"
18
19
  CHANGELOG_LINK: "https://github.com/appsignal/appsignal-ruby/blob/main/CHANGELOG.md"
19
20
  steps:
20
21
  - name: Checkout repository at tag
@@ -46,10 +47,12 @@ jobs:
46
47
  run: |
47
48
  # Prepare JSON payload using jq to ensure proper escaping
48
49
  payload=$(jq -n \
49
- --arg package "Ruby gem" \
50
- --arg version "${{ env.TAG_NAME }}" \
50
+ --arg title "$PACKAGE_NAME ${{ env.TAG_NAME }}" \
51
+ --arg category "$CHANGELOG_CATEGORY" \
52
+ --arg version "$(echo "${{ env.TAG_NAME }}" | sed 's/^v//')" \
51
53
  --arg changelog "$(cat CHANGELOG_TEXT.txt)" \
52
- '{ref: "main", inputs: {package: $package, version: $version, changelog: $changelog}}')
54
+ --arg assignee "${{ github.actor }}" \
55
+ '{ref: "main", inputs: {title: $title, category: $category, version: $version, changelog: $changelog, assignee: $assignee}}')
53
56
 
54
57
  curl -X POST \
55
58
  -H "Authorization: token ${{ secrets.INTEGRATIONS_CHANGELOG_TOKEN }}" \
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 3.8.1
4
+
5
+ _Published on 2024-06-17._
6
+
7
+ ### Added
8
+
9
+ - [5459a021](https://github.com/appsignal/appsignal-ruby/commit/5459a021d7d4bbbd09a0dcbdf5f3af7bf861b6f5) patch - Report the response status for Rails requests as the `response_status` tag on samples, e.g. 200, 301, 500. This tag is visible on the sample detail page.
10
+
11
+ The response status is also reported as the `response_status` metric.
12
+
13
+ ## 3.8.0
14
+
15
+ _Published on 2024-06-17._
16
+
17
+ ### Changed
18
+
19
+ - [ca53b043](https://github.com/appsignal/appsignal-ruby/commit/ca53b04360ae123498640d043ee7ba74efc4b295) minor - Report the time spent in Rails middleware as part of the request duration. The AppSignal Rack middleware is now higher in the middleware stack and reports more time of the request to give insights in how long other middleware took. This is reported under the new `process_request.rack` event in the event timeline.
20
+
21
+ ### Fixed
22
+
23
+ - [37fbae5a](https://github.com/appsignal/appsignal-ruby/commit/37fbae5a0f1a4e964baceb21837e5d5f0cf903c0) patch - Fix ArgumentError being raised on Ruby logger and Rails.logger error calls. This fixes the error from being raised from within the AppSignal Ruby gem.
24
+ Please do not use this for error reporting. We recommend using our error reporting feature instead to be notified of new errors. Read more on [exception handling in Ruby with our Ruby gem](https://docs.appsignal.com/ruby/instrumentation/exception-handling.html).
25
+
26
+ ```ruby
27
+ # No longer raises an error
28
+ Rails.logger.error StandardError.new("StandardError log message")
29
+ ```
30
+
3
31
  ## 3.7.6
4
32
 
5
33
  _Published on 2024-06-11._
@@ -29,6 +29,11 @@ module Appsignal
29
29
  # Start logger
30
30
  Appsignal.start_logger
31
31
 
32
+ app.middleware.insert(
33
+ 0,
34
+ ::Rack::Events,
35
+ [Appsignal::Rack::EventHandler.new]
36
+ )
32
37
  app.middleware.insert_after(
33
38
  ActionDispatch::DebugExceptions,
34
39
  Appsignal::Rack::RailsInstrumentation
@@ -53,6 +53,13 @@ module Appsignal
53
53
 
54
54
  message = formatter.call(severity, Time.now, group, message) if formatter
55
55
 
56
+ unless message.is_a?(String)
57
+ Appsignal.internal_logger.warn(
58
+ "Logger message was ignored, because it was not a String: #{message.inspect}"
59
+ )
60
+ return
61
+ end
62
+
56
63
  Appsignal::Extension.log(
57
64
  group,
58
65
  SEVERITY_MAP.fetch(severity, 0),
@@ -112,6 +119,8 @@ module Appsignal
112
119
  message = yield if message.nil? && block_given?
113
120
  return if message.nil?
114
121
 
122
+ message = "#{message.class}: #{message.message}" if message.is_a?(Exception)
123
+
115
124
  add_with_attributes(ERROR, message, @group, attributes)
116
125
  end
117
126
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Rack
5
+ APPSIGNAL_TRANSACTION = "appsignal.transaction"
6
+ RACK_AFTER_REPLY = "rack.after_reply"
7
+
8
+ class EventHandler
9
+ include ::Rack::Events::Abstract
10
+
11
+ def self.safe_execution(name)
12
+ yield
13
+ rescue => e
14
+ Appsignal.internal_logger.error(
15
+ "Error occurred in #{name}: #{e.class}: #{e}: #{e.backtrace}"
16
+ )
17
+ end
18
+
19
+ def on_start(request, _response)
20
+ self.class.safe_execution("Appsignal::Rack::EventHandler#on_start") do
21
+ transaction = Appsignal::Transaction.create(
22
+ SecureRandom.uuid,
23
+ Appsignal::Transaction::HTTP_REQUEST,
24
+ request
25
+ )
26
+ request.env[APPSIGNAL_TRANSACTION] = transaction
27
+
28
+ request.env[RACK_AFTER_REPLY] ||= []
29
+ request.env[RACK_AFTER_REPLY] << proc do
30
+ Appsignal::Rack::EventHandler
31
+ .safe_execution("Appsignal::Rack::EventHandler's after_reply") do
32
+ transaction.finish_event("process_request.rack", "", "")
33
+ transaction.set_http_or_background_queue_start
34
+
35
+ # Make sure the current transaction is always closed when the request
36
+ # is finished. This is a fallback for in case the `on_finish`
37
+ # callback is not called. This is supported by servers like Puma and
38
+ # Unicorn.
39
+ #
40
+ # The EventHandler.on_finish callback should be called first, this is
41
+ # just a fallback if that doesn't get called.
42
+ Appsignal::Transaction.complete_current!
43
+ end
44
+ end
45
+ transaction.start_event
46
+ end
47
+ end
48
+
49
+ def on_error(request, _response, error)
50
+ self.class.safe_execution("Appsignal::Rack::EventHandler#on_error") do
51
+ transaction = request.env[APPSIGNAL_TRANSACTION]
52
+ return unless transaction
53
+
54
+ transaction.set_error(error)
55
+ end
56
+ end
57
+
58
+ def on_finish(request, response)
59
+ self.class.safe_execution("Appsignal::Rack::EventHandler#on_finish") do
60
+ transaction = request.env[APPSIGNAL_TRANSACTION]
61
+ return unless transaction
62
+
63
+ transaction.finish_event("process_request.rack", "", "")
64
+ transaction.set_tags(:response_status => response.status)
65
+ transaction.set_http_or_background_queue_start
66
+ Appsignal.increment_counter(
67
+ :response_status,
68
+ 1,
69
+ :status => response.status,
70
+ :namespace => format_namespace(transaction.namespace)
71
+ )
72
+
73
+ # Make sure the current transaction is always closed when the request
74
+ # is finished
75
+ Appsignal::Transaction.complete_current!
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def format_namespace(namespace)
82
+ if namespace == Appsignal::Transaction::HTTP_REQUEST
83
+ :web
84
+ else
85
+ namespace
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -22,13 +22,14 @@ module Appsignal
22
22
 
23
23
  def call_with_appsignal_monitoring(env)
24
24
  request = ActionDispatch::Request.new(env)
25
- transaction = Appsignal::Transaction.create(
26
- request_id(env),
27
- Appsignal::Transaction::HTTP_REQUEST,
28
- request,
29
- :params_method => :filtered_parameters
25
+ transaction = env.fetch(
26
+ Appsignal::Rack::APPSIGNAL_TRANSACTION,
27
+ Appsignal::Transaction::NilTransaction.new
30
28
  )
29
+
31
30
  begin
31
+ transaction.params = fetch_params(request)
32
+
32
33
  @app.call(env)
33
34
  rescue Exception => error # rubocop:disable Lint/RescueException
34
35
  transaction.set_error(error)
@@ -38,19 +39,33 @@ module Appsignal
38
39
  if controller
39
40
  transaction.set_action_if_nil("#{controller.class}##{controller.action_name}")
40
41
  end
41
- transaction.set_http_or_background_queue_start
42
+ request_id = fetch_request_id(env)
43
+ transaction.set_tags(:request_id => request_id) if request_id
42
44
  transaction.set_metadata("path", request.path)
43
- begin
44
- transaction.set_metadata("method", request.request_method)
45
- rescue => error
46
- Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
47
- end
48
- Appsignal::Transaction.complete_current!
45
+ request_method = fetch_request_method(request)
46
+ transaction.set_metadata("method", request_method) if request_method
49
47
  end
50
48
  end
51
49
 
52
- def request_id(env)
53
- env["action_dispatch.request_id"] || SecureRandom.uuid
50
+ def fetch_request_id(env)
51
+ env["action_dispatch.request_id"]
52
+ end
53
+
54
+ def fetch_params(request)
55
+ return unless request.respond_to?(:filtered_parameters)
56
+
57
+ request.filtered_parameters
58
+ rescue => error
59
+ # Getting params from the request has been know to fail.
60
+ Appsignal.internal_logger.debug "Exception while getting Rails params: #{error}"
61
+ nil
62
+ end
63
+
64
+ def fetch_request_method(request)
65
+ request.request_method
66
+ rescue => error
67
+ Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
68
+ nil
54
69
  end
55
70
  end
56
71
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.7.6"
4
+ VERSION = "3.8.1"
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -327,5 +327,6 @@ require "appsignal/integrations/railtie" if defined?(::Rails)
327
327
  require "appsignal/transaction"
328
328
  require "appsignal/version"
329
329
  require "appsignal/rack/generic_instrumentation"
330
+ require "appsignal/rack/event_handler"
330
331
  require "appsignal/transmitter"
331
332
  require "appsignal/heartbeat"
@@ -1,6 +1,12 @@
1
1
  describe Appsignal::Logger do
2
+ let(:log_stream) { StringIO.new }
3
+ let(:logs) { log_contents(log_stream) }
2
4
  let(:logger) { Appsignal::Logger.new("group", :level => ::Logger::DEBUG) }
3
5
 
6
+ before do
7
+ Appsignal.internal_logger = test_logger(log_stream)
8
+ end
9
+
4
10
  it "should not create a logger with a nil group" do
5
11
  expect do
6
12
  Appsignal::Logger.new(nil)
@@ -14,6 +20,19 @@ describe Appsignal::Logger do
14
20
  logger.add(::Logger::INFO, "Log message")
15
21
  end
16
22
 
23
+ it "does not log a message that's not a String" do
24
+ expect(Appsignal::Extension).to_not receive(:log)
25
+ logger.add(::Logger::INFO, 123)
26
+ logger.add(::Logger::INFO, {})
27
+ logger.add(::Logger::INFO, [])
28
+ expect(logs)
29
+ .to contains_log(:warn, "Logger message was ignored, because it was not a String: 123")
30
+ expect(logs)
31
+ .to contains_log(:warn, "Logger message was ignored, because it was not a String: []")
32
+ expect(logs)
33
+ .to contains_log(:warn, "Logger message was ignored, because it was not a String: {}")
34
+ end
35
+
17
36
  it "should log with a block" do
18
37
  expect(Appsignal::Extension).to receive(:log)
19
38
  .with("group", 3, 0, "Log message", instance_of(Appsignal::Extension::Data))
@@ -162,4 +181,25 @@ describe Appsignal::Logger do
162
181
  end
163
182
  end
164
183
  end
184
+
185
+ describe "#error with exception object" do
186
+ it "logs the exception class and its message" do
187
+ error =
188
+ begin
189
+ raise ExampleStandardError, "oh no!"
190
+ rescue => e
191
+ # This makes the exception include a backtrace, so we can assert it's NOT included
192
+ e
193
+ end
194
+ expect(Appsignal::Extension).to receive(:log)
195
+ .with(
196
+ "group",
197
+ 6,
198
+ 0,
199
+ "ExampleStandardError: oh no!",
200
+ instance_of(Appsignal::Extension::Data)
201
+ )
202
+ logger.error(error)
203
+ end
204
+ end
165
205
  end
@@ -0,0 +1,218 @@
1
+ describe Appsignal::Rack::EventHandler do
2
+ let(:queue_start_time) { fixed_time * 1_000 }
3
+ let(:env) do
4
+ {
5
+ "HTTP_X_REQUEST_START" => "t=#{queue_start_time.to_i}", # in milliseconds
6
+ "REQUEST_METHOD" => "GET",
7
+ "PATH_INFO" => "/path"
8
+ }
9
+ end
10
+ let(:request) { Rack::Request.new(env) }
11
+ let(:response) { nil }
12
+ let(:log_stream) { StringIO.new }
13
+ let(:log) { log_contents(log_stream) }
14
+ before do
15
+ start_agent
16
+ Appsignal.internal_logger = test_logger(log_stream)
17
+ end
18
+ around { |example| keep_transactions { example.run } }
19
+
20
+ def on_start
21
+ described_class.new.on_start(request, response)
22
+ end
23
+
24
+ describe "#on_start" do
25
+ it "creates a new transaction" do
26
+ expect { on_start }.to change { created_transactions.length }.by(1)
27
+
28
+ transaction = last_transaction
29
+ expect(transaction.to_h).to include(
30
+ "id" => kind_of(String),
31
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST
32
+ )
33
+
34
+ expect(Appsignal::Transaction.current).to eq(last_transaction)
35
+ end
36
+
37
+ it "registers transaction on the request environment" do
38
+ on_start
39
+
40
+ expect(request.env[Appsignal::Rack::APPSIGNAL_TRANSACTION])
41
+ .to eq(last_transaction)
42
+ end
43
+
44
+ it "registers an rack.after_reply callback that completes the transaction" do
45
+ request.env[Appsignal::Rack::RACK_AFTER_REPLY] = []
46
+ expect do
47
+ on_start
48
+ end.to change { request.env[Appsignal::Rack::RACK_AFTER_REPLY].length }.by(1)
49
+
50
+ expect(Appsignal::Transaction.current).to eq(last_transaction)
51
+
52
+ callback = request.env[Appsignal::Rack::RACK_AFTER_REPLY].first
53
+ callback.call
54
+
55
+ expect(Appsignal::Transaction.current).to be_kind_of(Appsignal::Transaction::NilTransaction)
56
+
57
+ expect(last_transaction.ext.queue_start).to eq(queue_start_time)
58
+ end
59
+
60
+ it "logs errors from rack.after_reply callbacks" do
61
+ on_start
62
+
63
+ expect(request.env[Appsignal::Rack::APPSIGNAL_TRANSACTION])
64
+ .to receive(:finish_event)
65
+ .and_raise(ExampleStandardError, "oh no")
66
+ callback = request.env[Appsignal::Rack::RACK_AFTER_REPLY].first
67
+ callback.call
68
+
69
+ expect(log).to contains_log(
70
+ :error,
71
+ "Error occurred in Appsignal::Rack::EventHandler's after_reply: ExampleStandardError: oh no"
72
+ )
73
+ end
74
+
75
+ it "logs an error in case of an error" do
76
+ expect(Appsignal::Transaction)
77
+ .to receive(:create).and_raise(ExampleStandardError, "oh no")
78
+
79
+ on_start
80
+
81
+ expect(log).to contains_log(
82
+ :error,
83
+ "Error occurred in Appsignal::Rack::EventHandler#on_start: ExampleStandardError: oh no"
84
+ )
85
+ end
86
+ end
87
+
88
+ describe "#on_error" do
89
+ def on_error(error)
90
+ described_class.new.on_error(request, response, error)
91
+ end
92
+
93
+ it "reports the error" do
94
+ on_start
95
+ on_error(ExampleStandardError.new("the error"))
96
+
97
+ expect(last_transaction.to_h).to include(
98
+ "error" => {
99
+ "name" => "ExampleStandardError",
100
+ "message" => "the error",
101
+ "backtrace" => kind_of(String)
102
+ }
103
+ )
104
+ end
105
+
106
+ it "logs an error in case of an internal error" do
107
+ on_start
108
+
109
+ expect(request.env[Appsignal::Rack::APPSIGNAL_TRANSACTION])
110
+ .to receive(:set_error).and_raise(ExampleStandardError, "oh no")
111
+
112
+ on_error(ExampleStandardError.new("the error"))
113
+
114
+ expect(log).to contains_log(
115
+ :error,
116
+ "Error occurred in Appsignal::Rack::EventHandler#on_error: ExampleStandardError: oh no"
117
+ )
118
+ end
119
+ end
120
+
121
+ describe "#on_finish" do
122
+ let(:response) { Rack::Events::BufferedResponse.new(200, {}, ["body"]) }
123
+
124
+ def on_finish
125
+ described_class.new.on_finish(request, response)
126
+ end
127
+
128
+ it "doesn't do anything without a transaction" do
129
+ on_start
130
+
131
+ request.env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = nil
132
+
133
+ on_finish
134
+
135
+ expect(last_transaction.to_h).to include(
136
+ "action" => nil,
137
+ "sample_data" => {},
138
+ "events" => []
139
+ )
140
+ end
141
+
142
+ it "completes the transaction" do
143
+ on_start
144
+ on_finish
145
+
146
+ expect(last_transaction.to_h).to include(
147
+ # The action is not set on purpose, as we can't set a normalized route
148
+ # It requires the app to set an action name
149
+ "action" => nil,
150
+ "sample_data" => hash_including(
151
+ "environment" => {
152
+ "REQUEST_METHOD" => "GET",
153
+ "PATH_INFO" => "/path"
154
+ }
155
+ )
156
+ )
157
+ expect(last_transaction.ext.queue_start).to eq(queue_start_time)
158
+ end
159
+
160
+ it "doesn't set the action name if already set" do
161
+ on_start
162
+ last_transaction.set_action("My action")
163
+ on_finish
164
+
165
+ expect(last_transaction.to_h).to include(
166
+ "action" => "My action"
167
+ )
168
+ end
169
+
170
+ it "finishes the process_request.rack event" do
171
+ on_start
172
+ on_finish
173
+
174
+ expect(last_transaction.to_h).to include(
175
+ "events" => [
176
+ hash_including(
177
+ "name" => "process_request.rack",
178
+ "title" => "",
179
+ "body" => "",
180
+ "body_format" => Appsignal::EventFormatter::DEFAULT
181
+ )
182
+ ]
183
+ )
184
+ end
185
+
186
+ it "sets the response status as a tag" do
187
+ on_start
188
+ on_finish
189
+
190
+ expect(last_transaction.to_h).to include(
191
+ "sample_data" => hash_including(
192
+ "tags" => { "response_status" => 200 }
193
+ )
194
+ )
195
+ end
196
+
197
+ it "increments the response status counter for response status" do
198
+ expect(Appsignal).to receive(:increment_counter)
199
+ .with(:response_status, 1, :status => 200, :namespace => :web)
200
+
201
+ on_start
202
+ on_finish
203
+ end
204
+
205
+ it "logs an error in case of an error" do
206
+ expect(Appsignal::Transaction)
207
+ .to receive(:complete_current!).and_raise(ExampleStandardError, "oh no")
208
+
209
+ on_start
210
+ on_finish
211
+
212
+ expect(log).to contains_log(
213
+ :error,
214
+ "Error occurred in Appsignal::Rack::EventHandler#on_finish: ExampleStandardError: oh no"
215
+ )
216
+ end
217
+ end
218
+ end
@@ -1,14 +1,21 @@
1
1
  if DependencyHelper.rails_present?
2
- class MockController
3
- end
4
-
5
2
  describe Appsignal::Rack::RailsInstrumentation do
3
+ class MockController; end
4
+
6
5
  let(:log) { StringIO.new }
7
6
  before do
8
7
  start_agent
9
8
  Appsignal.internal_logger = test_logger(log)
10
9
  end
11
10
 
11
+ let(:transaction) do
12
+ Appsignal::Transaction.new(
13
+ "transaction_id",
14
+ Appsignal::Transaction::HTTP_REQUEST,
15
+ Rack::Request.new(env)
16
+ )
17
+ end
18
+ let(:app) { double(:call => true) }
12
19
  let(:params) do
13
20
  {
14
21
  "controller" => "blog_posts",
@@ -19,12 +26,11 @@ if DependencyHelper.rails_present?
19
26
  }
20
27
  end
21
28
  let(:env_extra) { {} }
22
- let(:app) { double(:call => true) }
23
29
  let(:env) do
24
30
  http_request_env_with_data({
25
31
  :params => params,
26
32
  :with_queue_start => true,
27
- "action_dispatch.request_id" => "1",
33
+ "action_dispatch.request_id" => "request_id123",
28
34
  "action_dispatch.parameter_filter" => [:my_custom_param, :password],
29
35
  "action_controller.instance" => double(
30
36
  :class => MockController,
@@ -34,6 +40,9 @@ if DependencyHelper.rails_present?
34
40
  end
35
41
  let(:middleware) { Appsignal::Rack::RailsInstrumentation.new(app, {}) }
36
42
  around { |example| keep_transactions { example.run } }
43
+ before do
44
+ env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = transaction
45
+ end
37
46
 
38
47
  describe "#call" do
39
48
  before do
@@ -43,7 +52,7 @@ if DependencyHelper.rails_present?
43
52
  context "when appsignal is active" do
44
53
  before { allow(Appsignal).to receive(:active?).and_return(true) }
45
54
 
46
- it "should call with monitoring" do
55
+ it "calls with monitoring" do
47
56
  expect(middleware).to receive(:call_with_appsignal_monitoring).with(env)
48
57
  end
49
58
  end
@@ -51,11 +60,11 @@ if DependencyHelper.rails_present?
51
60
  context "when appsignal is not active" do
52
61
  before { allow(Appsignal).to receive(:active?).and_return(false) }
53
62
 
54
- it "should not call with monitoring" do
63
+ it "does not call with monitoring" do
55
64
  expect(middleware).to_not receive(:call_with_appsignal_monitoring)
56
65
  end
57
66
 
58
- it "should call the app" do
67
+ it "calls the app" do
59
68
  expect(app).to receive(:call).with(env)
60
69
  end
61
70
  end
@@ -66,36 +75,34 @@ if DependencyHelper.rails_present?
66
75
  describe "#call_with_appsignal_monitoring" do
67
76
  def run
68
77
  middleware.call(env)
78
+ last_transaction.complete # Manually close transaction to set sample data
69
79
  end
70
80
 
71
81
  it "calls the wrapped app" do
72
- run
82
+ expect { run }.to_not(change { created_transactions.length })
73
83
  expect(app).to have_received(:call).with(env)
74
84
  end
75
85
 
76
- it "creates one transaction with metadata" do
86
+ it "sets request metadata on the transaction" do
77
87
  run
78
88
 
79
- expect(created_transactions.length).to eq(1)
80
- transaction_hash = last_transaction.to_h
81
- expect(transaction_hash).to include(
89
+ expect(last_transaction.to_h).to include(
82
90
  "namespace" => Appsignal::Transaction::HTTP_REQUEST,
83
91
  "action" => "MockController#index",
84
92
  "metadata" => hash_including(
85
93
  "method" => "GET",
86
94
  "path" => "/blog"
95
+ ),
96
+ "sample_data" => hash_including(
97
+ "tags" => { "request_id" => "request_id123" }
87
98
  )
88
99
  )
89
- expect(last_transaction.ext.queue_start).to eq(
90
- fixed_time * 1_000.0
91
- )
92
100
  end
93
101
 
94
- it "filter parameters in Rails" do
102
+ it "reports Rails filter parameters" do
95
103
  run
96
104
 
97
- transaction_hash = last_transaction.to_h
98
- expect(transaction_hash).to include(
105
+ expect(last_transaction.to_h).to include(
99
106
  "sample_data" => hash_including(
100
107
  "params" => params.merge(
101
108
  "my_custom_param" => "[FILTERED]",
@@ -105,6 +112,26 @@ if DependencyHelper.rails_present?
105
112
  )
106
113
  end
107
114
 
115
+ context "with custom params" do
116
+ let(:app) do
117
+ lambda do |env|
118
+ env[Appsignal::Rack::APPSIGNAL_TRANSACTION].params = { "custom_param" => "yes" }
119
+ end
120
+ end
121
+
122
+ it "allows custom params to be set" do
123
+ run
124
+
125
+ expect(last_transaction.to_h).to include(
126
+ "sample_data" => hash_including(
127
+ "params" => {
128
+ "custom_param" => "yes"
129
+ }
130
+ )
131
+ )
132
+ end
133
+ end
134
+
108
135
  context "with an invalid HTTP request method" do
109
136
  let(:env_extra) { { :request_method => "FOO", "REQUEST_METHOD" => "FOO" } }
110
137
 
@@ -113,8 +140,8 @@ if DependencyHelper.rails_present?
113
140
 
114
141
  transaction_hash = last_transaction.to_h
115
142
  expect(transaction_hash["metadata"]).to_not have_key("method")
116
- expect(log_contents(log)).to contains_log(:error,
117
- "Unable to report HTTP request method: '")
143
+ expect(log_contents(log))
144
+ .to contains_log(:error, "Unable to report HTTP request method: '")
118
145
  end
119
146
  end
120
147
 
@@ -137,25 +164,32 @@ if DependencyHelper.rails_present?
137
164
  )
138
165
  end
139
166
  end
140
- end
141
167
 
142
- describe "#request_id" do
143
- subject { middleware.request_id(env) }
168
+ context "with a request path that's not a route" do
169
+ let(:env_extra) do
170
+ {
171
+ :path => "/unknown-route",
172
+ "action_controller.instance" => nil
173
+ }
174
+ end
144
175
 
145
- context "with request id present" do
146
- let(:env) { { "action_dispatch.request_id" => "id" } }
176
+ it "doesn't set an action name" do
177
+ run
147
178
 
148
- it "returns the present id" do
149
- is_expected.to eq "id"
179
+ expect(last_transaction.to_h).to include(
180
+ "action" => nil
181
+ )
150
182
  end
151
183
  end
184
+ end
152
185
 
153
- context "with request id not present" do
154
- let(:env) { {} }
186
+ describe "#fetch_request_id" do
187
+ subject { middleware.fetch_request_id(env) }
155
188
 
156
- it "sets a new id" do
157
- expect(subject.length).to eq 36
158
- end
189
+ let(:env) { { "action_dispatch.request_id" => "id" } }
190
+
191
+ it "returns the action dispatch id" do
192
+ is_expected.to eq "id"
159
193
  end
160
194
  end
161
195
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.7.6
4
+ version: 3.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Beekman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-06-11 00:00:00.000000000 Z
13
+ date: 2024-06-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -271,6 +271,7 @@ files:
271
271
  - lib/appsignal/probes/helpers.rb
272
272
  - lib/appsignal/probes/mri.rb
273
273
  - lib/appsignal/probes/sidekiq.rb
274
+ - lib/appsignal/rack/event_handler.rb
274
275
  - lib/appsignal/rack/generic_instrumentation.rb
275
276
  - lib/appsignal/rack/rails_instrumentation.rb
276
277
  - lib/appsignal/rack/sinatra_instrumentation.rb
@@ -371,6 +372,7 @@ files:
371
372
  - spec/lib/appsignal/probes/mri_spec.rb
372
373
  - spec/lib/appsignal/probes/sidekiq_spec.rb
373
374
  - spec/lib/appsignal/probes_spec.rb
375
+ - spec/lib/appsignal/rack/event_handler_spec.rb
374
376
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
375
377
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb
376
378
  - spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb