Module: Datadog::DI Private
- Defined in:
- lib/datadog/di.rb,
lib/datadog/di/base.rb,
lib/datadog/di/error.rb,
lib/datadog/di/probe.rb,
lib/datadog/di/utils.rb,
lib/datadog/di/logger.rb,
lib/datadog/di/remote.rb,
lib/datadog/di/context.rb,
lib/datadog/di/contrib.rb,
lib/datadog/di/redactor.rb,
lib/datadog/di/component.rb,
lib/datadog/di/extensions.rb,
lib/datadog/di/serializer.rb,
lib/datadog/di/el/compiler.rb,
lib/datadog/di/code_tracker.rb,
lib/datadog/di/el/evaluator.rb,
lib/datadog/di/instrumenter.rb,
lib/datadog/di/configuration.rb,
lib/datadog/di/el/expression.rb,
lib/datadog/di/probe_builder.rb,
lib/datadog/di/probe_manager.rb,
lib/datadog/di/capture_limits.rb,
lib/datadog/di/proc_responder.rb,
lib/datadog/di/transport/http.rb,
lib/datadog/di/contrib/railtie.rb,
lib/datadog/di/transport/input.rb,
lib/datadog/di/fatal_exceptions.rb,
lib/datadog/di/probe_repository.rb,
lib/datadog/di/probe_file_loader.rb,
lib/datadog/di/capture_expression.rb,
lib/datadog/di/transport/http/input.rb,
lib/datadog/di/probe_notifier_worker.rb,
lib/datadog/di/transport/diagnostics.rb,
lib/datadog/di/probe_file_loader/railtie.rb,
lib/datadog/di/probe_notification_builder.rb,
lib/datadog/di/transport/http/diagnostics.rb,
lib/datadog/di/capture_expression_evaluator.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.
Namespace for Datadog dynamic instrumentation.
Defined Under Namespace
Modules: Configuration, Contrib, EL, Extensions, ProbeBuilder, ProbeFileLoader, Remote, Transport, Utils Classes: CaptureExpression, CaptureExpressionEvaluator, CaptureLimits, CodeTracker, Component, Context, Error, Instrumenter, Logger, Probe, ProbeManager, ProbeNotificationBuilder, ProbeNotifierWorker, ProbeRepository, ProcResponder, Redactor, Serializer
Constant Summary collapse
- INSTRUMENTED_COUNTERS_LOCK =
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.
Mutex.new
- EXCEPTION_BACKTRACE_LOCATIONS =
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.
Captured at load time from Exception itself (not a subclass). Used to bypass subclass overrides of backtrace_locations.
This does NOT protect against monkeypatching Exception#backtrace_locations before dd-trace-rb loads — in that case we'd capture the monkeypatch. The practical threat model is customer subclasses overriding the method:
class MyError < StandardError def backtrace_locations; []; end endThe UnboundMethod bypasses subclass overrides: bind(exception).call always dispatches to the original Exception implementation.
Note: if the subclass overrides #backtrace (not #backtrace_locations), MRI's setup_exception skips storing the VM backtrace entirely — both returns nil. See EXCEPTION_BACKTRACE comment for details.
Exception.instance_method(:backtrace_locations)
- EXCEPTION_BACKTRACE =
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.
Same UnboundMethod trick for Exception#backtrace (Array
). Used as a fallback when backtrace_locations returns nil — which happens when someone calls Exception#set_backtrace with an Array . set_backtrace accepts Array
or nil. When called with strings, it replaces the VM-level backtrace: backtrace returns the new strings, but backtrace_locations returns nil because the VM cannot reconstruct Location objects from formatted strings. This occurs in exception wrapping patterns where a library catches an exception, creates a new one, and copies the original's string backtrace onto it via set_backtrace before re-raising. Ruby 3.4+ also allows set_backtrace(Array
), which preserves backtrace_locations — but older Rubies and most existing code use the string form. Like EXCEPTION_BACKTRACE_LOCATIONS, this UnboundMethod bypasses subclass overrides of #backtrace: bind(exception).call dispatches to Exception#backtrace regardless of what the subclass defines.
However, when a subclass overrides #backtrace, MRI's setup_exception (eval.c) calls the override via rb_get_backtrace during raise. If it gets a non-nil result, it skips storing the VM backtrace in @bt and at dispatch but reads nil from @bt because the data was never stored.
This constant is used as a fallback when backtrace_locations returns nil. In the common set_backtrace-with-strings case, no subclass override is involved and the fallback works. The only unrecoverable case: a subclass overrides #backtrace, the exception is raised normally, and set_backtrace is never called. Both @bt and message are still reported).
Exception.instance_method(:backtrace)
- LOCK =
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.
Mutex.new
- FATAL_EXCEPTION_CLASSES =
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.
Exception classes that catch-all rescues must never swallow: they signal that the process is being torn down or has run out of memory and have to propagate. SignalException covers Interrupt (Ctrl+C) and every other signal delivered as an exception.
[SystemExit, SignalException, NoMemoryError].freeze
Class Attribute Summary collapse
- .code_tracker ⇒ Object readonly private
Class Method Summary collapse
-
.activate_tracking ⇒ Object
private
Activates code tracking if possible.
-
.activate_tracking! ⇒ Object
private
Activates code tracking.
-
.add_current_component(component) ⇒ Object
private
To avoid potential races with DI::Component being added and removed, we maintain a list of the components.
-
.code_tracking_active? ⇒ Boolean
private
Returns whether code tracking is available.
-
.component ⇒ Object
private
This method is called from DI Remote handler to issue DI operations to the probe manager (add or remove probes).
-
.current_component ⇒ Object
private
DI code tracker is instantiated globally before the regular set of components is created, but the code tracker needs to call out to the "current" DI component to perform instrumentation when application code is loaded.
-
.deactivate_tracking! ⇒ Object
private
Deactivates code tracking.
- .enabled? ⇒ Boolean private
-
.file_iseqs ⇒ Array<RubyVM::InstructionSequence>
private
Returns iseqs that correspond to loaded files (filtering out eval'd code).
-
.instrumented_count(kind = nil) ⇒ Object
private
Track how many outstanding instrumentations are in DI.
- .instrumented_count_dec(kind) ⇒ Object private
- .instrumented_count_inc(kind) ⇒ Object private
- .remove_current_component(component) ⇒ Object private
-
.reraise_if_fatal(exc) ⇒ nil
private
Re-raise
excwhen it is fatal (see FATAL_EXCEPTION_CLASSES). -
.supported_runtime? ⇒ Boolean
private
Whether the current Ruby runtime can run dynamic instrumentation: MRI (CRuby) on Ruby 2.6 or later.
-
.unsupported_reason(settings = Datadog.configuration) ⇒ String?
private
Returns a human-readable reason why dynamic instrumentation cannot run under the given settings, or nil if all build-time preconditions are met.
Class Attribute Details
.code_tracker ⇒ Object (readonly)
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.
29 30 31 |
# File 'lib/datadog/di/base.rb', line 29 def code_tracker @code_tracker end |
Class Method Details
.activate_tracking ⇒ 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.
Activates code tracking if possible.
This method does nothing if invoked in an environment that does not implement required trace points for code tracking (MRI Ruby < 2.6, JRuby) and rescues any exceptions that may be raised by downstream DI code.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/datadog/di/base.rb', line 49 def activate_tracking # :script_compiled trace point was added in Ruby 2.6. return unless RubyVersion.is?(">= 2.6") begin # Activate code tracking by default because line trace points will not work # without it. Datadog::DI.activate_tracking! rescue Exception => exc # standard:disable Lint/RescueException Datadog::DI.reraise_if_fatal(exc) if defined?(Datadog.logger) Datadog.logger.warn { "di: Failed to activate code tracking for DI: #{exc.class}: #{exc.}" } else # We do not have Datadog logger potentially because DI code tracker is # being loaded early in application boot process and the rest of datadog # wasn't loaded yet. Output to standard error. warn("datadog: di: Failed to activate code tracking for DI: #{exc.class}: #{exc.}") end end end |
.activate_tracking! ⇒ 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.
Activates code tracking. Normally this method should be called when the application starts. If instrumenting third-party code, code tracking needs to be enabled before the third-party libraries are loaded. Any third-party code loaded before code tracking is activated will NOT be instrumentable using dynamic instrumentation.
TODO test that activating tracker multiple times preserves existing mappings in the registry
39 40 41 |
# File 'lib/datadog/di/base.rb', line 39 def activate_tracking! (@code_tracker ||= CodeTracker.new).start end |
.add_current_component(component) ⇒ 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.
To avoid potential races with DI::Component being added and removed, we maintain a list of the components. Normally the list should contain either zero or one component depending on whether DI is enabled in Datadog configuration. However, if a new instance of DI::Component is created while the previous instance is still running, we are guaranteed to not end up with no component when one is running.
110 111 112 113 114 |
# File 'lib/datadog/di/base.rb', line 110 def add_current_component(component) LOCK.synchronize do @current_components << component end end |
.code_tracking_active? ⇒ 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.
Returns whether code tracking is available. This method should be used instead of querying #code_tracker because the latter one may be nil.
84 85 86 |
# File 'lib/datadog/di/base.rb', line 84 def code_tracking_active? code_tracker&.active? || false end |
.component ⇒ 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.
This method is called from DI Remote handler to issue DI operations to the probe manager (add or remove probes).
When DI Remote is executing, Datadog.components should be initialized and we should be able to reference it to get to the DI component.
Given that we need the current_component anyway for code tracker,
perhaps we should delete the component method and just use
current_component in all cases.
172 173 174 |
# File 'lib/datadog/di.rb', line 172 def component Datadog.send(:components).dynamic_instrumentation end |
.current_component ⇒ 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.
DI code tracker is instantiated globally before the regular set of components is created, but the code tracker needs to call out to the "current" DI component to perform instrumentation when application code is loaded. Because this call may happen prior to Datadog components having been initialized, we maintain the "current component" which contains a reference to the most recently instantiated DI::Component. This way, if a DI component hasn't been instantiated, we do not try to reference Datadog.components. In other words, this method exists so that we never attempt to call Datadog.components from the code tracker.
98 99 100 101 102 |
# File 'lib/datadog/di/base.rb', line 98 def current_component LOCK.synchronize do @current_components.last end end |
.deactivate_tracking! ⇒ 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.
Deactivates code tracking. In normal usage of DI this method should never be called, however it is used by DI's test suite to reset state for individual tests.
Note that deactivating tracking clears out the registry, losing the ability to look up files that have been loaded into the process already.
77 78 79 |
# File 'lib/datadog/di/base.rb', line 77 def deactivate_tracking! code_tracker&.stop end |
.enabled? ⇒ 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.
70 71 72 |
# File 'lib/datadog/di.rb', line 70 def enabled? Datadog.configuration.dynamic_instrumentation.enabled end |
.file_iseqs ⇒ Array<RubyVM::InstructionSequence>
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.
Returns iseqs that correspond to loaded files (filtering out eval'd code).
There are several types of iseqs returned by all_iseqs:
- Eval'd code — these have a nil
absolute_pathand are filtered out here. - Whole-file iseqs — have
absolute_pathset andfirst_linenoof 0. Only available for a subset of loaded files (the full-file iseq may be garbage collected after loading completes). Easiest to work with since we just match the file path to the probe specification. - Per-method iseqs — have
absolute_pathset andfirst_lineno> 0. Often the only iseqs available for third-party code. Require identifying the correct iseq containing the target line, which may involve examining the iseq'strace_pointssincedefine_methodcan create nested, non-contiguous line ranges.
Note: the same line of code can appear in multiple iseqs (e.g. when
define_method is used inside a method). DI treats this as an error
since a probe must resolve to exactly one code location.
157 158 159 160 161 |
# File 'lib/datadog/di.rb', line 157 def file_iseqs all_iseqs.select do |iseq| iseq.absolute_path end end |
.instrumented_count(kind = nil) ⇒ 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.
Track how many outstanding instrumentations are in DI.
It is hard to find the actual instrumentations - there is no method provided by Ruby to list all trace points, and we would need to manually track our instrumentation modules for method probes. Plus, tracking the modules could create active references to instrumentation, which is not desired.
A simpler solution is to maintain a counter which is increased whenever a probe is installed and decreased when a probe is removed.
This counter does not include pending probes - being not installed, those pose no concerns to customer applications.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/datadog/di.rb', line 189 def instrumented_count(kind = nil) INSTRUMENTED_COUNTERS_LOCK.synchronize do if defined?(@instrumented_count) if kind validate_kind!(kind) @instrumented_count[kind] || 0 else @instrumented_count.inject(0) do |sum, (_kind, count)| sum + count end end else 0 end end end |
.instrumented_count_dec(kind) ⇒ 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.
214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/datadog/di.rb', line 214 def instrumented_count_dec(kind) validate_kind!(kind) INSTRUMENTED_COUNTERS_LOCK.synchronize do @instrumented_count = Hash.new(0) unless defined?(@instrumented_count) if @instrumented_count[kind] <= 0 Datadog.logger.debug { "di: attempting to decrease instrumented count below zero for #{kind}" } return end @instrumented_count[kind] -= 1 end end |
.instrumented_count_inc(kind) ⇒ 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.
206 207 208 209 210 211 212 |
# File 'lib/datadog/di.rb', line 206 def instrumented_count_inc(kind) validate_kind!(kind) INSTRUMENTED_COUNTERS_LOCK.synchronize do @instrumented_count = Hash.new(0) unless defined?(@instrumented_count) @instrumented_count[kind] += 1 end end |
.remove_current_component(component) ⇒ 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.
116 117 118 119 120 |
# File 'lib/datadog/di/base.rb', line 116 def remove_current_component(component) LOCK.synchronize do @current_components.delete(component) end end |
.reraise_if_fatal(exc) ⇒ nil
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.
Re-raise exc when it is fatal (see FATAL_EXCEPTION_CLASSES). Call this as
the first statement of a rescue Exception handler so that fatal
conditions are not accidentally swallowed by a broad rescue.
22 23 24 |
# File 'lib/datadog/di/fatal_exceptions.rb', line 22 def self.reraise_if_fatal(exc) raise exc if FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) } end |
.supported_runtime? ⇒ 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.
Whether the current Ruby runtime can run dynamic instrumentation: MRI (CRuby) on Ruby 2.6 or later.
119 120 121 |
# File 'lib/datadog/di.rb', line 119 def supported_runtime? unsupported_platform_reason.nil? end |
.unsupported_reason(settings = Datadog.configuration) ⇒ String?
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.
Returns a human-readable reason why dynamic instrumentation cannot run under the given settings, or nil if all build-time preconditions are met.
Single source of truth for the preconditions checked in Datadog::DI::Component.build and reported back by Datadog::DI::Remote.handle_rc_enablement when an implicit enablement signal arrives but the component was not built at startup. Checks are ordered from most-actionable to platform-constraint so the most useful reason wins.
The settings argument is optional so the helper can be called from contexts that don't have settings in scope (e.g. the RC handler).
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 |
# File 'lib/datadog/di.rb', line 88 def unsupported_reason(settings = Datadog.configuration) # Symmetric to the respond_to?(:remote) guard below: in unusual # configurations (test doubles, partial Settings) the DI namespace # may be absent. Returning a reason here lets callers — most # importantly Remote.handle_rc_enablement — emit the customer-facing # warn instead of raising NoMethodError on the unguarded access at # line 92 (`settings.dynamic_instrumentation.internal.development`). unless settings.respond_to?(:dynamic_instrumentation) return "dynamic instrumentation settings are not available" end unless settings.respond_to?(:remote) && settings.remote.enabled return "Remote Configuration is not enabled. See https://docs.datadoghq.com/agent/remote_config" end unless settings.dynamic_instrumentation.internal.development if Datadog::Core::Environment::Execution.development? return "development environment detected" end end if (reason = unsupported_platform_reason) return reason end unless respond_to?(:exception_message) return "C extension is not available" end nil end |