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
@@ -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
  # Component for dynamic instrumentation.
@@ -15,45 +17,51 @@ module Datadog
15
17
  class Component
16
18
  class << self
17
19
  def build(settings, agent_settings, logger, telemetry: nil)
18
- return unless settings.respond_to?(:dynamic_instrumentation) && settings.dynamic_instrumentation.enabled
20
+ return unless settings.respond_to?(:dynamic_instrumentation)
19
21
 
20
- unless settings.respond_to?(:remote) && settings.remote.enabled
21
- logger.warn("di: dynamic instrumentation could not be enabled because Remote Configuration Management is not available. To enable Remote Configuration, see https://docs.datadoghq.com/agent/remote_config")
22
+ # Explicit DD_DYNAMIC_INSTRUMENTATION_ENABLED=false: do not build the
23
+ # component at all. This is customer intent, not a failure log at
24
+ # debug and emit no telemetry error. Capabilities#register skips the
25
+ # DI RC block under the same condition, so no enable signal arrives.
26
+ if Remote.explicitly_disabled?(settings)
27
+ logger.debug("di: dynamic instrumentation is explicitly disabled (DD_DYNAMIC_INSTRUMENTATION_ENABLED=false); not building component")
22
28
  return
23
29
  end
24
30
 
25
- return unless environment_supported?(settings, logger)
31
+ reason = DI.unsupported_reason(settings)
32
+ if reason
33
+ # Log level mirrors customer intent: if the customer explicitly
34
+ # opted in via DD_DYNAMIC_INSTRUMENTATION_ENABLED, warn. Otherwise
35
+ # debug — with always-build, this path runs on every tracer boot
36
+ # for every customer, including those who never wanted DI. Spamming
37
+ # warnings to silent users (especially on JRuby or Ruby 2.5) would
38
+ # be noise. Customers who later trigger implicit enablement via the
39
+ # Datadog UI get a symmetric warn from Remote.handle_rc_enablement
40
+ # when the RC enable signal finds no component to start.
41
+ level = explicitly_enabled?(settings) ? :warn : :debug
42
+ logger.public_send(level, "di: dynamic instrumentation is disabled: #{reason}")
43
+ return
44
+ end
26
45
 
27
46
  new(settings, agent_settings, logger, code_tracker: DI.code_tracker, telemetry: telemetry).tap do |component|
28
47
  DI.add_current_component(component)
29
48
  end
30
49
  end
31
50
 
32
- # Checks whether the runtime environment is supported by
33
- # dynamic instrumentation. Currently we only require that, if Rails
34
- # is used, that Rails environment is not development because
35
- # DI does not currently support code unloading and reloading.
36
- def environment_supported?(settings, logger)
37
- # TODO add tests?
38
- unless settings.dynamic_instrumentation.internal.development
39
- if Datadog::Core::Environment::Execution.development?
40
- logger.warn("di: development environment detected; not enabling dynamic instrumentation")
41
- return false
42
- end
43
- end
44
- if RUBY_ENGINE != 'ruby'
45
- logger.warn("di: cannot enable dynamic instrumentation: MRI is required, but running on #{RUBY_ENGINE}")
46
- return false
47
- end
48
- if RubyVersion.is?('< 2.6')
49
- logger.warn("di: cannot enable dynamic instrumentation: Ruby 2.6+ is required, but running on #{RUBY_VERSION}")
50
- return false
51
- end
52
- unless DI.respond_to?(:exception_message)
53
- logger.warn("di: cannot enable dynamic instrumentation: C extension is not available")
54
- return false
55
- end
56
- true
51
+ # True when the customer explicitly set
52
+ # DD_DYNAMIC_INSTRUMENTATION_ENABLED=true (or its equivalent in code).
53
+ # Symmetric to {Remote.explicitly_disabled?}.
54
+ #
55
+ # Uses {Datadog::Core::Configuration::Options::InstanceMethods#using_default?}
56
+ # rather than `options[:enabled].default_precedence?` because the option
57
+ # hash is populated lazily on first access; reading the underlying option
58
+ # before {Component.build} touches the value would NoMethodError on nil.
59
+ #
60
+ # @param settings [Datadog::Core::Configuration::Settings]
61
+ # @return [Boolean]
62
+ def explicitly_enabled?(settings)
63
+ !settings.dynamic_instrumentation.using_default?(:enabled) &&
64
+ settings.dynamic_instrumentation.enabled
57
65
  end
58
66
  end
59
67
 
@@ -68,7 +76,7 @@ module Datadog
68
76
  @serializer = Serializer.new(settings, redactor, telemetry: telemetry)
69
77
  @instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry)
70
78
  @probe_repository = ProbeRepository.new
71
- @probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer)
79
+ @probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger, telemetry: telemetry)
72
80
  @probe_notifier_worker = ProbeNotifierWorker.new(
73
81
  settings, logger,
74
82
  agent_settings: agent_settings,
@@ -80,7 +88,11 @@ module Datadog
80
88
  settings, instrumenter, probe_notification_builder, probe_notifier_worker, logger, probe_repository,
81
89
  telemetry: telemetry,
82
90
  )
83
- probe_notifier_worker.start
91
+ # @started transitions are serialized by @lifecycle_mutex so that
92
+ # concurrent RC callbacks (which run on the remote-config thread)
93
+ # cannot race a foreground start! with a background stop!.
94
+ @lifecycle_mutex = Mutex.new
95
+ @started = false
84
96
  end
85
97
 
86
98
  attr_reader :settings
@@ -96,9 +108,61 @@ module Datadog
96
108
  attr_reader :redactor
97
109
  attr_reader :serializer
98
110
 
99
- # Shuts down dynamic instrumentation.
111
+ # Starts the DI component: begins accepting probes and
112
+ # processing snapshots.
113
+ #
114
+ # Starts the probe notifier worker thread before enabling the
115
+ # definition trace point, so any future status emission from a
116
+ # trace-point-driven installation has a worker to drain it.
117
+ # Today {ProbeManager#reopen} re-hooks without emitting statuses, so
118
+ # the order is defensive rather than load-bearing. No-op if already
119
+ # started. Serialized by @lifecycle_mutex.
120
+ #
121
+ # @return [void]
122
+ def start!
123
+ @lifecycle_mutex.synchronize do
124
+ return if @started
125
+
126
+ probe_notifier_worker.start
127
+ probe_manager.reopen
128
+ @started = true
129
+ end
130
+ end
131
+
132
+ # Stops the DI component: removes all probes and stops
133
+ # background threads.
134
+ #
135
+ # The component remains alive and can be restarted with {#start!}.
136
+ # Does not clear out the code tracker.
137
+ # No-op if already stopped. Serialized by @lifecycle_mutex.
138
+ #
139
+ # @return [void]
140
+ def stop!
141
+ @lifecycle_mutex.synchronize do
142
+ return unless @started
143
+
144
+ probe_manager.stop
145
+ probe_notifier_worker.stop
146
+ @started = false
147
+ end
148
+ end
149
+
150
+ # Whether the component is currently started.
151
+ #
152
+ # Read by remote config dispatch to decide whether to apply probe
153
+ # changes (changes received while stopped are dropped, since the
154
+ # next start! will reconcile from the latest RC state).
155
+ #
156
+ # @return [Boolean] true if start! has been called and stop! has not
157
+ def started?
158
+ @started
159
+ end
160
+
161
+ # Shuts down dynamic instrumentation permanently.
100
162
  #
101
163
  # Removes all code hooks and stops background threads.
164
+ # Called by Components#shutdown! during component destruction.
165
+ # Unlike {#stop!}, this is not reversible.
102
166
  #
103
167
  # Does not clear out the code tracker, because it's only populated
104
168
  # by code when code is compiled and therefore, if the code tracker
@@ -107,21 +171,31 @@ module Datadog
107
171
  def shutdown!(replacement = nil)
108
172
  DI.remove_current_component(self)
109
173
 
110
- probe_manager.clear_hooks
111
- probe_manager.close
112
- probe_notifier_worker.stop
174
+ # Hold the lifecycle mutex so all transitions of @started are
175
+ # serialized — start! / stop! / shutdown! cannot interleave with
176
+ # one another. Without the mutex an in-flight stop! from an RC
177
+ # callback could complete after shutdown!'s probe_manager.close,
178
+ # producing an inconsistent state.
179
+ @lifecycle_mutex.synchronize do
180
+ @started = false
181
+ probe_manager.clear_hooks
182
+ probe_manager.close
183
+ probe_notifier_worker.stop
184
+ end
113
185
  end
114
186
 
115
187
  def parse_probe_spec_and_notify(probe_spec)
116
- probe = ProbeBuilder.build_from_remote_config(probe_spec)
117
- rescue => exc
188
+ probe = ProbeBuilder.build_from_remote_config(probe_spec, logger: logger)
189
+ rescue Exception => exc # standard:disable Lint/RescueException
190
+ Datadog::DI.reraise_if_fatal(exc)
118
191
  begin
119
192
  probe = Struct.new(:id).new(
120
193
  probe_spec['id'],
121
194
  )
122
195
  payload = probe_notification_builder.build_errored(probe, exc)
123
196
  probe_notifier_worker.add_status(payload)
124
- rescue => nested_exc
197
+ rescue Exception => nested_exc # standard:disable Lint/RescueException
198
+ Datadog::DI.reraise_if_fatal(nested_exc)
125
199
  logger.debug { "di: failed to build error notification: #{nested_exc.class}: #{nested_exc.message}" }
126
200
  telemetry&.report(nested_exc, description: 'Error building probe error notification')
127
201
  raise
@@ -134,6 +134,12 @@ module Datadog
134
134
  o.default 20
135
135
  end
136
136
 
137
+ option :max_time_to_serialize_ms do |o|
138
+ o.type :int
139
+ o.default 200
140
+ o.env 'DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE'
141
+ end
142
+
137
143
  # Settings in the 'internal' group are for internal Datadog
138
144
  # use only, and are needed to test dynamic instrumentation or
139
145
  # experiment with features not released to customers.
@@ -14,6 +14,8 @@ module Datadog
14
14
  target_self: nil,
15
15
  path: nil, caller_locations: nil,
16
16
  serialized_entry_args: nil,
17
+ entry_capture_expressions: nil,
18
+ entry_capture_evaluation_errors: nil,
17
19
  return_value: nil, duration: nil, exception: nil)
18
20
  @probe = probe
19
21
  @settings = settings
@@ -23,6 +25,8 @@ module Datadog
23
25
  @path = path
24
26
  @caller_locations = caller_locations
25
27
  @serialized_entry_args = serialized_entry_args
28
+ @entry_capture_expressions = entry_capture_expressions
29
+ @entry_capture_evaluation_errors = entry_capture_evaluation_errors
26
30
  @return_value = return_value
27
31
  @duration = duration
28
32
  @exception = exception
@@ -44,6 +48,10 @@ module Datadog
44
48
 
45
49
  attr_reader :serialized_entry_args
46
50
 
51
+ attr_reader :entry_capture_expressions
52
+
53
+ attr_reader :entry_capture_evaluation_errors
54
+
47
55
  # Return value for the method, for a method probe
48
56
  attr_reader :return_value
49
57
 
@@ -55,9 +63,7 @@ module Datadog
55
63
 
56
64
  def serialized_locals
57
65
  # TODO cache?
58
- locals && serializer.serialize_vars(locals,
59
- depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
60
- attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
66
+ locals && serializer.serialize_vars(locals, **probe.snapshot_serializer_limits(settings))
61
67
  end
62
68
 
63
69
  def fetch(var_name)
@@ -78,7 +78,7 @@ module Datadog
78
78
  # We could format to a string here but what if customer
79
79
  # has @duration as part of an expression and wants
80
80
  # to retain it as a number?
81
- "(context.duration * 1000)"
81
+ "(context.duration && context.duration * 1000)"
82
82
  when '@exception'
83
83
  "context.exception"
84
84
  else
@@ -18,6 +18,10 @@ module Datadog
18
18
  class MissingLineNumber < Error
19
19
  end
20
20
 
21
+ # Probe does not contain a method name (i.e., is not a method probe).
22
+ class MissingMethodName < Error
23
+ end
24
+
21
25
  # Failed to communicate to the local Datadog agent (e.g. to send
22
26
  # probe status or a snapshot).
23
27
  class AgentCommunicationError < Error
@@ -47,6 +51,12 @@ module Datadog
47
51
  class AlreadyInstrumented < Error
48
52
  end
49
53
 
54
+ # Raised when a probe targets a class or module that the tracer
55
+ # refuses to instrument (for example, anything in the Datadog
56
+ # namespace, which the tracer itself uses internally).
57
+ class ProbeTargetForbidden < Error
58
+ end
59
+
50
60
  # Raised when installing a line probe and multiple files match the
51
61
  # specified path suffix.
52
62
  # A probe must be installed into one file only, since UI only
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Defined under DI rather than Core because the DI preload path
4
+ # (datadog/di/preload -> base) activates code tracking before the rest of the
5
+ # library, including Datadog::Core, is loaded. Catch-all rescues on that path
6
+ # must be able to re-raise fatal exceptions without referencing Core.
7
+
8
+ module Datadog
9
+ module DI
10
+ # Exception classes that catch-all rescues must never swallow: they signal
11
+ # that the process is being torn down or has run out of memory and have to
12
+ # propagate. SignalException covers Interrupt (Ctrl+C) and every other
13
+ # signal delivered as an exception.
14
+ FATAL_EXCEPTION_CLASSES = [SystemExit, SignalException, NoMemoryError].freeze
15
+
16
+ # Re-raise +exc+ when it is fatal (see FATAL_EXCEPTION_CLASSES). Call this as
17
+ # the first statement of a `rescue Exception` handler so that fatal
18
+ # conditions are not accidentally swallowed by a broad rescue.
19
+ #
20
+ # @param exc [Exception] the currently-handled exception
21
+ # @return [nil] when +exc+ is not fatal; otherwise re-raises +exc+
22
+ def self.reraise_if_fatal(exc)
23
+ raise exc if FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
24
+ end
25
+ end
26
+ end