datadog 2.43.0 → 2.44.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 +44 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +36 -5
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +65 -31
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +25 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +1 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +4 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +3 -3
- data/ext/libdatadog_api/di.c +8 -3
- data/ext/libdatadog_api/extconf.rb +1 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +3 -1
- data/ext/libdatadog_api/otel_thread_context.c +38 -17
- data/ext/libdatadog_extconf_helpers.rb +14 -9
- data/lib/datadog/ai_guard/component.rb +17 -8
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +13 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +4 -60
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +25 -11
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +58 -57
- data/lib/datadog/core/configuration/components.rb +9 -1
- data/lib/datadog/core/configuration/settings.rb +5 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
- data/lib/datadog/core/remote/client/capabilities.rb +10 -4
- data/lib/datadog/core/remote/component.rb +9 -2
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/di/instrumenter.rb +1 -1
- data/lib/datadog/di/probe_manager.rb +10 -5
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +57 -1
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +118 -0
- data/lib/datadog/open_feature/native_evaluator.rb +1 -14
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/symbol_database/extractor.rb +84 -102
- data/lib/datadog/tracing/component.rb +2 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +9 -5
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/otel_thread_context.rb +76 -11
- data/lib/datadog/tracing/tracer.rb +15 -2
- data/lib/datadog/tracing/transport/native.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
- data/lib/datadog/tracing/transport/traces.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +23 -7
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
|
@@ -5,6 +5,25 @@ module Datadog
|
|
|
5
5
|
module Configuration
|
|
6
6
|
# A settings class for the OpenFeature component.
|
|
7
7
|
module Settings
|
|
8
|
+
DEFAULT_POLL_INTERVAL_SECONDS = 30
|
|
9
|
+
DEFAULT_REQUEST_TIMEOUT_SECONDS = 5
|
|
10
|
+
DEFAULT_INITIALIZATION_TIMEOUT_MS = 30_000
|
|
11
|
+
|
|
12
|
+
MAX_REQUEST_TIMEOUT_SECONDS = 300
|
|
13
|
+
MAX_INITIALIZATION_TIMEOUT_MS = 2_147_483_647
|
|
14
|
+
|
|
15
|
+
def self.configure_integer_option(option, environment_variable:, default:, setting_name:)
|
|
16
|
+
option.type :int
|
|
17
|
+
option.env environment_variable
|
|
18
|
+
option.env_parser do |value|
|
|
19
|
+
Integer(value, 10)
|
|
20
|
+
rescue ArgumentError
|
|
21
|
+
Datadog.logger.warn("#{setting_name} must be an integer; using the default")
|
|
22
|
+
default
|
|
23
|
+
end
|
|
24
|
+
option.default default
|
|
25
|
+
end
|
|
26
|
+
|
|
8
27
|
def self.extended(base)
|
|
9
28
|
base = base.singleton_class unless base.is_a?(Class)
|
|
10
29
|
add_settings!(base)
|
|
@@ -15,10 +34,19 @@ module Datadog
|
|
|
15
34
|
# Steep does not update `self` for this `class_eval` block.
|
|
16
35
|
# @type self: Datadog::Core::Configuration::Base::_DslContext
|
|
17
36
|
settings :open_feature do
|
|
37
|
+
# Deprecated switch retained for existing Remote Configuration adopters.
|
|
18
38
|
option :enabled do |o|
|
|
19
39
|
o.type :bool
|
|
20
40
|
o.env "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED"
|
|
21
41
|
o.default false
|
|
42
|
+
o.after_set do |_, _, precedence|
|
|
43
|
+
next if precedence == Core::Configuration::Option::Precedence::DEFAULT
|
|
44
|
+
|
|
45
|
+
Datadog.logger.warn(
|
|
46
|
+
"DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED is deprecated; use " \
|
|
47
|
+
"DD_FEATURE_FLAGS_ENABLED and DD_FEATURE_FLAGS_CONFIGURATION_SOURCE instead"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
22
50
|
end
|
|
23
51
|
|
|
24
52
|
# Opt-in gate for APM feature-flag span enrichment. When enabled,
|
|
@@ -42,6 +70,96 @@ module Datadog
|
|
|
42
70
|
o.default true
|
|
43
71
|
end
|
|
44
72
|
end
|
|
73
|
+
|
|
74
|
+
settings :feature_flags do
|
|
75
|
+
option :enabled do |o|
|
|
76
|
+
o.type :bool
|
|
77
|
+
o.env "DD_FEATURE_FLAGS_ENABLED"
|
|
78
|
+
o.default true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
option :configuration_source do |o|
|
|
82
|
+
o.type :string
|
|
83
|
+
o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE"
|
|
84
|
+
o.default "agentless"
|
|
85
|
+
o.setter do |value|
|
|
86
|
+
value.to_s.strip.downcase
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
settings :agentless do
|
|
91
|
+
option :base_url do |o|
|
|
92
|
+
o.type :string, nilable: true
|
|
93
|
+
o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL"
|
|
94
|
+
o.skip_telemetry true
|
|
95
|
+
o.setter do |value|
|
|
96
|
+
next unless value
|
|
97
|
+
|
|
98
|
+
stripped_value = value.to_s.strip
|
|
99
|
+
stripped_value.empty? ? nil : stripped_value
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
option :poll_interval_seconds do |o|
|
|
104
|
+
Settings.configure_integer_option(
|
|
105
|
+
o,
|
|
106
|
+
environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS",
|
|
107
|
+
default: Settings::DEFAULT_POLL_INTERVAL_SECONDS,
|
|
108
|
+
setting_name: "Feature Flags agentless poll interval",
|
|
109
|
+
)
|
|
110
|
+
o.setter do |value|
|
|
111
|
+
if value.is_a?(Integer) && value > 0
|
|
112
|
+
value
|
|
113
|
+
else
|
|
114
|
+
Datadog.logger.warn(
|
|
115
|
+
"Feature Flags agentless poll interval must be positive; using the default"
|
|
116
|
+
)
|
|
117
|
+
Settings::DEFAULT_POLL_INTERVAL_SECONDS
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
option :request_timeout_seconds do |o|
|
|
123
|
+
Settings.configure_integer_option(
|
|
124
|
+
o,
|
|
125
|
+
environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS",
|
|
126
|
+
default: Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
|
127
|
+
setting_name: "Feature Flags agentless request timeout",
|
|
128
|
+
)
|
|
129
|
+
o.setter do |value|
|
|
130
|
+
if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_REQUEST_TIMEOUT_SECONDS
|
|
131
|
+
value
|
|
132
|
+
else
|
|
133
|
+
Datadog.logger.warn(
|
|
134
|
+
"Feature Flags agentless request timeout must be within (0, " \
|
|
135
|
+
"#{Settings::MAX_REQUEST_TIMEOUT_SECONDS}]; using the default"
|
|
136
|
+
)
|
|
137
|
+
Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
option :initialization_timeout_ms do |o|
|
|
144
|
+
Settings.configure_integer_option(
|
|
145
|
+
o,
|
|
146
|
+
environment_variable: "DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS",
|
|
147
|
+
default: Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS,
|
|
148
|
+
setting_name: "Feature Flags provider initialization timeout",
|
|
149
|
+
)
|
|
150
|
+
o.setter do |value|
|
|
151
|
+
if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_INITIALIZATION_TIMEOUT_MS
|
|
152
|
+
value
|
|
153
|
+
else
|
|
154
|
+
Datadog.logger.warn(
|
|
155
|
+
"Feature Flags provider initialization timeout must be within (0, " \
|
|
156
|
+
"#{Settings::MAX_INITIALIZATION_TIMEOUT_MS}]; using the default"
|
|
157
|
+
)
|
|
158
|
+
Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
45
163
|
end
|
|
46
164
|
end
|
|
47
165
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
3
|
require_relative "../core/feature_flags"
|
|
5
4
|
require_relative "ext"
|
|
6
5
|
require_relative "resolution_details"
|
|
@@ -16,7 +15,7 @@ module Datadog
|
|
|
16
15
|
# in the format expected by `libdatadog` without any modifications
|
|
17
16
|
def initialize(configuration)
|
|
18
17
|
@configuration = Core::FeatureFlags::Configuration.new(configuration)
|
|
19
|
-
@observe_full_evaluation_data =
|
|
18
|
+
@observe_full_evaluation_data = @configuration.observe_full_evaluation_data
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
attr_reader :observe_full_evaluation_data
|
|
@@ -42,18 +41,6 @@ module Datadog
|
|
|
42
41
|
|
|
43
42
|
private
|
|
44
43
|
|
|
45
|
-
# Parse observe_full_evaluation_data from the top level of the UFC JSON (a sibling
|
|
46
|
-
# of `environment`). Absent, null, or wrong-typed values return false.
|
|
47
|
-
def parse_observe_full_evaluation_data(configuration)
|
|
48
|
-
return false unless configuration.is_a?(String) && !configuration.empty?
|
|
49
|
-
|
|
50
|
-
parsed = JSON.parse(configuration)
|
|
51
|
-
parsed.is_a?(Hash) && parsed["observeFullEvaluationData"] == true
|
|
52
|
-
rescue
|
|
53
|
-
# This secondary policy parse must not reject configuration accepted by the native evaluator.
|
|
54
|
-
false
|
|
55
|
-
end
|
|
56
|
-
|
|
57
44
|
def build_resolution_details(result, default_value)
|
|
58
45
|
ResolutionDetails.new(
|
|
59
46
|
value: result.variant.nil? ? default_value : result.value,
|
|
@@ -19,11 +19,11 @@ module Datadog
|
|
|
19
19
|
FFE_PRODUCTS
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def receivers(telemetry)
|
|
22
|
+
def receivers(telemetry, component_provider:)
|
|
23
23
|
matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
|
|
24
24
|
receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
|
|
25
|
-
|
|
26
|
-
next unless
|
|
25
|
+
component = component_provider.call
|
|
26
|
+
next unless component
|
|
27
27
|
|
|
28
28
|
changes.each do |change|
|
|
29
29
|
content = repository[change.path]
|
|
@@ -38,7 +38,7 @@ module Datadog
|
|
|
38
38
|
when :insert, :update
|
|
39
39
|
begin
|
|
40
40
|
# @type var content: Core::Remote::Configuration::Content
|
|
41
|
-
|
|
41
|
+
component.reconfigure!(read_content(content))
|
|
42
42
|
content.applied
|
|
43
43
|
rescue EvaluationEngine::ReconfigurationError => e
|
|
44
44
|
content.errored("Error applying OpenFeature configuration: #{e.class}: #{e.message}")
|
|
@@ -46,7 +46,7 @@ module Datadog
|
|
|
46
46
|
when :delete
|
|
47
47
|
# NOTE: For now, we treat deletion as clearing the configuration
|
|
48
48
|
# In a multi-config scenario, we might track configs per path
|
|
49
|
-
|
|
49
|
+
component.reconfigure!(nil)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -12,23 +12,24 @@ module Datadog
|
|
|
12
12
|
def create_resource
|
|
13
13
|
resource_attributes = {}
|
|
14
14
|
|
|
15
|
-
@settings.tags&.each do |key, value|
|
|
15
|
+
@settings.tags&.each do |key, value|
|
|
16
16
|
otel_key = case key
|
|
17
17
|
when "service" then "service.name"
|
|
18
|
-
when "env"
|
|
18
|
+
when "env", "deployment.environment.name", "deployment.environment" then next
|
|
19
19
|
when "version" then "service.version"
|
|
20
20
|
else key
|
|
21
21
|
end
|
|
22
22
|
resource_attributes[otel_key] = value
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
resource_attributes["service.name"] = @settings.service_without_fallback || resource_attributes["service.name"] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME
|
|
26
|
-
|
|
27
|
-
resource_attributes["
|
|
25
|
+
resource_attributes["service.name"] = @settings.service_without_fallback || resource_attributes["service.name"] || Datadog::Core::Environment::Ext::FALLBACK_SERVICE_NAME
|
|
26
|
+
environment = @settings.env || @settings.tags["env"] || @settings.tags["deployment.environment.name"] || @settings.tags["deployment.environment"]
|
|
27
|
+
resource_attributes["deployment.environment.name"] = environment if environment
|
|
28
|
+
resource_attributes["service.version"] = @settings.version if @settings.version
|
|
28
29
|
|
|
29
|
-
hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings)
|
|
30
|
+
hostname = Datadog::Core::Environment::Socket.resolved_hostname(@settings)
|
|
30
31
|
if hostname
|
|
31
|
-
if hostname == @settings.hostname
|
|
32
|
+
if hostname == @settings.hostname
|
|
32
33
|
resource_attributes["host.name"] = hostname
|
|
33
34
|
elsif !resource_attributes.key?("host.name")
|
|
34
35
|
resource_attributes["host.name"] = hostname
|
|
@@ -41,9 +42,12 @@ module Datadog
|
|
|
41
42
|
# Returns the signal-specific option value when explicitly set,
|
|
42
43
|
# otherwise falls back to the general OTLP exporter config or computed_default.
|
|
43
44
|
def config_or_exporter_fallback(signal:, option_name:, computed_default: nil)
|
|
44
|
-
signal_settings =
|
|
45
|
+
signal_settings = case signal
|
|
46
|
+
when :logs then @settings.opentelemetry.logs
|
|
47
|
+
when :metrics then @settings.opentelemetry.metrics
|
|
48
|
+
end
|
|
45
49
|
if signal_settings.using_default?(option_name)
|
|
46
|
-
@settings.opentelemetry.exporter.public_send(option_name) || computed_default
|
|
50
|
+
@settings.opentelemetry.exporter.public_send(option_name) || computed_default
|
|
47
51
|
else
|
|
48
52
|
signal_settings.public_send(option_name)
|
|
49
53
|
end
|
|
@@ -51,7 +51,7 @@ module Datadog
|
|
|
51
51
|
# profiler overhead!
|
|
52
52
|
dynamic_sampling_rate_enabled: true,
|
|
53
53
|
skip_idle_samples_for_testing: false,
|
|
54
|
-
idle_sampling_helper: IdleSamplingHelper.new
|
|
54
|
+
idle_sampling_helper: IdleSamplingHelper.new
|
|
55
55
|
)
|
|
56
56
|
unless dynamic_sampling_rate_enabled
|
|
57
57
|
Datadog.logger.warn(
|
|
@@ -98,7 +98,7 @@ module Datadog
|
|
|
98
98
|
|
|
99
99
|
Datadog.logger.debug { "Starting thread for: #{self}" }
|
|
100
100
|
|
|
101
|
-
@idle_sampling_helper.start
|
|
101
|
+
@idle_sampling_helper.start(self)
|
|
102
102
|
|
|
103
103
|
@worker_thread = Thread.new do
|
|
104
104
|
Thread.current.name = self.class.name
|
|
@@ -10,7 +10,6 @@ module Datadog
|
|
|
10
10
|
class IdleSamplingHelper
|
|
11
11
|
# @rbs @worker_thread: untyped
|
|
12
12
|
# @rbs @start_stop_mutex: ::Thread::Mutex
|
|
13
|
-
# @rbs @thread_context_collector: Datadog::Profiling::Collectors::ThreadContext?
|
|
14
13
|
|
|
15
14
|
private
|
|
16
15
|
|
|
@@ -18,15 +17,14 @@ module Datadog
|
|
|
18
17
|
|
|
19
18
|
public
|
|
20
19
|
|
|
21
|
-
#: (
|
|
22
|
-
def initialize
|
|
20
|
+
#: () -> void
|
|
21
|
+
def initialize
|
|
23
22
|
@worker_thread = nil
|
|
24
23
|
@start_stop_mutex = Mutex.new
|
|
25
|
-
@thread_context_collector = thread_context_collector
|
|
26
24
|
end
|
|
27
25
|
|
|
28
|
-
#: () ->
|
|
29
|
-
def start
|
|
26
|
+
#: (CpuAndWallTimeWorker cpu_and_wall_time_worker) -> true?
|
|
27
|
+
def start(cpu_and_wall_time_worker)
|
|
30
28
|
@start_stop_mutex.synchronize do
|
|
31
29
|
return if @worker_thread&.alive?
|
|
32
30
|
|
|
@@ -39,7 +37,7 @@ module Datadog
|
|
|
39
37
|
@worker_thread = Thread.new do
|
|
40
38
|
Thread.current.name = self.class.name
|
|
41
39
|
|
|
42
|
-
self.class._native_idle_sampling_loop(self,
|
|
40
|
+
self.class._native_idle_sampling_loop(self, cpu_and_wall_time_worker)
|
|
43
41
|
|
|
44
42
|
Datadog.logger.debug("IdleSamplingHelper thread stopping cleanly")
|
|
45
43
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
@@ -43,7 +43,7 @@ module Datadog
|
|
|
43
43
|
# These are expected failures — no logging needed.
|
|
44
44
|
#
|
|
45
45
|
# 2. **Method-level rescues** (`rescue => e` with logging):
|
|
46
|
-
# Catch failures in
|
|
46
|
+
# Catch failures in build_class_method_scopes, find_source_file, etc. Log at debug
|
|
47
47
|
# for post-hoc diagnosis, return nil or empty array. One bad method/module
|
|
48
48
|
# doesn't kill the entire class extraction.
|
|
49
49
|
#
|
|
@@ -133,16 +133,16 @@ module Datadog
|
|
|
133
133
|
return nil unless Module === mod
|
|
134
134
|
mod_name = safe_mod_name(mod)
|
|
135
135
|
return nil unless mod_name
|
|
136
|
-
|
|
137
|
-
return nil unless user_code_module?(mod)
|
|
136
|
+
return nil unless user_code_module_name?(mod)
|
|
138
137
|
|
|
139
138
|
source_file = find_source_file(mod)
|
|
140
139
|
return nil unless source_file
|
|
140
|
+
return nil unless user_code_path?(source_file)
|
|
141
141
|
|
|
142
142
|
inner_scope = if Class === mod
|
|
143
|
-
extract_class_scope(mod)
|
|
143
|
+
extract_class_scope(mod, source_file)
|
|
144
144
|
else
|
|
145
|
-
extract_module_scope(mod)
|
|
145
|
+
extract_module_scope(mod, source_file)
|
|
146
146
|
end
|
|
147
147
|
|
|
148
148
|
wrap_in_file_scope(source_file, [inner_scope])
|
|
@@ -275,10 +275,12 @@ module Datadog
|
|
|
275
275
|
false
|
|
276
276
|
end
|
|
277
277
|
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
|
|
278
|
+
# Return whether +mod+ passes the name-based user-code exclusions
|
|
279
|
+
# (named, outside Datadog, not a Ruby root class). Does not resolve
|
|
280
|
+
# a source file.
|
|
281
|
+
# @param mod [Module]
|
|
282
|
+
# @return [Boolean]
|
|
283
|
+
def user_code_module_name?(mod)
|
|
282
284
|
mod_name = safe_mod_name(mod)
|
|
283
285
|
return false unless mod_name
|
|
284
286
|
|
|
@@ -297,6 +299,15 @@ module Datadog
|
|
|
297
299
|
return false if mod.equal?(Object) || mod.equal?(BasicObject) ||
|
|
298
300
|
mod.equal?(Kernel) || mod.equal?(Module) || mod.equal?(Class)
|
|
299
301
|
|
|
302
|
+
true
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Check if module is from user code (not gems or stdlib)
|
|
306
|
+
# @param mod [Module] The module to check
|
|
307
|
+
# @return [Boolean] true if user code
|
|
308
|
+
def user_code_module?(mod)
|
|
309
|
+
return false unless user_code_module_name?(mod)
|
|
310
|
+
|
|
300
311
|
source_file = find_source_file(mod)
|
|
301
312
|
return false unless source_file
|
|
302
313
|
|
|
@@ -331,6 +342,25 @@ module Datadog
|
|
|
331
342
|
true
|
|
332
343
|
end
|
|
333
344
|
|
|
345
|
+
# Resolve the UnboundMethod +mod+ itself declares for +method_name+,
|
|
346
|
+
# skipping prepended wrapper modules (e.g. a DI method-logpoint wrapper)
|
|
347
|
+
# that shadow it.
|
|
348
|
+
#
|
|
349
|
+
# @param mod [Module] Module or class expected to declare the method
|
|
350
|
+
# @param method_name [Symbol] Name of the method to resolve
|
|
351
|
+
# @return [UnboundMethod] Method whose owner is +mod+, or the
|
|
352
|
+
# prepend-resolved method when +mod+ owns no method in the super chain
|
|
353
|
+
def declared_instance_method(mod, method_name)
|
|
354
|
+
original_method = mod.instance_method(method_name)
|
|
355
|
+
method = original_method
|
|
356
|
+
until method.owner.equal?(mod)
|
|
357
|
+
super_method = method.super_method
|
|
358
|
+
return original_method unless super_method
|
|
359
|
+
method = super_method
|
|
360
|
+
end
|
|
361
|
+
method
|
|
362
|
+
end
|
|
363
|
+
|
|
334
364
|
# Find source file for a module.
|
|
335
365
|
# Prefers user code paths over gem/stdlib paths. ActiveRecord models have
|
|
336
366
|
# generated methods (autosave callbacks) whose source is in the gem, but
|
|
@@ -349,7 +379,7 @@ module Datadog
|
|
|
349
379
|
|
|
350
380
|
# Try instance methods first
|
|
351
381
|
mod.instance_methods(false).each do |method_name|
|
|
352
|
-
method = mod
|
|
382
|
+
method = declared_instance_method(mod, method_name)
|
|
353
383
|
location = method.source_location
|
|
354
384
|
next unless location
|
|
355
385
|
|
|
@@ -357,10 +387,15 @@ module Datadog
|
|
|
357
387
|
return path if user_code_path?(path)
|
|
358
388
|
|
|
359
389
|
fallback ||= path # steep:ignore
|
|
390
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
391
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
392
|
+
@logger.debug { "symdb: error resolving #{safe_mod_name(mod)}##{method_name}: #{e.class}: #{e.message}" }
|
|
360
393
|
end
|
|
361
394
|
|
|
362
395
|
# Try singleton methods
|
|
363
396
|
mod.singleton_methods(false).each do |method_name|
|
|
397
|
+
# Method-logpoint probes prepend instance methods only, so singleton
|
|
398
|
+
# methods are never shadowed by a DI wrapper.
|
|
364
399
|
method = mod.method(method_name)
|
|
365
400
|
location = method.source_location
|
|
366
401
|
next unless location
|
|
@@ -369,6 +404,9 @@ module Datadog
|
|
|
369
404
|
return path if user_code_path?(path)
|
|
370
405
|
|
|
371
406
|
fallback ||= path # steep:ignore
|
|
407
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
408
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
409
|
+
@logger.debug { "symdb: error resolving #{safe_mod_name(mod)}.#{method_name}: #{e.class}: #{e.message}" }
|
|
372
410
|
end
|
|
373
411
|
|
|
374
412
|
# Use const_source_location to find where this class/module is declared.
|
|
@@ -460,10 +498,9 @@ module Datadog
|
|
|
460
498
|
# Extract MODULE scope (without file_hash — that belongs on the FILE root scope).
|
|
461
499
|
# Does not include nested classes — nesting is handled by extract_all via FQN splitting.
|
|
462
500
|
# @param mod [Module] The module
|
|
501
|
+
# @param source_file [String] Source file resolved by the caller
|
|
463
502
|
# @return [Scope] The module scope
|
|
464
|
-
def extract_module_scope(mod)
|
|
465
|
-
source_file = find_source_file(mod)
|
|
466
|
-
|
|
503
|
+
def extract_module_scope(mod, source_file)
|
|
467
504
|
Scope.new(
|
|
468
505
|
scope_type: "MODULE",
|
|
469
506
|
name: safe_mod_name(mod),
|
|
@@ -476,11 +513,11 @@ module Datadog
|
|
|
476
513
|
|
|
477
514
|
# Extract CLASS scope
|
|
478
515
|
# @param klass [Class] The class
|
|
516
|
+
# @param source_file [String] Source file resolved by the caller
|
|
479
517
|
# @return [Scope] The class scope
|
|
480
|
-
def extract_class_scope(klass)
|
|
481
|
-
|
|
482
|
-
start_line, end_line =
|
|
483
|
-
source_file = find_source_file(klass)
|
|
518
|
+
def extract_class_scope(klass, source_file)
|
|
519
|
+
method_scopes = build_class_method_scopes(klass)
|
|
520
|
+
start_line, end_line = line_range_from_scopes(method_scopes)
|
|
484
521
|
|
|
485
522
|
Scope.new(
|
|
486
523
|
scope_type: "CLASS",
|
|
@@ -489,36 +526,20 @@ module Datadog
|
|
|
489
526
|
start_line: start_line,
|
|
490
527
|
end_line: end_line,
|
|
491
528
|
language_specifics: build_class_language_specifics(klass),
|
|
492
|
-
scopes:
|
|
529
|
+
scopes: method_scopes,
|
|
493
530
|
symbols: extract_scope_symbols(klass)
|
|
494
531
|
)
|
|
495
532
|
end
|
|
496
533
|
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
# @param
|
|
501
|
-
# @param methods [Array<Symbol>] Method names
|
|
534
|
+
# Derive a [start_line, end_line] range from method scopes, ignoring
|
|
535
|
+
# UNKNOWN sentinel values. The end is the max of method end_lines so a
|
|
536
|
+
# multi-line method's last line is not truncated to its start_line.
|
|
537
|
+
# @param method_scopes [Array<Scope>]
|
|
502
538
|
# @return [Array<Integer, Integer>] [start_line, end_line]
|
|
503
|
-
def
|
|
504
|
-
starts =
|
|
505
|
-
ends =
|
|
506
|
-
|
|
507
|
-
method = klass.instance_method(method_name)
|
|
508
|
-
location = method.source_location
|
|
509
|
-
next unless location && location[0]
|
|
510
|
-
starts << location[1]
|
|
511
|
-
_ranges, method_end = extract_targetable_lines(method, location[1])
|
|
512
|
-
ends << method_end
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
return [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE] if starts.empty?
|
|
516
|
-
|
|
517
|
-
[starts.min, ends.max]
|
|
518
|
-
rescue Exception => e # standard:disable Lint/RescueException
|
|
519
|
-
Datadog::DI.reraise_if_fatal(e)
|
|
520
|
-
@logger.debug { "symdb: error calculating line range for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
521
|
-
[UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE]
|
|
539
|
+
def line_range_from_scopes(method_scopes)
|
|
540
|
+
starts = method_scopes.map(&:start_line).reject { |line| line == UNKNOWN_MIN_LINE }
|
|
541
|
+
ends = method_scopes.map(&:end_line).reject { |line| line == UNKNOWN_MAX_LINE }
|
|
542
|
+
[starts.min || UNKNOWN_MIN_LINE, ends.max || UNKNOWN_MAX_LINE]
|
|
522
543
|
end
|
|
523
544
|
|
|
524
545
|
# Build language specifics for CLASS
|
|
@@ -566,66 +587,33 @@ module Datadog
|
|
|
566
587
|
{}
|
|
567
588
|
end
|
|
568
589
|
|
|
569
|
-
#
|
|
570
|
-
#
|
|
571
|
-
# @
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
# Get all instance methods (public, protected, private)
|
|
576
|
-
all_instance_methods = klass.instance_methods(false) +
|
|
590
|
+
# Build a METHOD scope for each user-code instance method +klass+
|
|
591
|
+
# declares.
|
|
592
|
+
# @param klass [Class]
|
|
593
|
+
# @return [Array<Scope>]
|
|
594
|
+
def build_class_method_scopes(klass)
|
|
595
|
+
method_names = (klass.instance_methods(false) +
|
|
577
596
|
klass.protected_instance_methods(false) +
|
|
578
|
-
klass.private_instance_methods(false)
|
|
579
|
-
all_instance_methods.uniq!
|
|
597
|
+
klass.private_instance_methods(false)).uniq
|
|
580
598
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
599
|
+
Core::Utils::EnumerableCompat.filter_map(method_names) do |method_name|
|
|
600
|
+
method = declared_instance_method(klass, method_name)
|
|
601
|
+
location = method.source_location
|
|
602
|
+
next unless location
|
|
603
|
+
next unless user_code_path?(location[0])
|
|
585
604
|
|
|
586
|
-
|
|
605
|
+
build_instance_method_scope(klass, method_name, method)
|
|
606
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
607
|
+
Datadog::DI.reraise_if_fatal(e)
|
|
608
|
+
@logger.debug { "symdb: error resolving #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
|
|
609
|
+
nil
|
|
610
|
+
end
|
|
587
611
|
rescue Exception => e # standard:disable Lint/RescueException
|
|
588
612
|
Datadog::DI.reraise_if_fatal(e)
|
|
589
613
|
@logger.debug { "symdb: failed to extract methods from #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
|
|
590
614
|
[]
|
|
591
615
|
end
|
|
592
616
|
|
|
593
|
-
# Extract a single method scope
|
|
594
|
-
# @param klass [Class] The class
|
|
595
|
-
# @param method_name [Symbol] Method name
|
|
596
|
-
# @param method_type [Symbol] :instance or :class
|
|
597
|
-
# @return [Scope, nil] Method scope or nil
|
|
598
|
-
def extract_method_scope(klass, method_name, method_type)
|
|
599
|
-
method = klass.instance_method(method_name)
|
|
600
|
-
location = method.source_location
|
|
601
|
-
|
|
602
|
-
return nil unless location # Skip methods without source location
|
|
603
|
-
|
|
604
|
-
source_file, line = location
|
|
605
|
-
return nil unless user_code_path?(source_file) # Skip gem/stdlib methods
|
|
606
|
-
|
|
607
|
-
targetable_lines, end_line = extract_targetable_lines(method, line)
|
|
608
|
-
|
|
609
|
-
Scope.new(
|
|
610
|
-
scope_type: "METHOD",
|
|
611
|
-
name: method_name.to_s,
|
|
612
|
-
source_file: source_file,
|
|
613
|
-
start_line: line,
|
|
614
|
-
end_line: end_line,
|
|
615
|
-
targetable_lines: targetable_lines,
|
|
616
|
-
language_specifics: {
|
|
617
|
-
visibility: method_visibility(klass, method_name),
|
|
618
|
-
method_type: method_type.to_s,
|
|
619
|
-
arity: method.arity,
|
|
620
|
-
},
|
|
621
|
-
symbols: extract_method_parameters(method)
|
|
622
|
-
)
|
|
623
|
-
rescue Exception => e # standard:disable Lint/RescueException
|
|
624
|
-
Datadog::DI.reraise_if_fatal(e)
|
|
625
|
-
@logger.debug { "symdb: failed to extract method #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
|
|
626
|
-
nil
|
|
627
|
-
end
|
|
628
|
-
|
|
629
617
|
# Get method visibility
|
|
630
618
|
# @param klass [Class] The class
|
|
631
619
|
# @param method_name [Symbol] Method name
|
|
@@ -808,7 +796,7 @@ module Datadog
|
|
|
808
796
|
# visibilities without an intermediate merged array.
|
|
809
797
|
[mod.instance_methods(false), mod.private_instance_methods(false)].each do |method_names|
|
|
810
798
|
method_names.each do |method_name|
|
|
811
|
-
method = mod
|
|
799
|
+
method = declared_instance_method(mod, method_name)
|
|
812
800
|
loc = method.source_location
|
|
813
801
|
next unless loc
|
|
814
802
|
next unless user_code_path?(loc[0])
|
|
@@ -852,7 +840,7 @@ module Datadog
|
|
|
852
840
|
# live only as long as the tree node holds them; they are released when
|
|
853
841
|
# convert_tree_to_scope finishes building the file's Scope.
|
|
854
842
|
method_infos = Core::Utils::EnumerableCompat.filter_map(method_names) do |name|
|
|
855
|
-
method = mod
|
|
843
|
+
method = declared_instance_method(mod, name)
|
|
856
844
|
# Pass 1 (build_per_file_index) recorded this method under file_path.
|
|
857
845
|
# If the method has been redefined in another file between the two
|
|
858
846
|
# passes (e.g. a class reopened during a Rails reload while extract_all
|
|
@@ -989,13 +977,7 @@ module Datadog
|
|
|
989
977
|
# Recurse into child scopes (nested modules/classes)
|
|
990
978
|
child_scopes = node[:children].values.map { |child| convert_node_to_scope(child) }
|
|
991
979
|
|
|
992
|
-
|
|
993
|
-
# method end. Using max(start_line) would underreport the class's end_line for
|
|
994
|
-
# classes whose last method spans multiple lines.
|
|
995
|
-
starts = method_scopes.map(&:start_line).reject { |l| l == UNKNOWN_MIN_LINE } # steep:ignore
|
|
996
|
-
ends = method_scopes.map(&:end_line).reject { |l| l == UNKNOWN_MAX_LINE } # steep:ignore
|
|
997
|
-
start_line = starts.empty? ? UNKNOWN_MIN_LINE : starts.min
|
|
998
|
-
end_line = ends.empty? ? UNKNOWN_MAX_LINE : ends.max
|
|
980
|
+
start_line, end_line = line_range_from_scopes(method_scopes)
|
|
999
981
|
|
|
1000
982
|
# Extract symbols (constants, class variables) if we have the actual module object
|
|
1001
983
|
symbols = node[:mod] ? extract_scope_symbols(node[:mod]) : []
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "otel_thread_context"
|
|
3
4
|
require_relative "tracer"
|
|
4
5
|
require_relative "flush"
|
|
5
6
|
require_relative "sync_writer"
|
|
@@ -42,6 +43,7 @@ module Datadog
|
|
|
42
43
|
default_service: settings.service,
|
|
43
44
|
enabled: settings.tracing.enabled,
|
|
44
45
|
logger: logger,
|
|
46
|
+
otel_thread_context: OTelThreadContext.build(settings.tracing),
|
|
45
47
|
trace_flush: trace_flush,
|
|
46
48
|
sampler: sampler_delegator,
|
|
47
49
|
span_sampler: build_span_sampler(settings),
|
|
@@ -13,6 +13,7 @@ module Datadog
|
|
|
13
13
|
ENV_BAGGAGE_TAG_KEYS = "DD_TRACE_BAGGAGE_TAG_KEYS"
|
|
14
14
|
ENV_TRACE_ID_128_BIT_GENERATION_ENABLED = "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED"
|
|
15
15
|
ENV_NATIVE_SPAN_EVENTS = "DD_TRACE_NATIVE_SPAN_EVENTS"
|
|
16
|
+
ENV_OTEL_CONTEXT_ENABLED = "DD_TRACE_OTEL_CTX_ENABLED"
|
|
16
17
|
ENV_RESOURCE_RENAMING_ENABLED = "DD_TRACE_RESOURCE_RENAMING_ENABLED"
|
|
17
18
|
ENV_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT = "DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT"
|
|
18
19
|
ENV_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED = "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED"
|
|
@@ -255,6 +255,12 @@ module Datadog
|
|
|
255
255
|
o.type :bool
|
|
256
256
|
end
|
|
257
257
|
|
|
258
|
+
option :otel_thread_context_enabled do |o|
|
|
259
|
+
o.env Tracing::Configuration::Ext::ENV_OTEL_CONTEXT_ENABLED
|
|
260
|
+
o.default true
|
|
261
|
+
o.type :bool
|
|
262
|
+
end
|
|
263
|
+
|
|
258
264
|
# Configures an alternative trace transport behavior, where
|
|
259
265
|
# traces can be sent to the agent and backend before all spans
|
|
260
266
|
# have finished.
|