appsignal 3.7.6 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/create_release_from_tag.yml +6 -3
- data/CHANGELOG.md +18 -0
- data/lib/appsignal/integrations/railtie.rb +5 -0
- data/lib/appsignal/logger.rb +9 -0
- data/lib/appsignal/rack/event_handler.rb +83 -0
- data/lib/appsignal/rack/rails_instrumentation.rb +29 -14
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -0
- data/spec/lib/appsignal/logger_spec.rb +40 -0
- data/spec/lib/appsignal/rack/event_handler_spec.rb +199 -0
- data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +67 -33
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb665c8606b2dea836702e2e4cd1baa4d0684230e245e058a9701ae42cc84e33
|
4
|
+
data.tar.gz: 152b8860752631fd41779d8648a0236ee34a7bd2629452011926d6d9cb9aea2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c004f89b5f735367b13404278d8049ff990d7e87baf566a41cf0fdd901d991fbc61c48ac0c1e946abc4f3a934dc53ffac70839a947775017bd66c8fd7c951ac
|
7
|
+
data.tar.gz: fdc354654701f26ae6124df92b45453f1791e3fe9d66ebb36dde90492a98da9fda3394dbc30d7940d53205700a8b558b566243ca0d09e4de847dc1b07ee99c18
|
@@ -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
|
50
|
-
--arg
|
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
|
-
|
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,23 @@
|
|
1
1
|
# AppSignal for Ruby gem Changelog
|
2
2
|
|
3
|
+
## 3.8.0
|
4
|
+
|
5
|
+
_Published on 2024-06-17._
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
|
9
|
+
- [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.
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
- [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.
|
14
|
+
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).
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# No longer raises an error
|
18
|
+
Rails.logger.error StandardError.new("StandardError log message")
|
19
|
+
```
|
20
|
+
|
3
21
|
## 3.7.6
|
4
22
|
|
5
23
|
_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
|
data/lib/appsignal/logger.rb
CHANGED
@@ -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,83 @@
|
|
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_http_or_background_queue_start
|
65
|
+
|
66
|
+
# Make sure the current transaction is always closed when the request
|
67
|
+
# is finished
|
68
|
+
Appsignal::Transaction.complete_current!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def format_namespace(namespace)
|
75
|
+
if namespace == Appsignal::Transaction::HTTP_REQUEST
|
76
|
+
:web
|
77
|
+
else
|
78
|
+
namespace
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
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 =
|
26
|
-
|
27
|
-
Appsignal::Transaction::
|
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
|
-
|
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
|
-
|
44
|
-
|
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
|
53
|
-
env["action_dispatch.request_id"]
|
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
|
data/lib/appsignal/version.rb
CHANGED
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,199 @@
|
|
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 "logs an error in case of an error" do
|
187
|
+
expect(Appsignal::Transaction)
|
188
|
+
.to receive(:complete_current!).and_raise(ExampleStandardError, "oh no")
|
189
|
+
|
190
|
+
on_start
|
191
|
+
on_finish
|
192
|
+
|
193
|
+
expect(log).to contains_log(
|
194
|
+
:error,
|
195
|
+
"Error occurred in Appsignal::Rack::EventHandler#on_finish: ExampleStandardError: oh no"
|
196
|
+
)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
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" => "
|
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 "
|
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 "
|
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 "
|
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 "
|
86
|
+
it "sets request metadata on the transaction" do
|
77
87
|
run
|
78
88
|
|
79
|
-
expect(
|
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
|
102
|
+
it "reports Rails filter parameters" do
|
95
103
|
run
|
96
104
|
|
97
|
-
|
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))
|
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
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
176
|
+
it "doesn't set an action name" do
|
177
|
+
run
|
147
178
|
|
148
|
-
|
149
|
-
|
179
|
+
expect(last_transaction.to_h).to include(
|
180
|
+
"action" => nil
|
181
|
+
)
|
150
182
|
end
|
151
183
|
end
|
184
|
+
end
|
152
185
|
|
153
|
-
|
154
|
-
|
186
|
+
describe "#fetch_request_id" do
|
187
|
+
subject { middleware.fetch_request_id(env) }
|
155
188
|
|
156
|
-
|
157
|
-
|
158
|
-
|
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.
|
4
|
+
version: 3.8.0
|
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-
|
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
|