File: http.rb

package info (click to toggle)
ruby-sentry-ruby-core 5.28.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 672 kB
  • sloc: ruby: 6,118; makefile: 8; sh: 4
file content (86 lines) | stat: -rw-r--r-- 2,626 bytes parent folder | download | duplicates (2)
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
# frozen_string_literal: true

require "net/http"
require "resolv"
require "sentry/utils/http_tracing"

module Sentry
  # @api private
  module Net
    module HTTP
      include Utils::HttpTracing

      OP_NAME = "http.client"
      SPAN_ORIGIN = "auto.http.net_http"
      BREADCRUMB_CATEGORY = "net.http"
      URI_PARSER = URI.const_defined?("RFC2396_PARSER") ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER

      # 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.initialized?
        return super if from_sentry_sdk?

        Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
          request_info = extract_request_info(req)

          if propagate_trace?(request_info[:url])
            set_propagation_headers(req)
          end

          res = super
          response_status = res.code.to_i

          if record_sentry_breadcrumb?
            record_sentry_breadcrumb(request_info, response_status)
          end

          if sentry_span
            set_span_info(sentry_span, request_info, response_status)
          end

          res
        end
      end

      private

      def from_sentry_sdk?
        dsn = Sentry.configuration.dsn
        dsn && dsn.host == self.address
      end

      def extract_request_info(req)
        # IPv6 url could look like '::1/path', and that won't parse without
        # wrapping it in square brackets.
        hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
        uri = req.uri || URI.parse(URI_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{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[:query] = uri.query
          result[:body] = req.body
        end

        result
      end
    end
  end
end

Sentry.register_patch(:http, Sentry::Net::HTTP, Net::HTTP)