Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for tracking http.rb HTTP requests #889

Merged
merged 8 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 2.6.9 for http5
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 2.6.9
- name: GEMSET
value: http5
- name: BUNDLE_GEMFILE
value: gemfiles/http5.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 2.6.9 for padrino
env_vars:
- *2
Expand Down Expand Up @@ -923,6 +941,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 2.7.5 for http5
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 2.7.5
- name: GEMSET
value: http5
- name: BUNDLE_GEMFILE
value: gemfiles/http5.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 2.7.5 for padrino
env_vars:
- *2
Expand Down Expand Up @@ -1294,6 +1330,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.0.3 for http5
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 3.0.3
- name: GEMSET
value: http5
- name: BUNDLE_GEMFILE
value: gemfiles/http5.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.0.3 for padrino
env_vars:
- *2
Expand Down Expand Up @@ -1611,6 +1665,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.1.1 for http5
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 3.1.1
- name: GEMSET
value: http5
- name: BUNDLE_GEMFILE
value: gemfiles/http5.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.1.1 for padrino
env_vars:
- *2
Expand Down Expand Up @@ -1910,6 +1982,24 @@ blocks:
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.2.0-preview1 for http5
env_vars:
- *2
- *3
- *4
- *5
- name: RUBY_VERSION
value: 3.2.0-preview1
- name: GEMSET
value: http5
- name: BUNDLE_GEMFILE
value: gemfiles/http5.gemfile
- name: _RUBYGEMS_VERSION
value: latest
- name: _BUNDLER_VERSION
value: latest
commands:
- "./support/bundler_wrapper exec rake test"
- name: Ruby 3.2.0-preview1 for padrino
env_vars:
- *2
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ configurations you need to run the spec suite with a specific Gemfile.
BUNDLE_GEMFILE=gemfiles/capistrano2.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/capistrano3.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/grape.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/http5.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/no_dependencies.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/padrino.gemfile bundle exec rspec
BUNDLE_GEMFILE=gemfiles/que.gemfile bundle exec rspec
Expand Down
1 change: 1 addition & 0 deletions build_matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ matrix:
- gem: "capistrano2"
- gem: "capistrano3"
- gem: "grape"
- gem: "http5"
- gem: "padrino"
- gem: "psych-3"
only:
Expand Down
5 changes: 5 additions & 0 deletions gemfiles/http5.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source 'https://rubygems.org'

gem 'http', '~> 5.0'

gemspec :path => '../'
3 changes: 3 additions & 0 deletions lib/appsignal/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Config
:ignore_actions => [],
:ignore_errors => [],
:ignore_namespaces => [],
:instrument_http_rb => true,
:instrument_net_http => true,
:instrument_redis => true,
:instrument_sequel => true,
Expand Down Expand Up @@ -74,6 +75,7 @@ class Config
"APPSIGNAL_IGNORE_ACTIONS" => :ignore_actions,
"APPSIGNAL_IGNORE_ERRORS" => :ignore_errors,
"APPSIGNAL_IGNORE_NAMESPACES" => :ignore_namespaces,
"APPSIGNAL_INSTRUMENT_HTTP_RB" => :instrument_http_rb,
"APPSIGNAL_INSTRUMENT_NET_HTTP" => :instrument_net_http,
"APPSIGNAL_INSTRUMENT_REDIS" => :instrument_redis,
"APPSIGNAL_INSTRUMENT_SEQUEL" => :instrument_sequel,
Expand Down Expand Up @@ -119,6 +121,7 @@ class Config
APPSIGNAL_ENABLE_MINUTELY_PROBES
APPSIGNAL_ENABLE_STATSD
APPSIGNAL_FILES_WORLD_ACCESSIBLE
APPSIGNAL_INSTRUMENT_HTTP_RB
APPSIGNAL_INSTRUMENT_NET_HTTP
APPSIGNAL_INSTRUMENT_REDIS
APPSIGNAL_INSTRUMENT_SEQUEL
Expand Down
1 change: 1 addition & 0 deletions lib/appsignal/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def self.const_missing(name)
require "appsignal/hooks/active_support_notifications"
require "appsignal/hooks/celluloid"
require "appsignal/hooks/delayed_job"
require "appsignal/hooks/http"
require "appsignal/hooks/mri"
require "appsignal/hooks/net_http"
require "appsignal/hooks/passenger"
Expand Down
21 changes: 21 additions & 0 deletions lib/appsignal/hooks/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Appsignal
class Hooks
# @api private
class HttpHook < Appsignal::Hooks::Hook
register :http_rb

def dependencies_present?
defined?(HTTP::Client) && Appsignal.config && Appsignal.config[:instrument_http_rb]
end

def install
require "appsignal/integrations/http"
HTTP::Client.send(:prepend, Appsignal::Integrations::HttpIntegration)

Appsignal::Environment.report_enabled("http_rb")
end
end
end
end
21 changes: 21 additions & 0 deletions lib/appsignal/integrations/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Appsignal
module Integrations
module HttpIntegration
def request(verb, uri, opts = {})
parsed_request_uri = URI.parse(uri)
request_uri = "#{parsed_request_uri.scheme}://#{parsed_request_uri.host}"

begin
Appsignal.instrument("request.http_rb", "#{verb.upcase} #{request_uri}") do
super
end
rescue Exception => error # rubocop:disable Lint/RescueException
Appsignal.set_error(error)
raise error
end
end
end
end
end
2 changes: 1 addition & 1 deletion spec/integration/diagnose
1 change: 1 addition & 0 deletions spec/lib/appsignal/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
:ignore_actions => [],
:ignore_errors => [],
:ignore_namespaces => [],
:instrument_http_rb => true,
:instrument_net_http => true,
:instrument_redis => true,
:instrument_sequel => true,
Expand Down
38 changes: 38 additions & 0 deletions spec/lib/appsignal/hooks/http_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

describe Appsignal::Hooks::HttpHook do
before :context do
start_agent
end

if DependencyHelper.http_present?
context "with instrument_http_rb set to true" do
describe "#dependencies_present?" do
subject { described_class.new.dependencies_present? }

it { is_expected.to be_truthy }
end
end

context "with instrument_http_rb set to false" do
describe "#dependencies_present?" do
before { Appsignal.config.config_hash[:instrument_http_rb] = false }
after { Appsignal.config.config_hash[:instrument_http_rb] = true }
subject { described_class.new.dependencies_present? }

it { is_expected.to be_falsy }
end
end

it "installs the HTTP plugin" do
expect(HTTP::Client.included_modules)
.to include(Appsignal::Integrations::HttpIntegration)
end
else
describe "#dependencies_present?" do
subject { described_class.new.dependencies_present? }

it { is_expected.to be_falsy }
end
end
end
103 changes: 103 additions & 0 deletions spec/lib/appsignal/integrations/http_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

if DependencyHelper.http_present?
require "appsignal/integrations/http"

describe Appsignal::Integrations::HttpIntegration do
let(:transaction) { http_request_transaction }

around do |example|
keep_transactions { example.run }
end
stefanvermaas marked this conversation as resolved.
Show resolved Hide resolved

before do
set_current_transaction(transaction)
end

before :context do
start_agent
end

it "should instrument a HTTP request" do
stub_request(:get, "http://www.google.com")

HTTP.get("http://www.google.com")

transaction_hash = transaction.to_h
expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST)
expect(transaction_hash["events"].first).to include(
"body" => "",
"body_format" => Appsignal::EventFormatter::DEFAULT,
"name" => "request.http_rb",
"title" => "GET http://www.google.com"
)
end

it "should instrument a HTTPS request" do
stub_request(:get, "https://www.google.com")

HTTP.get("https://www.google.com")

transaction_hash = transaction.to_h
expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST)
expect(transaction_hash["events"].first).to include(
"body" => "",
"body_format" => Appsignal::EventFormatter::DEFAULT,
"name" => "request.http_rb",
"title" => "GET https://www.google.com"
)
end

context "with request parameters" do
it "does not include the query parameters in the title" do
stub_request(:get, "https://www.google.com?q=Appsignal")

HTTP.get("https://www.google.com", :params => { :q => "Appsignal" })

expect(transaction.to_h["events"].first).to include(
"body" => "",
"title" => "GET https://www.google.com"
)
end

it "does not include the request body in the title" do
stub_request(:post, "https://www.google.com")
.with(:body => { :q => "Appsignal" }.to_json)

HTTP.post("https://www.google.com", :json => { :q => "Appsignal" })

expect(transaction.to_h["events"].first).to include(
"body" => "",
"title" => "POST https://www.google.com"
stefanvermaas marked this conversation as resolved.
Show resolved Hide resolved
)
end
end

context "with an HTTP exception" do
let(:error) { ExampleException.new("oh no!") }

it "reports the exception and re-raises it" do
stub_request(:get, "https://www.google.com").and_raise(error)

expect do
HTTP.get("https://www.google.com")
end.to raise_error(ExampleException)

transaction_hash = transaction.to_h
expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST)
expect(transaction_hash["events"].first).to include(
"body" => "",
"body_format" => Appsignal::EventFormatter::DEFAULT,
"name" => "request.http_rb",
"title" => "GET https://www.google.com"
)

expect(transaction_hash["error"]).to include(
"backtrace" => kind_of(String),
"name" => error.class.name,
"message" => error.message
)
end
end
end
end
4 changes: 4 additions & 0 deletions spec/support/helpers/dependency_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ def capistrano3_present?
Gem.loaded_specs["capistrano"].version >= Gem::Version.new("3.0")
end

def http_present?
dependency_present? "http"
end

def que_present?
dependency_present? "que"
end
Expand Down