datadog 2.40.0 → 2.41.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -58
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +2 -2
  4. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -3
  5. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +26 -12
  6. data/ext/libdatadog_api/trace_exporter.c +409 -71
  7. data/ext/libdatadog_extconf_helpers.rb +1 -1
  8. data/lib/datadog/ai_guard/api_client.rb +1 -1
  9. data/lib/datadog/ai_guard/evaluation/request.rb +8 -8
  10. data/lib/datadog/ai_guard/evaluation.rb +10 -3
  11. data/lib/datadog/ai_guard/ext.rb +1 -1
  12. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
  13. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
  14. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  15. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
  16. data/lib/datadog/appsec/configuration.rb +2 -2
  17. data/lib/datadog/appsec/context.rb +1 -1
  18. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
  19. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
  20. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  21. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
  22. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
  23. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  24. data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
  25. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
  26. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +2 -2
  27. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  28. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +2 -2
  29. data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
  30. data/lib/datadog/appsec/response.rb +1 -1
  31. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
  32. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  33. data/lib/datadog/core/configuration/components.rb +2 -2
  34. data/lib/datadog/core/configuration/option_definition.rb +1 -1
  35. data/lib/datadog/core/configuration/options.rb +1 -1
  36. data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
  37. data/lib/datadog/core/metrics/helpers.rb +1 -1
  38. data/lib/datadog/core/metrics/options.rb +2 -2
  39. data/lib/datadog/core/process_discovery.rb +1 -1
  40. data/lib/datadog/core/remote/client.rb +1 -1
  41. data/lib/datadog/core/remote/configuration/repository.rb +2 -2
  42. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  43. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
  44. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
  45. data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
  46. data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
  47. data/lib/datadog/core/telemetry/event/log.rb +2 -2
  48. data/lib/datadog/core/telemetry/metric.rb +2 -2
  49. data/lib/datadog/core/telemetry/request.rb +2 -2
  50. data/lib/datadog/core/transport/http.rb +1 -1
  51. data/lib/datadog/core/transport/transport.rb +1 -1
  52. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +127 -43
  53. data/lib/datadog/data_streams/processor.rb +14 -14
  54. data/lib/datadog/di/el/compiler.rb +23 -0
  55. data/lib/datadog/di/el/expression.rb +8 -5
  56. data/lib/datadog/di/instrumenter.rb +5 -4
  57. data/lib/datadog/di/probe_builder.rb +5 -4
  58. data/lib/datadog/di/probe_notification_builder.rb +2 -2
  59. data/lib/datadog/di/serializer.rb +29 -4
  60. data/lib/datadog/error_tracking/component.rb +1 -1
  61. data/lib/datadog/kit/appsec/events/v2.rb +2 -2
  62. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  63. data/lib/datadog/open_feature/exposures/event.rb +5 -5
  64. data/lib/datadog/profiling/collectors/info.rb +1 -1
  65. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  66. data/lib/datadog/symbol_database/extractor.rb +6 -6
  67. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  68. data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
  69. data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
  70. data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
  71. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  72. data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
  73. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
  74. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  75. data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
  76. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
  77. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  78. data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
  79. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
  80. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  81. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
  82. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  83. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
  84. data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
  85. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
  86. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  87. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
  88. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
  89. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  90. data/lib/datadog/tracing/contrib/patcher.rb +1 -1
  91. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  92. data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
  93. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
  94. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
  95. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  96. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
  97. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
  98. data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
  99. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
  100. data/lib/datadog/tracing/contrib/sequel/utils.rb +1 -1
  101. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
  102. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
  103. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
  104. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
  105. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
  106. data/lib/datadog/tracing/correlation.rb +2 -2
  107. data/lib/datadog/tracing/diagnostics/environment_logger.rb +2 -2
  108. data/lib/datadog/tracing/distributed/baggage.rb +1 -2
  109. data/lib/datadog/tracing/metadata/tagging.rb +1 -1
  110. data/lib/datadog/tracing/remote.rb +2 -2
  111. data/lib/datadog/tracing/span.rb +1 -1
  112. data/lib/datadog/tracing/span_operation.rb +1 -1
  113. data/lib/datadog/tracing/transport/io/traces.rb +1 -1
  114. data/lib/datadog/tracing/transport/native.rb +78 -54
  115. data/lib/datadog/tracing/writer.rb +1 -1
  116. data/lib/datadog/version.rb +1 -1
  117. metadata +6 -5
@@ -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
@@ -79,7 +79,7 @@ module Datadog
79
79
  topic: topic,
80
80
  partition: partition,
81
81
  offset: offset,
82
- timestamp_ns: (now.to_f * 1e9).to_i
82
+ timestamp_ns: (now.to_f * 1e9).to_i,
83
83
  }
84
84
  )
85
85
  true
@@ -98,7 +98,7 @@ module Datadog
98
98
  topic: topic,
99
99
  partition: partition,
100
100
  offset: offset,
101
- timestamp: now
101
+ timestamp: now,
102
102
  }
103
103
  )
104
104
  true
@@ -186,7 +186,7 @@ module Datadog
186
186
 
187
187
  bucket[:latest_produce_offsets][partition_key] = [
188
188
  event[:offset],
189
- bucket[:latest_produce_offsets][partition_key] || 0
189
+ bucket[:latest_produce_offsets][partition_key] || 0,
190
190
  ].max
191
191
  end
192
192
 
@@ -196,7 +196,7 @@ module Datadog
196
196
  partition: event[:partition],
197
197
  offset: event[:offset],
198
198
  timestamp: event[:timestamp],
199
- timestamp_sec: event[:timestamp].to_f
199
+ timestamp_sec: event[:timestamp].to_f,
200
200
  }
201
201
 
202
202
  timestamp_ns = (event[:timestamp].to_f * 1e9).to_i
@@ -216,7 +216,7 @@ module Datadog
216
216
  expected_offset: previous_offset + 1,
217
217
  actual_offset: event[:offset],
218
218
  gap_size: event[:offset] - previous_offset - 1,
219
- timestamp_sec: event[:timestamp].to_f
219
+ timestamp_sec: event[:timestamp].to_f,
220
220
  }
221
221
  end
222
222
 
@@ -321,7 +321,7 @@ module Datadog
321
321
  "TracerVersion" => Datadog::VERSION::STRING,
322
322
  "Lang" => "ruby",
323
323
  "Stats" => stats_buckets,
324
- "Hostname" => hostname
324
+ "Hostname" => hostname,
325
325
  } # : ::Hash[::String, (::String | ::Array[::String])]
326
326
 
327
327
  payload["ProcessTags"] = Core::Environment::Process.tags if @settings.experimental_propagate_process_tags_enabled
@@ -400,7 +400,7 @@ module Datadog
400
400
  full_pathway_latency_sec: full_pathway_latency_sec,
401
401
  payload_size: payload_size,
402
402
  tags: tags,
403
- timestamp_sec: timestamp_sec
403
+ timestamp_sec: timestamp_sec,
404
404
  }
405
405
  )
406
406
  true
@@ -449,14 +449,14 @@ module Datadog
449
449
  topic, partition = key.split(":", 2)
450
450
  backlogs << {
451
451
  "Tags" => ["type:kafka_produce", "topic:#{topic}", "partition:#{partition}"],
452
- "Value" => offset
452
+ "Value" => offset,
453
453
  }
454
454
  end
455
455
  bucket[:latest_commit_offsets].each do |key, offset|
456
456
  group, topic, partition = key.split(":", 3)
457
457
  backlogs << {
458
458
  "Tags" => ["type:kafka_commit", "consumer_group:#{group}", "topic:#{topic}", "partition:#{partition}"],
459
- "Value" => offset
459
+ "Value" => offset,
460
460
  }
461
461
  end
462
462
 
@@ -464,7 +464,7 @@ module Datadog
464
464
  "Start" => bucket_time_ns,
465
465
  "Duration" => @bucket_size_ns,
466
466
  "Stats" => bucket_stats,
467
- "Backlogs" => backlogs + serialize_consumer_backlogs
467
+ "Backlogs" => backlogs + serialize_consumer_backlogs,
468
468
  }
469
469
  end
470
470
 
@@ -479,9 +479,9 @@ module Datadog
479
479
  "Tags" => [
480
480
  "type:kafka_commit",
481
481
  "topic:#{stat[:topic]}",
482
- "partition:#{stat[:partition]}"
482
+ "partition:#{stat[:partition]}",
483
483
  ],
484
- "Value" => stat[:offset]
484
+ "Value" => stat[:offset],
485
485
  }
486
486
  end
487
487
  end
@@ -494,7 +494,7 @@ module Datadog
494
494
  {
495
495
  pathway_stats: {},
496
496
  latest_produce_offsets: {},
497
- latest_commit_offsets: {}
497
+ latest_commit_offsets: {},
498
498
  }
499
499
  end
500
500
 
@@ -503,7 +503,7 @@ module Datadog
503
503
  edge_latency: Datadog::Core::DDSketch.new,
504
504
  full_pathway_latency: Datadog::Core::DDSketch.new,
505
505
  payload_size_sum: 0,
506
- payload_size_count: 0
506
+ payload_size_count: 0,
507
507
  }
508
508
  end
509
509
  end
@@ -31,6 +31,29 @@ module Datadog
31
31
  [code, regexps]
32
32
  end
33
33
 
34
+ # Returns the identifier that +ast+ directly references at its top
35
+ # level, so the message path can redact direct references the same
36
+ # way snapshots redact by name.
37
+ #
38
+ # @param ast [untyped] top-level expression AST from a probe definition.
39
+ # @return [String, nil] the variable name for a `ref`/`iref`, the field
40
+ # name for a `getmember`, or the string key for an `index`; nil when
41
+ # the top-level expression is not a direct reference.
42
+ def redaction_identifier(ast)
43
+ return nil unless Hash === ast && ast.length == 1
44
+
45
+ entry = ast.first
46
+ return nil if entry.nil?
47
+
48
+ op, target = entry
49
+ case op
50
+ when "ref"
51
+ target if String === target
52
+ when "getmember", "index"
53
+ target[1] if Array === target && target.length == 2 && String === target[1]
54
+ end
55
+ end
56
+
34
57
  private
35
58
 
36
59
  # Steep: https://github.com/soutaro/steep/issues/363
@@ -12,12 +12,17 @@ module Datadog
12
12
  # @param regexps [Array<Regexp>] precompiled `matches` regexps (see
13
13
  # Compiler#precompile_regexp), the second element returned by
14
14
  # Compiler#compile.
15
- def initialize(dsl_expr, compiled_expr, regexps = [])
15
+ # @param redaction_identifier [String, nil] the identifier this
16
+ # expression directly references at its top level (see
17
+ # Compiler#redaction_identifier); nil when it is not a direct
18
+ # reference.
19
+ def initialize(dsl_expr, compiled_expr, regexps: [], redaction_identifier: nil)
16
20
  unless String === compiled_expr
17
21
  raise ArgumentError, "compiled_expr must be a string"
18
22
  end
19
23
 
20
24
  @dsl_expr = dsl_expr
25
+ @redaction_identifier = redaction_identifier
21
26
 
22
27
  cls = Class.new(Evaluator)
23
28
  cls.class_exec do
@@ -28,14 +33,12 @@ module Datadog
28
33
  end
29
34
  RUBY
30
35
  end
31
- # cls inherits Evaluator#initialize(regexps), but Steep types
32
- # Class#new as () -> untyped and cannot see that initializer
33
- # through this dynamically created subclass.
34
- @evaluator = cls.new(regexps) # steep:ignore UnexpectedPositionalArgument
36
+ @evaluator = cls.new(regexps)
35
37
  end
36
38
 
37
39
  attr_reader :dsl_expr
38
40
  attr_reader :evaluator
41
+ attr_reader :redaction_identifier
39
42
 
40
43
  def evaluate(context)
41
44
  @evaluator.evaluate(context)