Skip to content

Commit d7fd56a

Browse files
authored
Merge pull request #1528 from appsignal/http-rb-session-instrumentation
Instrument HTTP.rb chained requests on version 6
2 parents 6e91223 + 9de9522 commit d7fd56a

5 files changed

Lines changed: 52 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
bump: patch
3+
type: fix
4+
---
5+
6+
Instrument HTTP.rb chained requests on http 6. Requests made through a chained
7+
client -- `HTTP.follow.get(...)`, `HTTP.headers(...).get(...)`, and so on -- go
8+
through `HTTP::Session` rather than `HTTP::Client`, and were not being recorded.
9+
They now produce a `request.http_rb` event like any other request.

lib/appsignal/hooks/http.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@ def dependencies_present?
1616

1717
def install
1818
require "appsignal/integrations/http"
19+
# `Client#request` takes positional options in http5 and keyword options
20+
# in http6.
1921
integration =
2022
if self.class.http6_or_higher?
2123
Appsignal::Integrations::HttpIntegration::KeywordOptions
2224
else
2325
Appsignal::Integrations::HttpIntegration::HashOptions
2426
end
2527
HTTP::Client.prepend integration
28+
# In http6 a chained request (`.follow`, `.headers`, ...) goes through
29+
# `HTTP::Session#request` instead of `HTTP::Client#request`, so
30+
# instrument it too (keyword options). http5 has no Session; chained
31+
# requests run through `Client#request` there.
32+
if defined?(HTTP::Session)
33+
HTTP::Session.prepend Appsignal::Integrations::HttpIntegration::KeywordOptions
34+
end
2635

2736
Appsignal::Environment.report_enabled("http_rb")
2837
end

lib/appsignal/integrations/http.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ def self.instrument(verb, uri, &block)
1212
Appsignal.instrument("request.http_rb", "#{verb.upcase} #{request_uri}", &block)
1313
end
1414

15+
# The event is recorded at the request boundary, so a redirected request
16+
# stays a single `request.http_rb` event spanning every hop. That boundary
17+
# lives in more than one place: a bare request runs through
18+
# `HTTP::Client#request`, but in http6 a chained request (`.follow`,
19+
# `.headers`, `.timeout`, ...) runs through `HTTP::Session#request`
20+
# instead, which never touches `Client#request`. The hook prepends one of
21+
# these onto each. `Client#request` takes positional options in http5 and
22+
# keyword options in http6; `Session#request` (http6 only) takes keyword
23+
# options.
1524
module HashOptions
1625
def request(verb, uri, opts = {})
1726
HttpIntegration.instrument(verb, uri) { super }

spec/lib/appsignal/hooks/http_spec.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
expect(HTTP::Client.included_modules)
1818
.to include(Appsignal::Integrations::HttpIntegration::KeywordOptions)
1919
end
20+
21+
it "instruments chained requests through HTTP::Session" do
22+
expect(HTTP::Session.included_modules)
23+
.to include(Appsignal::Integrations::HttpIntegration::KeywordOptions)
24+
end
2025
else
2126
it "installs the HTTP plugin with hash options" do
2227
expect(HTTP::Client.included_modules)

spec/lib/appsignal/integrations/http_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@
6767
end
6868
end
6969

70+
describe "following redirects" do
71+
# `HTTP.follow` chains through `HTTP::Session#request` in http6, which is
72+
# instrumented separately from `HTTP::Client#request`. The event is
73+
# recorded at the request boundary, so a redirected request is a single
74+
# `request.http_rb` event spanning every hop.
75+
it "records a single event spanning every hop" do
76+
stub_request(:get, "http://www.google.com")
77+
.to_return(:status => 301, :headers => { "Location" => "http://www.example.com" })
78+
stub_request(:get, "http://www.example.com").to_return(:status => 200)
79+
80+
HTTP.follow.get("http://www.google.com")
81+
82+
events = transaction.to_h["events"]
83+
.select { |event| event["name"] == "request.http_rb" }
84+
expect(events.map { |event| event["title"] }).to eq(
85+
["GET http://www.google.com"]
86+
)
87+
end
88+
end
89+
7090
context "with various URI objects" do
7191
it "parses an object responding to #to_s" do
7292
request_uri = Struct.new(:uri) do

0 commit comments

Comments
 (0)