mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-12-09 00:01:48 -05:00
Merge branch 'issue-191' into 'master'
Sentry Integration Closes #191 See merge request honeyryderchuck/httpx!208
This commit is contained in:
commit
6a7bf30b58
1
Gemfile
1
Gemfile
@ -16,6 +16,7 @@ group :test do
|
|||||||
gem "minitest"
|
gem "minitest"
|
||||||
gem "minitest-proveit"
|
gem "minitest-proveit"
|
||||||
gem "ruby-ntlm"
|
gem "ruby-ntlm"
|
||||||
|
gem "sentry-ruby" if RUBY_VERSION >= "2.4"
|
||||||
gem "spy"
|
gem "spy"
|
||||||
gem "webmock"
|
gem "webmock"
|
||||||
gem "websocket-driver"
|
gem "websocket-driver"
|
||||||
|
|||||||
118
integration_tests/sentry_test.rb
Normal file
118
integration_tests/sentry_test.rb
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "logger"
|
||||||
|
require "stringio"
|
||||||
|
require "test_helper"
|
||||||
|
require "support/http_helpers"
|
||||||
|
begin
|
||||||
|
require "httpx/adapters/sentry"
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
|
||||||
|
class SentryTest < Minitest::Test
|
||||||
|
include HTTPHelpers
|
||||||
|
|
||||||
|
DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
|
||||||
|
|
||||||
|
def test_sentry_send_yes_pii
|
||||||
|
before_pii = Sentry.configuration.send_default_pii
|
||||||
|
begin
|
||||||
|
Sentry.configuration.send_default_pii = true
|
||||||
|
|
||||||
|
transaction = Sentry.start_transaction
|
||||||
|
Sentry.get_current_scope.set_span(transaction)
|
||||||
|
|
||||||
|
uri = build_uri("/get")
|
||||||
|
|
||||||
|
response = HTTPX.get(uri, params: { "foo" => "bar" })
|
||||||
|
|
||||||
|
verify_status(response, 200)
|
||||||
|
verify_spans(transaction, response, description: "GET #{uri}?foo=bar")
|
||||||
|
ensure
|
||||||
|
Sentry.configuration.send_default_pii = before_pii
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sentry_send_no_pii
|
||||||
|
before_pii = Sentry.configuration.send_default_pii
|
||||||
|
begin
|
||||||
|
Sentry.configuration.send_default_pii = false
|
||||||
|
|
||||||
|
transaction = Sentry.start_transaction
|
||||||
|
Sentry.get_current_scope.set_span(transaction)
|
||||||
|
|
||||||
|
uri = build_uri("/get")
|
||||||
|
|
||||||
|
response = HTTPX.get(uri, params: { "foo" => "bar" })
|
||||||
|
|
||||||
|
verify_status(response, 200)
|
||||||
|
verify_spans(transaction, response, description: "GET #{uri}")
|
||||||
|
ensure
|
||||||
|
Sentry.configuration.send_default_pii = before_pii
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sentry_post_request
|
||||||
|
before_pii = Sentry.configuration.send_default_pii
|
||||||
|
begin
|
||||||
|
Sentry.configuration.send_default_pii = true
|
||||||
|
transaction = Sentry.start_transaction
|
||||||
|
Sentry.get_current_scope.set_span(transaction)
|
||||||
|
|
||||||
|
response = HTTPX.post(build_uri("/post"), form: { foo: "bar" })
|
||||||
|
verify_status(response, 200)
|
||||||
|
verify_spans(transaction, response, verb: "POST")
|
||||||
|
ensure
|
||||||
|
Sentry.configuration.send_default_pii = before_pii
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sentry_multiple_requests
|
||||||
|
transaction = Sentry.start_transaction
|
||||||
|
Sentry.get_current_scope.set_span(transaction)
|
||||||
|
|
||||||
|
responses = HTTPX.get(build_uri("/status/200"), build_uri("/status/404"))
|
||||||
|
verify_status(responses[0], 200)
|
||||||
|
verify_status(responses[1], 404)
|
||||||
|
verify_spans(transaction, *responses)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def verify_spans(transaction, *responses, verb: nil, description: nil)
|
||||||
|
assert transaction.span_recorder.spans.count == responses.size + 1
|
||||||
|
assert transaction.span_recorder.spans[0] == transaction
|
||||||
|
|
||||||
|
response_spans = transaction.span_recorder.spans[1..-1]
|
||||||
|
|
||||||
|
responses.each_with_index do |response, idx|
|
||||||
|
request_span = response_spans[idx]
|
||||||
|
assert request_span.op == "httpx.client"
|
||||||
|
assert !request_span.start_timestamp.nil?
|
||||||
|
assert !request_span.timestamp.nil?
|
||||||
|
assert request_span.start_timestamp != request_span.timestamp
|
||||||
|
assert request_span.description == (description || "#{verb || "GET"} #{response.uri}")
|
||||||
|
assert request_span.data == { status: response.status }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
|
||||||
|
mock_io = StringIO.new
|
||||||
|
mock_logger = Logger.new(mock_io)
|
||||||
|
|
||||||
|
Sentry.init do |config|
|
||||||
|
config.traces_sample_rate = 1.0
|
||||||
|
config.logger = mock_logger
|
||||||
|
config.dsn = DUMMY_DSN
|
||||||
|
config.transport.transport_class = Sentry::DummyTransport
|
||||||
|
# so the events will be sent synchronously for testing
|
||||||
|
config.background_worker_threads = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def origin
|
||||||
|
"https://#{httpbin}"
|
||||||
|
end
|
||||||
|
end if RUBY_VERSION >= "2.4.0"
|
||||||
102
lib/httpx/adapters/sentry.rb
Normal file
102
lib/httpx/adapters/sentry.rb
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "sentry-ruby"
|
||||||
|
|
||||||
|
module HTTPX::Plugins
|
||||||
|
module Sentry
|
||||||
|
module Tracer
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def call(request)
|
||||||
|
sentry_span = start_sentry_span
|
||||||
|
|
||||||
|
return unless sentry_span
|
||||||
|
|
||||||
|
set_sentry_trace_header(request, sentry_span)
|
||||||
|
|
||||||
|
request.on(:response, &method(:finish_sentry_span).curry(3)[sentry_span, request])
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_sentry_span
|
||||||
|
return unless ::Sentry.initialized? && (span = ::Sentry.get_current_scope.get_span)
|
||||||
|
return if span.sampled == false
|
||||||
|
|
||||||
|
span.start_child(op: "httpx.client", start_timestamp: ::Sentry.utc_now.to_f)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_sentry_trace_header(request, sentry_span)
|
||||||
|
return unless sentry_span
|
||||||
|
|
||||||
|
trace = ::Sentry.get_current_client.generate_sentry_trace(sentry_span)
|
||||||
|
request.headers[::Sentry::SENTRY_TRACE_HEADER_NAME] = trace if trace
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish_sentry_span(span, request, response)
|
||||||
|
return unless ::Sentry.initialized?
|
||||||
|
|
||||||
|
record_sentry_breadcrumb(request, response)
|
||||||
|
record_sentry_span(request, response, span)
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_sentry_breadcrumb(req, res)
|
||||||
|
return unless ::Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
||||||
|
|
||||||
|
request_info = extract_request_info(req)
|
||||||
|
|
||||||
|
data = if response.is_a?(HTTPX::ErrorResponse)
|
||||||
|
{ error: res.message, **request_info }
|
||||||
|
else
|
||||||
|
{ status: res.status, **request_info }
|
||||||
|
end
|
||||||
|
|
||||||
|
crumb = ::Sentry::Breadcrumb.new(
|
||||||
|
level: :info,
|
||||||
|
category: "httpx",
|
||||||
|
type: :info,
|
||||||
|
data: data
|
||||||
|
)
|
||||||
|
::Sentry.add_breadcrumb(crumb)
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_sentry_span(req, res, sentry_span)
|
||||||
|
return unless sentry_span
|
||||||
|
|
||||||
|
request_info = extract_request_info(req)
|
||||||
|
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
|
||||||
|
sentry_span.set_data(:status, res.status)
|
||||||
|
sentry_span.set_timestamp(::Sentry.utc_now.to_f)
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_request_info(req)
|
||||||
|
uri = req.uri
|
||||||
|
|
||||||
|
result = {
|
||||||
|
method: req.verb.to_s.upcase,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ::Sentry.configuration.send_default_pii
|
||||||
|
uri += "?#{req.query}" unless req.query.empty?
|
||||||
|
result[:body] = req.body.to_s unless req.body.empty? || req.body.unbounded_body?
|
||||||
|
end
|
||||||
|
|
||||||
|
result[:url] = uri.to_s
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ConnectionMethods
|
||||||
|
def send(request)
|
||||||
|
Tracer.call(request)
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Sentry.register_patch do
|
||||||
|
sentry_session = ::HTTPX.plugin(HTTPX::Plugins::Sentry)
|
||||||
|
|
||||||
|
HTTPX.send(:remove_const, :Session)
|
||||||
|
HTTPX.send(:const_set, :Session, sentry_session.class)
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user