datadog 2.41.0 → 2.43.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/README.md +3 -0
  4. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  5. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
  9. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
  10. data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
  11. data/ext/datadog_profiling_native_extension/heap_recorder.c +351 -255
  12. data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
  13. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
  14. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
  15. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
  16. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  17. data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
  19. data/ext/datadog_profiling_native_extension/stack_recorder.c +106 -61
  20. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
  21. data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
  22. data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
  23. data/ext/libdatadog_api/di.c +10 -0
  24. data/ext/libdatadog_api/extconf.rb +3 -0
  25. data/ext/libdatadog_api/init.c +2 -0
  26. data/ext/libdatadog_api/otel_thread_context.c +247 -0
  27. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  28. data/ext/libdatadog_api/trace_exporter.c +642 -22
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/blocked.html +1 -108
  31. data/lib/datadog/core/configuration/components.rb +1 -0
  32. data/lib/datadog/core/crashtracking/component.rb +5 -1
  33. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  34. data/lib/datadog/data_streams/processor.rb +31 -0
  35. data/lib/datadog/di/instrumenter.rb +41 -1
  36. data/lib/datadog/di/logger.rb +2 -2
  37. data/lib/datadog/di/probe.rb +9 -1
  38. data/lib/datadog/di/probe_notification_builder.rb +1 -0
  39. data/lib/datadog/di/remote.rb +3 -3
  40. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  41. data/lib/datadog/open_feature/exposures/event.rb +10 -3
  42. data/lib/datadog/open_feature/ext.rb +19 -0
  43. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  44. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  45. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  46. data/lib/datadog/open_feature/native_evaluator.rb +33 -6
  47. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  48. data/lib/datadog/open_feature/provider.rb +11 -2
  49. data/lib/datadog/open_feature/transport.rb +5 -0
  50. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  51. data/lib/datadog/opentelemetry/trace.rb +3 -0
  52. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
  53. data/lib/datadog/profiling/collectors/info.rb +2 -6
  54. data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
  55. data/lib/datadog/profiling/component.rb +8 -16
  56. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  57. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  58. data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
  59. data/lib/datadog/tracing/distributed/baggage.rb +0 -1
  60. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  61. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  62. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  63. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  64. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  65. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  66. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  67. data/lib/datadog/tracing/otel_thread_context.rb +34 -0
  68. data/lib/datadog/tracing/remote.rb +195 -27
  69. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  70. data/lib/datadog/tracing/trace_digest.rb +22 -4
  71. data/lib/datadog/tracing/trace_operation.rb +22 -10
  72. data/lib/datadog/tracing/tracer.rb +5 -5
  73. data/lib/datadog/tracing/transport/native.rb +17 -25
  74. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  75. data/lib/datadog/tracing/transport/traces.rb +2 -22
  76. data/lib/datadog/version.rb +1 -1
  77. metadata +16 -8
  78. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -10,7 +10,7 @@ module Datadog
10
10
  module LibdatadogExtconfHelpers
11
11
  # Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
12
12
  # may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
13
- LIBDATADOG_VERSION = "~> 40.0.0.1.0"
13
+ LIBDATADOG_VERSION = "~> 40.0.0.2.0"
14
14
 
15
15
  # Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
16
16
  # libdatadog are moved after the extension gets compiled.
@@ -1,108 +1 @@
1
- <!-- Sorry, you've been blocked -->
2
- <!DOCTYPE html>
3
- <html lang="en">
4
-
5
- <head>
6
- <meta charset="UTF-8">
7
- <meta name="viewport" content="width=device-width,initial-scale=1">
8
- <title>You've been blocked</title>
9
- <style>
10
- a,
11
- body,
12
- div,
13
- html,
14
- span {
15
- margin: 0;
16
- padding: 0;
17
- border: 0;
18
- font-size: 100%;
19
- font: inherit;
20
- vertical-align: baseline
21
- }
22
-
23
- body {
24
- background: -webkit-radial-gradient(26% 19%, circle, #fff, #f4f7f9);
25
- background: radial-gradient(circle at 26% 19%, #fff, #f4f7f9);
26
- display: -webkit-box;
27
- display: -ms-flexbox;
28
- display: flex;
29
- -webkit-box-pack: center;
30
- -ms-flex-pack: center;
31
- justify-content: center;
32
- -webkit-box-align: center;
33
- -ms-flex-align: center;
34
- align-items: center;
35
- -ms-flex-line-pack: center;
36
- align-content: center;
37
- width: 100%;
38
- min-height: 100vh;
39
- line-height: 1;
40
- flex-direction: column
41
- }
42
-
43
- p {
44
- display: block
45
- }
46
-
47
-
48
- main {
49
- text-align: center;
50
- flex: 1;
51
- display: -webkit-box;
52
- display: -ms-flexbox;
53
- display: flex;
54
- -webkit-box-pack: center;
55
- -ms-flex-pack: center;
56
- justify-content: center;
57
- -webkit-box-align: center;
58
- -ms-flex-align: center;
59
- align-items: center;
60
- -ms-flex-line-pack: center;
61
- align-content: center;
62
- flex-direction: column
63
- }
64
-
65
- p {
66
- font-size: 18px;
67
- line-height: normal;
68
- color: #646464;
69
- font-family: sans-serif;
70
- font-weight: 400
71
- }
72
-
73
- a {
74
- color: #4842b7
75
- }
76
-
77
- footer {
78
- width: 100%;
79
- text-align: center
80
- }
81
-
82
- footer p {
83
- font-size: 16px
84
- }
85
-
86
- .security-response-id {
87
- font-size:14px;
88
- color:#999;
89
- margin-top:20px;
90
- font-family:monospace
91
- }
92
- </style>
93
- </head>
94
-
95
- <body>
96
- <main>
97
- <p>Sorry, you cannot access this page. Please contact the customer service team.</p>
98
- <p class="security-response-id">Security Response ID: [security_response_id]</p>
99
- </main>
100
- <footer>
101
- <p>Security provided by <a
102
- href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/"
103
- target="_blank"
104
- rel="noopener noreferrer">Datadog</a></p>
105
- </footer>
106
- </body>
107
-
108
- </html>
1
+ <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}.security-response-id{font-size:14px;color:#999;margin-top:20px;font-family:monospace}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p><p class="security-response-id">Security Response ID: [security_response_id]</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank" rel="noopener noreferrer">Datadog</a></p></footer></body></html>
@@ -248,6 +248,7 @@ module Datadog
248
248
  crashtracker&.update_on_fork
249
249
  ProcessDiscovery.after_fork
250
250
  symbol_database&.after_fork!
251
+ data_streams&.restart_flush_thread
251
252
  end
252
253
 
253
254
  # Hot-swaps with a new sampler.
@@ -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
@@ -98,40 +98,38 @@ module Datadog
98
98
  end
99
99
  private_class_method :decode
100
100
 
101
- # Encode an unsigned 64-bit integer using LEB128 variable-length encoding.
101
+ # Encode a signed 64-bit integer using ZigZag + LEB128 variable-length encoding.
102
102
  #
103
- # This implements unsigned LEB128 (Little Endian Base 128) encoding as specified
104
- # in DWARF5 standard section 7.6:
105
- # https://dwarfstd.org/doc/DWARF5.pdf#page=301
103
+ # dd-trace-go's DSM pathway encoder (internal/datastreams/propagator.go, via
104
+ # sketches-go's ddsketch/encoding package) ZigZag-maps the signed value before
105
+ # applying plain unsigned LEB128 (DWARF5 section 7.6:
106
+ # https://dwarfstd.org/doc/DWARF5.pdf#page=301). This implementation previously
107
+ # skipped the ZigZag step, which silently decoded to the wrong timestamp on any
108
+ # dd-trace-go consumer -- see https://github.com/DataDog/dd-trace-go/issues/5163.
106
109
  #
107
- # Each byte uses 7 bits for data and 1 bit to indicate continuation.
108
- # The high bit is set if more bytes follow, clear for the final byte.
110
+ # ZigZag maps signed values to unsigned ones so small-magnitude negative numbers
111
+ # still encode to few bytes: 0, -1, 1, -2, 2, ... -> 0, 1, 2, 3, 4, ...
109
112
  #
110
- # @param value [Integer] Unsigned integer value to encode
113
+ # @param value [Integer] Signed integer value to encode
111
114
  # @return [String] Binary string of encoded bytes
112
115
  def encode_var_int_64(value)
116
+ zigzag = (value << 1) ^ (value >> 63)
113
117
  bytes = []
114
- while value >= 0x80
115
- bytes << ((value & 0x7F) | 0x80)
116
- value >>= 7
118
+ while zigzag >= 0x80
119
+ bytes << ((zigzag & 0x7F) | 0x80)
120
+ zigzag >>= 7
117
121
  end
118
- bytes << value
122
+ bytes << zigzag
119
123
  bytes.pack("C*")
120
124
  end
121
125
 
122
- # Decode an unsigned LEB128 variable-length integer from IO stream.
126
+ # Decode a signed 64-bit integer encoded with ZigZag + LEB128 from an IO stream.
123
127
  #
124
- # This implements unsigned LEB128 (Little Endian Base 128) decoding as specified
125
- # in DWARF5 standard section 7.6:
126
- # https://dwarfstd.org/doc/DWARF5.pdf#page=301
127
- #
128
- # VarInt format: Each byte uses 7 bits for data, 1 bit for continuation
129
- # - High bit set = more bytes follow
130
- # - High bit clear = final byte
131
- # - Data bits accumulated in little-endian order
128
+ # See #encode_var_int_64 for why the ZigZag un-map is required to match
129
+ # dd-trace-go's wire format.
132
130
  #
133
131
  # @param io [StringIO] IO stream to read from
134
- # @return [Integer, nil] Decoded unsigned integer, or nil if malformed
132
+ # @return [Integer, nil] Decoded signed integer, or nil if malformed
135
133
  def self.decode_varint(io)
136
134
  value = 0
137
135
  shift = 0
@@ -143,7 +141,7 @@ module Datadog
143
141
  value |= (byte & 0x7F) << shift
144
142
 
145
143
  # If high bit is clear, we're done
146
- return value unless (byte & 0x80).nonzero?
144
+ return (value >> 1) ^ -(value & 1) unless (byte & 0x80).nonzero?
147
145
 
148
146
  shift += 7
149
147
 
@@ -62,10 +62,27 @@ module Datadog
62
62
 
63
63
  super()
64
64
  self.loop_base_interval = interval
65
+ # Without this, a preload-then-fork deployment model (e.g. Puma cluster mode with
66
+ # preload_app!, or Sidekiq Enterprise's sidekiqswarm with SIDEKIQ_PRELOAD_APP) leaves
67
+ # every forked child with a dead flush thread: Ruby threads don't survive fork, and the
68
+ # default FORK_POLICY_STOP means #perform would otherwise just mark the worker stopped
69
+ # instead of restarting it. See #restart_flush_thread.
70
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
65
71
 
66
72
  perform
67
73
  end
68
74
 
75
+ # Restarts the flush thread if a fork has been detected since this processor was created.
76
+ # Called by Components#after_fork. Safe to call even when no fork occurred: #perform (via
77
+ # Workers::Async::Thread) only restarts the worker when +forked?+ is true. Deliberately not
78
+ # named +after_fork+: Workers::Async::Thread already defines a protected +after_fork+
79
+ # template method that #perform's internal restart path (+restart_after_fork+) calls on
80
+ # every restart -- overriding it here would recurse.
81
+ def restart_flush_thread
82
+ discard_inherited_state_after_fork! if forked?
83
+ perform
84
+ end
85
+
69
86
  # Track Kafka produce offset for lag monitoring
70
87
  # @param topic [String] The Kafka topic name
71
88
  # @param partition [Integer] The partition number
@@ -302,6 +319,20 @@ module Datadog
302
319
  PathwayContext.decode_b64(encoded_ctx)
303
320
  end
304
321
 
322
+ # A fork copies whatever was buffered in the parent (unflushed events, aggregated
323
+ # buckets, consumer stats) into the child. If the parent process is still alive and
324
+ # flushing (e.g. a supervisor process that stays up after forking workers), letting
325
+ # the child flush that same inherited data as well would double-report it to the
326
+ # agent. Discard it instead: losing a few seconds of pre-fork stats is preferable to
327
+ # corrupting pipeline throughput/lag numbers with duplicates.
328
+ def discard_inherited_state_after_fork!
329
+ @event_buffer.pop
330
+ @stats_mutex.synchronize do
331
+ @buckets.clear
332
+ @consumer_stats.clear
333
+ end
334
+ end
335
+
305
336
  def flush_stats
306
337
  stats_buckets = @stats_mutex.synchronize do
307
338
  return if @buckets.empty? && @consumer_stats.empty?
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../core/rate_limiter"
3
4
  require_relative "../core/utils/time"
4
5
  require_relative "../ruby_version"
5
6
  require_relative "fatal_exceptions"
@@ -69,12 +70,22 @@ module Datadog
69
70
  #
70
71
  # @api private
71
72
  class Instrumenter
73
+ # Maximum number of capturing-probe snapshots admitted per second across the
74
+ # whole process.
75
+ GLOBAL_SNAPSHOT_RATE_LIMIT = 20
76
+
77
+ # Maximum number of non-capturing probe emissions admitted per second across
78
+ # the whole process.
79
+ GLOBAL_LOG_RATE_LIMIT = 5000
80
+
72
81
  def initialize(settings, serializer, logger, code_tracker: nil, telemetry: nil)
73
82
  @settings = settings
74
83
  @serializer = serializer
75
84
  @logger = logger
76
85
  @telemetry = telemetry
77
86
  @code_tracker = code_tracker
87
+ @global_snapshot_rate_limiter = Datadog::Core::TokenBucket.new(GLOBAL_SNAPSHOT_RATE_LIMIT)
88
+ @global_log_rate_limiter = Datadog::Core::TokenBucket.new(GLOBAL_LOG_RATE_LIMIT)
78
89
 
79
90
  @lock = Mutex.new
80
91
  end
@@ -91,12 +102,31 @@ module Datadog
91
102
  # Component#start! assigns the now-current tracker here.
92
103
  attr_writer :code_tracker
93
104
 
105
+ # Process-wide snapshot rate limiter.
106
+ # @return [Datadog::Core::TokenBucket]
107
+ attr_reader :global_snapshot_rate_limiter
108
+ # Process-wide log rate limiter.
109
+ # @return [Datadog::Core::TokenBucket]
110
+ attr_reader :global_log_rate_limiter
111
+
94
112
  def capture_expression_evaluator
95
113
  @capture_expression_evaluator ||= CaptureExpressionEvaluator.new(
96
114
  settings: settings, serializer: serializer, logger: logger, telemetry: telemetry,
97
115
  )
98
116
  end
99
117
 
118
+ # Returns the process-wide rate limiter for the probe.
119
+ #
120
+ # @param probe [Probe] the probe whose invocation is being rate limited
121
+ # @return [Datadog::Core::TokenBucket] the shared limiter for the probe's category
122
+ def probe_global_rate_limiter(probe)
123
+ if probe.capturing?
124
+ global_snapshot_rate_limiter
125
+ else
126
+ global_log_rate_limiter
127
+ end
128
+ end
129
+
100
130
  # This is a substitute for Thread::Backtrace::Location
101
131
  # which does not have a public constructor.
102
132
  # Used for the fabricated stack frame for the method itself
@@ -529,7 +559,12 @@ module Datadog
529
559
  end
530
560
 
531
561
  rate_limiter = probe.rate_limiter
532
- if continue and rate_limiter.nil? || rate_limiter.allow?
562
+ admitted = continue && (rate_limiter.nil? || rate_limiter.allow?)
563
+ if admitted && !probe_global_rate_limiter(probe).allow?
564
+ admitted = false
565
+ logger.trace { "di: #{probe.type} probe #{probe.id}: skipping due to global rate limit" }
566
+ end
567
+ if admitted
533
568
  # Arguments may be mutated by the method, therefore
534
569
  # they need to be serialized prior to method invocation.
535
570
  serialized_entry_args = if probe.capture_snapshot?
@@ -794,6 +829,11 @@ module Datadog
794
829
  # and check that it is in fact set.
795
830
  return if probe.rate_limiter && !probe.rate_limiter.allow?
796
831
 
832
+ unless probe_global_rate_limiter(probe).allow?
833
+ logger.trace { "di: #{probe.type} probe #{probe.id}: skipping due to global rate limit" }
834
+ return
835
+ end
836
+
797
837
  # The context creation is relatively expensive and we don't
798
838
  # want to run it if the callback won't be executed due to the
799
839
  # rate limit.
@@ -20,9 +20,9 @@ module Datadog
20
20
 
21
21
  def_delegators :target, :debug
22
22
 
23
- def trace(&block)
23
+ def trace
24
24
  if settings.dynamic_instrumentation.internal.trace_logging
25
- debug(&block)
25
+ debug { yield }
26
26
  end
27
27
  end
28
28
  end
@@ -97,7 +97,7 @@ module Datadog
97
97
  @evaluate_at = evaluate_at
98
98
  @condition = condition
99
99
 
100
- @rate_limit = rate_limit || ((@capture_snapshot || !@capture_expressions.empty?) ? 1 : 5000)
100
+ @rate_limit = rate_limit || (capturing? ? 1 : 5000)
101
101
  @rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
102
102
 
103
103
  # At most one report per second.
@@ -166,6 +166,14 @@ module Datadog
166
166
  !@capture_expressions.empty?
167
167
  end
168
168
 
169
+ # Whether this probe captures user data, either as a full snapshot
170
+ # or via capture expressions.
171
+ #
172
+ # @return [Boolean]
173
+ def capturing?
174
+ capture_snapshot? || capture_expressions?
175
+ end
176
+
169
177
  def evaluate_at_entry?
170
178
  evaluate_at == :entry
171
179
  end
@@ -379,6 +379,7 @@ module Datadog
379
379
  thread_id: nil,
380
380
  version: 2,
381
381
  },
382
+ runtime_id: Core::Environment::Identity.id,
382
383
  # TODO add tests that the trace/span id is correctly propagated
383
384
  "dd.trace_id": active_trace&.id&.to_s,
384
385
  "dd.span_id": active_span&.id&.to_s,
@@ -22,7 +22,7 @@ module Datadog
22
22
  # when DI is explicitly disabled, or the runtime cannot run DI
23
23
  # (JRuby, Ruby 2.5), that block — including this bit — is skipped.
24
24
  # The enable signal itself is delivered in APM_TRACING payloads and
25
- # routed here by Tracing::Remote.process_config.
25
+ # routed here by Tracing::Remote.merge_and_apply_configs.
26
26
  CAPABILITIES = [
27
27
  1 << 38, # APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION: Implicit DI enablement
28
28
  ].freeze
@@ -37,7 +37,7 @@ module Datadog
37
37
 
38
38
  # Entry point for the RC-driven DI enable/disable path.
39
39
  #
40
- # Invoked from {Datadog::Tracing::Remote.process_config} when an
40
+ # Invoked from {Datadog::Tracing::Remote.merge_and_apply_configs} when an
41
41
  # APM_TRACING payload carries `dynamic_instrumentation_enabled`. Runs
42
42
  # on the remote-config thread; never raises.
43
43
  #
@@ -45,7 +45,7 @@ module Datadog
45
45
  # false to stop it. The `DD_DYNAMIC_INSTRUMENTATION_ENABLED=false`
46
46
  # env var blocks an enable here (see {.explicitly_disabled?}).
47
47
  # @param repository [Datadog::Core::Remote::Configuration::Repository, nil]
48
- # the RC repository, passed by {Datadog::Tracing::Remote.process_config}
48
+ # the RC repository, passed by {Datadog::Tracing::Remote.apply_lib_config}
49
49
  # so that a stopped->started transition can reconcile against probes that
50
50
  # were delivered in an earlier poll. nil when called outside the RC
51
51
  # dispatch path (e.g. unit tests), in which case no reconcile happens.
@@ -23,27 +23,42 @@ module Datadog
23
23
  end
24
24
 
25
25
  def fetch_value(flag_key, default_value:, expected_type:, evaluation_context: nil)
26
+ # Pre-bound to the safe default so the rescue below can still read it if the
27
+ # evaluator read itself raises.
28
+ observe_full_evaluation_data = false
29
+ # Snapshot the evaluator once: a concurrent reconfigure! must not retroactively
30
+ # apply a different environment's full-evaluation-data policy to this evaluation.
31
+ evaluator = @evaluator
32
+ observe_full_evaluation_data = evaluator.observe_full_evaluation_data == true
33
+
26
34
  unless ALLOWED_TYPES.include?(expected_type)
27
35
  message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
28
- return ResolutionDetails.build_error(
36
+ result = ResolutionDetails.build_error(
29
37
  value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message
30
38
  )
39
+
40
+ return with_observe_full_evaluation_data(result, observe_full_evaluation_data)
31
41
  end
32
42
 
33
43
  context = evaluation_context&.fields.to_h
34
- result = @evaluator.get_assignment(
44
+ result = evaluator.get_assignment(
35
45
  flag_key, default_value: default_value, context: context, expected_type: expected_type
36
46
  )
37
47
 
48
+ result = with_observe_full_evaluation_data(result, observe_full_evaluation_data)
49
+
38
50
  @reporter.report(result, flag_key: flag_key, context: evaluation_context)
39
51
 
40
52
  result
41
53
  rescue => e
42
54
  @telemetry.report(e, description: "OpenFeature: Failed to fetch flag value")
43
55
 
44
- ResolutionDetails.build_error(
56
+ result = ResolutionDetails.build_error(
45
57
  value: default_value, error_code: Ext::GENERAL, error_message: "#{e.class}: #{e.message}"
46
58
  )
59
+ # `== true` is load-bearing: Steep types this local as `(nil | bool)` in a rescue
60
+ # body, and coercing at the use site is what narrows it to `bool`.
61
+ with_observe_full_evaluation_data(result, observe_full_evaluation_data == true)
47
62
  end
48
63
 
49
64
  # NOTE: In a currect implementation configuration is expected to be a raw
@@ -65,6 +80,17 @@ module Datadog
65
80
 
66
81
  raise ReconfigurationError, "#{e.class}: #{e.message}"
67
82
  end
83
+
84
+ private
85
+
86
+ # Stamps observe_full_evaluation_data onto the result metadata so the hook
87
+ # reads it from the event, not live config.
88
+ def with_observe_full_evaluation_data(result, observe_full_evaluation_data)
89
+ result = result.dup if result.frozen?
90
+ metadata = result.flag_metadata ||= {}
91
+ metadata[Ext::METADATA_OBSERVE_FULL_EVALUATION_DATA] = observe_full_evaluation_data
92
+ result
93
+ end
68
94
  end
69
95
  end
70
96
  end
@@ -15,12 +15,14 @@ module Datadog
15
15
  "#{flag_key}:#{context.targeting_key}"
16
16
  end
17
17
 
18
+ # NOTE: The serial id is part of the value because it can appear or change on a
19
+ # configuration refresh while the allocation and the variant stay the same.
18
20
  def cache_value(result, flag_key:, context:)
19
- "#{result.allocation_key}:#{result.variant}"
21
+ "#{result.allocation_key}:#{result.variant}:#{result.serial_id}"
20
22
  end
21
23
 
22
24
  def build(result, flag_key:, context:)
23
- {
25
+ event = {
24
26
  timestamp: current_timestamp_ms,
25
27
  allocation: {
26
28
  key: result.allocation_key,
@@ -35,7 +37,12 @@ module Datadog
35
37
  id: context.targeting_key,
36
38
  attributes: extract_attributes(context),
37
39
  },
38
- }.freeze
40
+ }
41
+
42
+ serial_id = result.serial_id
43
+ event[:serial_id] = serial_id unless serial_id.nil?
44
+
45
+ event.freeze
39
46
  end
40
47
 
41
48
  private
@@ -11,11 +11,30 @@ module Datadog
11
11
  GENERAL = "GENERAL"
12
12
  PROVIDER_FATAL = "PROVIDER_FATAL"
13
13
  PROVIDER_NOT_READY = "PROVIDER_NOT_READY"
14
+ FLAG_NOT_FOUND = "FLAG_NOT_FOUND"
15
+ TYPE_MISMATCH = "TYPE_MISMATCH"
16
+ TARGETING_KEY_MISSING = "TARGETING_KEY_MISSING"
17
+ INVALID_CONTEXT = "INVALID_CONTEXT"
18
+
19
+ STANDARD_ERROR_CODES = [
20
+ PROVIDER_NOT_READY,
21
+ FLAG_NOT_FOUND,
22
+ PARSE_ERROR,
23
+ TYPE_MISMATCH,
24
+ TARGETING_KEY_MISSING,
25
+ INVALID_CONTEXT,
26
+ PROVIDER_FATAL,
27
+ GENERAL,
28
+ ].freeze
14
29
 
15
30
  # Flag-metadata key under which the provider threads the assignment's
16
31
  # allocation key to the flag-evaluation hooks. The wire string is the
17
32
  # value so the writer (provider) and readers (EVP/metrics hooks) can't drift.
18
33
  METADATA_ALLOCATION_KEY = "__dd_allocation_key"
34
+
35
+ # Stamped from the UFC the evaluation ran against; the hook reads only this
36
+ # key, never live config.
37
+ METADATA_OBSERVE_FULL_EVALUATION_DATA = "__dd_observe_full_evaluation_data"
19
38
  end
20
39
  end
21
40
  end