datadog 2.36.0 → 2.38.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 +74 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +29 -18
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
- data/ext/datadog_profiling_native_extension/collectors_stack.c +19 -24
- data/ext/datadog_profiling_native_extension/collectors_stack.h +0 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +348 -213
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +11 -3
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
- data/ext/datadog_profiling_native_extension/time_helpers.h +12 -7
- data/ext/libdatadog_api/di.c +76 -0
- data/ext/libdatadog_api/extconf.rb +6 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/api_client.rb +6 -4
- data/lib/datadog/appsec/api_security/route_extractor.rb +7 -4
- data/lib/datadog/appsec/configuration.rb +6 -0
- data/lib/datadog/appsec/context.rb +4 -2
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +5 -0
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/contrib/rack/input_peeker.rb +4 -17
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +7 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +4 -0
- data/lib/datadog/appsec/ext.rb +1 -0
- data/lib/datadog/appsec/metrics/collector.rb +18 -1
- data/lib/datadog/appsec/metrics/exporter.rb +9 -3
- data/lib/datadog/appsec/remote.rb +9 -3
- data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
- data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
- data/lib/datadog/appsec/route_normalizer.rb +80 -0
- data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
- data/lib/datadog/core/configuration/components.rb +82 -2
- data/lib/datadog/core/configuration/components_state.rb +6 -1
- data/lib/datadog/core/configuration/settings.rb +4 -7
- data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
- data/lib/datadog/core/evp.rb +11 -0
- data/lib/datadog/core/knuth_sampler.rb +3 -1
- data/lib/datadog/core/metrics/client.rb +1 -1
- data/lib/datadog/core/remote/client/capabilities.rb +34 -7
- data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
- data/lib/datadog/core/transport/http/builder.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/time.rb +5 -1
- data/lib/datadog/di/base.rb +3 -1
- data/lib/datadog/di/boot.rb +3 -0
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/code_tracker.rb +5 -2
- data/lib/datadog/di/component.rb +112 -38
- data/lib/datadog/di/configuration.rb +6 -0
- data/lib/datadog/di/context.rb +9 -3
- data/lib/datadog/di/el/compiler.rb +1 -1
- data/lib/datadog/di/error.rb +10 -0
- data/lib/datadog/di/fatal_exceptions.rb +26 -0
- data/lib/datadog/di/instrumenter.rb +412 -165
- data/lib/datadog/di/probe.rb +61 -2
- data/lib/datadog/di/probe_builder.rb +83 -1
- data/lib/datadog/di/probe_file_loader.rb +8 -3
- data/lib/datadog/di/probe_manager.rb +67 -9
- data/lib/datadog/di/probe_notification_builder.rb +52 -8
- data/lib/datadog/di/probe_notifier_worker.rb +52 -32
- data/lib/datadog/di/redactor.rb +16 -1
- data/lib/datadog/di/remote.rb +176 -15
- data/lib/datadog/di/serializer.rb +34 -15
- data/lib/datadog/di/transport/input.rb +8 -4
- data/lib/datadog/di.rb +63 -0
- data/lib/datadog/kit/appsec/events/v2.rb +60 -2
- data/lib/datadog/open_feature/component.rb +29 -6
- data/lib/datadog/open_feature/configuration.rb +8 -0
- data/lib/datadog/open_feature/ext.rb +2 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
- data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
- data/lib/datadog/open_feature/native_evaluator.rb +22 -0
- data/lib/datadog/open_feature/provider.rb +59 -27
- data/lib/datadog/open_feature/transport.rb +55 -1
- data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
- data/lib/datadog/opentelemetry/sdk.rb +19 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +6 -1
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +9 -1
- data/lib/datadog/profiling/component.rb +17 -0
- data/lib/datadog/symbol_database/component.rb +164 -47
- data/lib/datadog/symbol_database/configuration.rb +12 -4
- data/lib/datadog/symbol_database/extensions.rb +19 -0
- data/lib/datadog/symbol_database/extractor.rb +124 -87
- data/lib/datadog/symbol_database/file_hash.rb +3 -1
- data/lib/datadog/symbol_database/remote.rb +8 -3
- data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
- data/lib/datadog/symbol_database/uploader.rb +9 -2
- data/lib/datadog/symbol_database.rb +22 -0
- data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +12 -0
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +2 -3
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -12
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -2
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
- data/lib/datadog/tracing/distributed/baggage.rb +2 -1
- data/lib/datadog/tracing/ext.rb +9 -0
- data/lib/datadog/tracing/remote.rb +34 -3
- data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +1 -0
- metadata +25 -8
data/lib/datadog/di/remote.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'fatal_exceptions'
|
|
4
|
+
|
|
3
5
|
module Datadog
|
|
4
6
|
module DI
|
|
5
7
|
# Provides an interface expected by the core Remote subsystem to
|
|
@@ -15,14 +17,125 @@ module Datadog
|
|
|
15
17
|
class << self
|
|
16
18
|
PRODUCT = 'LIVE_DEBUGGING'
|
|
17
19
|
|
|
20
|
+
# Declared here (not in Tracing::Remote::CAPABILITIES) so it is
|
|
21
|
+
# registered only with the gated DI block in Capabilities#register:
|
|
22
|
+
# when DI is explicitly disabled, or the runtime cannot run DI
|
|
23
|
+
# (JRuby, Ruby 2.5), that block — including this bit — is skipped.
|
|
24
|
+
# The enable signal itself is delivered in APM_TRACING payloads and
|
|
25
|
+
# routed here by Tracing::Remote.process_config.
|
|
26
|
+
CAPABILITIES = [
|
|
27
|
+
1 << 38, # APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION: Implicit DI enablement
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
18
30
|
def products
|
|
19
|
-
# TODO: do not send our product on unsupported runtimes
|
|
20
|
-
# (Ruby 2.5 / JRuby)
|
|
21
31
|
[PRODUCT]
|
|
22
32
|
end
|
|
23
33
|
|
|
24
34
|
def capabilities
|
|
25
|
-
|
|
35
|
+
CAPABILITIES
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Entry point for the RC-driven DI enable/disable path.
|
|
39
|
+
#
|
|
40
|
+
# Invoked from {Datadog::Tracing::Remote.process_config} when an
|
|
41
|
+
# APM_TRACING payload carries `dynamic_instrumentation_enabled`. Runs
|
|
42
|
+
# on the remote-config thread; never raises.
|
|
43
|
+
#
|
|
44
|
+
# @param enabled [Boolean] desired state from RC: true to start DI,
|
|
45
|
+
# false to stop it. The `DD_DYNAMIC_INSTRUMENTATION_ENABLED=false`
|
|
46
|
+
# env var blocks an enable here (see {.explicitly_disabled?}).
|
|
47
|
+
# @param repository [Datadog::Core::Remote::Configuration::Repository, nil]
|
|
48
|
+
# the RC repository, passed by {Datadog::Tracing::Remote.process_config}
|
|
49
|
+
# so that a stopped->started transition can reconcile against probes that
|
|
50
|
+
# were delivered in an earlier poll. nil when called outside the RC
|
|
51
|
+
# dispatch path (e.g. unit tests), in which case no reconcile happens.
|
|
52
|
+
# @return [void]
|
|
53
|
+
def handle_rc_enablement(enabled, repository = nil)
|
|
54
|
+
# allow_initialization: false because this runs on the remote-config
|
|
55
|
+
# thread (a callback context). The default `true` would synchronously
|
|
56
|
+
# build the entire component tree from the wrong thread if the RC
|
|
57
|
+
# signal lands before Components#initialize completed.
|
|
58
|
+
components = Datadog.send(:components, allow_initialization: false)
|
|
59
|
+
component = components&.dynamic_instrumentation
|
|
60
|
+
unless component
|
|
61
|
+
# The component is nil because Component.build returned nil at
|
|
62
|
+
# startup — a runtime precondition is not met (RC disabled, MRI
|
|
63
|
+
# required, Ruby 2.6+ required, Rails dev env, C extension absent).
|
|
64
|
+
# On disable, silently no-op: RC asking us to turn off something we
|
|
65
|
+
# don't have is fine. On enable, warn with the reason: this is the
|
|
66
|
+
# implicit-enablement counterpart to the warn-on-explicit message
|
|
67
|
+
# at build time. The customer who clicked "create probe" in the UI
|
|
68
|
+
# gets the same visibility a customer who set
|
|
69
|
+
# DD_DYNAMIC_INSTRUMENTATION_ENABLED would have gotten at boot.
|
|
70
|
+
if enabled
|
|
71
|
+
if explicitly_disabled?
|
|
72
|
+
Datadog.logger.warn(
|
|
73
|
+
"di: cannot enable dynamic instrumentation via remote configuration " \
|
|
74
|
+
"because DD_DYNAMIC_INSTRUMENTATION_ENABLED is explicitly set to false",
|
|
75
|
+
)
|
|
76
|
+
else
|
|
77
|
+
reason = DI.unsupported_reason
|
|
78
|
+
Datadog.logger.warn(
|
|
79
|
+
"di: cannot enable dynamic instrumentation via remote configuration: " \
|
|
80
|
+
"#{reason || "dynamic instrumentation was not initialized at startup"}",
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if enabled
|
|
88
|
+
if explicitly_disabled?
|
|
89
|
+
Datadog.logger.warn(
|
|
90
|
+
"di: ignoring implicit enablement signal from remote configuration " \
|
|
91
|
+
"because DD_DYNAMIC_INSTRUMENTATION_ENABLED is explicitly set to false. " \
|
|
92
|
+
"To allow remote enablement, unset DD_DYNAMIC_INSTRUMENTATION_ENABLED.",
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
# component is non-nil here only because Component.build's preconditions
|
|
97
|
+
# passed, which is the same condition under which di/base.rb is loaded
|
|
98
|
+
# and DI.activate_tracking is defined.
|
|
99
|
+
DI.activate_tracking
|
|
100
|
+
was_started = component.started?
|
|
101
|
+
component.start!
|
|
102
|
+
# A probe delivered in an earlier poll while the component was stopped
|
|
103
|
+
# was dropped by #receivers (which ignores changes while !started?) and
|
|
104
|
+
# never entered the probe repository. RC dispatch only re-delivers a
|
|
105
|
+
# config when its content hash changes (Core::Remote::Client#apply_config
|
|
106
|
+
# skips unchanged content), so the probe would otherwise stay dropped
|
|
107
|
+
# until the customer edits it. On the stopped->started transition,
|
|
108
|
+
# reconcile against the current LIVE_DEBUGGING contents so it installs now.
|
|
109
|
+
replay_current_probes(repository, component) if repository && !was_started
|
|
110
|
+
else
|
|
111
|
+
component.stop!
|
|
112
|
+
end
|
|
113
|
+
rescue => e
|
|
114
|
+
Datadog.logger.debug { "di: error handling implicit enablement: #{e.class}: #{e.message}" }
|
|
115
|
+
Datadog.send(:components, allow_initialization: false)&.telemetry&.report(
|
|
116
|
+
e,
|
|
117
|
+
description: "Error handling DI implicit enablement",
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Symmetric to {DI::Component.explicitly_enabled?} (see there for why
|
|
122
|
+
# using_default? rather than options[:enabled].default_precedence?).
|
|
123
|
+
#
|
|
124
|
+
# Canonical home for the explicit-disable check: it gates
|
|
125
|
+
# {DI::Component.build} and the DI block in
|
|
126
|
+
# {Core::Remote::Client::Capabilities#register}. The latter runs before
|
|
127
|
+
# DI::Component is loaded, so the check lives here on DI::Remote (always
|
|
128
|
+
# required by capabilities.rb) rather than on DI::Component. The
|
|
129
|
+
# `settings` argument lets those startup callers pass the settings being
|
|
130
|
+
# configured; the RC handler omits it and reads the global config.
|
|
131
|
+
#
|
|
132
|
+
# @param settings [Datadog::Core::Configuration::Settings]
|
|
133
|
+
# @return [Boolean] true when the customer set
|
|
134
|
+
# `DD_DYNAMIC_INSTRUMENTATION_ENABLED=false` (or the programmatic
|
|
135
|
+
# equivalent), which blocks DI build and RC-driven enablement.
|
|
136
|
+
def explicitly_disabled?(settings = Datadog.configuration)
|
|
137
|
+
!settings.dynamic_instrumentation.using_default?(:enabled) &&
|
|
138
|
+
!settings.dynamic_instrumentation.enabled
|
|
26
139
|
end
|
|
27
140
|
|
|
28
141
|
def receivers(telemetry)
|
|
@@ -32,17 +145,18 @@ module Datadog
|
|
|
32
145
|
# DEV: Apply this refactor to AppSec as well if implemented.
|
|
33
146
|
|
|
34
147
|
component = DI.component
|
|
35
|
-
|
|
36
|
-
# only add DI product to remote config request if DI is enabled.
|
|
37
|
-
# Ideally, we should be injected with the DI component here
|
|
38
|
-
# rather than having to retrieve it from global state.
|
|
39
|
-
# If the component is nil for some reason, we also don't have a
|
|
40
|
-
# logger instance to report the issue.
|
|
41
|
-
if component
|
|
148
|
+
if component&.started?
|
|
42
149
|
changes.each do |change|
|
|
43
150
|
case change.type
|
|
44
151
|
when :insert
|
|
45
|
-
|
|
152
|
+
# A stopped->started reconcile (#replay_current_probes) may have
|
|
153
|
+
# installed this probe earlier in the same dispatch — the enable
|
|
154
|
+
# signal is carried by the Tracing receiver, which runs before the
|
|
155
|
+
# DI receiver. Skip the redundant install rather than letting
|
|
156
|
+
# probe_manager raise AlreadyInstrumented and report a false error.
|
|
157
|
+
unless probe_in_content_known?(change.content, component) # steep:ignore NoMethod
|
|
158
|
+
add_probe(change.content, component) # steep:ignore NoMethod
|
|
159
|
+
end
|
|
46
160
|
when :update
|
|
47
161
|
# We do not implement updates at the moment, remove the
|
|
48
162
|
# probe and reinstall.
|
|
@@ -89,7 +203,8 @@ module Datadog
|
|
|
89
203
|
# message.
|
|
90
204
|
# TODO assert content state (errored for this example)
|
|
91
205
|
content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
|
|
92
|
-
rescue => exc
|
|
206
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
207
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
93
208
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
94
209
|
|
|
95
210
|
component.logger.debug { "di: unhandled exception adding #{probe.type} probe at #{probe.location} (#{probe.id}) in DI remote receiver: #{exc.class}: #{exc.message}" }
|
|
@@ -113,8 +228,9 @@ module Datadog
|
|
|
113
228
|
# we need to note it as being current so that we do not
|
|
114
229
|
# try to remove instrumentation that is still supposed to be
|
|
115
230
|
# active.
|
|
116
|
-
#current_probe_ids[probe_spec.fetch('id')] = true
|
|
117
|
-
rescue => exc
|
|
231
|
+
# current_probe_ids[probe_spec.fetch('id')] = true
|
|
232
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
233
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
118
234
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
119
235
|
|
|
120
236
|
component.logger.debug { "di: unhandled exception handling a probe in DI remote receiver: #{exc.class}: #{exc.message}" }
|
|
@@ -124,6 +240,50 @@ module Datadog
|
|
|
124
240
|
content.errored("Error applying dynamic instrumentation configuration: #{exc.class}: #{exc.message}")
|
|
125
241
|
end
|
|
126
242
|
|
|
243
|
+
# Reconciles the DI probe repository against the LIVE_DEBUGGING configs
|
|
244
|
+
# currently held by the RC repository. Called on the stopped->started
|
|
245
|
+
# transition to install probes that arrived while DI was stopped: those
|
|
246
|
+
# were dropped by {.receivers} and RC will not re-dispatch them because
|
|
247
|
+
# their content hash is unchanged.
|
|
248
|
+
#
|
|
249
|
+
# Probes already tracked by the component (installed, pending, or failed)
|
|
250
|
+
# are skipped, so a probe handled in the same dispatch by {.receivers} is
|
|
251
|
+
# not added twice.
|
|
252
|
+
def replay_current_probes(repository, component)
|
|
253
|
+
repository.contents.each do |content|
|
|
254
|
+
next unless content.path.product == PRODUCT
|
|
255
|
+
|
|
256
|
+
begin
|
|
257
|
+
probe_id = parse_content(content)["id"]
|
|
258
|
+
rescue => exc
|
|
259
|
+
component.logger.debug { "di: skipping unparseable LIVE_DEBUGGING content on enable reconcile: #{exc.class}: #{exc.message}" }
|
|
260
|
+
next
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
next if probe_id && probe_known?(probe_id, component)
|
|
264
|
+
|
|
265
|
+
add_probe(content, component)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def probe_known?(probe_id, component)
|
|
270
|
+
repo = component.probe_manager.probe_repository
|
|
271
|
+
!!(repo.find_installed(probe_id) ||
|
|
272
|
+
repo.find_pending(probe_id) ||
|
|
273
|
+
repo.find_failed(probe_id))
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# True when the probe carried by +content+ is already tracked by the
|
|
277
|
+
# component. Returns false when the id cannot be determined, so add_probe
|
|
278
|
+
# still runs and reports the parse error through its own handling.
|
|
279
|
+
def probe_in_content_known?(content, component)
|
|
280
|
+
probe_id = parse_content(content)["id"]
|
|
281
|
+
!!(probe_id && probe_known?(probe_id, component))
|
|
282
|
+
rescue => exc
|
|
283
|
+
component.logger.debug { "di: could not check probe id for insert dedup: #{exc.class}: #{exc.message}" }
|
|
284
|
+
false
|
|
285
|
+
end
|
|
286
|
+
|
|
127
287
|
# This method does not mark +previous_content+ as succeeded or errored,
|
|
128
288
|
# because that content is from a previous RC response and has already
|
|
129
289
|
# been marked. Removal of probes happens when an RC entry disappears,
|
|
@@ -133,7 +293,8 @@ module Datadog
|
|
|
133
293
|
probe_spec = parse_content(previous_content)
|
|
134
294
|
probe_id = probe_spec.fetch('id')
|
|
135
295
|
component.probe_manager.remove_probe(probe_id)
|
|
136
|
-
rescue => exc
|
|
296
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
297
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
137
298
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
138
299
|
|
|
139
300
|
component.logger.debug { "di: unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc.message}" }
|
|
@@ -44,8 +44,11 @@ module Datadog
|
|
|
44
44
|
class Serializer
|
|
45
45
|
# Exception classes that should never be caught during serialization.
|
|
46
46
|
# These represent fatal conditions (signals, interrupts, system exit)
|
|
47
|
-
# that must propagate to the caller.
|
|
48
|
-
FATAL_EXCEPTION_CLASSES
|
|
47
|
+
# that must propagate to the caller. NoMemoryError is deliberately
|
|
48
|
+
# excluded (unlike DI::FATAL_EXCEPTION_CLASSES): serialization of large
|
|
49
|
+
# or deeply recursive objects can exhaust memory, and the serializer must
|
|
50
|
+
# return a safe stub rather than tear the process down.
|
|
51
|
+
SERIALIZABLE_FATAL_EXCEPTION_CLASSES = [SystemExit, SignalException].freeze
|
|
49
52
|
|
|
50
53
|
# Third-party library integration / custom serializers.
|
|
51
54
|
#
|
|
@@ -128,9 +131,12 @@ module Datadog
|
|
|
128
131
|
# in upstream code.
|
|
129
132
|
def serialize_args(args, kwargs, target_self,
|
|
130
133
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
|
131
|
-
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count
|
|
134
|
+
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count,
|
|
135
|
+
length: nil,
|
|
136
|
+
collection_size: nil)
|
|
132
137
|
combined = combine_args(args, kwargs, target_self)
|
|
133
|
-
serialize_vars(combined, depth: depth, attribute_count: attribute_count
|
|
138
|
+
serialize_vars(combined, depth: depth, attribute_count: attribute_count,
|
|
139
|
+
length: length, collection_size: collection_size)
|
|
134
140
|
end
|
|
135
141
|
|
|
136
142
|
# Serializes variables captured by a line probe.
|
|
@@ -139,9 +145,12 @@ module Datadog
|
|
|
139
145
|
# of executed code.
|
|
140
146
|
def serialize_vars(vars,
|
|
141
147
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
|
142
|
-
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count
|
|
148
|
+
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count,
|
|
149
|
+
length: nil,
|
|
150
|
+
collection_size: nil)
|
|
143
151
|
vars.each_with_object({}) do |(k, v), agg|
|
|
144
|
-
agg[k] = serialize_value(v, name: k, depth: depth, attribute_count: attribute_count
|
|
152
|
+
agg[k] = serialize_value(v, name: k, depth: depth, attribute_count: attribute_count,
|
|
153
|
+
length: length, collection_size: collection_size)
|
|
145
154
|
end
|
|
146
155
|
end
|
|
147
156
|
|
|
@@ -160,6 +169,8 @@ module Datadog
|
|
|
160
169
|
def serialize_value(value, name: nil,
|
|
161
170
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
|
162
171
|
attribute_count: nil,
|
|
172
|
+
length: nil,
|
|
173
|
+
collection_size: nil,
|
|
163
174
|
type: nil)
|
|
164
175
|
attribute_count ||= settings.dynamic_instrumentation.max_capture_attribute_count
|
|
165
176
|
cls = type || value.class
|
|
@@ -177,7 +188,9 @@ module Datadog
|
|
|
177
188
|
if condition
|
|
178
189
|
begin
|
|
179
190
|
condition_result = condition.call(value)
|
|
180
|
-
rescue => e
|
|
191
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
|
192
|
+
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| e.is_a?(klass) }
|
|
193
|
+
|
|
181
194
|
# If a custom serializer condition raises an exception (e.g., regex match
|
|
182
195
|
# against invalid UTF-8), skip it and continue with the next serializer.
|
|
183
196
|
# We don't want custom serializer conditions to break the entire serialization.
|
|
@@ -230,7 +243,8 @@ module Datadog
|
|
|
230
243
|
#
|
|
231
244
|
# Truncate binary data BEFORE escaping to avoid cutting mid-escape-sequence.
|
|
232
245
|
# For regular strings, the limit is applied to string length in characters.
|
|
233
|
-
|
|
246
|
+
length ||= settings.dynamic_instrumentation.max_capture_string_length
|
|
247
|
+
max = length
|
|
234
248
|
|
|
235
249
|
if value.encoding == Encoding::BINARY || !value.valid_encoding?
|
|
236
250
|
# Truncate binary data BEFORE escaping to avoid cutting mid-escape-sequence
|
|
@@ -258,7 +272,8 @@ module Datadog
|
|
|
258
272
|
if depth <= 0
|
|
259
273
|
serialized.update(notCapturedReason: "depth")
|
|
260
274
|
else
|
|
261
|
-
|
|
275
|
+
collection_size ||= settings.dynamic_instrumentation.max_capture_collection_size
|
|
276
|
+
max = collection_size
|
|
262
277
|
if max != 0 && value.length > max
|
|
263
278
|
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
|
264
279
|
# same steep failure with array slices.
|
|
@@ -266,7 +281,7 @@ module Datadog
|
|
|
266
281
|
value = value[0...max] || []
|
|
267
282
|
end
|
|
268
283
|
entries = value.map do |elt|
|
|
269
|
-
serialize_value(elt, depth: depth - 1)
|
|
284
|
+
serialize_value(elt, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count)
|
|
270
285
|
end
|
|
271
286
|
serialized.update(elements: entries)
|
|
272
287
|
end
|
|
@@ -274,7 +289,8 @@ module Datadog
|
|
|
274
289
|
if depth <= 0
|
|
275
290
|
serialized.update(notCapturedReason: "depth")
|
|
276
291
|
else
|
|
277
|
-
|
|
292
|
+
collection_size ||= settings.dynamic_instrumentation.max_capture_collection_size
|
|
293
|
+
max = collection_size
|
|
278
294
|
cur = 0
|
|
279
295
|
entries = []
|
|
280
296
|
value.each do |k, v|
|
|
@@ -283,7 +299,8 @@ module Datadog
|
|
|
283
299
|
break
|
|
284
300
|
end
|
|
285
301
|
cur += 1
|
|
286
|
-
entries << [serialize_value(k, depth: depth - 1
|
|
302
|
+
entries << [serialize_value(k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count),
|
|
303
|
+
serialize_value(v, name: k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count)]
|
|
287
304
|
end
|
|
288
305
|
serialized.update(entries: entries)
|
|
289
306
|
end
|
|
@@ -322,7 +339,7 @@ module Datadog
|
|
|
322
339
|
break
|
|
323
340
|
end
|
|
324
341
|
cur += 1
|
|
325
|
-
fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1)
|
|
342
|
+
fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count)
|
|
326
343
|
end
|
|
327
344
|
serialized.update(fields: fields)
|
|
328
345
|
end
|
|
@@ -331,7 +348,7 @@ module Datadog
|
|
|
331
348
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
332
349
|
# Re-raise fatal exceptions that should not be caught
|
|
333
350
|
# (signals, interrupts, system exit)
|
|
334
|
-
raise if
|
|
351
|
+
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
|
|
335
352
|
|
|
336
353
|
# Catch all other exceptions including SystemStackError and NoMemoryError.
|
|
337
354
|
# These inherit from Exception (not StandardError) but can occur during
|
|
@@ -429,7 +446,9 @@ module Datadog
|
|
|
429
446
|
end
|
|
430
447
|
"#<#{class_name(value.class)}#{serialized}>"
|
|
431
448
|
end
|
|
432
|
-
rescue => exc
|
|
449
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
450
|
+
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
|
|
451
|
+
|
|
433
452
|
telemetry&.report(exc, description: "Error serializing for message")
|
|
434
453
|
# TODO class_name(foo) can also fail, which we don't handle here.
|
|
435
454
|
# Telemetry reporting could potentially also fail?
|
|
@@ -7,6 +7,7 @@ require_relative '../../core/transport/parcel'
|
|
|
7
7
|
require_relative '../../core/transport/request'
|
|
8
8
|
require_relative '../../core/transport/transport'
|
|
9
9
|
require_relative '../error'
|
|
10
|
+
require_relative '../fatal_exceptions'
|
|
10
11
|
require_relative 'http/input'
|
|
11
12
|
|
|
12
13
|
module Datadog
|
|
@@ -77,7 +78,8 @@ module Datadog
|
|
|
77
78
|
next
|
|
78
79
|
end
|
|
79
80
|
encoded_snapshots << encoded
|
|
80
|
-
rescue => exc
|
|
81
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
82
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
81
83
|
# Serialization failed for this snapshot - report via callback
|
|
82
84
|
# This catches JSON::GeneratorError, Encoding errors, TypeError, etc.
|
|
83
85
|
probe_id = snapshot.dig(:debugger, :snapshot, :probe, :id)
|
|
@@ -87,7 +89,8 @@ module Datadog
|
|
|
87
89
|
if probe_id
|
|
88
90
|
begin
|
|
89
91
|
on_serialization_error.call(probe_id, exc)
|
|
90
|
-
rescue => callback_exc
|
|
92
|
+
rescue Exception => callback_exc # standard:disable Lint/RescueException
|
|
93
|
+
Datadog::DI.reraise_if_fatal(callback_exc)
|
|
91
94
|
logger.debug { "di: error in serialization error callback for probe #{probe_id}: #{callback_exc.class}: #{callback_exc.message}" }
|
|
92
95
|
telemetry&.report(callback_exc, description: "Error in serialization error callback")
|
|
93
96
|
end
|
|
@@ -109,8 +112,9 @@ module Datadog
|
|
|
109
112
|
# subsequent chunks are attempted to be sent.
|
|
110
113
|
begin
|
|
111
114
|
send_input_chunk(chunked_payload, serialized_tags)
|
|
112
|
-
rescue => exc
|
|
113
|
-
|
|
115
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
116
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
117
|
+
logger.debug { "di: failed to send snapshot chunk: #{exc.class}: #{exc.message} (at #{exc.backtrace&.first})" }
|
|
114
118
|
telemetry&.report(exc, description: "Error sending snapshot chunk")
|
|
115
119
|
end
|
|
116
120
|
end
|
data/lib/datadog/di.rb
CHANGED
|
@@ -71,6 +71,69 @@ module Datadog
|
|
|
71
71
|
Datadog.configuration.dynamic_instrumentation.enabled
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# Returns a human-readable reason why dynamic instrumentation cannot run
|
|
75
|
+
# under the given settings, or nil if all build-time preconditions are met.
|
|
76
|
+
#
|
|
77
|
+
# Single source of truth for the preconditions checked in
|
|
78
|
+
# {Component.build} and reported back by {Remote.handle_rc_enablement}
|
|
79
|
+
# when an implicit enablement signal arrives but the component was not
|
|
80
|
+
# built at startup. Checks are ordered from most-actionable to
|
|
81
|
+
# platform-constraint so the most useful reason wins.
|
|
82
|
+
#
|
|
83
|
+
# The settings argument is optional so the helper can be called from
|
|
84
|
+
# contexts that don't have settings in scope (e.g. the RC handler).
|
|
85
|
+
#
|
|
86
|
+
# @param settings [Datadog::Core::Configuration::Settings]
|
|
87
|
+
# @return [String, nil] reason string or nil when supported
|
|
88
|
+
def unsupported_reason(settings = Datadog.configuration)
|
|
89
|
+
# Symmetric to the respond_to?(:remote) guard below: in unusual
|
|
90
|
+
# configurations (test doubles, partial Settings) the DI namespace
|
|
91
|
+
# may be absent. Returning a reason here lets callers — most
|
|
92
|
+
# importantly Remote.handle_rc_enablement — emit the customer-facing
|
|
93
|
+
# warn instead of raising NoMethodError on the unguarded access at
|
|
94
|
+
# line 92 (`settings.dynamic_instrumentation.internal.development`).
|
|
95
|
+
unless settings.respond_to?(:dynamic_instrumentation)
|
|
96
|
+
return "dynamic instrumentation settings are not available"
|
|
97
|
+
end
|
|
98
|
+
unless settings.respond_to?(:remote) && settings.remote.enabled
|
|
99
|
+
return "Remote Configuration is not enabled. See https://docs.datadoghq.com/agent/remote_config"
|
|
100
|
+
end
|
|
101
|
+
unless settings.dynamic_instrumentation.internal.development
|
|
102
|
+
if Datadog::Core::Environment::Execution.development?
|
|
103
|
+
return "development environment detected"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
if (reason = unsupported_platform_reason)
|
|
107
|
+
return reason
|
|
108
|
+
end
|
|
109
|
+
unless respond_to?(:exception_message)
|
|
110
|
+
return "C extension is not available"
|
|
111
|
+
end
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Whether the current Ruby runtime can run dynamic instrumentation:
|
|
116
|
+
# MRI (CRuby) on Ruby 2.6 or later.
|
|
117
|
+
#
|
|
118
|
+
# @return [Boolean]
|
|
119
|
+
def supported_runtime?
|
|
120
|
+
unsupported_platform_reason.nil?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Reason the current Ruby runtime cannot run dynamic instrumentation, or
|
|
124
|
+
# nil when the platform is supported. Single source of truth for the
|
|
125
|
+
# minimum-runtime definition, shared by {unsupported_reason} (which layers
|
|
126
|
+
# the settings and C-extension checks on top) and {supported_runtime?}.
|
|
127
|
+
#
|
|
128
|
+
# @return [String, nil]
|
|
129
|
+
private def unsupported_platform_reason
|
|
130
|
+
if RUBY_ENGINE != 'ruby'
|
|
131
|
+
"MRI is required, but running on #{RUBY_ENGINE}"
|
|
132
|
+
elsif Datadog::RubyVersion.is?('< 2.6')
|
|
133
|
+
"Ruby 2.6+ is required, but running on #{RUBY_VERSION}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
74
137
|
# Returns iseqs that correspond to loaded files (filtering out eval'd code).
|
|
75
138
|
#
|
|
76
139
|
# There are several types of iseqs returned by +all_iseqs+:
|
|
@@ -9,14 +9,17 @@ module Datadog
|
|
|
9
9
|
module Events
|
|
10
10
|
# The second version of Business Logic Events SDK
|
|
11
11
|
module V2
|
|
12
|
+
SIGNUP_EVENT = 'users.signup'
|
|
12
13
|
LOGIN_SUCCESS_EVENT = 'users.login.success'
|
|
13
14
|
LOGIN_FAILURE_EVENT = 'users.login.failure'
|
|
15
|
+
|
|
14
16
|
TELEMETRY_METRICS_NAMESPACE = 'appsec'
|
|
15
17
|
TELEMETRY_METRICS_SDK_EVENT = 'sdk.event'
|
|
16
18
|
TELEMETRY_METRICS_SDK_VERSION = 'v2'
|
|
17
19
|
TELEMETRY_METRICS_EVENTS_INTO_TYPES = {
|
|
18
20
|
LOGIN_SUCCESS_EVENT => 'login_success',
|
|
19
|
-
LOGIN_FAILURE_EVENT => 'login_failure'
|
|
21
|
+
LOGIN_FAILURE_EVENT => 'login_failure',
|
|
22
|
+
SIGNUP_EVENT => 'signup'
|
|
20
23
|
}.freeze
|
|
21
24
|
|
|
22
25
|
class << self
|
|
@@ -125,6 +128,61 @@ module Datadog
|
|
|
125
128
|
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
|
|
126
129
|
end
|
|
127
130
|
|
|
131
|
+
# Attach user signup information to the service entry span
|
|
132
|
+
# and trigger AppSec event processing.
|
|
133
|
+
#
|
|
134
|
+
# @param login [String] The user login (e.g., username or email).
|
|
135
|
+
# @param user_or_id [String, Hash<Symbol, String>] (optional) If a
|
|
136
|
+
# String, considered as a user ID, if a Hash, considered as a user
|
|
137
|
+
# attributes. The Hash must include `:id` as a key.
|
|
138
|
+
# @param metadata [Hash<Symbol, String>] Additional flat free-form
|
|
139
|
+
# metadata to attach to the event.
|
|
140
|
+
#
|
|
141
|
+
# @example Login only
|
|
142
|
+
# Datadog::Kit::AppSec::Events::V2.track_user_signup('alice@example.com')
|
|
143
|
+
#
|
|
144
|
+
# @example Login, user attributes, and metadata
|
|
145
|
+
# Datadog::Kit::AppSec::Events::V2.track_user_signup(
|
|
146
|
+
# 'alice@example.com',
|
|
147
|
+
# { id: 'user-123', email: 'alice@example.com', name: 'Alice' },
|
|
148
|
+
# ip: '192.168.1.1', device: 'mobile', 'usr.country': 'US'
|
|
149
|
+
# )
|
|
150
|
+
#
|
|
151
|
+
# @return [void]
|
|
152
|
+
def track_user_signup(login, user_or_id = nil, metadata = {})
|
|
153
|
+
trace = service_entry_trace
|
|
154
|
+
span = service_entry_span
|
|
155
|
+
|
|
156
|
+
if trace.nil? || span.nil?
|
|
157
|
+
return Datadog.logger.warn(
|
|
158
|
+
'Kit::AppSec: Tracing is not enabled. Please enable tracing if you want to track events'
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
raise TypeError, '`login` argument must be a String' unless login.is_a?(String)
|
|
163
|
+
raise TypeError, '`metadata` argument must be a Hash' unless metadata.is_a?(Hash)
|
|
164
|
+
|
|
165
|
+
user_attributes = build_user_attributes(user_or_id, login)
|
|
166
|
+
|
|
167
|
+
set_span_tags(span, metadata, namespace: SIGNUP_EVENT)
|
|
168
|
+
set_span_tags(span, user_attributes, namespace: "#{SIGNUP_EVENT}.usr")
|
|
169
|
+
span.set_tag('appsec.events.users.signup.track', 'true')
|
|
170
|
+
span.set_tag('_dd.appsec.events.users.signup.sdk', 'true')
|
|
171
|
+
|
|
172
|
+
::Datadog::AppSec::TraceKeeper.keep!(trace)
|
|
173
|
+
|
|
174
|
+
record_event_telemetry_metric(SIGNUP_EVENT)
|
|
175
|
+
|
|
176
|
+
if user_attributes.key?(:id)
|
|
177
|
+
Kit::Identity.set_user(trace, span, **user_attributes)
|
|
178
|
+
else
|
|
179
|
+
# NOTE: {Datadog::Kit::Identity.set_user} requires an ID,
|
|
180
|
+
# but WAF can evaluate login alone
|
|
181
|
+
user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(nil, login)
|
|
182
|
+
::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
128
186
|
private
|
|
129
187
|
|
|
130
188
|
# NOTE: Current tracer implementation does not provide a way to
|
|
@@ -155,7 +213,7 @@ module Datadog
|
|
|
155
213
|
raise ArgumentError, 'missing required user key `:id`' unless user_or_id.key?(:id)
|
|
156
214
|
raise TypeError, 'user key `:id` must be a String' unless user_or_id[:id].is_a?(String)
|
|
157
215
|
|
|
158
|
-
user_or_id.merge(login: login)
|
|
216
|
+
user_or_id.merge(login: login)
|
|
159
217
|
else
|
|
160
218
|
raise TypeError, '`user_or_id` argument must be either String or Hash'
|
|
161
219
|
end
|
|
@@ -12,7 +12,7 @@ module Datadog
|
|
|
12
12
|
module OpenFeature
|
|
13
13
|
# This class is the entry point for the OpenFeature component
|
|
14
14
|
class Component
|
|
15
|
-
attr_reader :engine, :
|
|
15
|
+
attr_reader :engine, :flag_eval_metrics_hook, :flag_eval_evp_hook
|
|
16
16
|
|
|
17
17
|
def self.build(settings, agent_settings, logger:, telemetry:)
|
|
18
18
|
return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
|
|
@@ -54,21 +54,44 @@ module Datadog
|
|
|
54
54
|
|
|
55
55
|
@telemetry = telemetry
|
|
56
56
|
@logger = logger
|
|
57
|
-
@
|
|
57
|
+
@settings = settings
|
|
58
|
+
@agent_settings = agent_settings
|
|
59
|
+
@flag_eval_metrics_hook = create_flag_eval_metrics_hook
|
|
60
|
+
@flag_eval_evp_hook = create_flag_eval_evp_hook
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
def shutdown!
|
|
61
64
|
@worker.graceful_shutdown
|
|
65
|
+
@flag_eval_evp_writer&.stop
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
private
|
|
65
69
|
|
|
66
|
-
def
|
|
67
|
-
require_relative 'hooks/
|
|
68
|
-
return unless Hooks::
|
|
70
|
+
def create_flag_eval_metrics_hook
|
|
71
|
+
require_relative 'hooks/flag_eval_metrics_hook'
|
|
72
|
+
return unless Hooks::FlagEvalMetricsHook.available?
|
|
69
73
|
|
|
70
74
|
metrics = Metrics::FlagEvalMetrics.new(telemetry: @telemetry, logger: @logger)
|
|
71
|
-
Hooks::
|
|
75
|
+
Hooks::FlagEvalMetricsHook.new(metrics)
|
|
76
|
+
rescue LoadError
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Killswitch: DD_FLAGGING_EVALUATION_COUNTS_ENABLED (default on) gates only the EVP path.
|
|
81
|
+
# Read through the datadog config registry, not raw ENV.
|
|
82
|
+
def create_flag_eval_evp_hook
|
|
83
|
+
return unless @settings.open_feature.evaluation_counts_enabled
|
|
84
|
+
|
|
85
|
+
require_relative 'hooks/flag_eval_evp_hook'
|
|
86
|
+
return unless Hooks::FlagEvalEVPHook.available?
|
|
87
|
+
|
|
88
|
+
evp_transport = Transport::HTTP.build_flagevaluations(
|
|
89
|
+
agent_settings: @agent_settings,
|
|
90
|
+
logger: @logger,
|
|
91
|
+
)
|
|
92
|
+
require_relative 'flag_evaluation/writer'
|
|
93
|
+
@flag_eval_evp_writer = FlagEvaluation::Writer.new(transport: evp_transport, logger: @logger, telemetry: @telemetry)
|
|
94
|
+
Hooks::FlagEvalEVPHook.new(@flag_eval_evp_writer)
|
|
72
95
|
rescue LoadError
|
|
73
96
|
nil
|
|
74
97
|
end
|
|
@@ -20,6 +20,14 @@ module Datadog
|
|
|
20
20
|
o.env 'DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED'
|
|
21
21
|
o.default false
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# Killswitch for the EVP `flagevaluation` emission path only. Default on; when
|
|
25
|
+
# disabled the existing OTel `feature_flag.evaluations` metric is unaffected.
|
|
26
|
+
option :evaluation_counts_enabled do |o|
|
|
27
|
+
o.type :bool
|
|
28
|
+
o.env 'DD_FLAGGING_EVALUATION_COUNTS_ENABLED'
|
|
29
|
+
o.default true
|
|
30
|
+
end
|
|
23
31
|
end
|
|
24
32
|
end
|
|
25
33
|
end
|
|
@@ -4,8 +4,10 @@ module Datadog
|
|
|
4
4
|
module OpenFeature
|
|
5
5
|
module Ext
|
|
6
6
|
ERROR = 'ERROR'
|
|
7
|
+
DEFAULT = 'DEFAULT'
|
|
7
8
|
INITIALIZING = 'INITIALIZING'
|
|
8
9
|
UNKNOWN_TYPE = 'UNKNOWN_TYPE'
|
|
10
|
+
PARSE_ERROR = 'PARSE_ERROR'
|
|
9
11
|
GENERAL = 'GENERAL'
|
|
10
12
|
PROVIDER_FATAL = 'PROVIDER_FATAL'
|
|
11
13
|
PROVIDER_NOT_READY = 'PROVIDER_NOT_READY'
|