Module: Datadog::DI::Remote Private

Defined in:
lib/datadog/di/remote.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Provides an interface expected by the core Remote subsystem to receive DI-specific remote configuration.

In order to apply (i.e., act on) the configuration, we need the state stored under DI Component. Thus, this module forwards actual configuration application to the ProbeManager associated with the global DI Component.

Constant Summary collapse

PRODUCT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"LIVE_DEBUGGING"
CAPABILITIES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Declared here (not in Tracing::Remote::CAPABILITIES) so it is registered only with the gated DI block in Capabilities#register: when DI is explicitly disabled, or the runtime cannot run DI (JRuby, Ruby 2.5), that block — including this bit — is skipped. The enable signal itself is delivered in APM_TRACING payloads and routed here by Tracing::Remote.merge_and_apply_configs.

[
  1 << 38, # APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION: Implicit DI enablement
].freeze

Class Method Summary collapse

Class Method Details

.capabilitiesObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



34
35
36
# File 'lib/datadog/di/remote.rb', line 34

def capabilities
  CAPABILITIES
end

.explicitly_disabled?(settings = Datadog.configuration) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Symmetric to Component.explicitly_enabled? (see there for why using_default? rather than options.default_precedence?).

Canonical home for the explicit-disable check: it gates Component.build and the DI block in Core::Remote::Client::Capabilities#register. The latter runs before DI::Component is loaded, so the check lives here on DI::Remote (always required by capabilities.rb) rather than on DI::Component. The settings argument lets those startup callers pass the settings being configured; the RC handler omits it and reads the global config.

Parameters:

Returns:

  • (Boolean)

    true when the customer set DD_DYNAMIC_INSTRUMENTATION_ENABLED=false (or the programmatic equivalent), which blocks DI build and RC-driven enablement.



136
137
138
139
# File 'lib/datadog/di/remote.rb', line 136

def explicitly_disabled?(settings = Datadog.configuration)
  !settings.dynamic_instrumentation.using_default?(:enabled) &&
    !settings.dynamic_instrumentation.enabled
end

.handle_rc_enablement(enabled, repository = nil) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Entry point for the RC-driven DI enable/disable path.

Invoked from Tracing::Remote.merge_and_apply_configs when an APM_TRACING payload carries dynamic_instrumentation_enabled. Runs on the remote-config thread; never raises.

Parameters:

  • enabled (Boolean)

    desired state from RC: true to start DI, false to stop it. The DD_DYNAMIC_INSTRUMENTATION_ENABLED=false env var blocks an enable here (see explicitly_disabled?).

  • repository (Datadog::Core::Remote::Configuration::Repository, nil) (defaults to: nil)

    the RC repository, passed by Tracing::Remote.apply_lib_config so that a stopped->started transition can reconcile against probes that were delivered in an earlier poll. nil when called outside the RC dispatch path (e.g. unit tests), in which case no reconcile happens.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/datadog/di/remote.rb', line 53

def handle_rc_enablement(enabled, repository = nil)
  # allow_initialization: false because this runs on the remote-config
  # thread (a callback context). The default `true` would synchronously
  # build the entire component tree from the wrong thread if the RC
  # signal lands before Components#initialize completed.
  components = Datadog.send(:components, allow_initialization: false)
  component = components&.dynamic_instrumentation
  unless component
    # The component is nil because Component.build returned nil at
    # startup — a runtime precondition is not met (RC disabled, MRI
    # required, Ruby 2.6+ required, Rails dev env, C extension absent).
    # On disable, silently no-op: RC asking us to turn off something we
    # don't have is fine. On enable, warn with the reason: this is the
    # implicit-enablement counterpart to the warn-on-explicit message
    # at build time. The customer who clicked "create probe" in the UI
    # gets the same visibility a customer who set
    # DD_DYNAMIC_INSTRUMENTATION_ENABLED would have gotten at boot.
    if enabled
      if explicitly_disabled?
        Datadog.logger.warn(
          "di: cannot enable dynamic instrumentation via remote configuration " \
          "because DD_DYNAMIC_INSTRUMENTATION_ENABLED is explicitly set to false",
        )
      else
        reason = DI.unsupported_reason
        Datadog.logger.warn(
          "di: cannot enable dynamic instrumentation via remote configuration: " \
          "#{reason || "dynamic instrumentation was not initialized at startup"}",
        )
      end
    end
    return
  end

  if enabled
    if explicitly_disabled?
      Datadog.logger.warn(
        "di: ignoring implicit enablement signal from remote configuration " \
        "because DD_DYNAMIC_INSTRUMENTATION_ENABLED is explicitly set to false. " \
        "To allow remote enablement, unset DD_DYNAMIC_INSTRUMENTATION_ENABLED.",
      )
      return
    end
    # component is non-nil here only because Component.build's preconditions
    # passed, which is the same condition under which di/base.rb is loaded
    # and DI.activate_tracking is defined.
    DI.activate_tracking
    was_started = component.started?
    component.start!
    # A probe delivered in an earlier poll while the component was stopped
    # was dropped by #receivers (which ignores changes while !started?) and
    # never entered the probe repository. RC dispatch only re-delivers a
    # config when its content hash changes (Core::Remote::Client#apply_config
    # skips unchanged content), so the probe would otherwise stay dropped
    # until the customer edits it. On the stopped->started transition,
    # reconcile against the current LIVE_DEBUGGING contents so it installs now.
    replay_current_probes(repository, component) if repository && !was_started
  else
    component.stop!
  end
rescue => e
  Datadog.logger.debug { "di: error handling implicit enablement: #{e.class}: #{e.message}" }
  Datadog.send(:components, allow_initialization: false)&.telemetry&.report(
    e,
    description: "Error handling DI implicit enablement",
  )
end

.productsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



30
31
32
# File 'lib/datadog/di/remote.rb', line 30

def products
  [PRODUCT]
end

.receiver(products = [PRODUCT], &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



177
178
179
180
# File 'lib/datadog/di/remote.rb', line 177

def receiver(products = [PRODUCT], &block)
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(products)
  [Core::Remote::Dispatcher::Receiver.new(matcher, &block)]
end

.receivers(telemetry) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/datadog/di/remote.rb', line 141

def receivers(telemetry)
  receiver do |repository, changes|
    # DEV: Filter our by product. Given it will be very common
    # DEV: we can filter this out before we receive the data in this method.
    # DEV: Apply this refactor to AppSec as well if implemented.

    component = DI.component
    if component&.started?
      changes.each do |change|
        case change.type
        when :insert
          # A stopped->started reconcile (#replay_current_probes) may have
          # installed this probe earlier in the same dispatch — the enable
          # signal is carried by the Tracing receiver, which runs before the
          # DI receiver. Skip the redundant install rather than letting
          # probe_manager raise AlreadyInstrumented and report a false error.
          unless probe_in_content_known?(change.content, component) # steep:ignore NoMethod
            add_probe(change.content, component) # steep:ignore NoMethod
          end
        when :update
          # We do not implement updates at the moment, remove the
          # probe and reinstall.
          remove_probe(change.content, component) # steep:ignore NoMethod
          add_probe(change.content, component) # steep:ignore NoMethod
        when :delete
          remove_probe(change.previous, component) # steep:ignore NoMethod
        else
          # This really should never happen since we generate the
          # change types in the library.
          component.logger.debug { "di: unrecognized change type: #{change.type}" }
        end
      end
    end
  end
end