1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
# frozen_string_literal: true
require "net/http"
module Sentry
# @api private
module Net
module HTTP
OP_NAME = "http.client"
BREADCRUMB_CATEGORY = "net.http"
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
#
# ```
# def request(req, body = nil, &block)
# unless started?
# start {
# req['connection'] ||= 'close'
# return request(req, body, &block) # <- request will be called for the second time from the first call
# }
# end
# # .....
# end
# ```
#
# So we're only instrumenting request when `Net::HTTP` is already started
def request(req, body = nil, &block)
return super unless started?
sentry_span = start_sentry_span
set_sentry_trace_header(req, sentry_span)
super.tap do |res|
record_sentry_breadcrumb(req, res)
record_sentry_span(req, res, sentry_span)
end
end
private
def set_sentry_trace_header(req, sentry_span)
return unless sentry_span
trace = Sentry.get_current_client.generate_sentry_trace(sentry_span)
req[SENTRY_TRACE_HEADER_NAME] = trace if trace
end
def record_sentry_breadcrumb(req, res)
return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
return if from_sentry_sdk?
request_info = extract_request_info(req)
crumb = Sentry::Breadcrumb.new(
level: :info,
category: BREADCRUMB_CATEGORY,
type: :info,
data: {
status: res.code.to_i,
**request_info
}
)
Sentry.add_breadcrumb(crumb)
end
def record_sentry_span(req, res, sentry_span)
return unless Sentry.initialized? && sentry_span
request_info = extract_request_info(req)
sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
sentry_span.set_data(:status, res.code.to_i)
finish_sentry_span(sentry_span)
end
def start_sentry_span
return unless Sentry.initialized? && span = Sentry.get_current_scope.get_span
return if from_sentry_sdk?
return if span.sampled == false
span.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
end
def finish_sentry_span(sentry_span)
return unless Sentry.initialized? && sentry_span
sentry_span.set_timestamp(Sentry.utc_now.to_f)
end
def from_sentry_sdk?
dsn = Sentry.configuration.dsn
dsn && dsn.host == self.address
end
def extract_request_info(req)
uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{address}#{req.path}")
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
result = { method: req.method, url: url }
if Sentry.configuration.send_default_pii
result[:url] = result[:url] + "?#{uri.query}"
result[:body] = req.body
end
result
end
end
end
end
Sentry.register_patch do
patch = Sentry::Net::HTTP
Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
end
|