datadog 2.8.0 → 2.9.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 +36 -1
- data/ext/datadog_profiling_native_extension/clock_id.h +2 -2
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +64 -54
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +16 -16
- data/ext/datadog_profiling_native_extension/collectors_stack.c +7 -7
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -122
- data/ext/datadog_profiling_native_extension/heap_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/http_transport.c +4 -4
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -1
- data/ext/datadog_profiling_native_extension/profiling.c +10 -8
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +8 -8
- data/ext/datadog_profiling_native_extension/stack_recorder.c +54 -54
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -1
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -1
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +47 -0
- data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +31 -0
- data/ext/libdatadog_api/crashtracker.c +3 -0
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +355 -157
- data/lib/datadog/appsec/assets/waf_rules/strict.json +62 -32
- data/lib/datadog/appsec/context.rb +54 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +7 -7
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +6 -6
- data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +4 -4
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +19 -28
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/gateway/response.rb +3 -3
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +64 -96
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +10 -10
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +5 -5
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +6 -6
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +10 -11
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +43 -49
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +21 -32
- data/lib/datadog/appsec/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +6 -6
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +41 -63
- data/lib/datadog/appsec/contrib/sinatra/patcher.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +5 -5
- data/lib/datadog/appsec/event.rb +6 -6
- data/lib/datadog/appsec/ext.rb +3 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +22 -32
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +5 -5
- data/lib/datadog/appsec/processor/rule_loader.rb +0 -3
- data/lib/datadog/appsec.rb +3 -3
- data/lib/datadog/auto_instrument.rb +3 -0
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +39 -11
- data/lib/datadog/core/configuration/components.rb +4 -2
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/{tracing → core}/contrib/rails/utils.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +1 -3
- data/lib/datadog/core/telemetry/event.rb +87 -3
- data/lib/datadog/core/telemetry/logging.rb +2 -2
- data/lib/datadog/core/telemetry/metric.rb +22 -0
- data/lib/datadog/core/telemetry/worker.rb +33 -0
- data/lib/datadog/di/base.rb +115 -0
- data/lib/datadog/di/code_tracker.rb +7 -4
- data/lib/datadog/di/component.rb +17 -11
- data/lib/datadog/di/configuration/settings.rb +11 -1
- data/lib/datadog/di/contrib/railtie.rb +15 -0
- data/lib/datadog/di/contrib.rb +26 -0
- data/lib/datadog/di/error.rb +5 -0
- data/lib/datadog/di/instrumenter.rb +39 -18
- data/lib/datadog/di/{init.rb → preload.rb} +2 -4
- data/lib/datadog/di/probe_manager.rb +4 -4
- data/lib/datadog/di/probe_notification_builder.rb +16 -2
- data/lib/datadog/di/probe_notifier_worker.rb +5 -6
- data/lib/datadog/di/remote.rb +4 -4
- data/lib/datadog/di/transport.rb +2 -4
- data/lib/datadog/di.rb +5 -108
- data/lib/datadog/kit/appsec/events.rb +3 -3
- data/lib/datadog/kit/identity.rb +4 -4
- data/lib/datadog/profiling/component.rb +55 -53
- data/lib/datadog/profiling/http_transport.rb +1 -26
- data/lib/datadog/tracing/contrib/action_cable/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_mailer/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/action_pack/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/action_view/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_job/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/active_record/integration.rb +6 -2
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/configuration/settings.rb +10 -0
- data/lib/datadog/tracing/contrib/active_support/integration.rb +5 -2
- data/lib/datadog/tracing/contrib/auto_instrument.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/concurrent_ruby/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/httprb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/kafka/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/mongodb/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/opensearch/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/presto/integration.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/integration.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/framework.rb +2 -2
- data/lib/datadog/tracing/contrib/rails/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rest_client/integration.rb +3 -0
- data/lib/datadog/tracing/span.rb +12 -4
- data/lib/datadog/tracing/span_event.rb +123 -3
- data/lib/datadog/tracing/span_operation.rb +6 -0
- data/lib/datadog/tracing/transport/serializable_trace.rb +24 -6
- data/lib/datadog/version.rb +1 -1
- metadata +19 -10
- data/lib/datadog/appsec/reactive/operation.rb +0 -68
- data/lib/datadog/appsec/scope.rb +0 -58
- data/lib/datadog/core/crashtracking/agent_base_url.rb +0 -21
| @@ -41,6 +41,18 @@ module Datadog | |
| 41 41 | 
             
                        }
         | 
| 42 42 | 
             
                      end
         | 
| 43 43 |  | 
| 44 | 
            +
                      def ==(other)
         | 
| 45 | 
            +
                        other.is_a?(self.class) &&
         | 
| 46 | 
            +
                          name == other.name &&
         | 
| 47 | 
            +
                          values == other.values && tags == other.tags && common == other.common && type == other.type
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      alias eql? ==
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      def hash
         | 
| 53 | 
            +
                        [self.class, name, values, tags, common, type].hash
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
             | 
| 44 56 | 
             
                      private
         | 
| 45 57 |  | 
| 46 58 | 
             
                      def tags_to_array(tags)
         | 
| @@ -71,6 +83,16 @@ module Datadog | |
| 71 83 | 
             
                        res[:interval] = interval
         | 
| 72 84 | 
             
                        res
         | 
| 73 85 | 
             
                      end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      def ==(other)
         | 
| 88 | 
            +
                        super && interval == other.interval
         | 
| 89 | 
            +
                      end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                      alias eql? ==
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                      def hash
         | 
| 94 | 
            +
                        [super, interval].hash
         | 
| 95 | 
            +
                      end
         | 
| 74 96 | 
             
                    end
         | 
| 75 97 |  | 
| 76 98 | 
             
                    # Count metric adds up all the submitted values in a time interval. This would be suitable for a
         | 
| @@ -97,6 +97,8 @@ module Datadog | |
| 97 97 | 
             
                      return if events.empty?
         | 
| 98 98 | 
             
                      return if !enabled? || !sent_started_event?
         | 
| 99 99 |  | 
| 100 | 
            +
                      events = deduplicate_logs(events)
         | 
| 101 | 
            +
             | 
| 100 102 | 
             
                      Datadog.logger.debug { "Sending #{events&.count} telemetry events" }
         | 
| 101 103 | 
             
                      send_event(Event::MessageBatch.new(events))
         | 
| 102 104 | 
             
                    end
         | 
| @@ -167,6 +169,37 @@ module Datadog | |
| 167 169 | 
             
                      Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
         | 
| 168 170 | 
             
                      disable!
         | 
| 169 171 | 
             
                    end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    # Deduplicate logs by counting the number of repeated occurrences of the same log
         | 
| 174 | 
            +
                    # entry and replacing them with a single entry with the calculated `count` value.
         | 
| 175 | 
            +
                    # Non-log events are unchanged.
         | 
| 176 | 
            +
                    def deduplicate_logs(events)
         | 
| 177 | 
            +
                      return events if events.empty?
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                      all_logs = []
         | 
| 180 | 
            +
                      other_events = events.reject do |event|
         | 
| 181 | 
            +
                        if event.is_a?(Event::Log)
         | 
| 182 | 
            +
                          all_logs << event
         | 
| 183 | 
            +
                          true
         | 
| 184 | 
            +
                        else
         | 
| 185 | 
            +
                          false
         | 
| 186 | 
            +
                        end
         | 
| 187 | 
            +
                      end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                      return events if all_logs.empty?
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                      uniq_logs = all_logs.group_by(&:itself).map do |_, logs|
         | 
| 192 | 
            +
                        log = logs.first
         | 
| 193 | 
            +
                        if logs.size > 1
         | 
| 194 | 
            +
                          # New log event with a count of repeated occurrences
         | 
| 195 | 
            +
                          Event::Log.new(message: log.message, level: log.level, stack_trace: log.stack_trace, count: logs.size)
         | 
| 196 | 
            +
                        else
         | 
| 197 | 
            +
                          log
         | 
| 198 | 
            +
                        end
         | 
| 199 | 
            +
                      end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                      other_events + uniq_logs
         | 
| 202 | 
            +
                    end
         | 
| 170 203 | 
             
                  end
         | 
| 171 204 | 
             
                end
         | 
| 172 205 | 
             
              end
         | 
| @@ -0,0 +1,115 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # This file is loaded by datadog/di/preload.rb.
         | 
| 4 | 
            +
            # It contains just the global DI reference to the (normally one and only)
         | 
| 5 | 
            +
            # code tracker for the current process.
         | 
| 6 | 
            +
            # This file should not require the rest of DI, specifically none of the
         | 
| 7 | 
            +
            # contrib code that is meant to be loaded after third-party libraries
         | 
| 8 | 
            +
            # are loaded, and also none of the rest of datadog library which also
         | 
| 9 | 
            +
            # has contrib code in other products.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require_relative 'code_tracker'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module Datadog
         | 
| 14 | 
            +
              # Namespace for Datadog dynamic instrumentation.
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              # @api private
         | 
| 17 | 
            +
              module DI
         | 
| 18 | 
            +
                LOCK = Mutex.new
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                class << self
         | 
| 21 | 
            +
                  attr_reader :code_tracker
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  # Activates code tracking. Normally this method should be called
         | 
| 24 | 
            +
                  # when the application starts. If instrumenting third-party code,
         | 
| 25 | 
            +
                  # code tracking needs to be enabled before the third-party libraries
         | 
| 26 | 
            +
                  # are loaded. Any third-party code loaded before code tracking is
         | 
| 27 | 
            +
                  # activated will NOT be instrumentable using dynamic instrumentation.
         | 
| 28 | 
            +
                  #
         | 
| 29 | 
            +
                  # TODO test that activating tracker multiple times preserves
         | 
| 30 | 
            +
                  # existing mappings in the registry
         | 
| 31 | 
            +
                  def activate_tracking!
         | 
| 32 | 
            +
                    (@code_tracker ||= CodeTracker.new).start
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  # Activates code tracking if possible.
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  # This method does nothing if invoked in an environment that does not
         | 
| 38 | 
            +
                  # implement required trace points for code tracking (MRI Ruby < 2.6,
         | 
| 39 | 
            +
                  # JRuby) and rescues any exceptions that may be raised by downstream
         | 
| 40 | 
            +
                  # DI code.
         | 
| 41 | 
            +
                  def activate_tracking
         | 
| 42 | 
            +
                    # :script_compiled trace point was added in Ruby 2.6.
         | 
| 43 | 
            +
                    return unless RUBY_VERSION >= '2.6'
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    begin
         | 
| 46 | 
            +
                      # Activate code tracking by default because line trace points will not work
         | 
| 47 | 
            +
                      # without it.
         | 
| 48 | 
            +
                      Datadog::DI.activate_tracking!
         | 
| 49 | 
            +
                    rescue => exc
         | 
| 50 | 
            +
                      if defined?(Datadog.logger)
         | 
| 51 | 
            +
                        Datadog.logger.warn { "di: Failed to activate code tracking for DI: #{exc.class}: #{exc}" }
         | 
| 52 | 
            +
                      else
         | 
| 53 | 
            +
                        # We do not have Datadog logger potentially because DI code tracker is
         | 
| 54 | 
            +
                        # being loaded early in application boot process and the rest of datadog
         | 
| 55 | 
            +
                        # wasn't loaded yet. Output to standard error.
         | 
| 56 | 
            +
                        warn("datadog: di: Failed to activate code tracking for DI: #{exc.class}: #{exc}")
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  # Deactivates code tracking. In normal usage of DI this method should
         | 
| 62 | 
            +
                  # never be called, however it is used by DI's test suite to reset
         | 
| 63 | 
            +
                  # state for individual tests.
         | 
| 64 | 
            +
                  #
         | 
| 65 | 
            +
                  # Note that deactivating tracking clears out the registry, losing
         | 
| 66 | 
            +
                  # the ability to look up files that have been loaded into the process
         | 
| 67 | 
            +
                  # already.
         | 
| 68 | 
            +
                  def deactivate_tracking!
         | 
| 69 | 
            +
                    code_tracker&.stop
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  # Returns whether code tracking is available.
         | 
| 73 | 
            +
                  # This method should be used instead of querying #code_tracker
         | 
| 74 | 
            +
                  # because the latter one may be nil.
         | 
| 75 | 
            +
                  def code_tracking_active?
         | 
| 76 | 
            +
                    code_tracker&.active? || false
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  # DI code tracker is instantiated globally before the regular set of
         | 
| 80 | 
            +
                  # components is created, but the code tracker needs to call out to the
         | 
| 81 | 
            +
                  # "current" DI component to perform instrumentation when application
         | 
| 82 | 
            +
                  # code is loaded. Because this call may happen prior to Datadog
         | 
| 83 | 
            +
                  # components having been initialized, we maintain the "current component"
         | 
| 84 | 
            +
                  # which contains a reference to the most recently instantiated
         | 
| 85 | 
            +
                  # DI::Component. This way, if a DI component hasn't been instantiated,
         | 
| 86 | 
            +
                  # we do not try to reference Datadog.components.
         | 
| 87 | 
            +
                  # In other words, this method exists so that we never attempt to call
         | 
| 88 | 
            +
                  # Datadog.components from the code tracker.
         | 
| 89 | 
            +
                  def current_component
         | 
| 90 | 
            +
                    LOCK.synchronize do
         | 
| 91 | 
            +
                      @current_components&.last
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  # To avoid potential races with DI::Component being added and removed,
         | 
| 96 | 
            +
                  # we maintain a list of the components. Normally the list should contain
         | 
| 97 | 
            +
                  # either zero or one component depending on whether DI is enabled in
         | 
| 98 | 
            +
                  # Datadog configuration. However, if a new instance of DI::Component
         | 
| 99 | 
            +
                  # is created while the previous instance is still running, we are
         | 
| 100 | 
            +
                  # guaranteed to not end up with no component when one is running.
         | 
| 101 | 
            +
                  def add_current_component(component)
         | 
| 102 | 
            +
                    LOCK.synchronize do
         | 
| 103 | 
            +
                      @current_components ||= []
         | 
| 104 | 
            +
                      @current_components << component
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  def remove_current_component(component)
         | 
| 109 | 
            +
                    LOCK.synchronize do
         | 
| 110 | 
            +
                      @current_components&.delete(component)
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
            end
         | 
| @@ -2,6 +2,8 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            # rubocop:disable Lint/AssignmentInCondition
         | 
| 4 4 |  | 
| 5 | 
            +
            require_relative 'error'
         | 
| 6 | 
            +
             | 
| 5 7 | 
             
            module Datadog
         | 
| 6 8 | 
             
              module DI
         | 
| 7 9 | 
             
                # Tracks loaded Ruby code by source file and maintains a map from
         | 
| @@ -87,11 +89,12 @@ module Datadog | |
| 87 89 | 
             
                      # rescue any exceptions that might not be handled to not break said
         | 
| 88 90 | 
             
                      # customer applications.
         | 
| 89 91 | 
             
                      rescue => exc
         | 
| 90 | 
            -
                        #  | 
| 91 | 
            -
                        #  | 
| 92 | 
            -
                         | 
| 92 | 
            +
                        # Code tracker may be loaded without the rest of DI,
         | 
| 93 | 
            +
                        # in which case DI.component will not yet be defined,
         | 
| 94 | 
            +
                        # but we will have DI.current_component (set to nil).
         | 
| 95 | 
            +
                        if component = DI.current_component
         | 
| 93 96 | 
             
                          raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 94 | 
            -
                          component.logger. | 
| 97 | 
            +
                          component.logger.debug { "di: unhandled exception in script_compiled trace point: #{exc.class}: #{exc}" }
         | 
| 95 98 | 
             
                          component.telemetry&.report(exc, description: "Unhandled exception in script_compiled trace point")
         | 
| 96 99 | 
             
                          # TODO test this path
         | 
| 97 100 | 
             
                        else
         | 
    
        data/lib/datadog/di/component.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require_relative '../core'
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module Datadog
         | 
| 4 6 | 
             
              module DI
         | 
| 5 7 | 
             
                # Component for dynamic instrumentation.
         | 
| @@ -14,22 +16,22 @@ module Datadog | |
| 14 16 | 
             
                # resources and installed tracepoints upon shutdown.
         | 
| 15 17 | 
             
                class Component
         | 
| 16 18 | 
             
                  class << self
         | 
| 17 | 
            -
                    def build(settings, agent_settings, telemetry: nil)
         | 
| 19 | 
            +
                    def build(settings, agent_settings, logger, telemetry: nil)
         | 
| 18 20 | 
             
                      return unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
         | 
| 19 21 |  | 
| 20 22 | 
             
                      unless settings.respond_to?(:remote) && settings.remote.enabled
         | 
| 21 | 
            -
                         | 
| 23 | 
            +
                        logger.warn("di: dynamic instrumentation could not be enabled because Remote Configuration Management is not available. To enable Remote Configuration, see https://docs.datadoghq.com/agent/remote_config")
         | 
| 22 24 | 
             
                        return
         | 
| 23 25 | 
             
                      end
         | 
| 24 26 |  | 
| 25 | 
            -
                      return unless environment_supported?(settings)
         | 
| 27 | 
            +
                      return unless environment_supported?(settings, logger)
         | 
| 26 28 |  | 
| 27 | 
            -
                      new(settings, agent_settings,  | 
| 29 | 
            +
                      new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry).tap do |component|
         | 
| 28 30 | 
             
                        DI.add_current_component(component)
         | 
| 29 31 | 
             
                      end
         | 
| 30 32 | 
             
                    end
         | 
| 31 33 |  | 
| 32 | 
            -
                    def build!(settings, agent_settings, telemetry: nil)
         | 
| 34 | 
            +
                    def build!(settings, agent_settings, logger, telemetry: nil)
         | 
| 33 35 | 
             
                      unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
         | 
| 34 36 | 
             
                        raise "Requested DI component but DI is not enabled in settings"
         | 
| 35 37 | 
             
                      end
         | 
| @@ -38,27 +40,31 @@ module Datadog | |
| 38 40 | 
             
                        raise "Requested DI component but remote config is not enabled in settings"
         | 
| 39 41 | 
             
                      end
         | 
| 40 42 |  | 
| 41 | 
            -
                      unless environment_supported?(settings)
         | 
| 43 | 
            +
                      unless environment_supported?(settings, logger)
         | 
| 42 44 | 
             
                        raise "DI does not support the environment (development or Ruby version too low or not MRI)"
         | 
| 43 45 | 
             
                      end
         | 
| 44 46 |  | 
| 45 | 
            -
                      new(settings, agent_settings,  | 
| 47 | 
            +
                      new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry)
         | 
| 46 48 | 
             
                    end
         | 
| 47 49 |  | 
| 48 50 | 
             
                    # Checks whether the runtime environment is supported by
         | 
| 49 51 | 
             
                    # dynamic instrumentation. Currently we only require that, if Rails
         | 
| 50 52 | 
             
                    # is used, that Rails environment is not development because
         | 
| 51 53 | 
             
                    # DI does not currently support code unloading and reloading.
         | 
| 52 | 
            -
                    def environment_supported?(settings)
         | 
| 54 | 
            +
                    def environment_supported?(settings, logger)
         | 
| 53 55 | 
             
                      # TODO add tests?
         | 
| 54 56 | 
             
                      unless settings.dynamic_instrumentation.internal.development
         | 
| 55 57 | 
             
                        if Datadog::Core::Environment::Execution.development?
         | 
| 56 | 
            -
                           | 
| 58 | 
            +
                          logger.warn("di: development environment detected; not enabling dynamic instrumentation")
         | 
| 57 59 | 
             
                          return false
         | 
| 58 60 | 
             
                        end
         | 
| 59 61 | 
             
                      end
         | 
| 60 | 
            -
                      if RUBY_ENGINE != 'ruby' | 
| 61 | 
            -
                         | 
| 62 | 
            +
                      if RUBY_ENGINE != 'ruby'
         | 
| 63 | 
            +
                        logger.warn("di: cannot enable dynamic instrumentation: MRI is required, but running on #{RUBY_ENGINE}")
         | 
| 64 | 
            +
                        return false
         | 
| 65 | 
            +
                      end
         | 
| 66 | 
            +
                      if RUBY_VERSION < '2.6'
         | 
| 67 | 
            +
                        logger.warn("di: cannot enable dynamic instrumentation: Ruby 2.6+ is required, but running on #{RUBY_VERSION}")
         | 
| 62 68 | 
             
                        return false
         | 
| 63 69 | 
             
                      end
         | 
| 64 70 | 
             
                      true
         | 
| @@ -166,10 +166,20 @@ module Datadog | |
| 166 166 | 
             
                            # being sent out by the probe notifier worker) and creates a
         | 
| 167 167 | 
             
                            # possibility of dropping payloads if the queue gets too long.
         | 
| 168 168 | 
             
                            option :min_send_interval do |o|
         | 
| 169 | 
            -
                              o.type : | 
| 169 | 
            +
                              o.type :float
         | 
| 170 170 | 
             
                              o.default 3
         | 
| 171 171 | 
             
                            end
         | 
| 172 172 |  | 
| 173 | 
            +
                            # Number of snapshots that can be stored in the probe
         | 
| 174 | 
            +
                            # notifier worker queue. Larger capacity runs the risk of
         | 
| 175 | 
            +
                            # creating snapshots that exceed the agent's request size
         | 
| 176 | 
            +
                            # limit. Smaller capacity increases the risk of dropping
         | 
| 177 | 
            +
                            # snapshots.
         | 
| 178 | 
            +
                            option :snapshot_queue_capacity do |o|
         | 
| 179 | 
            +
                              o.type :int
         | 
| 180 | 
            +
                              o.default 100
         | 
| 181 | 
            +
                            end
         | 
| 182 | 
            +
             | 
| 173 183 | 
             
                            # Enable dynamic instrumentation in development environments.
         | 
| 174 184 | 
             
                            # Currently DI does not fully implement support for code
         | 
| 175 185 | 
             
                            # unloading and reloading, and is not supported in
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Datadog
         | 
| 4 | 
            +
              module DI
         | 
| 5 | 
            +
                module Contrib
         | 
| 6 | 
            +
                  # Railtie class initializes dynamic instrumentation contrib code
         | 
| 7 | 
            +
                  # in Rails environments.
         | 
| 8 | 
            +
                  class Railtie < Rails::Railtie
         | 
| 9 | 
            +
                    initializer 'datadog.dynamic_instrumentation.initialize' do |app|
         | 
| 10 | 
            +
                      Contrib.load_now
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative '../core/contrib/rails/utils'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Datadog
         | 
| 6 | 
            +
              module DI
         | 
| 7 | 
            +
                module Contrib
         | 
| 8 | 
            +
                  module_function def load_now_or_later
         | 
| 9 | 
            +
                    if Datadog::Core::Contrib::Rails::Utils.railtie_supported?
         | 
| 10 | 
            +
                      require_relative 'contrib/railtie'
         | 
| 11 | 
            +
                    else
         | 
| 12 | 
            +
                      load_now
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # This method can be called more than once, to attempt to load
         | 
| 17 | 
            +
                  # DI components that depend on third-party libraries after additional
         | 
| 18 | 
            +
                  # dependencies are loaded (or potentially loaded).
         | 
| 19 | 
            +
                  module_function def load_now
         | 
| 20 | 
            +
                    if defined?(ActiveRecord::Base)
         | 
| 21 | 
            +
                      require_relative 'contrib/active_record'
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
    
        data/lib/datadog/di/error.rb
    CHANGED
    
    | @@ -27,6 +27,11 @@ module Datadog | |
| 27 27 | 
             
                  class DITargetNotDefined < Error
         | 
| 28 28 | 
             
                  end
         | 
| 29 29 |  | 
| 30 | 
            +
                  # Attempting to instrument a line and the file containing the line
         | 
| 31 | 
            +
                  # was loaded prior to code tracking being enabled.
         | 
| 32 | 
            +
                  class DITargetNotInRegistry < Error
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 30 35 | 
             
                  # Raised when trying to install a probe whose installation failed
         | 
| 31 36 | 
             
                  # earlier in the same process. This exception should contain the
         | 
| 32 37 | 
             
                  # original exception report from initial installation attempt.
         | 
| @@ -1,8 +1,8 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            require_relative '../core/utils/time'
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 5 | 
            +
            # rubocop:disable Lint/AssignmentInCondition
         | 
| 6 6 |  | 
| 7 7 | 
             
            module Datadog
         | 
| 8 8 | 
             
              module DI
         | 
| @@ -115,22 +115,21 @@ module Datadog | |
| 115 115 | 
             
                              depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
         | 
| 116 116 | 
             
                              attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
         | 
| 117 117 | 
             
                          end
         | 
| 118 | 
            -
                           | 
| 118 | 
            +
                          start_time = Core::Utils::Time.get_time
         | 
| 119 119 | 
             
                          # Under Ruby 2.6 we cannot just call super(*args, **kwargs)
         | 
| 120 120 | 
             
                          # for methods defined via method_missing.
         | 
| 121 | 
            -
                           | 
| 122 | 
            -
                             | 
| 123 | 
            -
                               | 
| 124 | 
            -
                                super(*args, **kwargs, &target_block)
         | 
| 125 | 
            -
                              else
         | 
| 126 | 
            -
                                super(*args, &target_block)
         | 
| 127 | 
            -
                              end
         | 
| 128 | 
            -
                            elsif kwargs.any?
         | 
| 129 | 
            -
                              super(**kwargs, &target_block)
         | 
| 121 | 
            +
                          rv = if args.any?
         | 
| 122 | 
            +
                            if kwargs.any?
         | 
| 123 | 
            +
                              super(*args, **kwargs, &target_block)
         | 
| 130 124 | 
             
                            else
         | 
| 131 | 
            -
                              super(&target_block)
         | 
| 125 | 
            +
                              super(*args, &target_block)
         | 
| 132 126 | 
             
                            end
         | 
| 127 | 
            +
                          elsif kwargs.any?
         | 
| 128 | 
            +
                            super(**kwargs, &target_block)
         | 
| 129 | 
            +
                          else
         | 
| 130 | 
            +
                            super(&target_block)
         | 
| 133 131 | 
             
                          end
         | 
| 132 | 
            +
                          duration = Core::Utils::Time.get_time - start_time
         | 
| 134 133 | 
             
                          # The method itself is not part of the stack trace because
         | 
| 135 134 | 
             
                          # we are getting the stack trace from outside of the method.
         | 
| 136 135 | 
             
                          # Add the method in manually as the top frame.
         | 
| @@ -246,11 +245,12 @@ module Datadog | |
| 246 245 | 
             
                          #
         | 
| 247 246 | 
             
                          # If the requested file is not in code tracker's registry,
         | 
| 248 247 | 
             
                          # or the code tracker does not exist at all,
         | 
| 249 | 
            -
                          # do not attempt to  | 
| 248 | 
            +
                          # do not attempt to instrument now.
         | 
| 250 249 | 
             
                          # The caller should add the line to the list of pending lines
         | 
| 251 250 | 
             
                          # to instrument and install the hook when the file in
         | 
| 252 251 | 
             
                          # question is loaded (and hopefully, by then code tracking
         | 
| 253 252 | 
             
                          # is active, otherwise the line will never be instrumented.)
         | 
| 253 | 
            +
                          raise_if_probe_in_loaded_features(probe)
         | 
| 254 254 | 
             
                          raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
         | 
| 255 255 | 
             
                        end
         | 
| 256 256 | 
             
                      end
         | 
| @@ -258,6 +258,7 @@ module Datadog | |
| 258 258 | 
             
                      # Same as previous comment, if untargeted trace points are not
         | 
| 259 259 | 
             
                      # explicitly defined, and we do not have code tracking, do not
         | 
| 260 260 | 
             
                      # instrument the method.
         | 
| 261 | 
            +
                      raise_if_probe_in_loaded_features(probe)
         | 
| 261 262 | 
             
                      raise Error::DITargetNotDefined, "File not in code tracker registry: #{probe.file}"
         | 
| 262 263 | 
             
                    end
         | 
| 263 264 |  | 
| @@ -303,13 +304,13 @@ module Datadog | |
| 303 304 | 
             
                        end
         | 
| 304 305 | 
             
                      rescue => exc
         | 
| 305 306 | 
             
                        raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 306 | 
            -
                        logger. | 
| 307 | 
            +
                        logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
         | 
| 307 308 | 
             
                        telemetry&.report(exc, description: "Unhandled exception in line trace point")
         | 
| 308 309 | 
             
                        # TODO test this path
         | 
| 309 310 | 
             
                      end
         | 
| 310 311 | 
             
                    rescue => exc
         | 
| 311 312 | 
             
                      raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 312 | 
            -
                      logger. | 
| 313 | 
            +
                      logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
         | 
| 313 314 | 
             
                      telemetry&.report(exc, description: "Unhandled exception in line trace point")
         | 
| 314 315 | 
             
                      # TODO test this path
         | 
| 315 316 | 
             
                    end
         | 
| @@ -355,7 +356,7 @@ module Datadog | |
| 355 356 | 
             
                      hook_line(probe, &block)
         | 
| 356 357 | 
             
                    else
         | 
| 357 358 | 
             
                      # TODO add test coverage for this path
         | 
| 358 | 
            -
                      logger. | 
| 359 | 
            +
                      logger.debug { "di: unknown probe type to hook: #{probe}" }
         | 
| 359 360 | 
             
                    end
         | 
| 360 361 | 
             
                  end
         | 
| 361 362 |  | 
| @@ -366,7 +367,7 @@ module Datadog | |
| 366 367 | 
             
                      unhook_line(probe)
         | 
| 367 368 | 
             
                    else
         | 
| 368 369 | 
             
                      # TODO add test coverage for this path
         | 
| 369 | 
            -
                      logger. | 
| 370 | 
            +
                      logger.debug { "di: unknown probe type to unhook: #{probe}" }
         | 
| 370 371 | 
             
                    end
         | 
| 371 372 | 
             
                  end
         | 
| 372 373 |  | 
| @@ -374,6 +375,26 @@ module Datadog | |
| 374 375 |  | 
| 375 376 | 
             
                  attr_reader :lock
         | 
| 376 377 |  | 
| 378 | 
            +
                  def raise_if_probe_in_loaded_features(probe)
         | 
| 379 | 
            +
                    return unless probe.file
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                    # If the probe file is in the list of loaded files
         | 
| 382 | 
            +
                    # (as per $LOADED_FEATURES, using either exact or suffix match),
         | 
| 383 | 
            +
                    # raise an error indicating that
         | 
| 384 | 
            +
                    # code tracker is missing the loaded file because the file
         | 
| 385 | 
            +
                    # won't be loaded again (DI only works in production environments
         | 
| 386 | 
            +
                    # that do not normally reload code).
         | 
| 387 | 
            +
                    if $LOADED_FEATURES.include?(probe.file)
         | 
| 388 | 
            +
                      raise Error::DITargetNotInRegistry, "File loaded but is not in code tracker registry: #{probe.file}"
         | 
| 389 | 
            +
                    end
         | 
| 390 | 
            +
                    # Ths is an expensive check
         | 
| 391 | 
            +
                    $LOADED_FEATURES.each do |path|
         | 
| 392 | 
            +
                      if Utils.path_matches_suffix?(path, probe.file)
         | 
| 393 | 
            +
                        raise Error::DITargetNotInRegistry, "File matching probe path (#{probe.file}) was loaded and is not in code tracker registry: #{path}"
         | 
| 394 | 
            +
                      end
         | 
| 395 | 
            +
                    end
         | 
| 396 | 
            +
                  end
         | 
| 397 | 
            +
             | 
| 377 398 | 
             
                  # TODO test that this resolves qualified names e.g. A::B
         | 
| 378 399 | 
             
                  def symbolize_class_name(cls_name)
         | 
| 379 400 | 
             
                    Object.const_get(cls_name)
         | 
| @@ -1,12 +1,10 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            # Require 'datadog/di/ | 
| 3 | 
            +
            # Require 'datadog/di/preload' early in the application boot process to
         | 
| 4 4 | 
             
            # enable dynamic instrumentation for third-party libraries used by the
         | 
| 5 5 | 
             
            # application.
         | 
| 6 6 |  | 
| 7 | 
            -
            require_relative ' | 
| 8 | 
            -
            require_relative '../tracing/contrib'
         | 
| 9 | 
            -
            require_relative '../di'
         | 
| 7 | 
            +
            require_relative 'base'
         | 
| 10 8 |  | 
| 11 9 | 
             
            # Code tracking is required for line probes to work; see the comments
         | 
| 12 10 | 
             
            # on the activate_tracking methods in di.rb for further details.
         | 
| @@ -32,7 +32,7 @@ module Datadog | |
| 32 32 | 
             
                      install_pending_method_probes(tp.self)
         | 
| 33 33 | 
             
                    rescue => exc
         | 
| 34 34 | 
             
                      raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 35 | 
            -
                      logger. | 
| 35 | 
            +
                      logger.debug { "di: unhandled exception in definition trace point: #{exc.class}: #{exc}" }
         | 
| 36 36 | 
             
                      telemetry&.report(exc, description: "Unhandled exception in definition trace point")
         | 
| 37 37 | 
             
                      # TODO test this path
         | 
| 38 38 | 
             
                    end
         | 
| @@ -120,7 +120,7 @@ module Datadog | |
| 120 120 | 
             
                      # In "propagate all exceptions" mode we will try to instrument again.
         | 
| 121 121 | 
             
                      raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 122 122 |  | 
| 123 | 
            -
                      logger. | 
| 123 | 
            +
                      logger.debug { "di: error processing probe configuration: #{exc.class}: #{exc}" }
         | 
| 124 124 | 
             
                      telemetry&.report(exc, description: "Error processing probe configuration")
         | 
| 125 125 | 
             
                      # TODO report probe as failed to agent since we won't attempt to
         | 
| 126 126 | 
             
                      # install it again.
         | 
| @@ -160,7 +160,7 @@ module Datadog | |
| 160 160 | 
             
                            raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 161 161 | 
             
                            # Silence all exceptions?
         | 
| 162 162 | 
             
                            # TODO should we propagate here and rescue upstream?
         | 
| 163 | 
            -
                            logger. | 
| 163 | 
            +
                            logger.debug { "di: error removing probe #{probe.id}: #{exc.class}: #{exc}" }
         | 
| 164 164 | 
             
                            telemetry&.report(exc, description: "Error removing probe")
         | 
| 165 165 | 
             
                          end
         | 
| 166 166 | 
             
                        end
         | 
| @@ -190,7 +190,7 @@ module Datadog | |
| 190 190 | 
             
                            rescue => exc
         | 
| 191 191 | 
             
                              raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 192 192 |  | 
| 193 | 
            -
                              logger. | 
| 193 | 
            +
                              logger.debug { "di: error installing probe after class is defined: #{exc.class}: #{exc}" }
         | 
| 194 194 | 
             
                              telemetry&.report(exc, description: "Error installing probe after class is defined")
         | 
| 195 195 | 
             
                            end
         | 
| 196 196 | 
             
                          end
         | 
| @@ -141,8 +141,8 @@ module Datadog | |
| 141 141 | 
             
                        version: 2,
         | 
| 142 142 | 
             
                      },
         | 
| 143 143 | 
             
                      # TODO add tests that the trace/span id is correctly propagated
         | 
| 144 | 
            -
                      "dd.trace_id":  | 
| 145 | 
            -
                      "dd.span_id":  | 
| 144 | 
            +
                      "dd.trace_id": active_trace&.id&.to_s,
         | 
| 145 | 
            +
                      "dd.span_id": active_span&.id&.to_s,
         | 
| 146 146 | 
             
                      ddsource: 'dd_debugger',
         | 
| 147 147 | 
             
                      message: probe.template && evaluate_template(probe.template,
         | 
| 148 148 | 
             
                        duration: duration ? duration * 1000 : 0),
         | 
| @@ -150,6 +150,8 @@ module Datadog | |
| 150 150 | 
             
                    }
         | 
| 151 151 | 
             
                  end
         | 
| 152 152 |  | 
| 153 | 
            +
                  private
         | 
| 154 | 
            +
             | 
| 153 155 | 
             
                  def build_status(probe, message:, status:)
         | 
| 154 156 | 
             
                    {
         | 
| 155 157 | 
             
                      service: settings.service,
         | 
| @@ -200,6 +202,18 @@ module Datadog | |
| 200 202 | 
             
                      map[name] = value
         | 
| 201 203 | 
             
                    end
         | 
| 202 204 | 
             
                  end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                  def active_trace
         | 
| 207 | 
            +
                    if defined?(Datadog::Tracing)
         | 
| 208 | 
            +
                      Datadog::Tracing.active_trace
         | 
| 209 | 
            +
                    end
         | 
| 210 | 
            +
                  end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  def active_span
         | 
| 213 | 
            +
                    if defined?(Datadog::Tracing)
         | 
| 214 | 
            +
                      Datadog::Tracing.active_span
         | 
| 215 | 
            +
                    end
         | 
| 216 | 
            +
                  end
         | 
| 203 217 | 
             
                end
         | 
| 204 218 | 
             
              end
         | 
| 205 219 | 
             
            end
         | 
| @@ -77,7 +77,7 @@ module Datadog | |
| 77 77 | 
             
                        rescue => exc
         | 
| 78 78 | 
             
                          raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 79 79 |  | 
| 80 | 
            -
                          logger. | 
| 80 | 
            +
                          logger.debug { "di: error in probe notifier worker: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
         | 
| 81 81 | 
             
                          telemetry&.report(exc, description: "Error in probe notifier worker")
         | 
| 82 82 | 
             
                        end
         | 
| 83 83 | 
             
                        @lock.synchronize do
         | 
| @@ -183,9 +183,8 @@ module Datadog | |
| 183 183 | 
             
                    define_method("add_#{event_type}") do |event|
         | 
| 184 184 | 
             
                      @lock.synchronize do
         | 
| 185 185 | 
             
                        queue = send("#{event_type}_queue")
         | 
| 186 | 
            -
                         | 
| 187 | 
            -
             | 
| 188 | 
            -
                          logger.warn("#{self.class.name}: dropping #{event_type} because queue is full")
         | 
| 186 | 
            +
                        if queue.length > settings.dynamic_instrumentation.internal.snapshot_queue_capacity
         | 
| 187 | 
            +
                          logger.debug { "di: #{self.class.name}: dropping #{event_type} because queue is full" }
         | 
| 189 188 | 
             
                        else
         | 
| 190 189 | 
             
                          queue << event
         | 
| 191 190 | 
             
                        end
         | 
| @@ -242,7 +241,7 @@ module Datadog | |
| 242 241 | 
             
                          end
         | 
| 243 242 | 
             
                        rescue => exc
         | 
| 244 243 | 
             
                          raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
         | 
| 245 | 
            -
                          logger. | 
| 244 | 
            +
                          logger.debug { "di: failed to send #{event_name}: #{exc.class}: #{exc} (at #{exc.backtrace.first})" }
         | 
| 246 245 | 
             
                          # Should we report this error to telemetry? Most likely failure
         | 
| 247 246 | 
             
                          # to send is due to a network issue, and trying to send a
         | 
| 248 247 | 
             
                          # telemetry message would also fail.
         | 
| @@ -253,7 +252,7 @@ module Datadog | |
| 253 252 | 
             
                      # Normally the queue should only be consumed in this method,
         | 
| 254 253 | 
             
                      # however if anyone consumes it elsewhere we don't want to block
         | 
| 255 254 | 
             
                      # while consuming it here. Rescue ThreadError and return.
         | 
| 256 | 
            -
                      logger. | 
| 255 | 
            +
                      logger.debug { "di: unexpected #{event_name} queue underflow - consumed elsewhere?" }
         | 
| 257 256 | 
             
                      telemetry&.report(exc, description: "Unexpected #{event_name} queue underflow")
         | 
| 258 257 | 
             
                    ensure
         | 
| 259 258 | 
             
                      @lock.synchronize do
         |