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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -1
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +120 -61
- data/ext/datadog_profiling_native_extension/collectors_stack.c +11 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +156 -108
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +7 -3
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +52 -93
- data/ext/datadog_profiling_native_extension/heap_recorder.c +351 -255
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +0 -24
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +518 -442
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +15 -3
- data/ext/datadog_profiling_native_extension/profiling.c +2 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +1 -79
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +0 -7
- data/ext/datadog_profiling_native_extension/stack_recorder.c +106 -61
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +2 -1
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -10
- data/ext/libdatadog_api/di.c +10 -0
- data/ext/libdatadog_api/extconf.rb +3 -0
- data/ext/libdatadog_api/init.c +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +247 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/trace_exporter.c +642 -22
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/core/configuration/components.rb +1 -0
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +31 -0
- data/lib/datadog/di/instrumenter.rb +41 -1
- data/lib/datadog/di/logger.rb +2 -2
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_notification_builder.rb +1 -0
- data/lib/datadog/di/remote.rb +3 -3
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/event.rb +10 -3
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +33 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +11 -2
- data/lib/datadog/open_feature/transport.rb +5 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -0
- data/lib/datadog/profiling/collectors/info.rb +2 -6
- data/lib/datadog/profiling/collectors/thread_context.rb +0 -4
- data/lib/datadog/profiling/component.rb +8 -16
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +49 -62
- data/lib/datadog/tracing/distributed/baggage.rb +0 -1
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/otel_thread_context.rb +34 -0
- data/lib/datadog/tracing/remote.rb +195 -27
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +5 -5
- data/lib/datadog/tracing/transport/native.rb +17 -25
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/traces.rb +2 -22
- data/lib/datadog/version.rb +1 -1
- metadata +16 -8
- 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.
|
|
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
|
-
|
|
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>
|
|
@@ -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 &&
|
|
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
|
|
101
|
+
# Encode a signed 64-bit integer using ZigZag + LEB128 variable-length encoding.
|
|
102
102
|
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
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
|
-
#
|
|
108
|
-
#
|
|
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]
|
|
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
|
|
115
|
-
bytes << ((
|
|
116
|
-
|
|
118
|
+
while zigzag >= 0x80
|
|
119
|
+
bytes << ((zigzag & 0x7F) | 0x80)
|
|
120
|
+
zigzag >>= 7
|
|
117
121
|
end
|
|
118
|
-
bytes <<
|
|
122
|
+
bytes << zigzag
|
|
119
123
|
bytes.pack("C*")
|
|
120
124
|
end
|
|
121
125
|
|
|
122
|
-
# Decode
|
|
126
|
+
# Decode a signed 64-bit integer encoded with ZigZag + LEB128 from an IO stream.
|
|
123
127
|
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
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
|
|
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
|
-
|
|
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.
|
data/lib/datadog/di/logger.rb
CHANGED
data/lib/datadog/di/probe.rb
CHANGED
|
@@ -97,7 +97,7 @@ module Datadog
|
|
|
97
97
|
@evaluate_at = evaluate_at
|
|
98
98
|
@condition = condition
|
|
99
99
|
|
|
100
|
-
@rate_limit = rate_limit || (
|
|
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,
|
data/lib/datadog/di/remote.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
}
|
|
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
|