sentry-ruby 5.3.1 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +313 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +27 -0
- data/Makefile +4 -0
- data/Rakefile +13 -0
- data/bin/console +18 -0
- data/bin/setup +8 -0
- data/lib/sentry/background_worker.rb +72 -0
- data/lib/sentry/backtrace.rb +124 -0
- data/lib/sentry/breadcrumb/sentry_logger.rb +90 -0
- data/lib/sentry/breadcrumb.rb +70 -0
- data/lib/sentry/breadcrumb_buffer.rb +64 -0
- data/lib/sentry/client.rb +190 -0
- data/lib/sentry/configuration.rb +502 -0
- data/lib/sentry/core_ext/object/deep_dup.rb +61 -0
- data/lib/sentry/core_ext/object/duplicable.rb +155 -0
- data/lib/sentry/dsn.rb +53 -0
- data/lib/sentry/envelope.rb +96 -0
- data/lib/sentry/error_event.rb +38 -0
- data/lib/sentry/event.rb +178 -0
- data/lib/sentry/exceptions.rb +9 -0
- data/lib/sentry/hub.rb +220 -0
- data/lib/sentry/integrable.rb +26 -0
- data/lib/sentry/interface.rb +16 -0
- data/lib/sentry/interfaces/exception.rb +43 -0
- data/lib/sentry/interfaces/request.rb +144 -0
- data/lib/sentry/interfaces/single_exception.rb +57 -0
- data/lib/sentry/interfaces/stacktrace.rb +87 -0
- data/lib/sentry/interfaces/stacktrace_builder.rb +79 -0
- data/lib/sentry/interfaces/threads.rb +42 -0
- data/lib/sentry/linecache.rb +47 -0
- data/lib/sentry/logger.rb +20 -0
- data/lib/sentry/net/http.rb +115 -0
- data/lib/sentry/rack/capture_exceptions.rb +80 -0
- data/lib/sentry/rack.rb +5 -0
- data/lib/sentry/rake.rb +41 -0
- data/lib/sentry/redis.rb +90 -0
- data/lib/sentry/release_detector.rb +39 -0
- data/lib/sentry/scope.rb +295 -0
- data/lib/sentry/session.rb +35 -0
- data/lib/sentry/session_flusher.rb +90 -0
- data/lib/sentry/span.rb +226 -0
- data/lib/sentry/test_helper.rb +76 -0
- data/lib/sentry/transaction.rb +206 -0
- data/lib/sentry/transaction_event.rb +29 -0
- data/lib/sentry/transport/configuration.rb +25 -0
- data/lib/sentry/transport/dummy_transport.rb +21 -0
- data/lib/sentry/transport/http_transport.rb +175 -0
- data/lib/sentry/transport.rb +210 -0
- data/lib/sentry/utils/argument_checking_helper.rb +13 -0
- data/lib/sentry/utils/custom_inspection.rb +14 -0
- data/lib/sentry/utils/exception_cause_chain.rb +20 -0
- data/lib/sentry/utils/logging_helper.rb +26 -0
- data/lib/sentry/utils/real_ip.rb +84 -0
- data/lib/sentry/utils/request_id.rb +18 -0
- data/lib/sentry/version.rb +5 -0
- data/lib/sentry-ruby.rb +505 -0
- data/sentry-ruby-core.gemspec +23 -0
- data/sentry-ruby.gemspec +24 -0
- metadata +64 -16
    
        data/lib/sentry/dsn.rb
    ADDED
    
    | @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "uri"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Sentry
         | 
| 6 | 
            +
              class DSN
         | 
| 7 | 
            +
                PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
         | 
| 8 | 
            +
                REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(dsn_string)
         | 
| 13 | 
            +
                  @raw_value = dsn_string
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  uri = URI.parse(dsn_string)
         | 
| 16 | 
            +
                  uri_path = uri.path.split('/')
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  if uri.user
         | 
| 19 | 
            +
                    # DSN-style string
         | 
| 20 | 
            +
                    @project_id = uri_path.pop
         | 
| 21 | 
            +
                    @public_key = uri.user
         | 
| 22 | 
            +
                    @secret_key = !(uri.password.nil? || uri.password.empty?) ? uri.password : nil
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  @scheme = uri.scheme
         | 
| 26 | 
            +
                  @host = uri.host
         | 
| 27 | 
            +
                  @port = uri.port if uri.port
         | 
| 28 | 
            +
                  @path = uri_path.join('/')
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def valid?
         | 
| 32 | 
            +
                  REQUIRED_ATTRIBUTES.all? { |k| public_send(k) }
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def to_s
         | 
| 36 | 
            +
                  @raw_value
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def server
         | 
| 40 | 
            +
                  server = "#{scheme}://#{host}"
         | 
| 41 | 
            +
                  server += ":#{port}" unless port == PORT_MAP[scheme]
         | 
| 42 | 
            +
                  server
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def csp_report_uri
         | 
| 46 | 
            +
                  "#{server}/api/#{project_id}/security/?sentry_key=#{public_key}"
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def envelope_endpoint
         | 
| 50 | 
            +
                  "#{path}/api/#{project_id}/envelope/"
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sentry
         | 
| 4 | 
            +
              # @api private
         | 
| 5 | 
            +
              class Envelope
         | 
| 6 | 
            +
                class Item
         | 
| 7 | 
            +
                  STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
         | 
| 8 | 
            +
                  MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  attr_accessor :headers, :payload
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def initialize(headers, payload)
         | 
| 13 | 
            +
                    @headers = headers
         | 
| 14 | 
            +
                    @payload = payload
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def type
         | 
| 18 | 
            +
                    @headers[:type] || 'event'
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def to_s
         | 
| 22 | 
            +
                    <<~ITEM
         | 
| 23 | 
            +
                      #{JSON.generate(@headers)}
         | 
| 24 | 
            +
                      #{JSON.generate(@payload)}
         | 
| 25 | 
            +
                    ITEM
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def serialize
         | 
| 29 | 
            +
                    result = to_s
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
         | 
| 32 | 
            +
                      remove_breadcrumbs!
         | 
| 33 | 
            +
                      result = to_s
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
         | 
| 37 | 
            +
                      reduce_stacktrace!
         | 
| 38 | 
            +
                      result = to_s
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    [result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def size_breakdown
         | 
| 45 | 
            +
                    payload.map do |key, value|
         | 
| 46 | 
            +
                      "#{key}: #{JSON.generate(value).bytesize}"
         | 
| 47 | 
            +
                    end.join(", ")
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  private
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def remove_breadcrumbs!
         | 
| 53 | 
            +
                    if payload.key?(:breadcrumbs)
         | 
| 54 | 
            +
                      payload.delete(:breadcrumbs)
         | 
| 55 | 
            +
                    elsif payload.key?("breadcrumbs")
         | 
| 56 | 
            +
                      payload.delete("breadcrumbs")
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def reduce_stacktrace!
         | 
| 61 | 
            +
                    if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
         | 
| 62 | 
            +
                      exceptions.each do |exception|
         | 
| 63 | 
            +
                        # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
         | 
| 64 | 
            +
                        traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                        if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
         | 
| 67 | 
            +
                          size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
         | 
| 68 | 
            +
                          traces.replace(
         | 
| 69 | 
            +
                            traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
         | 
| 70 | 
            +
                          )
         | 
| 71 | 
            +
                        end
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                attr_accessor :headers, :items
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def initialize(headers = {})
         | 
| 80 | 
            +
                  @headers = headers
         | 
| 81 | 
            +
                  @items = []
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def add_item(headers, payload)
         | 
| 85 | 
            +
                  @items << Item.new(headers, payload)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def item_types
         | 
| 89 | 
            +
                  @items.map(&:type)
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def event_id
         | 
| 93 | 
            +
                  @headers[:event_id]
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sentry
         | 
| 4 | 
            +
              # ErrorEvent represents error or normal message events.
         | 
| 5 | 
            +
              class ErrorEvent < Event
         | 
| 6 | 
            +
                # @return [ExceptionInterface]
         | 
| 7 | 
            +
                attr_reader :exception
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # @return [ThreadsInterface]
         | 
| 10 | 
            +
                attr_reader :threads
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # @return [Hash]
         | 
| 13 | 
            +
                def to_hash
         | 
| 14 | 
            +
                  data = super
         | 
| 15 | 
            +
                  data[:threads] = threads.to_hash if threads
         | 
| 16 | 
            +
                  data[:exception] = exception.to_hash if exception
         | 
| 17 | 
            +
                  data
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # @!visibility private
         | 
| 21 | 
            +
                def add_threads_interface(backtrace: nil, **options)
         | 
| 22 | 
            +
                  @threads = ThreadsInterface.build(
         | 
| 23 | 
            +
                    backtrace: backtrace,
         | 
| 24 | 
            +
                    stacktrace_builder: @stacktrace_builder,
         | 
| 25 | 
            +
                    **options
         | 
| 26 | 
            +
                  )
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                # @!visibility private
         | 
| 30 | 
            +
                def add_exception_interface(exception)
         | 
| 31 | 
            +
                  if exception.respond_to?(:sentry_context)
         | 
| 32 | 
            +
                    @extra.merge!(exception.sentry_context)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
    
        data/lib/sentry/event.rb
    ADDED
    
    | @@ -0,0 +1,178 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'socket'
         | 
| 4 | 
            +
            require 'securerandom'
         | 
| 5 | 
            +
            require 'sentry/interface'
         | 
| 6 | 
            +
            require 'sentry/backtrace'
         | 
| 7 | 
            +
            require 'sentry/utils/real_ip'
         | 
| 8 | 
            +
            require 'sentry/utils/request_id'
         | 
| 9 | 
            +
            require 'sentry/utils/custom_inspection'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module Sentry
         | 
| 12 | 
            +
              # This is an abstract class that defines the shared attributes of an event.
         | 
| 13 | 
            +
              # Please don't use it directly. The user-facing classes are its child classes.
         | 
| 14 | 
            +
              class Event
         | 
| 15 | 
            +
                TYPE = "event"
         | 
| 16 | 
            +
                # These are readable attributes.
         | 
| 17 | 
            +
                SERIALIZEABLE_ATTRIBUTES = %i(
         | 
| 18 | 
            +
                  event_id level timestamp
         | 
| 19 | 
            +
                  release environment server_name modules
         | 
| 20 | 
            +
                  message user tags contexts extra
         | 
| 21 | 
            +
                  fingerprint breadcrumbs transaction
         | 
| 22 | 
            +
                  platform sdk type
         | 
| 23 | 
            +
                )
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                # These are writable attributes.
         | 
| 26 | 
            +
                WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                include CustomInspection
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                attr_writer(*WRITER_ATTRIBUTES)
         | 
| 35 | 
            +
                attr_reader(*SERIALIZEABLE_ATTRIBUTES)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # @return [RequestInterface]
         | 
| 38 | 
            +
                attr_reader :request
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                # @param configuration [Configuration]
         | 
| 41 | 
            +
                # @param integration_meta [Hash, nil]
         | 
| 42 | 
            +
                # @param message [String, nil]
         | 
| 43 | 
            +
                def initialize(configuration:, integration_meta: nil, message: nil)
         | 
| 44 | 
            +
                  # Set some simple default values
         | 
| 45 | 
            +
                  @event_id      = SecureRandom.uuid.delete("-")
         | 
| 46 | 
            +
                  @timestamp     = Sentry.utc_now.iso8601
         | 
| 47 | 
            +
                  @platform      = :ruby
         | 
| 48 | 
            +
                  @type          = self.class::TYPE
         | 
| 49 | 
            +
                  @sdk           = integration_meta || Sentry.sdk_meta
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  @user          = {}
         | 
| 52 | 
            +
                  @extra         = {}
         | 
| 53 | 
            +
                  @contexts      = {}
         | 
| 54 | 
            +
                  @tags          = {}
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  @fingerprint = []
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  # configuration data that's directly used by events
         | 
| 59 | 
            +
                  @server_name = configuration.server_name
         | 
| 60 | 
            +
                  @environment = configuration.environment
         | 
| 61 | 
            +
                  @release = configuration.release
         | 
| 62 | 
            +
                  @modules = configuration.gem_specs if configuration.send_modules
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  # configuration options to help events process data
         | 
| 65 | 
            +
                  @send_default_pii = configuration.send_default_pii
         | 
| 66 | 
            +
                  @trusted_proxies = configuration.trusted_proxies
         | 
| 67 | 
            +
                  @stacktrace_builder = configuration.stacktrace_builder
         | 
| 68 | 
            +
                  @rack_env_whitelist = configuration.rack_env_whitelist
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  @message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                class << self
         | 
| 74 | 
            +
                  # @!visibility private
         | 
| 75 | 
            +
                  def get_log_message(event_hash)
         | 
| 76 | 
            +
                    message = event_hash[:message] || event_hash['message']
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    return message unless message.nil? || message.empty?
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    message = get_message_from_exception(event_hash)
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    return message unless message.nil? || message.empty?
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    message = event_hash[:transaction] || event_hash["transaction"]
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    return message unless message.nil? || message.empty?
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    '<no message value>'
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  # @!visibility private
         | 
| 92 | 
            +
                  def get_message_from_exception(event_hash)
         | 
| 93 | 
            +
                    if exception = event_hash.dig(:exception, :values, 0)
         | 
| 94 | 
            +
                      "#{exception[:type]}: #{exception[:value]}"
         | 
| 95 | 
            +
                    elsif exception = event_hash.dig("exception", "values", 0)
         | 
| 96 | 
            +
                      "#{exception["type"]}: #{exception["value"]}"
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                # @deprecated This method will be removed in v5.0.0. Please just use Sentry.configuration
         | 
| 102 | 
            +
                # @return [Configuration]
         | 
| 103 | 
            +
                def configuration
         | 
| 104 | 
            +
                  Sentry.configuration
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                # Sets the event's timestamp.
         | 
| 108 | 
            +
                # @param time [Time, Float]
         | 
| 109 | 
            +
                # @return [void]
         | 
| 110 | 
            +
                def timestamp=(time)
         | 
| 111 | 
            +
                  @timestamp = time.is_a?(Time) ? time.to_f : time
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                # Sets the event's level.
         | 
| 115 | 
            +
                # @param level [String, Symbol]
         | 
| 116 | 
            +
                # @return [void]
         | 
| 117 | 
            +
                def level=(level) # needed to meet the Sentry spec
         | 
| 118 | 
            +
                  @level = level.to_s == "warn" ? :warning : level
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                # Sets the event's request environment data with RequestInterface.
         | 
| 122 | 
            +
                # @see RequestInterface
         | 
| 123 | 
            +
                # @param env [Hash]
         | 
| 124 | 
            +
                # @return [void]
         | 
| 125 | 
            +
                def rack_env=(env)
         | 
| 126 | 
            +
                  unless request || env.empty?
         | 
| 127 | 
            +
                    add_request_interface(env)
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    if @send_default_pii
         | 
| 130 | 
            +
                      user[:ip_address] = calculate_real_ip_from_rack(env)
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    if request_id = Utils::RequestId.read_from(env)
         | 
| 134 | 
            +
                      tags[:request_id] = request_id
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                # @return [Hash]
         | 
| 140 | 
            +
                def to_hash
         | 
| 141 | 
            +
                  data = serialize_attributes
         | 
| 142 | 
            +
                  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
         | 
| 143 | 
            +
                  data[:request] = request.to_hash if request
         | 
| 144 | 
            +
                  data
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                # @return [Hash]
         | 
| 148 | 
            +
                def to_json_compatible
         | 
| 149 | 
            +
                  JSON.parse(JSON.generate(to_hash))
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                private
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def add_request_interface(env)
         | 
| 155 | 
            +
                  @request = Sentry::RequestInterface.new(env: env, send_default_pii: @send_default_pii, rack_env_whitelist: @rack_env_whitelist)
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                def serialize_attributes
         | 
| 159 | 
            +
                  self.class::SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |att, memo|
         | 
| 160 | 
            +
                    if value = public_send(att)
         | 
| 161 | 
            +
                      memo[att] = value
         | 
| 162 | 
            +
                    end
         | 
| 163 | 
            +
                  end
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                # When behind a proxy (or if the user is using a proxy), we can't use
         | 
| 167 | 
            +
                # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
         | 
| 168 | 
            +
                def calculate_real_ip_from_rack(env)
         | 
| 169 | 
            +
                  Utils::RealIp.new(
         | 
| 170 | 
            +
                    :remote_addr => env["REMOTE_ADDR"],
         | 
| 171 | 
            +
                    :client_ip => env["HTTP_CLIENT_IP"],
         | 
| 172 | 
            +
                    :real_ip => env["HTTP_X_REAL_IP"],
         | 
| 173 | 
            +
                    :forwarded_for => env["HTTP_X_FORWARDED_FOR"],
         | 
| 174 | 
            +
                    :trusted_proxies => @trusted_proxies
         | 
| 175 | 
            +
                  ).calculate_ip
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
              end
         | 
| 178 | 
            +
            end
         | 
    
        data/lib/sentry/hub.rb
    ADDED
    
    | @@ -0,0 +1,220 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "sentry/scope"
         | 
| 4 | 
            +
            require "sentry/client"
         | 
| 5 | 
            +
            require "sentry/session"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Sentry
         | 
| 8 | 
            +
              class Hub
         | 
| 9 | 
            +
                include ArgumentCheckingHelper
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                attr_reader :last_event_id
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def initialize(client, scope)
         | 
| 14 | 
            +
                  first_layer = Layer.new(client, scope)
         | 
| 15 | 
            +
                  @stack = [first_layer]
         | 
| 16 | 
            +
                  @last_event_id = nil
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def new_from_top
         | 
| 20 | 
            +
                  Hub.new(current_client, current_scope)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def current_client
         | 
| 24 | 
            +
                  current_layer&.client
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def configuration
         | 
| 28 | 
            +
                  current_client.configuration
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def current_scope
         | 
| 32 | 
            +
                  current_layer&.scope
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def clone
         | 
| 36 | 
            +
                  layer = current_layer
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  if layer
         | 
| 39 | 
            +
                    scope = layer.scope&.dup
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    Hub.new(layer.client, scope)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def bind_client(client)
         | 
| 46 | 
            +
                  layer = current_layer
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  if layer
         | 
| 49 | 
            +
                    layer.client = client
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def configure_scope(&block)
         | 
| 54 | 
            +
                  block.call(current_scope)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def with_scope(&block)
         | 
| 58 | 
            +
                  push_scope
         | 
| 59 | 
            +
                  yield(current_scope)
         | 
| 60 | 
            +
                ensure
         | 
| 61 | 
            +
                  pop_scope
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def push_scope
         | 
| 65 | 
            +
                  new_scope =
         | 
| 66 | 
            +
                    if current_scope
         | 
| 67 | 
            +
                      current_scope.dup
         | 
| 68 | 
            +
                    else
         | 
| 69 | 
            +
                      Scope.new
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  @stack << Layer.new(current_client, new_scope)
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def pop_scope
         | 
| 76 | 
            +
                  @stack.pop
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def start_transaction(transaction: nil, custom_sampling_context: {}, **options)
         | 
| 80 | 
            +
                  return unless configuration.tracing_enabled?
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  transaction ||= Transaction.new(**options.merge(hub: self))
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  sampling_context = {
         | 
| 85 | 
            +
                    transaction_context: transaction.to_hash,
         | 
| 86 | 
            +
                    parent_sampled: transaction.parent_sampled
         | 
| 87 | 
            +
                  }
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  sampling_context.merge!(custom_sampling_context)
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  transaction.set_initial_sample_decision(sampling_context: sampling_context)
         | 
| 92 | 
            +
                  transaction
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def capture_exception(exception, **options, &block)
         | 
| 96 | 
            +
                  check_argument_type!(exception, ::Exception)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  return if Sentry.exception_captured?(exception)
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  return unless current_client
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  options[:hint] ||= {}
         | 
| 103 | 
            +
                  options[:hint][:exception] = exception
         | 
| 104 | 
            +
                  event = current_client.event_from_exception(exception, options[:hint])
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  return unless event
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  current_scope.session&.update_from_exception(event.exception)
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  capture_event(event, **options, &block).tap do
         | 
| 111 | 
            +
                    # mark the exception as captured so we can use this information to avoid duplicated capturing
         | 
| 112 | 
            +
                    exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def capture_message(message, **options, &block)
         | 
| 117 | 
            +
                  check_argument_type!(message, ::String)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  return unless current_client
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  options[:hint] ||= {}
         | 
| 122 | 
            +
                  options[:hint][:message] = message
         | 
| 123 | 
            +
                  backtrace = options.delete(:backtrace)
         | 
| 124 | 
            +
                  event = current_client.event_from_message(message, options[:hint], backtrace: backtrace)
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  return unless event
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  capture_event(event, **options, &block)
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                def capture_event(event, **options, &block)
         | 
| 132 | 
            +
                  check_argument_type!(event, Sentry::Event)
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  return unless current_client
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  hint = options.delete(:hint) || {}
         | 
| 137 | 
            +
                  scope = current_scope.dup
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  if block
         | 
| 140 | 
            +
                    block.call(scope)
         | 
| 141 | 
            +
                  elsif custom_scope = options[:scope]
         | 
| 142 | 
            +
                    scope.update_from_scope(custom_scope)
         | 
| 143 | 
            +
                  elsif !options.empty?
         | 
| 144 | 
            +
                    scope.update_from_options(**options)
         | 
| 145 | 
            +
                  end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  event = current_client.capture_event(event, scope, hint)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  if event && configuration.debug
         | 
| 150 | 
            +
                    configuration.log_debug(event.to_json_compatible)
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  @last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
         | 
| 154 | 
            +
                  event
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                def add_breadcrumb(breadcrumb, hint: {})
         | 
| 158 | 
            +
                  return unless configuration.enabled_in_current_env?
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                  if before_breadcrumb = current_client.configuration.before_breadcrumb
         | 
| 161 | 
            +
                    breadcrumb = before_breadcrumb.call(breadcrumb, hint)
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  return unless breadcrumb
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  current_scope.add_breadcrumb(breadcrumb)
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                # this doesn't do anything to the already initialized background worker
         | 
| 170 | 
            +
                # but it temporarily disables dispatching events to it
         | 
| 171 | 
            +
                def with_background_worker_disabled(&block)
         | 
| 172 | 
            +
                  original_background_worker_threads = configuration.background_worker_threads
         | 
| 173 | 
            +
                  configuration.background_worker_threads = 0
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  block.call
         | 
| 176 | 
            +
                ensure
         | 
| 177 | 
            +
                  configuration.background_worker_threads = original_background_worker_threads
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                def start_session
         | 
| 181 | 
            +
                  return unless current_scope
         | 
| 182 | 
            +
                  current_scope.set_session(Session.new)
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                def end_session
         | 
| 186 | 
            +
                  return unless current_scope
         | 
| 187 | 
            +
                  session = current_scope.session
         | 
| 188 | 
            +
                  current_scope.set_session(nil)
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  return unless session
         | 
| 191 | 
            +
                  session.close
         | 
| 192 | 
            +
                  Sentry.session_flusher.add_session(session)
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                def with_session_tracking(&block)
         | 
| 196 | 
            +
                  return yield unless configuration.auto_session_tracking
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  start_session
         | 
| 199 | 
            +
                  yield
         | 
| 200 | 
            +
                ensure
         | 
| 201 | 
            +
                  end_session
         | 
| 202 | 
            +
                end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                private
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                def current_layer
         | 
| 207 | 
            +
                  @stack.last
         | 
| 208 | 
            +
                end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                class Layer
         | 
| 211 | 
            +
                  attr_accessor :client
         | 
| 212 | 
            +
                  attr_reader :scope
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  def initialize(client, scope)
         | 
| 215 | 
            +
                    @client = client
         | 
| 216 | 
            +
                    @scope = scope
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
              end
         | 
| 220 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sentry
         | 
| 4 | 
            +
              module Integrable
         | 
| 5 | 
            +
                def register_integration(name:, version:)
         | 
| 6 | 
            +
                  Sentry.register_integration(name, version)
         | 
| 7 | 
            +
                  @integration_name = name
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def integration_name
         | 
| 11 | 
            +
                  @integration_name
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def capture_exception(exception, **options, &block)
         | 
| 15 | 
            +
                  options[:hint] ||= {}
         | 
| 16 | 
            +
                  options[:hint][:integration] = integration_name
         | 
| 17 | 
            +
                  Sentry.capture_exception(exception, **options, &block)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def capture_message(message, **options, &block)
         | 
| 21 | 
            +
                  options[:hint] ||= {}
         | 
| 22 | 
            +
                  options[:hint][:integration] = integration_name
         | 
| 23 | 
            +
                  Sentry.capture_message(message, **options, &block)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sentry
         | 
| 4 | 
            +
              class Interface
         | 
| 5 | 
            +
                # @return [Hash]
         | 
| 6 | 
            +
                def to_hash
         | 
| 7 | 
            +
                  Hash[instance_variables.map { |name| [name[1..-1].to_sym, instance_variable_get(name)] }]
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            require "sentry/interfaces/exception"
         | 
| 13 | 
            +
            require "sentry/interfaces/request"
         | 
| 14 | 
            +
            require "sentry/interfaces/single_exception"
         | 
| 15 | 
            +
            require "sentry/interfaces/stacktrace"
         | 
| 16 | 
            +
            require "sentry/interfaces/threads"
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            require "set"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Sentry
         | 
| 5 | 
            +
              class ExceptionInterface < Interface
         | 
| 6 | 
            +
                # @return [<Array[SingleExceptionInterface]>]
         | 
| 7 | 
            +
                attr_reader :values
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                # @param exceptions [Array<SingleExceptionInterface>]
         | 
| 10 | 
            +
                def initialize(exceptions:)
         | 
| 11 | 
            +
                  @values = exceptions
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # @return [Hash]
         | 
| 15 | 
            +
                def to_hash
         | 
| 16 | 
            +
                  data = super
         | 
| 17 | 
            +
                  data[:values] = data[:values].map(&:to_hash) if data[:values]
         | 
| 18 | 
            +
                  data
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # Builds ExceptionInterface with given exception and stacktrace_builder.
         | 
| 22 | 
            +
                # @param exception [Exception]
         | 
| 23 | 
            +
                # @param stacktrace_builder [StacktraceBuilder]
         | 
| 24 | 
            +
                # @see SingleExceptionInterface#build_with_stacktrace
         | 
| 25 | 
            +
                # @see SingleExceptionInterface#initialize
         | 
| 26 | 
            +
                # @return [ExceptionInterface]
         | 
| 27 | 
            +
                def self.build(exception:, stacktrace_builder:)
         | 
| 28 | 
            +
                  exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
         | 
| 29 | 
            +
                  processed_backtrace_ids = Set.new
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  exceptions = exceptions.map do |e|
         | 
| 32 | 
            +
                    if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
         | 
| 33 | 
            +
                      processed_backtrace_ids << e.backtrace.object_id
         | 
| 34 | 
            +
                      SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
         | 
| 35 | 
            +
                    else
         | 
| 36 | 
            +
                      SingleExceptionInterface.new(exception: exception)
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  new(exceptions: exceptions)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         |