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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +29 -18
  5. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +19 -24
  7. data/ext/datadog_profiling_native_extension/collectors_stack.h +0 -1
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +348 -213
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -0
  10. data/ext/datadog_profiling_native_extension/extconf.rb +11 -3
  11. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -0
  12. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
  13. data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
  14. data/ext/datadog_profiling_native_extension/time_helpers.h +12 -7
  15. data/ext/libdatadog_api/di.c +76 -0
  16. data/ext/libdatadog_api/extconf.rb +6 -0
  17. data/ext/libdatadog_extconf_helpers.rb +1 -1
  18. data/lib/datadog/ai_guard/api_client.rb +6 -4
  19. data/lib/datadog/appsec/api_security/route_extractor.rb +7 -4
  20. data/lib/datadog/appsec/configuration.rb +6 -0
  21. data/lib/datadog/appsec/context.rb +4 -2
  22. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
  23. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
  24. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +1 -1
  25. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +5 -0
  26. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -0
  27. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +4 -17
  28. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
  29. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +7 -1
  30. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
  31. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +4 -0
  32. data/lib/datadog/appsec/ext.rb +1 -0
  33. data/lib/datadog/appsec/metrics/collector.rb +18 -1
  34. data/lib/datadog/appsec/metrics/exporter.rb +9 -3
  35. data/lib/datadog/appsec/remote.rb +9 -3
  36. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  37. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  38. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  39. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  40. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  41. data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
  42. data/lib/datadog/core/configuration/components.rb +82 -2
  43. data/lib/datadog/core/configuration/components_state.rb +6 -1
  44. data/lib/datadog/core/configuration/settings.rb +4 -7
  45. data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
  46. data/lib/datadog/core/evp.rb +11 -0
  47. data/lib/datadog/core/knuth_sampler.rb +3 -1
  48. data/lib/datadog/core/metrics/client.rb +1 -1
  49. data/lib/datadog/core/remote/client/capabilities.rb +34 -7
  50. data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
  51. data/lib/datadog/core/transport/http/builder.rb +1 -1
  52. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  53. data/lib/datadog/core/utils/time.rb +5 -1
  54. data/lib/datadog/di/base.rb +3 -1
  55. data/lib/datadog/di/boot.rb +3 -0
  56. data/lib/datadog/di/capture_expression.rb +21 -0
  57. data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
  58. data/lib/datadog/di/capture_limits.rb +41 -0
  59. data/lib/datadog/di/code_tracker.rb +5 -2
  60. data/lib/datadog/di/component.rb +112 -38
  61. data/lib/datadog/di/configuration.rb +6 -0
  62. data/lib/datadog/di/context.rb +9 -3
  63. data/lib/datadog/di/el/compiler.rb +1 -1
  64. data/lib/datadog/di/error.rb +10 -0
  65. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  66. data/lib/datadog/di/instrumenter.rb +412 -165
  67. data/lib/datadog/di/probe.rb +61 -2
  68. data/lib/datadog/di/probe_builder.rb +83 -1
  69. data/lib/datadog/di/probe_file_loader.rb +8 -3
  70. data/lib/datadog/di/probe_manager.rb +67 -9
  71. data/lib/datadog/di/probe_notification_builder.rb +52 -8
  72. data/lib/datadog/di/probe_notifier_worker.rb +52 -32
  73. data/lib/datadog/di/redactor.rb +16 -1
  74. data/lib/datadog/di/remote.rb +176 -15
  75. data/lib/datadog/di/serializer.rb +34 -15
  76. data/lib/datadog/di/transport/input.rb +8 -4
  77. data/lib/datadog/di.rb +63 -0
  78. data/lib/datadog/kit/appsec/events/v2.rb +60 -2
  79. data/lib/datadog/open_feature/component.rb +29 -6
  80. data/lib/datadog/open_feature/configuration.rb +8 -0
  81. data/lib/datadog/open_feature/ext.rb +2 -0
  82. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
  83. data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
  84. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
  85. data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
  86. data/lib/datadog/open_feature/native_evaluator.rb +22 -0
  87. data/lib/datadog/open_feature/provider.rb +59 -27
  88. data/lib/datadog/open_feature/transport.rb +55 -1
  89. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
  90. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
  91. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  92. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +6 -1
  93. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
  94. data/lib/datadog/profiling/collectors/thread_context.rb +9 -1
  95. data/lib/datadog/profiling/component.rb +17 -0
  96. data/lib/datadog/symbol_database/component.rb +164 -47
  97. data/lib/datadog/symbol_database/configuration.rb +12 -4
  98. data/lib/datadog/symbol_database/extensions.rb +19 -0
  99. data/lib/datadog/symbol_database/extractor.rb +124 -87
  100. data/lib/datadog/symbol_database/file_hash.rb +3 -1
  101. data/lib/datadog/symbol_database/remote.rb +8 -3
  102. data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
  103. data/lib/datadog/symbol_database/uploader.rb +9 -2
  104. data/lib/datadog/symbol_database.rb +22 -0
  105. data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
  106. data/lib/datadog/tracing/contrib/active_job/patcher.rb +12 -0
  107. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +2 -3
  108. data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -12
  109. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  110. data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -2
  111. data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
  112. data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
  113. data/lib/datadog/tracing/distributed/baggage.rb +2 -1
  114. data/lib/datadog/tracing/ext.rb +9 -0
  115. data/lib/datadog/tracing/remote.rb +34 -3
  116. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  117. data/lib/datadog/version.rb +1 -1
  118. data/lib/datadog.rb +1 -0
  119. metadata +25 -8
@@ -46,6 +46,38 @@ module Datadog
46
46
  Core::Diagnostics::Health::Metrics.new(telemetry: telemetry, logger: logger, **options)
47
47
  end
48
48
 
49
+ # Decides whether to build the SymbolDatabase component, for the
50
+ # tri-state symbol_database.enabled setting: true/false are explicit
51
+ # overrides; nil (the default) mirrors Dynamic Instrumentation by
52
+ # building whenever DI's component was built. dynamic_instrumentation
53
+ # is DI's build result — nil only when DI is explicitly disabled or the
54
+ # runtime can't support it (Rails development mode, missing C
55
+ # extension, remote config off, non-MRI). Under the always-build model
56
+ # it is non-nil even when the DI setting defaults to off, so a built
57
+ # SymbolDatabase component is inert until an upload is actually
58
+ # permitted: the component's own gate (see Component#upload_allowed?)
59
+ # only extracts and uploads when DI is truly active or the customer
60
+ # opted in explicitly. This mirrors DI advertising/building a component
61
+ # by default while installing no probes until it is enabled.
62
+ # force_upload (internal/testing) always builds, since it uploads
63
+ # regardless of DI or the enabled setting.
64
+ # @param settings [Configuration::Settings]
65
+ # @param dynamic_instrumentation [DI::Component, nil]
66
+ # @return [Boolean]
67
+ def enable_symbol_database?(settings, dynamic_instrumentation)
68
+ # The symbol_database settings group is only registered on the full
69
+ # library load path; a partial load (e.g. require 'datadog/di') leaves
70
+ # it absent, in which case the feature cannot be enabled.
71
+ return false unless settings.respond_to?(:symbol_database)
72
+
73
+ # force_upload uploads unconditionally, so the component must be built
74
+ # even when the setting is nil and DI's component was not built.
75
+ return true if settings.symbol_database.internal.force_upload
76
+
77
+ # nil (the default) follows whether DI's component was built.
78
+ Datadog::SymbolDatabase.resolve_enabled(settings.symbol_database.enabled, !dynamic_instrumentation.nil?)
79
+ end
80
+
49
81
  def build_logger(settings)
50
82
  logger = settings.logger.instance || Core::Logger.new($stderr)
51
83
  logger.level = settings.diagnostics.debug ? ::Logger::DEBUG : settings.logger.level
@@ -174,10 +206,31 @@ module Datadog
174
206
  @ai_guard = Datadog::AIGuard::Component.build(settings, logger: @logger, telemetry: telemetry)
175
207
  @open_feature = OpenFeature::Component.build(settings, agent_settings, logger: @logger, telemetry: telemetry)
176
208
  @dynamic_instrumentation = Datadog::DI::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
177
- @symbol_database = Datadog::SymbolDatabase::Component.build(settings, agent_settings, @logger, telemetry: telemetry)
209
+ # Only build symbol database when enabled, so a disabled component is
210
+ # never constructed.
211
+ @symbol_database =
212
+ if self.class.enable_symbol_database?(settings, @dynamic_instrumentation)
213
+ # di_active is a live predicate, not a snapshot: DI may start after
214
+ # this component is built (implicit enablement via remote config).
215
+ # Symbol Database holds only this opaque proc, never a DI reference,
216
+ # so the two modules stay independent — the orchestration layer
217
+ # owns the cross-feature knowledge.
218
+ Datadog::SymbolDatabase::Component.build(
219
+ settings, agent_settings, @logger,
220
+ telemetry: telemetry,
221
+ di_active: -> { dynamic_instrumentation&.started? || false },
222
+ )
223
+ end
178
224
  @error_tracking = Datadog::ErrorTracking::Component.build(settings, @tracer, @logger)
179
225
  @data_streams = self.class.build_data_streams(settings, agent_settings, @logger, @agent_info)
180
- @environment_logger_extra[:dynamic_instrumentation_enabled] = !!@dynamic_instrumentation
226
+ # Reflects "the customer configured DI to be on" — true iff the
227
+ # component was built AND the env-var-driven enabled flag is set.
228
+ # This is the post-initialize / pre-startup value visible to tests
229
+ # that don't call startup!. Production overwrites this in startup!
230
+ # below with `dynamic_instrumentation&.started?`, which also accounts
231
+ # for RC-driven enablement carried over via ComponentsState.
232
+ @environment_logger_extra[:dynamic_instrumentation_enabled] =
233
+ !!(@dynamic_instrumentation && settings.dynamic_instrumentation.enabled)
181
234
 
182
235
  # Configure non-privileged components.
183
236
  Datadog::Tracing::Contrib::Component.configure(settings)
@@ -228,11 +281,25 @@ module Datadog
228
281
  remote.start
229
282
  end
230
283
 
284
+ # Start DI component if enabled by env var or if it was implicitly enabled
285
+ # (via RC) in the previous Components instance. dynamic_instrumentation is
286
+ # non-nil only when Component.build's preconditions passed (Ruby 2.6+ MRI,
287
+ # etc.), which is exactly the condition under which di/base.rb is loaded
288
+ # and DI.activate_tracking is defined.
289
+ if dynamic_instrumentation
290
+ if settings.dynamic_instrumentation.enabled || old_state&.di_implicitly_enabled?
291
+ DI.activate_tracking
292
+ dynamic_instrumentation.start!
293
+ end
294
+ end
295
+
231
296
  # This should stay here, not in initialize. During reconfiguration, the order of the calls is:
232
297
  # initialize new components, shutdown old components, startup new components.
233
298
  # Because this is a singleton, if we call it in initialize, it will be shutdown right away.
234
299
  Core::ProcessDiscovery.publish(settings)
235
300
 
301
+ @environment_logger_extra[:dynamic_instrumentation_enabled] = dynamic_instrumentation&.started? || false
302
+
236
303
  Core::Diagnostics::EnvironmentLogger.collect_and_log!(@environment_logger_extra)
237
304
  end
238
305
 
@@ -309,9 +376,22 @@ module Datadog
309
376
 
310
377
  # Returns the current state of various components.
311
378
  def state
379
+ # di_implicitly_enabled distinguishes RC-driven start from explicit
380
+ # start (env var or programmatic) so that an explicit
381
+ # `enabled = false` on reconfiguration can take effect.
382
+ #
383
+ # using_default? — not `!enabled` — because Datadog.configure has
384
+ # already mutated the singleton settings by the time #state runs,
385
+ # so `!enabled` would treat a customer's explicit
386
+ # `enabled = false` as "implicitly enabled" and restart DI.
387
+ # using_default? asks "did the customer ever touch this setting",
388
+ # which is the right question for RC carry-over.
389
+ di_implicit = dynamic_instrumentation&.started? &&
390
+ @settings.dynamic_instrumentation.using_default?(:enabled)
312
391
  ComponentsState.new(
313
392
  telemetry_enabled: telemetry.enabled,
314
393
  remote_started: remote&.started?,
394
+ di_implicitly_enabled: di_implicit || false,
315
395
  )
316
396
  end
317
397
  end
@@ -5,9 +5,10 @@ module Datadog
5
5
  module Configuration
6
6
  # Stores the state of component tree when replacing the tree.
7
7
  class ComponentsState
8
- def initialize(telemetry_enabled:, remote_started:)
8
+ def initialize(telemetry_enabled:, remote_started:, di_implicitly_enabled: false)
9
9
  @telemetry_enabled = !!telemetry_enabled
10
10
  @remote_started = !!remote_started
11
+ @di_implicitly_enabled = !!di_implicitly_enabled
11
12
  end
12
13
 
13
14
  def telemetry_enabled?
@@ -17,6 +18,10 @@ module Datadog
17
18
  def remote_started?
18
19
  @remote_started
19
20
  end
21
+
22
+ def di_implicitly_enabled?
23
+ @di_implicitly_enabled
24
+ end
20
25
  end
21
26
  end
22
27
  end
@@ -14,7 +14,6 @@ require_relative '../../profiling/ext'
14
14
 
15
15
  require_relative '../../tracing/configuration/settings'
16
16
  require_relative '../../opentelemetry/configuration/settings'
17
- require_relative '../../symbol_database/configuration'
18
17
 
19
18
  module Datadog
20
19
  module Core
@@ -825,14 +824,14 @@ module Datadog
825
824
  # It must respect the interface of [Datadog::Core::Utils::Time#get_time] method.
826
825
  #
827
826
  # For [Timecop](https://rubygems.org/gems/timecop), for example,
828
- # `->(unit = :float_second) { ::Process.clock_gettime_without_mock(::Process::CLOCK_MONOTONIC, unit) }`
827
+ # `->(unit = :float_second) { ::Process.clock_gettime_without_mock(Datadog::Core::Utils::Time::MONOTONIC_CLOCK_ID, unit) }`
829
828
  # allows Datadog features to use the real monotonic time when time is frozen with
830
829
  # `Timecop.mock_process_clock = true`.
831
830
  #
832
- # @default `->(unit = :float_second) { ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit)}`
831
+ # @default `->(unit = :float_second) { ::Process.clock_gettime(Core::Utils::Time::MONOTONIC_CLOCK_ID, unit) }`
833
832
  # @return [Proc<Numeric>]
834
833
  option :get_time_provider do |o|
835
- o.default_proc { |unit = :float_second| ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit) }
834
+ o.default_proc { |unit = :float_second| ::Process.clock_gettime(Core::Utils::Time::MONOTONIC_CLOCK_ID, unit) }
836
835
  o.type :proc
837
836
 
838
837
  o.after_set do |get_time_provider|
@@ -840,7 +839,7 @@ module Datadog
840
839
  end
841
840
 
842
841
  o.resetter do |_value|
843
- ->(unit = :float_second) { ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, unit) }.tap do |default|
842
+ ->(unit = :float_second) { ::Process.clock_gettime(Core::Utils::Time::MONOTONIC_CLOCK_ID, unit) }.tap do |default|
844
843
  Core::Utils::Time.get_time_provider = default
845
844
  end
846
845
  end
@@ -1130,8 +1129,6 @@ module Datadog
1130
1129
  extend Datadog::Tracing::Configuration::Settings
1131
1130
 
1132
1131
  extend Datadog::OpenTelemetry::Configuration::Settings
1133
-
1134
- extend Datadog::SymbolDatabase::Configuration::Settings
1135
1132
  end
1136
1133
  # standard:enable Metrics/BlockLength
1137
1134
  end
@@ -19,6 +19,7 @@ module Datadog
19
19
  "DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE",
20
20
  "DD_API_SECURITY_ENABLED",
21
21
  "DD_API_SECURITY_ENDPOINT_COLLECTION_ENABLED",
22
+ "DD_API_SECURITY_MAX_DOWNSTREAM_BODY_BYTES",
22
23
  "DD_API_SECURITY_MAX_DOWNSTREAM_REQUEST_BODY_ANALYSIS",
23
24
  "DD_API_SECURITY_REQUEST_SAMPLE_RATE",
24
25
  "DD_API_SECURITY_SAMPLE_DELAY",
@@ -49,6 +50,7 @@ module Datadog
49
50
  "DD_DBM_PROPAGATION_MODE",
50
51
  "DD_DISABLE_DATADOG_RAILS",
51
52
  "DD_DYNAMIC_INSTRUMENTATION_ENABLED",
53
+ "DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE",
52
54
  "DD_DYNAMIC_INSTRUMENTATION_PROBE_FILE",
53
55
  "DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS",
54
56
  "DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES",
@@ -59,6 +61,7 @@ module Datadog
59
61
  "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED",
60
62
  "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED",
61
63
  "DD_EXTERNAL_ENV",
64
+ "DD_FLAGGING_EVALUATION_COUNTS_ENABLED",
62
65
  "DD_GIT_COMMIT_SHA",
63
66
  "DD_GIT_REPOSITORY_URL",
64
67
  "DD_HEALTH_METRICS_ENABLED",
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module EVP
6
+ SUBDOMAIN_HEADER_NAME = 'X-Datadog-EVP-Subdomain'
7
+ EVENT_PLATFORM_INTAKE_SUBDOMAIN = 'event-platform-intake'
8
+ PAYLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024
9
+ end
10
+ end
11
+ end
@@ -32,7 +32,9 @@ module Datadog
32
32
  @knuth_factor = knuth_factor
33
33
 
34
34
  rate = rate.to_f
35
- unless rate >= 0.0 && rate <= 1.0
35
+ # Not using `between?` here: it raises on NaN instead of falling back to 1.0 below.
36
+ # No current caller passes NaN, but this guards against it defensively for future ones.
37
+ unless rate >= 0.0 && rate <= 1.0 # rubocop:disable Style/ComparableBetween
36
38
  Datadog.logger.warn("Sample rate #{rate} is not between 0.0 and 1.0, falling back to 1.0")
37
39
  rate = 1.0
38
40
  end
@@ -157,7 +157,7 @@ module Datadog
157
157
  begin
158
158
  if send_stats? && !start.nil?
159
159
  finished = Utils::Time.get_time
160
- distribution(stat, ((finished - start) * 1000), options)
160
+ distribution(stat, (finished - start) * 1000, options)
161
161
  end
162
162
  rescue => e
163
163
  # TODO: Likely to be redundant, since `distribution` handles its own errors.
@@ -4,6 +4,7 @@ require_relative '../../utils/base64_codec'
4
4
  require_relative '../../../appsec/remote'
5
5
  require_relative '../../../tracing/remote'
6
6
  require_relative '../../../di/remote'
7
+ require_relative '../../../symbol_database'
7
8
  require_relative '../../../symbol_database/remote'
8
9
  require_relative '../../../open_feature/remote'
9
10
 
@@ -35,13 +36,43 @@ module Datadog
35
36
  register_receivers(Datadog::AppSec::Remote.receivers(@telemetry))
36
37
  end
37
38
 
38
- if settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
39
+ # Tracing must register before DI: on a combined RC dispatch,
40
+ # the APM_TRACING handler must run first to call
41
+ # Datadog::DI::Remote.handle_rc_enablement and start the
42
+ # component before the DI receiver processes LIVE_DEBUGGING
43
+ # changes against `component.started?`. Reversing the order
44
+ # silently drops the probe — the remote client only
45
+ # redispatches on content hash changes.
46
+ register_capabilities(Datadog::Tracing::Remote.capabilities)
47
+ register_products(Datadog::Tracing::Remote.products)
48
+ register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))
49
+
50
+ # Skip DI registration entirely when DI is explicitly disabled
51
+ # (DD_DYNAMIC_INSTRUMENTATION_ENABLED=false) or when the runtime
52
+ # cannot run DI (JRuby, Ruby 2.5): in either case no component will
53
+ # run, so advertising bit 38 or the LIVE_DEBUGGING product would
54
+ # invite probe configs and an enable signal the tracer must refuse.
55
+ # When the env var is unset (default) on a supported runtime, DI is
56
+ # registered so RC can enable it.
57
+ if settings.respond_to?(:dynamic_instrumentation) &&
58
+ !Datadog::DI::Remote.explicitly_disabled?(settings) &&
59
+ Datadog::DI.supported_runtime?
39
60
  register_capabilities(Datadog::DI::Remote.capabilities)
40
61
  register_products(Datadog::DI::Remote.products)
41
62
  register_receivers(Datadog::DI::Remote.receivers(@telemetry))
63
+ end
42
64
 
43
- # Symbol Database
44
- if settings.respond_to?(:symbol_database) && settings.symbol_database.enabled
65
+ # Skip symbol database registration on runtimes that cannot run it
66
+ # (JRuby, Ruby < 2.7): DI supports Ruby 2.6 but Symbol Database does
67
+ # not, so advertising LIVE_DEBUGGING_SYMBOL_DB there would invite
68
+ # symbol-upload configs the component can never serve.
69
+ if settings.respond_to?(:symbol_database) && Datadog::SymbolDatabase.supported_runtime?
70
+ # Symbol database follows DI: when unset it advertises whenever DI
71
+ # advertises (mirror the DI branch above, including the unset/default
72
+ # case that RC may enable). An explicit symbol_database.enabled wins.
73
+ di_enabled = settings.respond_to?(:dynamic_instrumentation) &&
74
+ !Datadog::DI::Remote.explicitly_disabled?(settings)
75
+ if Datadog::SymbolDatabase.resolve_enabled(settings.symbol_database.enabled, di_enabled)
45
76
  register_capabilities(Datadog::SymbolDatabase::Remote.capabilities)
46
77
  register_products(Datadog::SymbolDatabase::Remote.products)
47
78
  register_receivers(Datadog::SymbolDatabase::Remote.receivers(@telemetry))
@@ -53,10 +84,6 @@ module Datadog
53
84
  register_products(Datadog::OpenFeature::Remote.products)
54
85
  register_receivers(Datadog::OpenFeature::Remote.receivers(@telemetry))
55
86
  end
56
-
57
- register_capabilities(Datadog::Tracing::Remote.capabilities)
58
- register_products(Datadog::Tracing::Remote.products)
59
- register_receivers(Datadog::Tracing::Remote.receivers(@telemetry))
60
87
  end
61
88
 
62
89
  def register_capabilities(capabilities)
@@ -57,7 +57,14 @@ module Datadog
57
57
  enabled: !!components.profiler,
58
58
  },
59
59
  dynamic_instrumentation: {
60
- enabled: !!components.dynamic_instrumentation,
60
+ # With always-build, the DI component is constructed by
61
+ # default (when the runtime supports DI, RC is available, and
62
+ # DI is not explicitly disabled) regardless of whether the
63
+ # customer turned DI on. Its presence therefore no longer
64
+ # indicates the enabled state. started? reflects whether DI was
65
+ # actually enabled by env var or by implicit enablement via
66
+ # remote config.
67
+ enabled: components.dynamic_instrumentation&.started? || false,
61
68
  }
62
69
  }
63
70
 
@@ -108,7 +108,7 @@ module Datadog
108
108
 
109
109
  # Resolve fallback and merge headers
110
110
  fallback = api_options.delete(:fallback)
111
- api_options[:headers] = @default_headers.merge((api_options[:headers] || {}))
111
+ api_options[:headers] = @default_headers.merge(api_options[:headers] || {})
112
112
 
113
113
  # Add API::Instance with all settings
114
114
  instances[key] = API::Instance.new(
@@ -6,7 +6,7 @@ module Datadog
6
6
  # Monkey patches `Kernel#fork` and similar functions, adding an `at_fork` callback mechanism which
7
7
  # is used to restart observability after the VM forks (e.g. in multiprocess Ruby apps).
8
8
  module AtForkMonkeyPatch
9
- AT_FORK_CHILD_BLOCKS = [] # rubocop:disable Style/MutableConstant Used to store blocks to run, mutable by design.
9
+ AT_FORK_CHILD_BLOCKS = [] # rubocop:disable Style/MutableConstant -- Used to store blocks to run, mutable by design.
10
10
  private_constant :AT_FORK_CHILD_BLOCKS
11
11
 
12
12
  def self.supported?
@@ -8,11 +8,15 @@ module Datadog
8
8
  module_function
9
9
 
10
10
  # Current monotonic time
11
+ # On macOS, CLOCK_MONOTONIC only has microsecond precision,
12
+ # so we use CLOCK_MONOTONIC_RAW which has nanosecond precision instead.
11
13
  #
12
14
  # @param unit [Symbol] unit for the resulting value, same as ::Process#clock_gettime, defaults to :float_second
13
15
  # @return [Float|Integer] timestamp in the requested unit, since some unspecified starting point
16
+ MONOTONIC_CLOCK_ID = RUBY_PLATFORM.include?("darwin") ? Process::CLOCK_MONOTONIC_RAW : Process::CLOCK_MONOTONIC
17
+
14
18
  def get_time(unit = :float_second)
15
- Process.clock_gettime(Process::CLOCK_MONOTONIC, unit)
19
+ Process.clock_gettime(MONOTONIC_CLOCK_ID, unit)
16
20
  end
17
21
 
18
22
  # Current wall time.
@@ -8,6 +8,7 @@
8
8
  # are loaded, and also none of the rest of datadog library which also
9
9
  # has contrib code in other products.
10
10
 
11
+ require_relative 'fatal_exceptions'
11
12
  require_relative 'code_tracker'
12
13
 
13
14
  # Needed since this file can be loaded without core
@@ -53,7 +54,8 @@ module Datadog
53
54
  # Activate code tracking by default because line trace points will not work
54
55
  # without it.
55
56
  Datadog::DI.activate_tracking!
56
- rescue => exc
57
+ rescue Exception => exc # standard:disable Lint/RescueException
58
+ Datadog::DI.reraise_if_fatal(exc)
57
59
  if defined?(Datadog.logger)
58
60
  Datadog.logger.warn { "di: Failed to activate code tracking for DI: #{exc.class}: #{exc.message}" }
59
61
  else
@@ -6,6 +6,9 @@ require_relative 'error'
6
6
  require_relative 'code_tracker'
7
7
  require_relative 'component'
8
8
  require_relative 'context'
9
+ require_relative 'capture_limits'
10
+ require_relative 'capture_expression'
11
+ require_relative 'capture_expression_evaluator'
9
12
  require_relative 'instrumenter'
10
13
  require_relative 'probe'
11
14
  require_relative 'probe_builder'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "capture_limits"
4
+
5
+ module Datadog
6
+ module DI
7
+ class CaptureExpression
8
+ def initialize(name:, expr:, limits: nil)
9
+ @name = name
10
+ @expr = expr
11
+ @limits = limits
12
+ end
13
+
14
+ attr_reader :name
15
+
16
+ attr_reader :expr
17
+
18
+ attr_reader :limits
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "capture_expression"
4
+ require_relative "capture_limits"
5
+ require_relative "fatal_exceptions"
6
+
7
+ module Datadog
8
+ module DI
9
+ class CaptureExpressionEvaluator
10
+ TELEMETRY_NAMESPACE = "dynamic_instrumentation"
11
+
12
+ def initialize(settings:, serializer:, logger:, telemetry: nil)
13
+ @settings = settings
14
+ @serializer = serializer
15
+ @logger = logger
16
+ @telemetry = telemetry
17
+ end
18
+
19
+ attr_reader :settings
20
+
21
+ attr_reader :serializer
22
+
23
+ attr_reader :logger
24
+
25
+ attr_reader :telemetry
26
+
27
+ def evaluate(probe, context)
28
+ budget_ns = settings.dynamic_instrumentation.max_time_to_serialize_ms * 1_000_000
29
+ deadline_ns = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) + budget_ns
30
+
31
+ output = {}
32
+ evaluation_errors = []
33
+
34
+ probe.capture_expressions.each do |capture_expression|
35
+ name = capture_expression.name
36
+
37
+ if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) >= deadline_ns
38
+ output[name] = {notCapturedReason: "timeout"}
39
+ telemetry&.inc(TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
40
+ next
41
+ end
42
+
43
+ begin
44
+ value = capture_expression.expr.evaluate(context)
45
+ limits = CaptureLimits.resolve(
46
+ expr_limits: capture_expression.limits,
47
+ probe: probe,
48
+ settings: settings,
49
+ )
50
+ output[name] = serializer.serialize_value(
51
+ value, name: name,
52
+ depth: limits[:depth],
53
+ attribute_count: limits[:attribute_count],
54
+ length: limits[:length],
55
+ collection_size: limits[:collection_size],
56
+ )
57
+ rescue Exception => exc # standard:disable Lint/RescueException
58
+ Datadog::DI.reraise_if_fatal(exc)
59
+ evaluation_errors << {expr: name, message: "#{exc.class}: #{exc.message}"}
60
+ logger.debug do
61
+ "di: probe #{probe.id}: capture expression #{name}: evaluation failed: #{exc.class}: #{exc.message}"
62
+ end
63
+ telemetry&.report(exc, description: "DI capture-expression evaluation failed")
64
+ end
65
+ end
66
+
67
+ [output, evaluation_errors]
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module DI
5
+ class CaptureLimits
6
+ def initialize(max_reference_depth: nil, max_collection_size: nil,
7
+ max_length: nil, max_field_count: nil)
8
+ @max_reference_depth = max_reference_depth
9
+ @max_collection_size = max_collection_size
10
+ @max_length = max_length
11
+ @max_field_count = max_field_count
12
+ end
13
+
14
+ attr_reader :max_reference_depth
15
+
16
+ attr_reader :max_collection_size
17
+
18
+ attr_reader :max_length
19
+
20
+ attr_reader :max_field_count
21
+
22
+ def self.resolve(expr_limits:, probe:, settings:)
23
+ di = settings.dynamic_instrumentation
24
+ {
25
+ depth: expr_limits&.max_reference_depth ||
26
+ probe.max_capture_depth ||
27
+ di.max_capture_depth,
28
+ collection_size: expr_limits&.max_collection_size ||
29
+ probe.max_capture_collection_size ||
30
+ di.max_capture_collection_size,
31
+ length: expr_limits&.max_length ||
32
+ probe.max_capture_string_length ||
33
+ di.max_capture_string_length,
34
+ attribute_count: expr_limits&.max_field_count ||
35
+ probe.max_capture_attribute_count ||
36
+ di.max_capture_attribute_count,
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,6 +3,7 @@
3
3
  # rubocop:disable Lint/AssignmentInCondition
4
4
 
5
5
  require_relative 'error'
6
+ require_relative 'fatal_exceptions'
6
7
 
7
8
  module Datadog
8
9
  module DI
@@ -120,7 +121,8 @@ module Datadog
120
121
  end
121
122
  end
122
123
  nil
123
- rescue => exc
124
+ rescue Exception => exc # standard:disable Lint/RescueException
125
+ Datadog::DI.reraise_if_fatal(exc)
124
126
  # Backfill is best-effort — if it fails, line probes on
125
127
  # pre-loaded code won't work but everything else is unaffected.
126
128
  if component = DI.current_component
@@ -191,7 +193,8 @@ module Datadog
191
193
  # Since this method normally is called from customer applications,
192
194
  # rescue any exceptions that might not be handled to not break said
193
195
  # customer applications.
194
- rescue => exc
196
+ rescue Exception => exc # standard:disable Lint/RescueException
197
+ Datadog::DI.reraise_if_fatal(exc)
195
198
  # Code tracker may be loaded without the rest of DI,
196
199
  # in which case DI.component will not yet be defined,
197
200
  # but we will have DI.current_component (set to nil).