bugsnag 6.23.0 → 6.24.0
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/CHANGELOG.md +26 -0
- data/VERSION +1 -1
- data/lib/bugsnag/cleaner.rb +31 -18
- data/lib/bugsnag/configuration.rb +125 -17
- data/lib/bugsnag/endpoint_configuration.rb +11 -0
- data/lib/bugsnag/endpoint_validator.rb +80 -0
- data/lib/bugsnag/middleware/rack_request.rb +82 -17
- data/lib/bugsnag/middleware/session_data.rb +3 -1
- data/lib/bugsnag/report.rb +37 -1
- data/lib/bugsnag/session_tracker.rb +49 -5
- data/lib/bugsnag/stacktrace.rb +10 -1
- data/lib/bugsnag/utility/duplicator.rb +124 -0
- data/lib/bugsnag.rb +78 -2
- metadata +5 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ee1b5d5046b6ba8e68cf45b4ccac412c02051c774bcf8e205c97b4254d058468
         | 
| 4 | 
            +
              data.tar.gz: 787717ba06a5bba3266c0030e8de943bf02fe4c6a9c6d979ec4bf543aa7d82bd
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e53bfdf8c6c23f2a9e4412fbca888884936c3c26ff5f1262f80aa5081a8edb3ba51a6e0a64065c41490efc20c1fe32d8795ec841f09f733062d3691c0c007e2e
         | 
| 7 | 
            +
              data.tar.gz: 3fe002c3a094014f0fbb56a1863ed309b50b4e733cd651df0136ad8af2f72c6759d122baf7f6bbb34607baf18631395000bd9fd7312eec432b7dce14482d9cef
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,6 +1,32 @@ | |
| 1 1 | 
             
            Changelog
         | 
| 2 2 | 
             
            =========
         | 
| 3 3 |  | 
| 4 | 
            +
            ## v6.24.0 (6 October 2021)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ### Enhancements
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            * Allow overriding an event's unhandled flag
         | 
| 9 | 
            +
              | [#698](https://github.com/bugsnag/bugsnag-ruby/pull/698)
         | 
| 10 | 
            +
            * Add the ability to store metadata globally
         | 
| 11 | 
            +
              | [#699](https://github.com/bugsnag/bugsnag-ruby/pull/699)
         | 
| 12 | 
            +
            * Add `cookies`, `body` and `httpVersion` to the automatically captured request data for Rack apps
         | 
| 13 | 
            +
              | [#700](https://github.com/bugsnag/bugsnag-ruby/pull/700)
         | 
| 14 | 
            +
            * Add `Configuration#endpoints` for reading the notify and sessions endpoints and `Configuration#endpoints=` for setting them
         | 
| 15 | 
            +
              | [#701](https://github.com/bugsnag/bugsnag-ruby/pull/701)
         | 
| 16 | 
            +
            * Add `Configuration#redacted_keys`. This is like `meta_data_filters` but matches strings with case-insensitive equality, rather than matching based on inclusion
         | 
| 17 | 
            +
              | [#703](https://github.com/bugsnag/bugsnag-ruby/pull/703)
         | 
| 18 | 
            +
            * Allow pausing and resuming sessions, giving more control over the stability score
         | 
| 19 | 
            +
              | [#704](https://github.com/bugsnag/bugsnag-ruby/pull/704)
         | 
| 20 | 
            +
            * Add `Configuration#vendor_paths` to replace `Configuration#vendor_path`
         | 
| 21 | 
            +
              | [#705](https://github.com/bugsnag/bugsnag-ruby/pull/705)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ### Deprecated
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            * In the next major release, `params` will only contain query string parameters. Currently it also contains the request body for form data requests, but this is deprecated in favour of the new `body` property
         | 
| 26 | 
            +
            * The `Configuration#set_endpoints` method is now deprecated in favour of `Configuration#endpoints=`
         | 
| 27 | 
            +
            * The `Configuration#meta_data_filters` option is now deprecated in favour of `Configuration#redacted_keys`
         | 
| 28 | 
            +
            * The `Configuration#vendor_path` option is now deprecated in favour of `Configuration#vendor_paths`
         | 
| 29 | 
            +
             | 
| 4 30 | 
             
            ## v6.23.0 (21 September 2021)
         | 
| 5 31 |  | 
| 6 32 | 
             
            ### Enhancements
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            6. | 
| 1 | 
            +
            6.24.0
         | 
    
        data/lib/bugsnag/cleaner.rb
    CHANGED
    
    | @@ -25,7 +25,7 @@ module Bugsnag | |
| 25 25 | 
             
                # @param url [String]
         | 
| 26 26 | 
             
                # @return [String]
         | 
| 27 27 | 
             
                def clean_url(url)
         | 
| 28 | 
            -
                  return url if @configuration.meta_data_filters.empty?
         | 
| 28 | 
            +
                  return url if @configuration.meta_data_filters.empty? && @configuration.redacted_keys.empty?
         | 
| 29 29 |  | 
| 30 30 | 
             
                  uri = URI(url)
         | 
| 31 31 | 
             
                  return url unless uri.query
         | 
| @@ -43,6 +43,33 @@ module Bugsnag | |
| 43 43 | 
             
                  uri.to_s
         | 
| 44 44 | 
             
                end
         | 
| 45 45 |  | 
| 46 | 
            +
                ##
         | 
| 47 | 
            +
                # @param key [String, #to_s]
         | 
| 48 | 
            +
                # @return [Boolean]
         | 
| 49 | 
            +
                def filters_match?(key)
         | 
| 50 | 
            +
                  str = key.to_s
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  matched = @configuration.meta_data_filters.any? do |filter|
         | 
| 53 | 
            +
                    case filter
         | 
| 54 | 
            +
                    when Regexp
         | 
| 55 | 
            +
                      str.match(filter)
         | 
| 56 | 
            +
                    else
         | 
| 57 | 
            +
                      str.include?(filter.to_s)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  return true if matched
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  @configuration.redacted_keys.any? do |redaction_pattern|
         | 
| 64 | 
            +
                    case redaction_pattern
         | 
| 65 | 
            +
                    when Regexp
         | 
| 66 | 
            +
                      str.match(redaction_pattern)
         | 
| 67 | 
            +
                    when String
         | 
| 68 | 
            +
                      str.downcase == redaction_pattern.downcase
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 46 73 | 
             
                private
         | 
| 47 74 |  | 
| 48 75 | 
             
                ##
         | 
| @@ -54,9 +81,11 @@ module Bugsnag | |
| 54 81 | 
             
                #
         | 
| 55 82 | 
             
                # @return [Boolean]
         | 
| 56 83 | 
             
                def deep_filters?
         | 
| 57 | 
            -
                   | 
| 84 | 
            +
                  is_deep_filter = proc do |filter|
         | 
| 58 85 | 
             
                    filter.is_a?(Regexp) && filter.to_s.include?("\\.".freeze)
         | 
| 59 86 | 
             
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  @configuration.meta_data_filters.any?(&is_deep_filter) || @configuration.redacted_keys.any?(&is_deep_filter)
         | 
| 60 89 | 
             
                end
         | 
| 61 90 |  | 
| 62 91 | 
             
                def clean_string(str)
         | 
| @@ -137,22 +166,6 @@ module Bugsnag | |
| 137 166 | 
             
                  value
         | 
| 138 167 | 
             
                end
         | 
| 139 168 |  | 
| 140 | 
            -
                ##
         | 
| 141 | 
            -
                # @param key [String, #to_s]
         | 
| 142 | 
            -
                # @return [Boolean]
         | 
| 143 | 
            -
                def filters_match?(key)
         | 
| 144 | 
            -
                  str = key.to_s
         | 
| 145 | 
            -
             | 
| 146 | 
            -
                  @configuration.meta_data_filters.any? do |filter|
         | 
| 147 | 
            -
                    case filter
         | 
| 148 | 
            -
                    when Regexp
         | 
| 149 | 
            -
                      str.match(filter)
         | 
| 150 | 
            -
                    else
         | 
| 151 | 
            -
                      str.include?(filter.to_s)
         | 
| 152 | 
            -
                    end
         | 
| 153 | 
            -
                  end
         | 
| 154 | 
            -
                end
         | 
| 155 | 
            -
             | 
| 156 169 | 
             
                ##
         | 
| 157 170 | 
             
                # If someone has a Rails filter like /^stuff\.secret/, it won't match
         | 
| 158 171 | 
             
                # "request.params.stuff.secret", so we try it both with and without the
         | 
| @@ -13,6 +13,8 @@ require "bugsnag/middleware/breadcrumbs" | |
| 13 13 | 
             
            require "bugsnag/utility/circular_buffer"
         | 
| 14 14 | 
             
            require "bugsnag/breadcrumbs/breadcrumbs"
         | 
| 15 15 | 
             
            require "bugsnag/breadcrumbs/on_breadcrumb_callback_list"
         | 
| 16 | 
            +
            require "bugsnag/endpoint_configuration"
         | 
| 17 | 
            +
            require "bugsnag/endpoint_validator"
         | 
| 16 18 |  | 
| 17 19 | 
             
            module Bugsnag
         | 
| 18 20 | 
             
              class Configuration
         | 
| @@ -54,9 +56,20 @@ module Bugsnag | |
| 54 56 |  | 
| 55 57 | 
             
                # A list of keys that should be filtered out from the report and breadcrumb
         | 
| 56 58 | 
             
                # metadata before sending them to Bugsnag
         | 
| 59 | 
            +
                # @deprecated Use {#redacted_keys} instead
         | 
| 57 60 | 
             
                # @return [Set<String, Regexp>]
         | 
| 58 61 | 
             
                attr_accessor :meta_data_filters
         | 
| 59 62 |  | 
| 63 | 
            +
                # A set of keys that should be redacted from the report and breadcrumb
         | 
| 64 | 
            +
                # metadata before sending them to Bugsnag
         | 
| 65 | 
            +
                #
         | 
| 66 | 
            +
                # When adding strings, keys that are equal to the string (ignoring case)
         | 
| 67 | 
            +
                # will be redacted. When adding regular expressions, any keys which match
         | 
| 68 | 
            +
                # the regular expression will be redacted
         | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # @return [Set<String, Regexp>]
         | 
| 71 | 
            +
                attr_accessor :redacted_keys
         | 
| 72 | 
            +
             | 
| 60 73 | 
             
                # The logger to use for Bugsnag log messages
         | 
| 61 74 | 
             
                # @return [Logger]
         | 
| 62 75 | 
             
                attr_accessor :logger
         | 
| @@ -114,16 +127,17 @@ module Bugsnag | |
| 114 127 | 
             
                # @return [Set<Class, Proc>]
         | 
| 115 128 | 
             
                attr_accessor :ignore_classes
         | 
| 116 129 |  | 
| 117 | 
            -
                # The  | 
| 118 | 
            -
                # @return [ | 
| 119 | 
            -
                attr_reader : | 
| 120 | 
            -
                alias :endpoint :notify_endpoint
         | 
| 130 | 
            +
                # The URLs to send events and sessions to
         | 
| 131 | 
            +
                # @return [EndpointConfiguration]
         | 
| 132 | 
            +
                attr_reader :endpoints
         | 
| 121 133 |  | 
| 122 | 
            -
                #  | 
| 123 | 
            -
                # @ | 
| 124 | 
            -
                 | 
| 134 | 
            +
                # Whether events will be delivered
         | 
| 135 | 
            +
                # @api private
         | 
| 136 | 
            +
                # @return [Boolean]
         | 
| 137 | 
            +
                attr_reader :enable_events
         | 
| 125 138 |  | 
| 126 139 | 
             
                # Whether sessions will be delivered
         | 
| 140 | 
            +
                # @api private
         | 
| 127 141 | 
             
                # @return [Boolean]
         | 
| 128 142 | 
             
                attr_reader :enable_sessions
         | 
| 129 143 |  | 
| @@ -141,15 +155,28 @@ module Bugsnag | |
| 141 155 | 
             
                # @return [Integer]
         | 
| 142 156 | 
             
                attr_reader :max_breadcrumbs
         | 
| 143 157 |  | 
| 144 | 
            -
                #
         | 
| 158 | 
            +
                # @deprecated Use {vendor_paths} instead
         | 
| 145 159 | 
             
                # @return [Regexp]
         | 
| 146 160 | 
             
                attr_accessor :vendor_path
         | 
| 147 161 |  | 
| 162 | 
            +
                # An array of paths within the {project_root} that should not be considered
         | 
| 163 | 
            +
                # as "in project"
         | 
| 164 | 
            +
                #
         | 
| 165 | 
            +
                # These paths should be relative to the {project_root} and will only match
         | 
| 166 | 
            +
                # whole directory names
         | 
| 167 | 
            +
                #
         | 
| 168 | 
            +
                # @return [Array<String>]
         | 
| 169 | 
            +
                attr_accessor :vendor_paths
         | 
| 170 | 
            +
             | 
| 148 171 | 
             
                # The default context for all future events
         | 
| 149 172 | 
             
                # Setting this will disable automatic context setting
         | 
| 150 173 | 
             
                # @return [String, nil]
         | 
| 151 174 | 
             
                attr_accessor :context
         | 
| 152 175 |  | 
| 176 | 
            +
                # Global metadata added to every event
         | 
| 177 | 
            +
                # @return [Hash]
         | 
| 178 | 
            +
                attr_reader :metadata
         | 
| 179 | 
            +
             | 
| 153 180 | 
             
                # @api private
         | 
| 154 181 | 
             
                # @return [Array<String>]
         | 
| 155 182 | 
             
                attr_reader :scopes_to_filter
         | 
| @@ -194,6 +221,7 @@ module Bugsnag | |
| 194 221 | 
             
                  self.send_environment = false
         | 
| 195 222 | 
             
                  self.send_code = true
         | 
| 196 223 | 
             
                  self.meta_data_filters = Set.new(DEFAULT_META_DATA_FILTERS)
         | 
| 224 | 
            +
                  @redacted_keys = Set.new
         | 
| 197 225 | 
             
                  self.scopes_to_filter = DEFAULT_SCOPES_TO_FILTER
         | 
| 198 226 | 
             
                  self.hostname = default_hostname
         | 
| 199 227 | 
             
                  self.runtime_versions = {}
         | 
| @@ -213,11 +241,14 @@ module Bugsnag | |
| 213 241 | 
             
                  # to avoid infinite recursion when creating breadcrumb buffer
         | 
| 214 242 | 
             
                  @max_breadcrumbs = DEFAULT_MAX_BREADCRUMBS
         | 
| 215 243 |  | 
| 216 | 
            -
                   | 
| 217 | 
            -
             | 
| 218 | 
            -
                  @ | 
| 244 | 
            +
                  @endpoints = EndpointConfiguration.new(DEFAULT_NOTIFY_ENDPOINT, DEFAULT_SESSION_ENDPOINT)
         | 
| 245 | 
            +
             | 
| 246 | 
            +
                  @enable_events = true
         | 
| 219 247 | 
             
                  @enable_sessions = true
         | 
| 220 248 |  | 
| 249 | 
            +
                  @metadata = {}
         | 
| 250 | 
            +
                  @metadata_delegate = Utility::MetadataDelegate.new
         | 
| 251 | 
            +
             | 
| 221 252 | 
             
                  # SystemExit and SignalException are common Exception types seen with
         | 
| 222 253 | 
             
                  # successful exits and are not automatically reported to Bugsnag
         | 
| 223 254 | 
             
                  # TODO move these defaults into `discard_classes` when `ignore_classes`
         | 
| @@ -237,6 +268,7 @@ module Bugsnag | |
| 237 268 | 
             
                  # Stacktrace lines that matches regex will be marked as "out of project"
         | 
| 238 269 | 
             
                  # will only appear in the full trace.
         | 
| 239 270 | 
             
                  self.vendor_path = DEFAULT_VENDOR_PATH
         | 
| 271 | 
            +
                  @vendor_paths = []
         | 
| 240 272 |  | 
| 241 273 | 
             
                  # Set up logging
         | 
| 242 274 | 
             
                  self.logger = Logger.new(STDOUT)
         | 
| @@ -459,26 +491,44 @@ module Bugsnag | |
| 459 491 | 
             
                  request_data[:breadcrumbs] ||= Bugsnag::Utility::CircularBuffer.new(@max_breadcrumbs)
         | 
| 460 492 | 
             
                end
         | 
| 461 493 |  | 
| 494 | 
            +
                # The URL error notifications will be delivered to
         | 
| 495 | 
            +
                # @!attribute notify_endpoint
         | 
| 496 | 
            +
                # @return [String]
         | 
| 497 | 
            +
                # @deprecated Use {#endpoints} instead
         | 
| 498 | 
            +
                def notify_endpoint
         | 
| 499 | 
            +
                  @endpoints.notify
         | 
| 500 | 
            +
                end
         | 
| 501 | 
            +
             | 
| 502 | 
            +
                alias :endpoint :notify_endpoint
         | 
| 503 | 
            +
             | 
| 462 504 | 
             
                # Sets the notification endpoint
         | 
| 463 505 | 
             
                #
         | 
| 464 | 
            -
                # @deprecated Use {# | 
| 506 | 
            +
                # @deprecated Use {#endpoints} instead
         | 
| 465 507 | 
             
                #
         | 
| 466 508 | 
             
                # @param new_notify_endpoint [String] The URL to deliver error notifications to
         | 
| 467 509 | 
             
                # @return [void]
         | 
| 468 510 | 
             
                def endpoint=(new_notify_endpoint)
         | 
| 469 | 
            -
                  warn("The 'endpoint' configuration option is deprecated.  | 
| 511 | 
            +
                  warn("The 'endpoint' configuration option is deprecated. Set both endpoints with the 'endpoints=' method instead")
         | 
| 470 512 | 
             
                  set_endpoints(new_notify_endpoint, session_endpoint) # Pass the existing session_endpoint through so it doesn't get overwritten
         | 
| 471 513 | 
             
                end
         | 
| 472 514 |  | 
| 515 | 
            +
                # The URL session notifications will be delivered to
         | 
| 516 | 
            +
                # @!attribute session_endpoint
         | 
| 517 | 
            +
                # @return [String]
         | 
| 518 | 
            +
                # @deprecated Use {#endpoints} instead
         | 
| 519 | 
            +
                def session_endpoint
         | 
| 520 | 
            +
                  @endpoints.sessions
         | 
| 521 | 
            +
                end
         | 
| 522 | 
            +
             | 
| 473 523 | 
             
                ##
         | 
| 474 524 | 
             
                # Sets the sessions endpoint
         | 
| 475 525 | 
             
                #
         | 
| 476 | 
            -
                # @deprecated Use {# | 
| 526 | 
            +
                # @deprecated Use {#endpoints} instead
         | 
| 477 527 | 
             
                #
         | 
| 478 528 | 
             
                # @param new_session_endpoint [String] The URL to deliver session notifications to
         | 
| 479 529 | 
             
                # @return [void]
         | 
| 480 530 | 
             
                def session_endpoint=(new_session_endpoint)
         | 
| 481 | 
            -
                  warn("The 'session_endpoint' configuration option is deprecated.  | 
| 531 | 
            +
                  warn("The 'session_endpoint' configuration option is deprecated. Set both endpoints with the 'endpoints=' method instead")
         | 
| 482 532 | 
             
                  set_endpoints(notify_endpoint, new_session_endpoint) # Pass the existing notify_endpoint through so it doesn't get overwritten
         | 
| 483 533 | 
             
                end
         | 
| 484 534 |  | 
| @@ -488,9 +538,26 @@ module Bugsnag | |
| 488 538 | 
             
                # @param new_notify_endpoint [String] The URL to deliver error notifications to
         | 
| 489 539 | 
             
                # @param new_session_endpoint [String] The URL to deliver session notifications to
         | 
| 490 540 | 
             
                # @return [void]
         | 
| 541 | 
            +
                # @deprecated Use {#endpoints} instead
         | 
| 491 542 | 
             
                def set_endpoints(new_notify_endpoint, new_session_endpoint)
         | 
| 492 | 
            -
                   | 
| 493 | 
            -
             | 
| 543 | 
            +
                  self.endpoints = EndpointConfiguration.new(new_notify_endpoint, new_session_endpoint)
         | 
| 544 | 
            +
                end
         | 
| 545 | 
            +
             | 
| 546 | 
            +
                def endpoints=(endpoint_configuration)
         | 
| 547 | 
            +
                  result = EndpointValidator.validate(endpoint_configuration)
         | 
| 548 | 
            +
             | 
| 549 | 
            +
                  if result.valid?
         | 
| 550 | 
            +
                    @enable_events = true
         | 
| 551 | 
            +
                    @enable_sessions = true
         | 
| 552 | 
            +
                  else
         | 
| 553 | 
            +
                    warn(result.reason)
         | 
| 554 | 
            +
             | 
| 555 | 
            +
                    @enable_events = result.keep_events_enabled_for_backwards_compatibility?
         | 
| 556 | 
            +
                    @enable_sessions = false
         | 
| 557 | 
            +
                  end
         | 
| 558 | 
            +
             | 
| 559 | 
            +
                  # use the given endpoints even if they are invalid
         | 
| 560 | 
            +
                  @endpoints = endpoint_configuration
         | 
| 494 561 | 
             
                end
         | 
| 495 562 |  | 
| 496 563 | 
             
                ##
         | 
| @@ -556,6 +623,47 @@ module Bugsnag | |
| 556 623 | 
             
                  @on_breadcrumb_callbacks.remove(callback)
         | 
| 557 624 | 
             
                end
         | 
| 558 625 |  | 
| 626 | 
            +
                ##
         | 
| 627 | 
            +
                # Add values to metadata
         | 
| 628 | 
            +
                #
         | 
| 629 | 
            +
                # @overload add_metadata(section, data)
         | 
| 630 | 
            +
                #   Merges data into the given section of metadata
         | 
| 631 | 
            +
                #   @param section [String, Symbol]
         | 
| 632 | 
            +
                #   @param data [Hash]
         | 
| 633 | 
            +
                #
         | 
| 634 | 
            +
                # @overload add_metadata(section, key, value)
         | 
| 635 | 
            +
                #   Sets key to value in the given section of metadata. If the value is nil
         | 
| 636 | 
            +
                #   the key will be deleted
         | 
| 637 | 
            +
                #   @param section [String, Symbol]
         | 
| 638 | 
            +
                #   @param key [String, Symbol]
         | 
| 639 | 
            +
                #   @param value
         | 
| 640 | 
            +
                #
         | 
| 641 | 
            +
                # @return [void]
         | 
| 642 | 
            +
                def add_metadata(section, key_or_data, *args)
         | 
| 643 | 
            +
                  @mutex.synchronize do
         | 
| 644 | 
            +
                    @metadata_delegate.add_metadata(@metadata, section, key_or_data, *args)
         | 
| 645 | 
            +
                  end
         | 
| 646 | 
            +
                end
         | 
| 647 | 
            +
             | 
| 648 | 
            +
                ##
         | 
| 649 | 
            +
                # Clear values from metadata
         | 
| 650 | 
            +
                #
         | 
| 651 | 
            +
                # @overload clear_metadata(section)
         | 
| 652 | 
            +
                #   Clears the given section of metadata
         | 
| 653 | 
            +
                #   @param section [String, Symbol]
         | 
| 654 | 
            +
                #
         | 
| 655 | 
            +
                # @overload clear_metadata(section, key)
         | 
| 656 | 
            +
                #   Clears the key in the given section of metadata
         | 
| 657 | 
            +
                #   @param section [String, Symbol]
         | 
| 658 | 
            +
                #   @param key [String, Symbol]
         | 
| 659 | 
            +
                #
         | 
| 660 | 
            +
                # @return [void]
         | 
| 661 | 
            +
                def clear_metadata(section, *args)
         | 
| 662 | 
            +
                  @mutex.synchronize do
         | 
| 663 | 
            +
                    @metadata_delegate.clear_metadata(@metadata, section, *args)
         | 
| 664 | 
            +
                  end
         | 
| 665 | 
            +
                end
         | 
| 666 | 
            +
             | 
| 559 667 | 
             
                ##
         | 
| 560 668 | 
             
                # Has the context been explicitly set?
         | 
| 561 669 | 
             
                #
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            module Bugsnag
         | 
| 2 | 
            +
              # @api private
         | 
| 3 | 
            +
              class EndpointValidator
         | 
| 4 | 
            +
                def self.validate(endpoints)
         | 
| 5 | 
            +
                  # ensure we have an EndpointConfiguration object
         | 
| 6 | 
            +
                  return Result.missing_urls unless endpoints.is_a?(EndpointConfiguration)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  # check for missing URLs
         | 
| 9 | 
            +
                  return Result.missing_urls if endpoints.notify.nil? && endpoints.sessions.nil?
         | 
| 10 | 
            +
                  return Result.missing_notify if endpoints.notify.nil?
         | 
| 11 | 
            +
                  return Result.missing_session if endpoints.sessions.nil?
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  # check for empty URLs
         | 
| 14 | 
            +
                  return Result.invalid_urls if endpoints.notify.empty? && endpoints.sessions.empty?
         | 
| 15 | 
            +
                  return Result.invalid_notify if endpoints.notify.empty?
         | 
| 16 | 
            +
                  return Result.invalid_session if endpoints.sessions.empty?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  Result.valid
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # @api private
         | 
| 22 | 
            +
                class Result
         | 
| 23 | 
            +
                  # rubocop:disable Layout/LineLength
         | 
| 24 | 
            +
                  MISSING_URLS = "Invalid configuration. endpoints must be set with both a notify and session URL. Bugsnag will not send any requests.".freeze
         | 
| 25 | 
            +
                  MISSING_NOTIFY_URL = "Invalid configuration. endpoints.sessions cannot be set without also setting endpoints.notify. Bugsnag will not send any requests.".freeze
         | 
| 26 | 
            +
                  MISSING_SESSION_URL = "Invalid configuration. endpoints.notify cannot be set without also setting endpoints.sessions. Bugsnag will not send any sessions.".freeze
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  INVALID_URLS = "Invalid configuration. endpoints should be valid URLs, got empty strings. Bugsnag will not send any requests.".freeze
         | 
| 29 | 
            +
                  INVALID_NOTIFY_URL = "Invalid configuration. endpoints.notify should be a valid URL, got empty string. Bugsnag will not send any requests.".freeze
         | 
| 30 | 
            +
                  INVALID_SESSION_URL = "Invalid configuration. endpoints.sessions should be a valid URL, got empty string. Bugsnag will not send any sessions.".freeze
         | 
| 31 | 
            +
                  # rubocop:enable Layout/LineLength
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  attr_reader :reason
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def initialize(valid, keep_events_enabled_for_backwards_compatibility = true, reason = nil)
         | 
| 36 | 
            +
                    @valid = valid
         | 
| 37 | 
            +
                    @keep_events_enabled_for_backwards_compatibility = keep_events_enabled_for_backwards_compatibility
         | 
| 38 | 
            +
                    @reason = reason
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def valid?
         | 
| 42 | 
            +
                    @valid
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def keep_events_enabled_for_backwards_compatibility?
         | 
| 46 | 
            +
                    @keep_events_enabled_for_backwards_compatibility
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  # factory functions
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def self.valid
         | 
| 52 | 
            +
                    new(true)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def self.missing_urls
         | 
| 56 | 
            +
                    new(false, false, MISSING_URLS)
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def self.missing_notify
         | 
| 60 | 
            +
                    new(false, false, MISSING_NOTIFY_URL)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def self.missing_session
         | 
| 64 | 
            +
                    new(false, true, MISSING_SESSION_URL)
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def self.invalid_urls
         | 
| 68 | 
            +
                    new(false, false, INVALID_URLS)
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def self.invalid_notify
         | 
| 72 | 
            +
                    new(false, false, INVALID_NOTIFY_URL)
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def self.invalid_session
         | 
| 76 | 
            +
                    new(false, true, INVALID_SESSION_URL)
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
            end
         | 
| @@ -1,8 +1,11 @@ | |
| 1 | 
            +
            require "json"
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Bugsnag::Middleware
         | 
| 2 4 | 
             
              ##
         | 
| 3 5 | 
             
              # Extracts and attaches rack data to an error report
         | 
| 4 6 | 
             
              class RackRequest
         | 
| 5 7 | 
             
                SPOOF = "[SPOOF]".freeze
         | 
| 8 | 
            +
                COOKIE_HEADER = "Cookie".freeze
         | 
| 6 9 |  | 
| 7 10 | 
             
                def initialize(bugsnag)
         | 
| 8 11 | 
             
                  @bugsnag = bugsnag
         | 
| @@ -42,22 +45,6 @@ module Bugsnag::Middleware | |
| 42 45 | 
             
                      Bugsnag.configuration.warn "RackRequest - Rescued error while cleaning request.referer: #{stde}"
         | 
| 43 46 | 
             
                    end
         | 
| 44 47 |  | 
| 45 | 
            -
                    headers = {}
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                    env.each_pair do |key, value|
         | 
| 48 | 
            -
                      if key.to_s.start_with?("HTTP_")
         | 
| 49 | 
            -
                        header_key = key[5..-1]
         | 
| 50 | 
            -
                      elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
         | 
| 51 | 
            -
                        header_key = key
         | 
| 52 | 
            -
                      else
         | 
| 53 | 
            -
                        next
         | 
| 54 | 
            -
                      end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                      headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
         | 
| 57 | 
            -
                    end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                    headers["Referer"] = referer if headers["Referer"]
         | 
| 60 | 
            -
             | 
| 61 48 | 
             
                    # Add a request tab
         | 
| 62 49 | 
             
                    report.add_tab(:request, {
         | 
| 63 50 | 
             
                      :url => url,
         | 
| @@ -65,9 +52,17 @@ module Bugsnag::Middleware | |
| 65 52 | 
             
                      :params => params.to_hash,
         | 
| 66 53 | 
             
                      :referer => referer,
         | 
| 67 54 | 
             
                      :clientIp => client_ip,
         | 
| 68 | 
            -
                      :headers =>  | 
| 55 | 
            +
                      :headers => format_headers(env, referer)
         | 
| 69 56 | 
             
                    })
         | 
| 70 57 |  | 
| 58 | 
            +
                    # add the HTTP version if present
         | 
| 59 | 
            +
                    if env["SERVER_PROTOCOL"]
         | 
| 60 | 
            +
                      report.add_metadata(:request, :httpVersion, env["SERVER_PROTOCOL"])
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    add_request_body(report, request, env)
         | 
| 64 | 
            +
                    add_cookies(report, request)
         | 
| 65 | 
            +
             | 
| 71 66 | 
             
                    # Add an environment tab
         | 
| 72 67 | 
             
                    if report.configuration.send_environment
         | 
| 73 68 | 
             
                      report.add_tab(:environment, env)
         | 
| @@ -87,5 +82,75 @@ module Bugsnag::Middleware | |
| 87 82 |  | 
| 88 83 | 
             
                  @bugsnag.call(report)
         | 
| 89 84 | 
             
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                private
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def format_headers(env, referer)
         | 
| 89 | 
            +
                  headers = {}
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  env.each_pair do |key, value|
         | 
| 92 | 
            +
                    if key.to_s.start_with?("HTTP_")
         | 
| 93 | 
            +
                      header_key = key[5..-1]
         | 
| 94 | 
            +
                    elsif ["CONTENT_TYPE", "CONTENT_LENGTH"].include?(key)
         | 
| 95 | 
            +
                      header_key = key
         | 
| 96 | 
            +
                    else
         | 
| 97 | 
            +
                      next
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    headers[header_key.split("_").map {|s| s.capitalize}.join("-")] = value
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  headers["Referer"] = referer if headers["Referer"]
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  headers
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def add_request_body(report, request, env)
         | 
| 109 | 
            +
                  body = parsed_request_body(request, env)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  # this request may not have a body
         | 
| 112 | 
            +
                  return unless body.is_a?(Hash) && !body.empty?
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  report.add_metadata(:request, :body, body)
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def parsed_request_body(request, env)
         | 
| 118 | 
            +
                  return request.POST rescue nil if request.form_data?
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  content_type = env["CONTENT_TYPE"]
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  return nil if content_type.nil?
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  if content_type.include?('/json') || content_type.include?('+json')
         | 
| 125 | 
            +
                    begin
         | 
| 126 | 
            +
                      body = request.body
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                      return JSON.parse(body.read)
         | 
| 129 | 
            +
                    rescue StandardError
         | 
| 130 | 
            +
                      return nil
         | 
| 131 | 
            +
                    ensure
         | 
| 132 | 
            +
                      # the body must be rewound so other things can read it after we do
         | 
| 133 | 
            +
                      body.rewind
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  nil
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def add_cookies(report, request)
         | 
| 141 | 
            +
                  return unless record_cookies?
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  cookies = request.cookies rescue nil
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  return unless cookies.is_a?(Hash) && !cookies.empty?
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  report.add_metadata(:request, :cookies, cookies)
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                def record_cookies?
         | 
| 151 | 
            +
                  # only record cookies in the request if none of the filters match "Cookie"
         | 
| 152 | 
            +
                  # the "Cookie" header will be filtered as normal
         | 
| 153 | 
            +
                  !Bugsnag.cleaner.filters_match?(COOKIE_HEADER)
         | 
| 154 | 
            +
                end
         | 
| 90 155 | 
             
              end
         | 
| 91 156 | 
             
            end
         | 
| @@ -8,12 +8,14 @@ module Bugsnag::Middleware | |
| 8 8 |  | 
| 9 9 | 
             
                def call(report)
         | 
| 10 10 | 
             
                  session = Bugsnag::SessionTracker.get_current_session
         | 
| 11 | 
            -
             | 
| 11 | 
            +
             | 
| 12 | 
            +
                  if session && !session[:paused?]
         | 
| 12 13 | 
             
                    if report.unhandled
         | 
| 13 14 | 
             
                      session[:events][:unhandled] += 1
         | 
| 14 15 | 
             
                    else
         | 
| 15 16 | 
             
                      session[:events][:handled] += 1
         | 
| 16 17 | 
             
                    end
         | 
| 18 | 
            +
             | 
| 17 19 | 
             
                    report.session = session
         | 
| 18 20 | 
             
                  end
         | 
| 19 21 |  | 
    
        data/lib/bugsnag/report.rb
    CHANGED
    
    | @@ -119,6 +119,7 @@ module Bugsnag | |
| 119 119 |  | 
| 120 120 | 
             
                  @should_ignore = false
         | 
| 121 121 | 
             
                  @unhandled = auto_notify
         | 
| 122 | 
            +
                  @initial_unhandled = @unhandled
         | 
| 122 123 |  | 
| 123 124 | 
             
                  self.configuration = passed_configuration
         | 
| 124 125 |  | 
| @@ -135,7 +136,7 @@ module Bugsnag | |
| 135 136 | 
             
                  self.delivery_method = configuration.delivery_method
         | 
| 136 137 | 
             
                  self.hostname = configuration.hostname
         | 
| 137 138 | 
             
                  self.runtime_versions = configuration.runtime_versions.dup
         | 
| 138 | 
            -
                  self.meta_data =  | 
| 139 | 
            +
                  self.meta_data = Utility::Duplicator.duplicate(configuration.metadata)
         | 
| 139 140 | 
             
                  self.release_stage = configuration.release_stage
         | 
| 140 141 | 
             
                  self.severity = auto_notify ? "error" : "warning"
         | 
| 141 142 | 
             
                  self.severity_reason = auto_notify ? {:type => UNHANDLED_EXCEPTION} : {:type => HANDLED_EXCEPTION}
         | 
| @@ -374,8 +375,43 @@ module Bugsnag | |
| 374 375 | 
             
                  @user = new_user
         | 
| 375 376 | 
             
                end
         | 
| 376 377 |  | 
| 378 | 
            +
                def unhandled=(new_unhandled)
         | 
| 379 | 
            +
                  # fix the handled/unhandled counts in the current session
         | 
| 380 | 
            +
                  update_handled_counts(new_unhandled, @unhandled)
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                  @unhandled = new_unhandled
         | 
| 383 | 
            +
                end
         | 
| 384 | 
            +
             | 
| 385 | 
            +
                ##
         | 
| 386 | 
            +
                # Returns true if the unhandled flag has been changed from its initial value
         | 
| 387 | 
            +
                #
         | 
| 388 | 
            +
                # @api private
         | 
| 389 | 
            +
                # @return [Boolean]
         | 
| 390 | 
            +
                def unhandled_overridden?
         | 
| 391 | 
            +
                  @unhandled != @initial_unhandled
         | 
| 392 | 
            +
                end
         | 
| 393 | 
            +
             | 
| 377 394 | 
             
                private
         | 
| 378 395 |  | 
| 396 | 
            +
                def update_handled_counts(is_unhandled, was_unhandled)
         | 
| 397 | 
            +
                  # do nothing if there is no session to update
         | 
| 398 | 
            +
                  return if @session.nil?
         | 
| 399 | 
            +
             | 
| 400 | 
            +
                  # increment the counts for the current unhandled value
         | 
| 401 | 
            +
                  if is_unhandled
         | 
| 402 | 
            +
                    @session[:events][:unhandled] += 1
         | 
| 403 | 
            +
                  else
         | 
| 404 | 
            +
                    @session[:events][:handled] += 1
         | 
| 405 | 
            +
                  end
         | 
| 406 | 
            +
             | 
| 407 | 
            +
                  # decrement the counts for the previous unhandled value
         | 
| 408 | 
            +
                  if was_unhandled
         | 
| 409 | 
            +
                    @session[:events][:unhandled] -= 1
         | 
| 410 | 
            +
                  else
         | 
| 411 | 
            +
                    @session[:events][:handled] -= 1
         | 
| 412 | 
            +
                  end
         | 
| 413 | 
            +
                end
         | 
| 414 | 
            +
             | 
| 379 415 | 
             
                def generate_exception_list
         | 
| 380 416 | 
             
                  raw_exceptions.map do |exception|
         | 
| 381 417 | 
             
                    {
         | 
| @@ -34,17 +34,20 @@ module Bugsnag | |
| 34 34 | 
             
                # Starts a new session, storing it on the current thread.
         | 
| 35 35 | 
             
                #
         | 
| 36 36 | 
             
                # This allows Bugsnag to track error rates for a release.
         | 
| 37 | 
            +
                #
         | 
| 38 | 
            +
                # @return [void]
         | 
| 37 39 | 
             
                def start_session
         | 
| 38 40 | 
             
                  return unless Bugsnag.configuration.enable_sessions && Bugsnag.configuration.should_notify_release_stage?
         | 
| 39 41 |  | 
| 40 42 | 
             
                  start_delivery_thread
         | 
| 41 43 | 
             
                  start_time = Time.now().utc().strftime('%Y-%m-%dT%H:%M:00')
         | 
| 42 44 | 
             
                  new_session = {
         | 
| 43 | 
            -
                    : | 
| 44 | 
            -
                    : | 
| 45 | 
            -
                     | 
| 46 | 
            -
             | 
| 47 | 
            -
                      : | 
| 45 | 
            +
                    id: SecureRandom.uuid,
         | 
| 46 | 
            +
                    startedAt: start_time,
         | 
| 47 | 
            +
                    paused?: false,
         | 
| 48 | 
            +
                    events: {
         | 
| 49 | 
            +
                      handled: 0,
         | 
| 50 | 
            +
                      unhandled: 0
         | 
| 48 51 | 
             
                    }
         | 
| 49 52 | 
             
                  }
         | 
| 50 53 | 
             
                  SessionTracker.set_current_session(new_session)
         | 
| @@ -53,6 +56,47 @@ module Bugsnag | |
| 53 56 |  | 
| 54 57 | 
             
                alias_method :create_session, :start_session
         | 
| 55 58 |  | 
| 59 | 
            +
                ##
         | 
| 60 | 
            +
                # Stop any events being attributed to the current session until it is
         | 
| 61 | 
            +
                # resumed or a new session is started
         | 
| 62 | 
            +
                #
         | 
| 63 | 
            +
                # @see resume_session
         | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                # @return [void]
         | 
| 66 | 
            +
                def pause_session
         | 
| 67 | 
            +
                  current_session = SessionTracker.get_current_session
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  return unless current_session
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  current_session[:paused?] = true
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                ##
         | 
| 75 | 
            +
                # Resume the current session if it was previously paused. If there is no
         | 
| 76 | 
            +
                # current session, a new session will be started
         | 
| 77 | 
            +
                #
         | 
| 78 | 
            +
                # @see pause_session
         | 
| 79 | 
            +
                #
         | 
| 80 | 
            +
                # @return [Boolean] true if a paused session was resumed
         | 
| 81 | 
            +
                def resume_session
         | 
| 82 | 
            +
                  current_session = SessionTracker.get_current_session
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  if current_session
         | 
| 85 | 
            +
                    # if the session is paused then resume it, otherwise we don't need to
         | 
| 86 | 
            +
                    # do anything
         | 
| 87 | 
            +
                    if current_session[:paused?]
         | 
| 88 | 
            +
                      current_session[:paused?] = false
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                      return true
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  else
         | 
| 93 | 
            +
                    # if there's no current session, start a new one
         | 
| 94 | 
            +
                    start_session
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  false
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 56 100 | 
             
                ##
         | 
| 57 101 | 
             
                # Delivers the current session_counts lists to the session endpoint.
         | 
| 58 102 | 
             
                def send_sessions
         | 
    
        data/lib/bugsnag/stacktrace.rb
    CHANGED
    
    | @@ -43,7 +43,7 @@ module Bugsnag | |
| 43 43 | 
             
                    if defined?(configuration.project_root) && configuration.project_root.to_s != ''
         | 
| 44 44 | 
             
                      trace_hash[:inProject] = true if file.start_with?(configuration.project_root.to_s)
         | 
| 45 45 | 
             
                      file.sub!(/#{configuration.project_root}\//, "")
         | 
| 46 | 
            -
                      trace_hash.delete(:inProject) if  | 
| 46 | 
            +
                      trace_hash.delete(:inProject) if vendor_path?(configuration, file)
         | 
| 47 47 | 
             
                    end
         | 
| 48 48 |  | 
| 49 49 | 
             
                    # Strip common gem path prefixes
         | 
| @@ -67,5 +67,14 @@ module Bugsnag | |
| 67 67 |  | 
| 68 68 | 
             
                  processed_backtrace
         | 
| 69 69 | 
             
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                # @api private
         | 
| 72 | 
            +
                def self.vendor_path?(configuration, file_path)
         | 
| 73 | 
            +
                  return true if configuration.vendor_path && file_path.match(configuration.vendor_path)
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  configuration.vendor_paths.any? do |vendor_path|
         | 
| 76 | 
            +
                    file_path.start_with?("#{vendor_path.sub(/\/$/, '')}/")
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 70 79 | 
             
              end
         | 
| 71 80 | 
             
            end
         | 
| @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            module Bugsnag::Utility
         | 
| 2 | 
            +
              # @api private
         | 
| 3 | 
            +
              class Duplicator
         | 
| 4 | 
            +
                class << self
         | 
| 5 | 
            +
                  ##
         | 
| 6 | 
            +
                  # Duplicate (deep clone) the given object
         | 
| 7 | 
            +
                  #
         | 
| 8 | 
            +
                  # @param object [Object]
         | 
| 9 | 
            +
                  # @param seen_objects [Hash<String, Object>]
         | 
| 10 | 
            +
                  # @return [Object]
         | 
| 11 | 
            +
                  def duplicate(object, seen_objects = {})
         | 
| 12 | 
            +
                    case object
         | 
| 13 | 
            +
                    # return immutable & non-duplicatable objects as-is
         | 
| 14 | 
            +
                    when Symbol, Numeric, Method, TrueClass, FalseClass, NilClass
         | 
| 15 | 
            +
                      object
         | 
| 16 | 
            +
                    when Array
         | 
| 17 | 
            +
                      duplicate_array(object, seen_objects)
         | 
| 18 | 
            +
                    when Hash
         | 
| 19 | 
            +
                      duplicate_hash(object, seen_objects)
         | 
| 20 | 
            +
                    when Range
         | 
| 21 | 
            +
                      duplicate_range(object, seen_objects)
         | 
| 22 | 
            +
                    when Struct
         | 
| 23 | 
            +
                      duplicate_struct(object, seen_objects)
         | 
| 24 | 
            +
                    else
         | 
| 25 | 
            +
                      duplicate_generic_object(object, seen_objects)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  rescue StandardError
         | 
| 28 | 
            +
                    object
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def duplicate_array(array, seen_objects)
         | 
| 34 | 
            +
                    id = array.object_id
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    return seen_objects[id] if seen_objects.key?(id)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    copy = array.dup
         | 
| 39 | 
            +
                    seen_objects[id] = copy
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    copy.map! do |value|
         | 
| 42 | 
            +
                      duplicate(value, seen_objects)
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    copy
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def duplicate_hash(hash, seen_objects)
         | 
| 49 | 
            +
                    id = hash.object_id
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    return seen_objects[id] if seen_objects.key?(id)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    copy = {}
         | 
| 54 | 
            +
                    seen_objects[id] = copy
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    hash.each do |key, value|
         | 
| 57 | 
            +
                      copy[duplicate(key, seen_objects)] = duplicate(value, seen_objects)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    copy
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  ##
         | 
| 64 | 
            +
                  # Ranges are immutable but the values they contain may not be
         | 
| 65 | 
            +
                  #
         | 
| 66 | 
            +
                  # For example, a range of "a".."z" can be mutated: range.first.upcase!
         | 
| 67 | 
            +
                  def duplicate_range(range, seen_objects)
         | 
| 68 | 
            +
                    id = range.object_id
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    return seen_objects[id] if seen_objects.key?(id)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    begin
         | 
| 73 | 
            +
                      copy = range.class.new(
         | 
| 74 | 
            +
                        duplicate(range.first, seen_objects),
         | 
| 75 | 
            +
                        duplicate(range.last, seen_objects),
         | 
| 76 | 
            +
                        range.exclude_end?
         | 
| 77 | 
            +
                      )
         | 
| 78 | 
            +
                    rescue StandardError
         | 
| 79 | 
            +
                      copy = range.dup
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    seen_objects[id] = copy
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def duplicate_struct(struct, seen_objects)
         | 
| 86 | 
            +
                    id = struct.object_id
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    return seen_objects[id] if seen_objects.key?(id)
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    copy = struct.dup
         | 
| 91 | 
            +
                    seen_objects[id] = copy
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    struct.each_pair do |attribute, value|
         | 
| 94 | 
            +
                      begin
         | 
| 95 | 
            +
                        copy.send("#{attribute}=", duplicate(value, seen_objects))
         | 
| 96 | 
            +
                      rescue StandardError # rubocop:todo Lint/SuppressedException
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    copy
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def duplicate_generic_object(object, seen_objects)
         | 
| 104 | 
            +
                    id = object.object_id
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    return seen_objects[id] if seen_objects.key?(id)
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    copy = object.dup
         | 
| 109 | 
            +
                    seen_objects[id] = copy
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    begin
         | 
| 112 | 
            +
                      copy.instance_variables.each do |variable|
         | 
| 113 | 
            +
                        value = copy.instance_variable_get(variable)
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                        copy.instance_variable_set(variable, duplicate(value, seen_objects))
         | 
| 116 | 
            +
                      end
         | 
| 117 | 
            +
                    rescue StandardError # rubocop:todo Lint/SuppressedException
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    copy
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
            end
         | 
    
        data/lib/bugsnag.rb
    CHANGED
    
    | @@ -34,6 +34,7 @@ require "bugsnag/breadcrumbs/validator" | |
| 34 34 | 
             
            require "bugsnag/breadcrumbs/breadcrumb"
         | 
| 35 35 | 
             
            require "bugsnag/breadcrumbs/breadcrumbs"
         | 
| 36 36 |  | 
| 37 | 
            +
            require "bugsnag/utility/duplicator"
         | 
| 37 38 | 
             
            require "bugsnag/utility/metadata_delegate"
         | 
| 38 39 |  | 
| 39 40 | 
             
            # rubocop:todo Metrics/ModuleLength
         | 
| @@ -136,6 +137,11 @@ module Bugsnag | |
| 136 137 | 
             
                      report.severity_reason = initial_reason
         | 
| 137 138 | 
             
                    end
         | 
| 138 139 |  | 
| 140 | 
            +
                    if report.unhandled_overridden?
         | 
| 141 | 
            +
                      # let the dashboard know that the unhandled flag was overridden
         | 
| 142 | 
            +
                      report.severity_reason[:unhandledOverridden] = true
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
             | 
| 139 145 | 
             
                    deliver_notification(report)
         | 
| 140 146 | 
             
                  end
         | 
| 141 147 | 
             
                end
         | 
| @@ -191,13 +197,36 @@ module Bugsnag | |
| 191 197 | 
             
                end
         | 
| 192 198 |  | 
| 193 199 | 
             
                ##
         | 
| 194 | 
            -
                # Starts a session | 
| 200 | 
            +
                # Starts a new session, which allows Bugsnag to track error rates across
         | 
| 201 | 
            +
                # releases
         | 
| 195 202 | 
             
                #
         | 
| 196 | 
            -
                #  | 
| 203 | 
            +
                # @return [void]
         | 
| 197 204 | 
             
                def start_session
         | 
| 198 205 | 
             
                  session_tracker.start_session
         | 
| 199 206 | 
             
                end
         | 
| 200 207 |  | 
| 208 | 
            +
                ##
         | 
| 209 | 
            +
                # Stop any events being attributed to the current session until it is
         | 
| 210 | 
            +
                # resumed or a new session is started
         | 
| 211 | 
            +
                #
         | 
| 212 | 
            +
                # @see resume_session
         | 
| 213 | 
            +
                #
         | 
| 214 | 
            +
                # @return [void]
         | 
| 215 | 
            +
                def pause_session
         | 
| 216 | 
            +
                  session_tracker.pause_session
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                ##
         | 
| 220 | 
            +
                # Resume the current session if it was previously paused. If there is no
         | 
| 221 | 
            +
                # current session, a new session will be started
         | 
| 222 | 
            +
                #
         | 
| 223 | 
            +
                # @see pause_session
         | 
| 224 | 
            +
                #
         | 
| 225 | 
            +
                # @return [Boolean] true if a paused session was resumed
         | 
| 226 | 
            +
                def resume_session
         | 
| 227 | 
            +
                  session_tracker.resume_session
         | 
| 228 | 
            +
                end
         | 
| 229 | 
            +
             | 
| 201 230 | 
             
                ##
         | 
| 202 231 | 
             
                # Allow access to "before notify" callbacks as an array.
         | 
| 203 232 | 
             
                #
         | 
| @@ -352,9 +381,56 @@ module Bugsnag | |
| 352 381 | 
             
                  end
         | 
| 353 382 | 
             
                end
         | 
| 354 383 |  | 
| 384 | 
            +
                ##
         | 
| 385 | 
            +
                # Global metadata added to every event
         | 
| 386 | 
            +
                #
         | 
| 387 | 
            +
                # @return [Hash]
         | 
| 388 | 
            +
                def metadata
         | 
| 389 | 
            +
                  configuration.metadata
         | 
| 390 | 
            +
                end
         | 
| 391 | 
            +
             | 
| 392 | 
            +
                ##
         | 
| 393 | 
            +
                # Add values to metadata
         | 
| 394 | 
            +
                #
         | 
| 395 | 
            +
                # @overload add_metadata(section, data)
         | 
| 396 | 
            +
                #   Merges data into the given section of metadata
         | 
| 397 | 
            +
                #   @param section [String, Symbol]
         | 
| 398 | 
            +
                #   @param data [Hash]
         | 
| 399 | 
            +
                #
         | 
| 400 | 
            +
                # @overload add_metadata(section, key, value)
         | 
| 401 | 
            +
                #   Sets key to value in the given section of metadata. If the value is nil
         | 
| 402 | 
            +
                #   the key will be deleted
         | 
| 403 | 
            +
                #   @param section [String, Symbol]
         | 
| 404 | 
            +
                #   @param key [String, Symbol]
         | 
| 405 | 
            +
                #   @param value
         | 
| 406 | 
            +
                #
         | 
| 407 | 
            +
                # @return [void]
         | 
| 408 | 
            +
                def add_metadata(section, key_or_data, *args)
         | 
| 409 | 
            +
                  configuration.add_metadata(section, key_or_data, *args)
         | 
| 410 | 
            +
                end
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                ##
         | 
| 413 | 
            +
                # Clear values from metadata
         | 
| 414 | 
            +
                #
         | 
| 415 | 
            +
                # @overload clear_metadata(section)
         | 
| 416 | 
            +
                #   Clears the given section of metadata
         | 
| 417 | 
            +
                #   @param section [String, Symbol]
         | 
| 418 | 
            +
                #
         | 
| 419 | 
            +
                # @overload clear_metadata(section, key)
         | 
| 420 | 
            +
                #   Clears the key in the given section of metadata
         | 
| 421 | 
            +
                #   @param section [String, Symbol]
         | 
| 422 | 
            +
                #   @param key [String, Symbol]
         | 
| 423 | 
            +
                #
         | 
| 424 | 
            +
                # @return [void]
         | 
| 425 | 
            +
                def clear_metadata(section, *args)
         | 
| 426 | 
            +
                  configuration.clear_metadata(section, *args)
         | 
| 427 | 
            +
                end
         | 
| 428 | 
            +
             | 
| 355 429 | 
             
                private
         | 
| 356 430 |  | 
| 357 431 | 
             
                def should_deliver_notification?(exception, auto_notify)
         | 
| 432 | 
            +
                  return false unless configuration.enable_events
         | 
| 433 | 
            +
             | 
| 358 434 | 
             
                  reason = abort_reason(exception, auto_notify)
         | 
| 359 435 | 
             
                  configuration.debug(reason) unless reason.nil?
         | 
| 360 436 | 
             
                  reason.nil?
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: bugsnag
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 6. | 
| 4 | 
            +
              version: 6.24.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - James Smith
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021- | 
| 11 | 
            +
            date: 2021-10-06 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: concurrent-ruby
         | 
| @@ -51,6 +51,8 @@ files: | |
| 51 51 | 
             
            - lib/bugsnag/delivery.rb
         | 
| 52 52 | 
             
            - lib/bugsnag/delivery/synchronous.rb
         | 
| 53 53 | 
             
            - lib/bugsnag/delivery/thread_queue.rb
         | 
| 54 | 
            +
            - lib/bugsnag/endpoint_configuration.rb
         | 
| 55 | 
            +
            - lib/bugsnag/endpoint_validator.rb
         | 
| 54 56 | 
             
            - lib/bugsnag/error.rb
         | 
| 55 57 | 
             
            - lib/bugsnag/event.rb
         | 
| 56 58 | 
             
            - lib/bugsnag/helpers.rb
         | 
| @@ -94,6 +96,7 @@ files: | |
| 94 96 | 
             
            - lib/bugsnag/tasks.rb
         | 
| 95 97 | 
             
            - lib/bugsnag/tasks/bugsnag.rake
         | 
| 96 98 | 
             
            - lib/bugsnag/utility/circular_buffer.rb
         | 
| 99 | 
            +
            - lib/bugsnag/utility/duplicator.rb
         | 
| 97 100 | 
             
            - lib/bugsnag/utility/metadata_delegate.rb
         | 
| 98 101 | 
             
            - lib/bugsnag/version.rb
         | 
| 99 102 | 
             
            - lib/generators/bugsnag/bugsnag_generator.rb
         |