datadog 2.40.0 → 2.42.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +120 -59
  3. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +110 -61
  5. data/ext/datadog_profiling_native_extension/collectors_stack.c +13 -5
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +141 -103
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
  8. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
  9. data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
  10. data/ext/datadog_profiling_native_extension/heap_recorder.c +338 -255
  11. data/ext/datadog_profiling_native_extension/heap_recorder.h +46 -31
  12. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
  13. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +526 -443
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
  15. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  16. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
  18. data/ext/datadog_profiling_native_extension/stack_recorder.c +93 -61
  19. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
  20. data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
  21. data/ext/libdatadog_api/di.c +10 -0
  22. data/ext/libdatadog_api/extconf.rb +3 -0
  23. data/ext/libdatadog_api/init.c +2 -0
  24. data/ext/libdatadog_api/otel_thread_context.c +232 -0
  25. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  26. data/ext/libdatadog_api/trace_exporter.c +409 -71
  27. data/ext/libdatadog_extconf_helpers.rb +1 -1
  28. data/lib/datadog/ai_guard/api_client.rb +1 -1
  29. data/lib/datadog/ai_guard/evaluation/request.rb +8 -8
  30. data/lib/datadog/ai_guard/evaluation.rb +10 -3
  31. data/lib/datadog/ai_guard/ext.rb +1 -1
  32. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
  33. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
  34. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  35. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
  36. data/lib/datadog/appsec/assets/blocked.html +1 -108
  37. data/lib/datadog/appsec/configuration.rb +2 -2
  38. data/lib/datadog/appsec/context.rb +1 -1
  39. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
  40. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
  41. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  42. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
  43. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
  44. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  45. data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
  46. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
  47. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  48. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  49. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +2 -2
  50. data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
  51. data/lib/datadog/appsec/response.rb +1 -1
  52. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
  53. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  54. data/lib/datadog/core/configuration/components.rb +3 -2
  55. data/lib/datadog/core/configuration/option_definition.rb +1 -1
  56. data/lib/datadog/core/configuration/options.rb +1 -1
  57. data/lib/datadog/core/crashtracking/component.rb +5 -1
  58. data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
  59. data/lib/datadog/core/metrics/helpers.rb +1 -1
  60. data/lib/datadog/core/metrics/options.rb +2 -2
  61. data/lib/datadog/core/process_discovery.rb +1 -1
  62. data/lib/datadog/core/remote/client.rb +1 -1
  63. data/lib/datadog/core/remote/configuration/repository.rb +2 -2
  64. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  65. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
  66. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
  67. data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
  68. data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
  69. data/lib/datadog/core/telemetry/event/log.rb +2 -2
  70. data/lib/datadog/core/telemetry/metric.rb +2 -2
  71. data/lib/datadog/core/telemetry/request.rb +2 -2
  72. data/lib/datadog/core/transport/http.rb +1 -1
  73. data/lib/datadog/core/transport/transport.rb +1 -1
  74. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +127 -43
  75. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  76. data/lib/datadog/data_streams/processor.rb +45 -14
  77. data/lib/datadog/di/el/compiler.rb +23 -0
  78. data/lib/datadog/di/el/expression.rb +8 -5
  79. data/lib/datadog/di/instrumenter.rb +46 -5
  80. data/lib/datadog/di/logger.rb +2 -2
  81. data/lib/datadog/di/probe.rb +9 -1
  82. data/lib/datadog/di/probe_builder.rb +5 -4
  83. data/lib/datadog/di/probe_notification_builder.rb +3 -2
  84. data/lib/datadog/di/remote.rb +3 -3
  85. data/lib/datadog/di/serializer.rb +29 -4
  86. data/lib/datadog/error_tracking/component.rb +1 -1
  87. data/lib/datadog/kit/appsec/events/v2.rb +2 -2
  88. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  89. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  90. data/lib/datadog/open_feature/exposures/event.rb +15 -8
  91. data/lib/datadog/open_feature/ext.rb +19 -0
  92. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  93. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  94. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  95. data/lib/datadog/open_feature/native_evaluator.rb +33 -6
  96. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  97. data/lib/datadog/open_feature/provider.rb +11 -2
  98. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  99. data/lib/datadog/opentelemetry/trace.rb +3 -0
  100. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
  101. data/lib/datadog/profiling/collectors/info.rb +1 -1
  102. data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
  103. data/lib/datadog/profiling/component.rb +8 -16
  104. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  105. data/lib/datadog/symbol_database/extractor.rb +6 -6
  106. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  107. data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
  108. data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
  109. data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
  110. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  111. data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
  112. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
  113. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  114. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  115. data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
  116. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
  117. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  118. data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
  119. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
  120. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  121. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
  122. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  123. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
  124. data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
  125. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
  126. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  127. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
  128. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
  129. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  130. data/lib/datadog/tracing/contrib/patcher.rb +1 -1
  131. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  132. data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
  133. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
  134. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
  135. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  136. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
  137. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
  138. data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
  139. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
  140. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -1
  141. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
  142. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
  143. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
  144. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
  145. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
  146. data/lib/datadog/tracing/correlation.rb +2 -2
  147. data/lib/datadog/tracing/diagnostics/environment_logger.rb +2 -2
  148. data/lib/datadog/tracing/distributed/baggage.rb +1 -3
  149. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  150. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  151. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  152. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  153. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  154. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  155. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  156. data/lib/datadog/tracing/metadata/tagging.rb +1 -1
  157. data/lib/datadog/tracing/otel_thread_context.rb +30 -0
  158. data/lib/datadog/tracing/remote.rb +197 -29
  159. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  160. data/lib/datadog/tracing/span.rb +1 -1
  161. data/lib/datadog/tracing/span_operation.rb +1 -1
  162. data/lib/datadog/tracing/trace_digest.rb +22 -4
  163. data/lib/datadog/tracing/trace_operation.rb +22 -10
  164. data/lib/datadog/tracing/tracer.rb +5 -5
  165. data/lib/datadog/tracing/transport/io/traces.rb +1 -1
  166. data/lib/datadog/tracing/transport/native.rb +78 -54
  167. data/lib/datadog/tracing/writer.rb +1 -1
  168. data/lib/datadog/version.rb +1 -1
  169. metadata +15 -8
  170. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -165,7 +165,7 @@ module Datadog
165
165
  resetter: @resetter,
166
166
  setter: @setter,
167
167
  type: @type,
168
- type_options: @type_options
168
+ type_options: @type_options,
169
169
  }
170
170
  end
171
171
 
@@ -84,7 +84,7 @@ module Datadog
84
84
  end
85
85
  {
86
86
  option_name.to_sym => opt_getter,
87
- :"#{option_name}=" => opt_setter
87
+ :"#{option_name}=" => opt_setter,
88
88
  }
89
89
  end
90
90
 
@@ -44,7 +44,11 @@ module Datadog
44
44
  # Reports unhandled exceptions to the crash tracker if available and appropriate.
45
45
  # This is called from the at_exit hook to report unhandled exceptions.
46
46
  def self.report_unhandled_exception(exception)
47
- return unless exception && !exception.is_a?(SystemExit) && !exception.is_a?(NoMemoryError)
47
+ return unless exception &&
48
+ !exception.is_a?(SystemExit) &&
49
+ !exception.is_a?(SignalException) &&
50
+ !exception.is_a?(NoMemoryError) &&
51
+ !exception.is_a?(SystemStackError)
48
52
 
49
53
  begin
50
54
  crashtracker = Datadog.send(:components, allow_initialization: false)&.crashtracker
@@ -75,7 +75,10 @@ module Datadog
75
75
  tags: tags,
76
76
  runtime_metrics_enabled: runtime_metrics_enabled,
77
77
  vm: vm,
78
- health_metrics_enabled: health_metrics_enabled
78
+ health_metrics_enabled: health_metrics_enabled,
79
+ otlp_traces_export_enabled: otlp_traces_export_enabled,
80
+ otlp_metrics_export_enabled: otlp_metrics_export_enabled,
81
+ otlp_logs_export_enabled: otlp_logs_export_enabled,
79
82
  }
80
83
  end
81
84
 
@@ -159,8 +162,40 @@ module Datadog
159
162
  !!Datadog.configuration.health_metrics.enabled
160
163
  end
161
164
 
165
+ # @return [Boolean] whether the tracer exports traces over OTLP.
166
+ # Always false: Ruby exports spans in Datadog's native format and has no OTLP trace exporter.
167
+ def otlp_traces_export_enabled
168
+ false
169
+ end
170
+
171
+ # @return [Boolean] whether the tracer exports metrics over OTLP
172
+ def otlp_metrics_export_enabled
173
+ metrics = opentelemetry_settings&.metrics
174
+ # A "none" exporter skips the OTLP metric reader even when metrics are enabled
175
+ # (mirrors Datadog::OpenTelemetry::Ext::EXPORTER_NONE).
176
+ !!(metrics&.enabled && metrics.exporter != "none")
177
+ end
178
+
179
+ # @return [Boolean] whether the tracer exports logs over OTLP
180
+ def otlp_logs_export_enabled
181
+ logs = opentelemetry_settings&.logs
182
+ # A "none" exporter skips the OTLP log record processor even when logs are enabled
183
+ # (mirrors Datadog::OpenTelemetry::Ext::EXPORTER_NONE).
184
+ !!(logs&.enabled && logs.exporter != "none")
185
+ end
186
+
162
187
  private
163
188
 
189
+ # The `opentelemetry` settings namespace is registered by core configuration, but access it
190
+ # defensively so a missing/unregistered namespace defaults to `false` rather than raising.
191
+ def opentelemetry_settings
192
+ return unless Datadog.configuration.respond_to?(:opentelemetry)
193
+
194
+ Datadog.configuration.opentelemetry
195
+ rescue
196
+ nil
197
+ end
198
+
164
199
  # Outputs "k1:v1,k2:v2,..."
165
200
  def hash_serializer(h)
166
201
  h.map { |k, v| "#{k}:#{v}" }.join(",")
@@ -10,7 +10,7 @@ module Datadog
10
10
  :distribution,
11
11
  :increment,
12
12
  :gauge,
13
- :time
13
+ :time,
14
14
  ].each do |metric_type|
15
15
  define_method(metric_type) do |name, stat|
16
16
  name = name.to_sym
@@ -16,8 +16,8 @@ module Datadog
16
16
  "#{Ext::TAG_LANG_VERSION}:#{Environment::Identity.lang_version}",
17
17
  # TODO: Technically not accurate, if tracing version diverges from datadog gem version
18
18
  # If we extract tracing to its own gem, this needs to be updated.
19
- "#{Ext::TAG_TRACER_VERSION}:#{Environment::Identity.gem_datadog_version}"
20
- ].freeze
19
+ "#{Ext::TAG_TRACER_VERSION}:#{Environment::Identity.gem_datadog_version}",
20
+ ].freeze,
21
21
  }.freeze
22
22
 
23
23
  def metric_options(options = nil)
@@ -53,7 +53,7 @@ module Datadog
53
53
  service_version: settings.version || "",
54
54
  # Follows Java: https://github.com/DataDog/dd-trace-java/blob/2ebc964340ac530342cc389ba68ff0f5070d5f9f/dd-trace-core/src/main/java/datadog/trace/core/servicediscovery/ServiceDiscovery.java#L37-L38
55
55
  process_tags: settings.experimental_propagate_process_tags_enabled ? Core::Environment::Process.serialized : "",
56
- container_id: Core::Environment::Container.container_id || ""
56
+ container_id: Core::Environment::Container.container_id || "",
57
57
  }
58
58
  end
59
59
  end
@@ -185,7 +185,7 @@ module Datadog
185
185
  is_agent: false,
186
186
  client_tracer: client_tracer,
187
187
  # base64 is needed otherwise the Go agent fails with an unmarshal error
188
- capabilities: @capabilities.base64_capabilities
188
+ capabilities: @capabilities.base64_capabilities,
189
189
  },
190
190
  cached_target_files: state.cached_target_files,
191
191
  }
@@ -111,9 +111,9 @@ module Datadog
111
111
  hashes: content.hashes.map do |algorithm, hexdigest|
112
112
  {
113
113
  algorithm: algorithm,
114
- hash: hexdigest
114
+ hash: hexdigest,
115
115
  }
116
- end
116
+ end,
117
117
  }
118
118
  end
119
119
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module Core
5
+ module Telemetry
6
+ module ConfigurationValue
7
+ module_function
8
+
9
+ def convert(value)
10
+ case value
11
+ when Integer, String, true, false, nil
12
+ value
13
+ when Float
14
+ value.finite? ? value : value.to_s
15
+ when Hash
16
+ value.map { |key, entry_value| "#{key}:#{entry_value}" }.join(",")
17
+ when Array
18
+ value.join(",")
19
+ when Module
20
+ value.name.to_s
21
+ else
22
+ implements_to_s = begin
23
+ value.method(:to_s).owner != Kernel
24
+ rescue NameError
25
+ false
26
+ end
27
+
28
+ if implements_to_s
29
+ value.to_s
30
+ else
31
+ value.class.to_s
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base"
4
+ require_relative "../configuration_value"
4
5
 
5
6
  module Datadog
6
7
  module Core
@@ -27,10 +28,11 @@ module Datadog
27
28
  def configuration
28
29
  config = Datadog.configuration
29
30
 
31
+ # @type var res: Array[telemetry_configuration]
30
32
  res = @changes.map do |name, value|
31
33
  {
32
34
  name: name,
33
- value: value,
35
+ value: Telemetry::ConfigurationValue.convert(value),
34
36
  origin: @origin,
35
37
  seq_id: Configuration::Option::Precedence::REMOTE_CONFIGURATION.numeric.next,
36
38
  }
@@ -20,7 +20,7 @@ module Datadog
20
20
  def payload
21
21
  {
22
22
  is_first: @is_first,
23
- endpoints: @endpoints
23
+ endpoints: @endpoints,
24
24
  }
25
25
  end
26
26
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "base"
4
+ require_relative "../configuration_value"
4
5
 
5
6
  module Datadog
6
7
  module Core
@@ -65,7 +66,7 @@ module Datadog
65
66
  # actually enabled by env var or by implicit enablement via
66
67
  # remote config.
67
68
  enabled: components.dynamic_instrumentation&.started? || false,
68
- }
69
+ },
69
70
  }
70
71
 
71
72
  if (unsupported_reason = Datadog::Profiling.unsupported_reason)
@@ -214,23 +215,9 @@ module Datadog
214
215
  end
215
216
 
216
217
  def to_telemetry_value(value)
217
- # TODO: Add float if telemetry starts accepting it
218
- case value
219
- when Integer, String, true, false, nil
220
- value
221
- when Hash
222
- value.map { |key, entry_value| "#{key}:#{entry_value}" }.join(",")
223
- when Array
224
- value.join(",")
225
- when Module
226
- value.name.to_s
227
- else
228
- if implements_to_s?(value)
229
- value.to_s
230
- else
231
- value.class.to_s
232
- end
233
- end
218
+ return value.to_s if value.is_a?(Float)
219
+
220
+ Telemetry::ConfigurationValue.convert(value)
234
221
  end
235
222
 
236
223
  def install_signature(settings)
@@ -280,12 +267,6 @@ module Datadog
280
267
  parent_setting = settings.dig(*split_option)
281
268
  parent_setting.send(:resolve_option, option_name.to_sym)
282
269
  end
283
-
284
- def implements_to_s?(value)
285
- value.method(:to_s).owner != Kernel
286
- rescue NameError
287
- false
288
- end
289
270
  end
290
271
  end
291
272
  end
@@ -23,7 +23,7 @@ module Datadog
23
23
  def payload
24
24
  {
25
25
  namespace: @namespace,
26
- series: @metric_series.map(&:to_h)
26
+ series: @metric_series.map(&:to_h),
27
27
  }
28
28
  end
29
29
 
@@ -52,8 +52,8 @@ module Datadog
52
52
  level: @level,
53
53
  stack_trace: @stack_trace,
54
54
  count: @count,
55
- }.compact
56
- ]
55
+ }.compact,
56
+ ],
57
57
  }
58
58
  end
59
59
 
@@ -37,7 +37,7 @@ module Datadog
37
37
  points: values,
38
38
  type: type,
39
39
  tags: tags,
40
- common: common
40
+ common: common,
41
41
  }
42
42
  end
43
43
 
@@ -179,7 +179,7 @@ module Datadog
179
179
  metric: name,
180
180
  points: values,
181
181
  tags: tags,
182
- common: common
182
+ common: common,
183
183
  }
184
184
  end
185
185
  end
@@ -50,7 +50,7 @@ module Datadog
50
50
  runtime_version: Core::Environment::Ext::ENGINE_VERSION,
51
51
  service_name: config.service,
52
52
  service_version: config.version,
53
- tracer_version: tracer_version
53
+ tracer_version: tracer_version,
54
54
  }
55
55
 
56
56
  tag_process_tags!(app, config)
@@ -64,7 +64,7 @@ module Datadog
64
64
  hostname: Core::Environment::Platform.hostname,
65
65
  kernel_name: Core::Environment::Platform.kernel_name,
66
66
  kernel_release: Core::Environment::Platform.kernel_release,
67
- kernel_version: Core::Environment::Platform.kernel_version
67
+ kernel_version: Core::Environment::Platform.kernel_version,
68
68
  }
69
69
  end
70
70
 
@@ -61,7 +61,7 @@ module Datadog
61
61
  Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER_VENDOR =>
62
62
  Core::Environment::Ext::LANG_ENGINE,
63
63
  Core::Transport::Ext::HTTP::HEADER_META_TRACER_VERSION =>
64
- Datadog::Core::Environment::Ext::GEM_DATADOG_VERSION
64
+ Datadog::Core::Environment::Ext::GEM_DATADOG_VERSION,
65
65
  }.tap do |headers|
66
66
  # Add application container info
67
67
  headers.merge!(Core::Environment::Container.to_headers)
@@ -69,7 +69,7 @@ module Datadog
69
69
 
70
70
  @current_api_id = api_id
71
71
  client_class = self.class.http_client_class || Core::Transport::HTTP::Client
72
- @client = client_class.new(current_api, logger: logger) # steep:ignore
72
+ @client = client_class.new(current_api, logger: logger)
73
73
  end
74
74
 
75
75
  def downgrade?(response)
@@ -6,14 +6,30 @@ 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_BEFORE_BLOCKS = [] # rubocop:disable Style/MutableConstant -- Used to store blocks to run, mutable by design.
10
- private_constant :AT_FORK_BEFORE_BLOCKS
9
+ class Callback
10
+ attr_reader :block, :group
11
11
 
12
- AT_FORK_PARENT_BLOCKS = [] # rubocop:disable Style/MutableConstant -- Used to store blocks to run, mutable by design.
13
- private_constant :AT_FORK_PARENT_BLOCKS
12
+ def initialize(block, group = nil)
13
+ @block = block
14
+ @group = group
15
+ freeze
16
+ end
17
+ end
18
+ private_constant :Callback
19
+
20
+ EMPTY_AT_FORK_BLOCKS = {
21
+ before: [].freeze,
22
+ parent: [].freeze,
23
+ child: [].freeze,
24
+ }.freeze
25
+ private_constant :EMPTY_AT_FORK_BLOCKS
26
+
27
+ @at_fork_blocks = EMPTY_AT_FORK_BLOCKS
14
28
 
15
- AT_FORK_CHILD_BLOCKS = [] # rubocop:disable Style/MutableConstant -- Used to store blocks to run, mutable by design.
16
- private_constant :AT_FORK_CHILD_BLOCKS
29
+ # Serializes copy-on-write registry updates. Fork dispatch only reads the
30
+ # latest immutable registry reference and never acquires this mutex.
31
+ AT_FORK_REGISTRY_MUTEX = Mutex.new
32
+ private_constant :AT_FORK_REGISTRY_MUTEX
17
33
 
18
34
  def self.supported?
19
35
  Process.respond_to?(:fork)
@@ -37,9 +53,38 @@ module Datadog
37
53
  true
38
54
  end
39
55
 
40
- def self.run_at_fork_blocks(stage)
41
- blocks_for(stage).each(&:call)
56
+ # Runs the callbacks copied for one fork lifecycle. Before callbacks
57
+ # stop at the first failure; parent and child callbacks all run before
58
+ # the first failure is re-raised.
59
+ def self.run_at_fork_blocks(stage, snapshot:, started: nil)
60
+ callbacks = blocks_for(snapshot, stage)
61
+ if stage == :before
62
+ return callbacks.each do |callback|
63
+ started[callback.group] = true if started && callback.group
64
+ callback.block.call
65
+ end
66
+ end
67
+
68
+ error = nil
69
+ callbacks.each do |callback|
70
+ next if started && callback.group && !started.key?(callback.group)
71
+
72
+ callback.block.call
73
+ rescue Exception => e # rubocop:disable Lint/RescueException -- finish cleanup, then re-raise the first failure
74
+ error ||= e
75
+ end
76
+ raise error if error
77
+ end
78
+
79
+ def self.run_parent_cleanup(snapshot, started, fork_error)
80
+ run_at_fork_blocks(:parent, snapshot: snapshot, started: started)
81
+ rescue Exception => cleanup_error # rubocop:disable Lint/RescueException -- preserve the original fork failure
82
+ Datadog.logger.warn do
83
+ "Parent at-fork cleanup failed while handling #{fork_error.class}: " \
84
+ "#{cleanup_error.class}: #{cleanup_error.message}"
85
+ end
42
86
  end
87
+ private_class_method :run_parent_cleanup
43
88
 
44
89
  # Registers a block to run at the given fork +stage+ (+:before+,
45
90
  # +:parent+, or +:child+).
@@ -49,26 +94,63 @@ module Datadog
49
94
  def self.at_fork(stage, &block)
50
95
  raise(ArgumentError, "Missing block argument") unless block
51
96
 
52
- blocks_for(stage) << block
97
+ AT_FORK_REGISTRY_MUTEX.synchronize do
98
+ current = @at_fork_blocks
99
+ @at_fork_blocks = current.merge(
100
+ stage => (blocks_for(current, stage) + [Callback.new(block)]).freeze
101
+ ).freeze
102
+ end
53
103
 
54
104
  block
55
105
  end
56
106
 
107
+ # Registers one before/parent/child callback triplet atomically relative
108
+ # to the snapshots taken by fork dispatch.
109
+ def self.at_fork_blocks(before:, parent:, child:)
110
+ blocks = {before: before, parent: parent, child: child}
111
+ group = Object.new.freeze
112
+ AT_FORK_REGISTRY_MUTEX.synchronize do
113
+ current = @at_fork_blocks
114
+ @at_fork_blocks = {
115
+ before: (current.fetch(:before) + [Callback.new(before, group)]).freeze,
116
+ parent: (current.fetch(:parent) + [Callback.new(parent, group)]).freeze,
117
+ child: (current.fetch(:child) + [Callback.new(child, group)]).freeze,
118
+ }.freeze
119
+ end
120
+ blocks
121
+ end
122
+
57
123
  # Deregisters a block previously registered with {.at_fork} for the given
58
124
  # +stage+. It is a no-op (does not raise) when +block+ was never
59
125
  # registered (or was already removed). Raises +ArgumentError+ for an
60
126
  # unknown stage, matching the {.at_fork} contract.
61
127
  def self.remove_at_fork(stage, block)
62
- blocks_for(stage).delete(block)
128
+ AT_FORK_REGISTRY_MUTEX.synchronize do
129
+ current = @at_fork_blocks
130
+ callbacks = blocks_for(current, stage)
131
+ @at_fork_blocks = current.merge(
132
+ stage => callbacks.reject { |callback| callback.block.equal?(block) }.freeze
133
+ ).freeze
134
+ end
63
135
 
64
136
  nil
65
137
  end
66
138
 
67
- def self.blocks_for(stage)
139
+ # Returns the immutable callback registry for one fork lifecycle.
140
+ # Copy-on-write updates publish a new reference, so registrations made
141
+ # afterwards apply only to the next lifecycle without locking this read.
142
+ def self.snapshot_at_fork_blocks
143
+ @at_fork_blocks
144
+ end
145
+
146
+ def self.replace_at_fork_blocks(snapshot)
147
+ AT_FORK_REGISTRY_MUTEX.synchronize { @at_fork_blocks = snapshot }
148
+ end
149
+ private_class_method :replace_at_fork_blocks
150
+
151
+ def self.blocks_for(snapshot, stage)
68
152
  case stage
69
- when :before then AT_FORK_BEFORE_BLOCKS
70
- when :parent then AT_FORK_PARENT_BLOCKS
71
- when :child then AT_FORK_CHILD_BLOCKS
153
+ when :before, :parent, :child then snapshot.fetch(stage)
72
154
  else raise(ArgumentError, "Unsupported stage #{stage}")
73
155
  end
74
156
  end
@@ -77,27 +159,29 @@ module Datadog
77
159
  # Adds `at_fork` behavior; see parent module for details.
78
160
  module KernelMonkeyPatch
79
161
  def fork
162
+ snapshot = AtForkMonkeyPatch.snapshot_at_fork_blocks
163
+ started = {}
164
+
80
165
  # If a block is provided, it must be wrapped to trigger callbacks.
81
166
  child_block = if block_given?
82
167
  proc do
83
- AtForkMonkeyPatch.run_at_fork_blocks(:child)
168
+ AtForkMonkeyPatch.run_at_fork_blocks(:child, snapshot: snapshot)
84
169
 
85
170
  # Invoke original block
86
171
  yield
87
172
  end
88
173
  end
89
174
 
90
- # Run pre-fork callbacks in the parent, just before forking.
91
- AtForkMonkeyPatch.run_at_fork_blocks(:before)
92
-
93
- # Start fork
94
- # If a block is provided, use the wrapped version.
95
175
  begin
176
+ # Run pre-fork callbacks in the parent, just before forking.
177
+ AtForkMonkeyPatch.run_at_fork_blocks(:before, snapshot: snapshot, started: started)
178
+
179
+ # Start fork. If a block is provided, use the wrapped version.
96
180
  result = child_block.nil? ? super : super(&child_block)
97
- rescue Exception # rubocop:disable Lint/RescueException -- re-raised unchanged; we only need to run parent cleanup first
98
- # The fork failed and we are still in the parent; run `:parent` to
99
- # restore any state the `:before` blocks set up, then re-raise.
100
- AtForkMonkeyPatch.run_at_fork_blocks(:parent)
181
+ rescue Exception => e # rubocop:disable Lint/RescueException -- re-raised unchanged; we only need to run parent cleanup first
182
+ # The fork or a before-fork callback failed and we are still in
183
+ # the parent. Restore any state set up by earlier callbacks.
184
+ AtForkMonkeyPatch.send(:run_parent_cleanup, snapshot, started, e)
101
185
  raise
102
186
  end
103
187
 
@@ -106,9 +190,9 @@ module Datadog
106
190
  # If we're in the parent, result = pid: trigger parent callbacks.
107
191
  # (If it gets called with a block, it only returns on the parent)
108
192
  if result.nil?
109
- AtForkMonkeyPatch.run_at_fork_blocks(:child)
193
+ AtForkMonkeyPatch.run_at_fork_blocks(:child, snapshot: snapshot)
110
194
  else
111
- AtForkMonkeyPatch.run_at_fork_blocks(:parent)
195
+ AtForkMonkeyPatch.run_at_fork_blocks(:parent, snapshot: snapshot)
112
196
  end
113
197
 
114
198
  result
@@ -120,23 +204,22 @@ module Datadog
120
204
  # Hook provided by Ruby 3.1+ for observability libraries that want to know about fork, see
121
205
  # https://github.com/ruby/ruby/pull/5017 and https://bugs.ruby-lang.org/issues/17795
122
206
  def _fork
123
- AtForkMonkeyPatch.run_at_fork_blocks(:before)
124
-
207
+ snapshot = AtForkMonkeyPatch.snapshot_at_fork_blocks
208
+ started = {}
125
209
  begin
210
+ AtForkMonkeyPatch.run_at_fork_blocks(:before, snapshot: snapshot, started: started)
126
211
  pid = super
127
- rescue Exception # rubocop:disable Lint/RescueException -- re-raised unchanged; we only need to run parent cleanup first
128
- # The fork failed, so no child was created and we are still in the
129
- # parent. The `:before` blocks already ran (and may hold resources,
130
- # e.g. a locked mutex); run the `:parent` blocks so that state is
131
- # restored, then re-raise.
132
- AtForkMonkeyPatch.run_at_fork_blocks(:parent)
212
+ rescue Exception => e # rubocop:disable Lint/RescueException -- re-raised unchanged; we only need to run parent cleanup first
213
+ # The fork or a before-fork callback failed, so no child was
214
+ # created. Restore state set up by any earlier callbacks.
215
+ AtForkMonkeyPatch.send(:run_parent_cleanup, snapshot, started, e)
133
216
  raise
134
217
  end
135
218
 
136
219
  if pid == 0
137
- AtForkMonkeyPatch.run_at_fork_blocks(:child)
220
+ AtForkMonkeyPatch.run_at_fork_blocks(:child, snapshot: snapshot)
138
221
  else
139
- AtForkMonkeyPatch.run_at_fork_blocks(:parent)
222
+ AtForkMonkeyPatch.run_at_fork_blocks(:parent, snapshot: snapshot)
140
223
  end
141
224
 
142
225
  pid
@@ -146,20 +229,21 @@ module Datadog
146
229
  # keeps executing code in the child process, killing off the parent, thus effectively replacing it.
147
230
  # This is not covered by `_fork` and thus we have some extra code for it.
148
231
  def daemon(*args)
149
- AtForkMonkeyPatch.run_at_fork_blocks(:before)
150
-
232
+ snapshot = AtForkMonkeyPatch.snapshot_at_fork_blocks
233
+ started = {}
151
234
  begin
235
+ AtForkMonkeyPatch.run_at_fork_blocks(:before, snapshot: snapshot, started: started)
152
236
  result = super
153
- rescue Exception # rubocop:disable Lint/RescueException -- re-raised unchanged; we only need to run parent cleanup first
154
- # `daemon` failed, so the original process survives; run `:parent`
155
- # to restore state the `:before` blocks set up, then re-raise.
156
- AtForkMonkeyPatch.run_at_fork_blocks(:parent)
237
+ rescue Exception => e # rubocop:disable Lint/RescueException -- re-raised unchanged; we only need to run parent cleanup first
238
+ # `daemon` or a before-fork callback failed, so the original
239
+ # process survives. Restore state set up by earlier callbacks.
240
+ AtForkMonkeyPatch.send(:run_parent_cleanup, snapshot, started, e)
157
241
  raise
158
242
  end
159
243
 
160
244
  # `daemon` kills the parent, so there is no surviving parent to run
161
245
  # `:parent` callbacks in; only the child continues executing.
162
- AtForkMonkeyPatch.run_at_fork_blocks(:child)
246
+ AtForkMonkeyPatch.run_at_fork_blocks(:child, snapshot: snapshot)
163
247
 
164
248
  result
165
249
  end