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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../core/utils"
|
|
4
3
|
require_relative "helpers"
|
|
4
|
+
require_relative "trace_state"
|
|
5
5
|
|
|
6
6
|
module Datadog
|
|
7
7
|
module Tracing
|
|
@@ -30,7 +30,7 @@ module Datadog
|
|
|
30
30
|
if (traceparent = build_traceparent(digest))
|
|
31
31
|
data[@traceparent_key] = traceparent
|
|
32
32
|
|
|
33
|
-
if (tracestate =
|
|
33
|
+
if (tracestate = TraceState.serialize_digest(digest))
|
|
34
34
|
data[@tracestate_key] = tracestate
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -45,11 +45,12 @@ module Datadog
|
|
|
45
45
|
|
|
46
46
|
return unless trace_id # Could not parse traceparent
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
trace_state = TraceState.extract(fetcher[@tracestate_key])
|
|
49
|
+
dd = trace_state.datadog
|
|
50
|
+
ot = trace_state.open_telemetry
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
tags = dd.tags
|
|
53
|
+
sampling_priority = parse_priority_sampling(sampled, dd.sampling_priority) do |decision|
|
|
53
54
|
case decision
|
|
54
55
|
when String
|
|
55
56
|
tags ||= {}
|
|
@@ -61,17 +62,20 @@ module Datadog
|
|
|
61
62
|
|
|
62
63
|
tags ||= {}
|
|
63
64
|
tags[Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID] =
|
|
64
|
-
ts_parent_id || Tracing::Metadata::Ext::Distributed::DD_PARENT_ID_DEFAULT
|
|
65
|
+
dd.ts_parent_id || Tracing::Metadata::Ext::Distributed::DD_PARENT_ID_DEFAULT
|
|
65
66
|
|
|
66
67
|
TraceDigest.new(
|
|
67
68
|
span_id: parent_id,
|
|
68
69
|
trace_id: trace_id,
|
|
69
|
-
trace_origin: origin,
|
|
70
|
+
trace_origin: dd.origin,
|
|
70
71
|
trace_sampling_priority: sampling_priority,
|
|
71
72
|
trace_distributed_tags: tags,
|
|
72
73
|
trace_flags: trace_flags,
|
|
73
|
-
trace_state:
|
|
74
|
-
trace_state_unknown_fields: unknown_fields,
|
|
74
|
+
trace_state: trace_state.unknown_vendors,
|
|
75
|
+
trace_state_unknown_fields: dd.unknown_fields,
|
|
76
|
+
trace_otel_random_value: ot.random_value,
|
|
77
|
+
trace_otel_threshold: ot.threshold,
|
|
78
|
+
trace_otel_unknown_fields: ot.unknown_fields,
|
|
75
79
|
span_remote: true,
|
|
76
80
|
)
|
|
77
81
|
end
|
|
@@ -121,133 +125,6 @@ module Datadog
|
|
|
121
125
|
trace_flags
|
|
122
126
|
end
|
|
123
127
|
|
|
124
|
-
# @see https://www.w3.org/TR/trace-context/#tracestate-header
|
|
125
|
-
def build_tracestate(digest)
|
|
126
|
-
tracestate = +"dd="
|
|
127
|
-
append_dd(tracestate, last_dd_parent_id(digest))
|
|
128
|
-
append_dd(tracestate, "s:#{digest.trace_sampling_priority};") if digest.trace_sampling_priority
|
|
129
|
-
append_dd(tracestate, "o:#{serialize_origin(digest.trace_origin)};") if digest.trace_origin
|
|
130
|
-
|
|
131
|
-
# Replacing this by safe navigation seems to have a different behaviour on Rubies <= 3.0.
|
|
132
|
-
# It cause a LocalJumpError in the CI.
|
|
133
|
-
if digest.trace_distributed_tags # rubocop:disable Style/SafeNavigation
|
|
134
|
-
digest.trace_distributed_tags.each do |name, value|
|
|
135
|
-
tag = "t.#{serialize_tag_key(name)}:#{serialize_tag_value(value)};"
|
|
136
|
-
|
|
137
|
-
# If tracestate size limit is exceeded, drop the remaining data.
|
|
138
|
-
break unless append_dd(tracestate, tag)
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
append_dd(tracestate, digest.trace_state_unknown_fields) if digest.trace_state_unknown_fields
|
|
143
|
-
vendors = split_tracestate(digest.trace_state)
|
|
144
|
-
|
|
145
|
-
# Is there any Datadog-specific information to propagate.
|
|
146
|
-
# Check for > 3 size because the empty prefix `dd=` has 3 characters.
|
|
147
|
-
if tracestate.bytesize > 3
|
|
148
|
-
# Propagate upstream tracestate with `dd=...` appended to the list
|
|
149
|
-
tracestate.chop! # Removes trailing `;` from Datadog trace state string.
|
|
150
|
-
|
|
151
|
-
if vendors && !vendors.empty?
|
|
152
|
-
# Delete existing `dd=` tracestate fields, if present.
|
|
153
|
-
vendors.reject! { |v| v.start_with?("dd=") }
|
|
154
|
-
|
|
155
|
-
# Ensure the list has at most 31 elements, as we need to prepend Datadog's
|
|
156
|
-
# entry and the limit is 32 elements total.
|
|
157
|
-
vendors.first(TRACESTATE_MAX_LIST_MEMBERS - 1).each do |vendor|
|
|
158
|
-
break if tracestate.bytesize + vendor.bytesize + 1 > TRACESTATE_MAX_SIZE_LIMIT
|
|
159
|
-
|
|
160
|
-
tracestate << "," << vendor
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
tracestate.to_s
|
|
165
|
-
elsif vendors && !vendors.empty?
|
|
166
|
-
vendors.join(",")
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Appends a Datadog tracestate field when it fits.
|
|
171
|
-
def append_dd(tracestate, field)
|
|
172
|
-
return true if field.empty?
|
|
173
|
-
|
|
174
|
-
# We add 1 to the limit because of the trailing semicolon, which will be removed before returning.
|
|
175
|
-
return false if tracestate.bytesize + field.bytesize > (TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
176
|
-
|
|
177
|
-
tracestate << field
|
|
178
|
-
true
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def last_dd_parent_id(digest)
|
|
182
|
-
if !digest.span_remote
|
|
183
|
-
span_id = digest.span_id || 0 # Fall back to zero (invalid) if not present
|
|
184
|
-
"p:#{format("%016x", span_id)};"
|
|
185
|
-
elsif digest.trace_distributed_tags&.key?(Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID)
|
|
186
|
-
"p:#{digest.trace_distributed_tags[Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID]};"
|
|
187
|
-
else
|
|
188
|
-
""
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# If any characters in <origin_value> are invalid, replace each invalid character with 0x5F (underscore).
|
|
193
|
-
# Invalid characters are: characters outside the ASCII range 0x20 to 0x7E,
|
|
194
|
-
# 0x2C (comma), 0x3B (semi-colon), and 0x7E (tilde).
|
|
195
|
-
# Then, remap 0x3D (equals) to 0x7E (tilde)
|
|
196
|
-
def serialize_origin(value)
|
|
197
|
-
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
198
|
-
# DEV: come from Datadog-controlled sources.
|
|
199
|
-
# DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
|
|
200
|
-
value = if INVALID_ORIGIN_CHARS.match?(value)
|
|
201
|
-
value.gsub(INVALID_ORIGIN_CHARS, "_")
|
|
202
|
-
else
|
|
203
|
-
value
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
if REMAP_ORIGIN_CHARS.match?(value)
|
|
207
|
-
value.gsub(REMAP_ORIGIN_CHARS, "~")
|
|
208
|
-
else
|
|
209
|
-
value
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# Serialize `_dd.p.{key}` by first removing the `_dd.p.` prefix.
|
|
214
|
-
# Then replacing invalid characters with `_`.
|
|
215
|
-
#
|
|
216
|
-
# The argument `name` is always frozen.
|
|
217
|
-
# Returns a new String object for the serialized key.
|
|
218
|
-
def serialize_tag_key(name)
|
|
219
|
-
key = name.delete_prefix(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX)
|
|
220
|
-
|
|
221
|
-
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
222
|
-
# DEV: come from Datadog-controlled sources.
|
|
223
|
-
# DEV: Trying to `match?` is measurably faster than a `gsub!` that does not match.
|
|
224
|
-
key.gsub!(INVALID_TAG_KEY_CHARS, "_") if INVALID_TAG_KEY_CHARS.match?(key)
|
|
225
|
-
|
|
226
|
-
key
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# Replaces invalid characters with `_`, then replaces `=` with `~`.
|
|
230
|
-
#
|
|
231
|
-
# The argument `value` belongs to {TraceDigest}, thus should not be directly modified.
|
|
232
|
-
# Returns a new String object for the serialized value.
|
|
233
|
-
def serialize_tag_value(value)
|
|
234
|
-
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
235
|
-
# DEV: come from Datadog-controlled sources.
|
|
236
|
-
# DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
|
|
237
|
-
ret = if INVALID_TAG_VALUE_CHARS.match?(value)
|
|
238
|
-
value.gsub(INVALID_TAG_VALUE_CHARS, "_")
|
|
239
|
-
else
|
|
240
|
-
value
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
# DEV: Checking for an unlikely '=' is faster than a no-op `tr`.
|
|
244
|
-
if ret.include?("=")
|
|
245
|
-
ret.tr("=", "~")
|
|
246
|
-
else
|
|
247
|
-
ret
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
128
|
def extract_traceparent(traceparent)
|
|
252
129
|
trace_id, parent_id, trace_flags = parse_traceparent_string(traceparent)
|
|
253
130
|
|
|
@@ -285,78 +162,6 @@ module Datadog
|
|
|
285
162
|
trace_flags & TRACE_FLAGS_SAMPLED
|
|
286
163
|
end
|
|
287
164
|
|
|
288
|
-
# @return [nil] when the tracestate is absent or has no vendor entries.
|
|
289
|
-
# @return [String] when no `dd=` entry is present, the joined vendor list.
|
|
290
|
-
# @return [Array(String, Integer, String, String, Hash, String)] when a `dd=`
|
|
291
|
-
# entry is present: [tracestate without `dd=`, sampling_priority, origin,
|
|
292
|
-
# ts_parent_id, tags, unknown_fields]. All elements past the first may be nil.
|
|
293
|
-
def extract_tracestate(tracestate)
|
|
294
|
-
vendors = split_tracestate(tracestate)
|
|
295
|
-
return unless vendors && !vendors.empty?
|
|
296
|
-
|
|
297
|
-
tracestate = vendors.join(",")
|
|
298
|
-
|
|
299
|
-
# Find Datadog's `dd=` tracestate field.
|
|
300
|
-
idx = vendors.index { |v| v.start_with?("dd=") }
|
|
301
|
-
return tracestate unless idx
|
|
302
|
-
|
|
303
|
-
# Delete `dd=` prefix
|
|
304
|
-
dd_tracestate = vendors.delete_at(idx)
|
|
305
|
-
dd_tracestate.slice!(0..2)
|
|
306
|
-
|
|
307
|
-
origin, sampling_priority, ts_parent_id, tags, unknown_fields = extract_datadog_fields(dd_tracestate)
|
|
308
|
-
|
|
309
|
-
[vendors.join(","), sampling_priority, origin, ts_parent_id, tags, unknown_fields]
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def extract_datadog_fields(dd_tracestate)
|
|
313
|
-
sampling_priority = nil
|
|
314
|
-
origin = nil
|
|
315
|
-
ts_parent_id = nil
|
|
316
|
-
tags = nil
|
|
317
|
-
unknown_fields = nil
|
|
318
|
-
|
|
319
|
-
# DEV: Since Ruby 2.6 `split` can receive a block, so `each` can be removed then.
|
|
320
|
-
dd_tracestate.split(";").each do |pair|
|
|
321
|
-
key, value = pair.split(":", 2)
|
|
322
|
-
case key
|
|
323
|
-
when "s"
|
|
324
|
-
sampling_priority = begin
|
|
325
|
-
Integer(value)
|
|
326
|
-
rescue
|
|
327
|
-
nil
|
|
328
|
-
end
|
|
329
|
-
when "o"
|
|
330
|
-
origin = value
|
|
331
|
-
when "p"
|
|
332
|
-
ts_parent_id = value
|
|
333
|
-
when /^t\./
|
|
334
|
-
key.slice!(0..1) # Delete `t.` prefix
|
|
335
|
-
|
|
336
|
-
# Ignore the high order 64 bit trace id propagation tag to avoid confusion,
|
|
337
|
-
# the single source of truth is from traceparent
|
|
338
|
-
next if key == Tracing::Metadata::Ext::Distributed::TID
|
|
339
|
-
|
|
340
|
-
value = deserialize_tag_value(value)
|
|
341
|
-
|
|
342
|
-
tags ||= {}
|
|
343
|
-
tags["#{Tracing::Metadata::Ext::Distributed::TAGS_PREFIX}#{key}"] = value
|
|
344
|
-
else
|
|
345
|
-
unknown_fields ||= +""
|
|
346
|
-
unknown_fields << pair
|
|
347
|
-
unknown_fields << ";"
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
[origin, sampling_priority, ts_parent_id, tags, unknown_fields]
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
# Restore `~` back to `=`.
|
|
355
|
-
def deserialize_tag_value(value)
|
|
356
|
-
value.tr!("~", "=")
|
|
357
|
-
value
|
|
358
|
-
end
|
|
359
|
-
|
|
360
165
|
# If `sampled` and `sampling_priority` disagree, `sampled` overrides the decision.
|
|
361
166
|
# @return [Integer] one of the {Datadog::Tracing::Sampling::Ext::Priority} values
|
|
362
167
|
# @yieldparam the new decision maker (either :drop or a new decision maker String value).
|
|
@@ -382,47 +187,9 @@ module Datadog
|
|
|
382
187
|
end
|
|
383
188
|
end
|
|
384
189
|
|
|
385
|
-
# We MUST NOT propagate partial members, but we SHOULD try
|
|
386
|
-
# to parse as much of the tracestate as possible.
|
|
387
|
-
def split_tracestate(tracestate)
|
|
388
|
-
return unless tracestate
|
|
389
|
-
|
|
390
|
-
remove_last_member = false
|
|
391
|
-
if tracestate.bytesize > TRACESTATE_MAX_SIZE_LIMIT
|
|
392
|
-
# We parse 1 byte over the limit to detect if the last member
|
|
393
|
-
# is a partial member (toss) or ends exactly at the limit (keep).
|
|
394
|
-
remove_last_member = tracestate.byteslice(TRACESTATE_MAX_SIZE_LIMIT, 1) != ","
|
|
395
|
-
|
|
396
|
-
# To ensure we don't have a trailing partial UTF-8 codepoint, we keep one extra byte
|
|
397
|
-
# and safely remove it with `#chop`.
|
|
398
|
-
# `#chop` walks back the string until it finds a valid character boundary and deletes
|
|
399
|
-
# from there.
|
|
400
|
-
tracestate = tracestate.byteslice(0, TRACESTATE_MAX_SIZE_LIMIT + 1) #: String
|
|
401
|
-
tracestate = tracestate.chop
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
tracestate = ::Datadog::Core::Utils.utf8_encode(tracestate, placeholder: nil)
|
|
405
|
-
return unless tracestate
|
|
406
|
-
|
|
407
|
-
vendors = tracestate.split(",", TRACESTATE_MAX_LIST_MEMBERS + 1)
|
|
408
|
-
if vendors.length > TRACESTATE_MAX_LIST_MEMBERS || remove_last_member
|
|
409
|
-
vendors.pop
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
vendors.each(&:strip!)
|
|
413
|
-
vendors.pop while vendors.last == ""
|
|
414
|
-
vendors
|
|
415
|
-
end
|
|
416
|
-
|
|
417
190
|
TRACEPARENT_MAX_SIZE_LIMIT = 512
|
|
418
191
|
private_constant :TRACEPARENT_MAX_SIZE_LIMIT
|
|
419
192
|
|
|
420
|
-
TRACESTATE_MAX_SIZE_LIMIT = 512
|
|
421
|
-
private_constant :TRACESTATE_MAX_SIZE_LIMIT
|
|
422
|
-
|
|
423
|
-
TRACESTATE_MAX_LIST_MEMBERS = 32
|
|
424
|
-
private_constant :TRACESTATE_MAX_LIST_MEMBERS
|
|
425
|
-
|
|
426
193
|
# Version 0xFF is invalid as per spec
|
|
427
194
|
# @see https://www.w3.org/TR/trace-context/#version
|
|
428
195
|
INVALID_VERSION = "ff"
|
|
@@ -437,30 +204,6 @@ module Datadog
|
|
|
437
204
|
# @see https://www.w3.org/TR/trace-context/#trace-flags
|
|
438
205
|
TRACE_FLAGS_SAMPLED = 0b00000001
|
|
439
206
|
private_constant :TRACE_FLAGS_SAMPLED
|
|
440
|
-
|
|
441
|
-
# The limit is inclusive: sizes *greater* than 256 are disallowed.
|
|
442
|
-
# @see https://www.w3.org/TR/trace-context/#value
|
|
443
|
-
TRACESTATE_VALUE_SIZE_LIMIT = 256
|
|
444
|
-
private_constant :TRACESTATE_VALUE_SIZE_LIMIT
|
|
445
|
-
|
|
446
|
-
# Replace all characters with `_`, except ASCII characters 0x20-0x7E.
|
|
447
|
-
# Additionally, `,`, ';', and `~` must also be replaced by `_`.
|
|
448
|
-
INVALID_ORIGIN_CHARS = /[\u0000-\u0019,;~\u007F-\u{10FFFF}]/.freeze
|
|
449
|
-
private_constant :INVALID_ORIGIN_CHARS
|
|
450
|
-
|
|
451
|
-
# Additionally, remap `=` to `~`
|
|
452
|
-
REMAP_ORIGIN_CHARS = /=/.freeze
|
|
453
|
-
private_constant :REMAP_ORIGIN_CHARS
|
|
454
|
-
|
|
455
|
-
# Replace all characters with `_`, except ASCII characters 0x21-0x7E.
|
|
456
|
-
# Additionally, `,` and `=` must also be replaced by `_`.
|
|
457
|
-
INVALID_TAG_KEY_CHARS = /[\u0000-\u0020,=\u007F-\u{10FFFF}]/.freeze
|
|
458
|
-
private_constant :INVALID_TAG_KEY_CHARS
|
|
459
|
-
|
|
460
|
-
# Replace all characters with `_`, except ASCII characters 0x20-0x7D.
|
|
461
|
-
# Additionally, `,` and `;` must also be replaced by `_`.
|
|
462
|
-
INVALID_TAG_VALUE_CHARS = /[\u0000-\u001F,;\u007E-\u{10FFFF}]/.freeze
|
|
463
|
-
private_constant :INVALID_TAG_VALUE_CHARS
|
|
464
207
|
end
|
|
465
208
|
end
|
|
466
209
|
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ext"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module Tracing
|
|
7
|
+
module Distributed
|
|
8
|
+
class TraceState
|
|
9
|
+
# Encodes and decodes Datadog propagation state.
|
|
10
|
+
class Datadog
|
|
11
|
+
class << self
|
|
12
|
+
def from_digest(digest)
|
|
13
|
+
ts_parent_id = if !digest.span_remote
|
|
14
|
+
format("%016x", digest.span_id || 0)
|
|
15
|
+
elsif digest.trace_distributed_tags&.key?(Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID)
|
|
16
|
+
digest.trace_distributed_tags[Tracing::Metadata::Ext::Distributed::TAG_DD_PARENT_ID]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
new(
|
|
20
|
+
sampling_priority: digest.trace_sampling_priority,
|
|
21
|
+
origin: digest.trace_origin,
|
|
22
|
+
ts_parent_id: ts_parent_id,
|
|
23
|
+
tags: digest.trace_distributed_tags,
|
|
24
|
+
unknown_fields: digest.trace_state_unknown_fields,
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def from_tracestate_member(value)
|
|
29
|
+
return new unless value
|
|
30
|
+
|
|
31
|
+
# @type var sampling_priority: ::Integer?
|
|
32
|
+
# @type var origin: ::String?
|
|
33
|
+
# @type var ts_parent_id: ::String?
|
|
34
|
+
# @type var tags: ::Hash[::String, ::String]?
|
|
35
|
+
# @type var unknown_fields: ::String?
|
|
36
|
+
sampling_priority = nil
|
|
37
|
+
origin = nil
|
|
38
|
+
ts_parent_id = nil
|
|
39
|
+
tags = nil
|
|
40
|
+
unknown_fields = nil
|
|
41
|
+
|
|
42
|
+
value.split(";").each do |pair|
|
|
43
|
+
key, field = pair.split(":", 2)
|
|
44
|
+
case key
|
|
45
|
+
when "s"
|
|
46
|
+
sampling_priority = begin
|
|
47
|
+
Integer(field)
|
|
48
|
+
rescue
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
when "o"
|
|
52
|
+
origin = field
|
|
53
|
+
when "p"
|
|
54
|
+
ts_parent_id = field
|
|
55
|
+
when /^t\./
|
|
56
|
+
key = key.to_s
|
|
57
|
+
field = field.to_s
|
|
58
|
+
key.slice!(0..1)
|
|
59
|
+
next if key == Tracing::Metadata::Ext::Distributed::TID
|
|
60
|
+
|
|
61
|
+
field.tr!("~", "=")
|
|
62
|
+
current_tags = tags || {}
|
|
63
|
+
current_tags["#{Tracing::Metadata::Ext::Distributed::TAGS_PREFIX}#{key}"] = field
|
|
64
|
+
tags = current_tags
|
|
65
|
+
else
|
|
66
|
+
current_unknown_fields = unknown_fields || +""
|
|
67
|
+
current_unknown_fields << pair << ";"
|
|
68
|
+
unknown_fields = current_unknown_fields
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
new(
|
|
73
|
+
sampling_priority: sampling_priority,
|
|
74
|
+
origin: origin,
|
|
75
|
+
ts_parent_id: ts_parent_id,
|
|
76
|
+
tags: tags,
|
|
77
|
+
unknown_fields: unknown_fields,
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Serializes a {Hash<String,String>} into a `x-datadog-tags`-compatible
|
|
82
|
+
# String.
|
|
83
|
+
#
|
|
84
|
+
# @param tags [Hash<String,String>] trace tag hash
|
|
85
|
+
# @return [String] serialized tags hash
|
|
86
|
+
# @raise [EncodingError] if tags cannot be serialized to the `x-datadog-tags` format
|
|
87
|
+
def encode(tags)
|
|
88
|
+
tags.map do |raw_key, raw_value|
|
|
89
|
+
key = raw_key.to_s
|
|
90
|
+
value = raw_value.to_s
|
|
91
|
+
|
|
92
|
+
unless Ext::Datadog::VALID_KEY_CHARS.match?(key)
|
|
93
|
+
raise EncodingError, "Invalid key `#{key}` for value `#{value}`"
|
|
94
|
+
end
|
|
95
|
+
unless Ext::Datadog::VALID_VALUE_CHARS.match?(value)
|
|
96
|
+
raise EncodingError, "Invalid value `#{value}` for key `#{key}`"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
"#{key}=#{value.strip}"
|
|
100
|
+
end.join(",")
|
|
101
|
+
rescue => e
|
|
102
|
+
raise EncodingError, "Error encoding tags `#{tags}`: `#{e.class}: #{e.message}`"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Deserializes a `x-datadog-tags`-formatted String into a {Hash<String,String>}.
|
|
106
|
+
#
|
|
107
|
+
# @param string [String] tags as serialized by {#encode}
|
|
108
|
+
# @return [Hash<String,String>] decoded input as a hash of strings
|
|
109
|
+
# @raise [DecodingError] if string does not conform to the `x-datadog-tags` format
|
|
110
|
+
def decode(string)
|
|
111
|
+
result = (string.split(",").map do |raw_tag|
|
|
112
|
+
raw_tag.split("=", 2).tap do |raw_key, raw_value|
|
|
113
|
+
key = raw_key.to_s
|
|
114
|
+
value = raw_value.to_s
|
|
115
|
+
|
|
116
|
+
raise DecodingError, "Invalid key: #{key}" unless Ext::Datadog::VALID_KEY_CHARS.match?(key)
|
|
117
|
+
raise DecodingError, "Invalid value: #{value}" unless Ext::Datadog::VALID_VALUE_CHARS.match?(value)
|
|
118
|
+
|
|
119
|
+
value.strip!
|
|
120
|
+
end
|
|
121
|
+
end).to_h
|
|
122
|
+
|
|
123
|
+
raise DecodingError, "Invalid empty tags: #{string}" if result.empty? && !string.empty?
|
|
124
|
+
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# An error occurred during distributed tags encoding.
|
|
130
|
+
# See {#message} for more information.
|
|
131
|
+
class EncodingError < StandardError
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# An error occurred during distributed tags decoding.
|
|
135
|
+
# See {#message} for more information.
|
|
136
|
+
class DecodingError < StandardError
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
attr_reader :sampling_priority, :origin, :ts_parent_id, :tags, :unknown_fields
|
|
140
|
+
|
|
141
|
+
def initialize(sampling_priority: nil, origin: nil, ts_parent_id: nil, tags: nil, unknown_fields: nil)
|
|
142
|
+
@sampling_priority = sampling_priority
|
|
143
|
+
@origin = origin
|
|
144
|
+
@ts_parent_id = ts_parent_id
|
|
145
|
+
@tags = tags
|
|
146
|
+
@unknown_fields = unknown_fields
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Builds the `dd=` tracestate member.
|
|
150
|
+
def to_s
|
|
151
|
+
vendor = +"dd="
|
|
152
|
+
if ts_parent_id
|
|
153
|
+
field = "p:#{ts_parent_id};"
|
|
154
|
+
vendor << field if vendor.bytesize + field.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
155
|
+
end
|
|
156
|
+
if sampling_priority
|
|
157
|
+
field = "s:#{sampling_priority};"
|
|
158
|
+
vendor << field if vendor.bytesize + field.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
159
|
+
end
|
|
160
|
+
if origin
|
|
161
|
+
field = "o:#{serialize_origin(origin)};"
|
|
162
|
+
vendor << field if vendor.bytesize + field.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Replacing this with safe navigation causes a LocalJumpError on Rubies <= 3.0.
|
|
166
|
+
if tags # rubocop:disable Style/SafeNavigation
|
|
167
|
+
tags.each do |name, value|
|
|
168
|
+
tag_field = "t.#{serialize_tag_key(name)}:#{serialize_tag_value(value)};"
|
|
169
|
+
break if vendor.bytesize + tag_field.bytesize > (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
170
|
+
|
|
171
|
+
vendor << tag_field
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if unknown_fields && vendor.bytesize + unknown_fields.bytesize <= (Ext::TRACESTATE_VALUE_SIZE_LIMIT + 1)
|
|
176
|
+
vendor << unknown_fields
|
|
177
|
+
end
|
|
178
|
+
return "" if vendor.bytesize == 3
|
|
179
|
+
|
|
180
|
+
vendor.chop!
|
|
181
|
+
vendor
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
# If any characters in <origin_value> are invalid, replace each invalid character with 0x5F (underscore).
|
|
187
|
+
# Invalid characters are: characters outside the ASCII range 0x20 to 0x7E,
|
|
188
|
+
# 0x2C (comma), 0x3B (semi-colon), and 0x7E (tilde).
|
|
189
|
+
# Then, remap 0x3D (equals) to 0x7E (tilde)
|
|
190
|
+
def serialize_origin(value)
|
|
191
|
+
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
192
|
+
# DEV: come from Datadog-controlled sources.
|
|
193
|
+
# DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
|
|
194
|
+
invalid_chars = Ext::Datadog::INVALID_ORIGIN_CHARS
|
|
195
|
+
value = invalid_chars.match?(value) ? value.gsub(invalid_chars, "_") : value
|
|
196
|
+
remapped_chars = Ext::Datadog::REMAP_ORIGIN_CHARS
|
|
197
|
+
remapped_chars.match?(value) ? value.gsub(remapped_chars, "~") : value
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Serialize `_dd.p.{key}` by first removing the `_dd.p.` prefix.
|
|
201
|
+
# Then replacing invalid characters with `_`.
|
|
202
|
+
#
|
|
203
|
+
# The argument `name` is always frozen.
|
|
204
|
+
# Returns a new String object for the serialized key.
|
|
205
|
+
def serialize_tag_key(name)
|
|
206
|
+
key = name.delete_prefix(Tracing::Metadata::Ext::Distributed::TAGS_PREFIX)
|
|
207
|
+
|
|
208
|
+
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
209
|
+
# DEV: come from Datadog-controlled sources.
|
|
210
|
+
# DEV: Trying to `match?` is measurably faster than a `gsub!` that does not match.
|
|
211
|
+
invalid_chars = Ext::Datadog::INVALID_TAG_KEY_CHARS
|
|
212
|
+
key.gsub!(invalid_chars, "_") if invalid_chars.match?(key)
|
|
213
|
+
key
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Replaces invalid characters with `_`, then replaces `=` with `~`.
|
|
217
|
+
#
|
|
218
|
+
# The argument `value` belongs to {TraceDigest}, thus should not be directly modified.
|
|
219
|
+
# Returns a new String object for the serialized value.
|
|
220
|
+
def serialize_tag_value(value)
|
|
221
|
+
# DEV: It's unlikely that characters will be out of range, as they mostly
|
|
222
|
+
# DEV: come from Datadog-controlled sources.
|
|
223
|
+
# DEV: Trying to `match?` is measurably faster than a `gsub` that does not match.
|
|
224
|
+
invalid_chars = Ext::Datadog::INVALID_TAG_VALUE_CHARS
|
|
225
|
+
serialized = invalid_chars.match?(value) ? value.gsub(invalid_chars, "_") : value
|
|
226
|
+
# DEV: Checking for an unlikely '=' is faster than a no-op `tr`.
|
|
227
|
+
serialized.include?("=") ? serialized.tr("=", "~") : serialized
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
require_relative "../../sampling/ext"
|
|
6
|
+
|
|
7
|
+
module Datadog
|
|
8
|
+
module Tracing
|
|
9
|
+
module Distributed
|
|
10
|
+
class TraceState
|
|
11
|
+
module Ext
|
|
12
|
+
TRACESTATE_MAX_SIZE_LIMIT = 512
|
|
13
|
+
TRACESTATE_MAX_LIST_VENDORS = 32
|
|
14
|
+
TRACESTATE_VALUE_SIZE_LIMIT = 256
|
|
15
|
+
|
|
16
|
+
module Datadog
|
|
17
|
+
# ASCII characters 32-126, except `,`, `=`, and ` `. At least one character.
|
|
18
|
+
VALID_KEY_CHARS = /\A(?:(?![,= ])[\u0020-\u007E])+\Z/.freeze
|
|
19
|
+
# ASCII characters 32-126, except `,`. At least one character.
|
|
20
|
+
VALID_VALUE_CHARS = /\A(?:(?!,)[\u0020-\u007E])+\Z/.freeze
|
|
21
|
+
INVALID_ORIGIN_CHARS = /[\u0000-\u0019,;~\u007F-\u{10FFFF}]/.freeze
|
|
22
|
+
REMAP_ORIGIN_CHARS = /=/.freeze
|
|
23
|
+
INVALID_TAG_KEY_CHARS = /[\u0000-\u0020,=\u007F-\u{10FFFF}]/.freeze
|
|
24
|
+
INVALID_TAG_VALUE_CHARS = /[\u0000-\u001F,;\u007E-\u{10FFFF}]/.freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module OpenTelemetry
|
|
28
|
+
MAX_THRESHOLD = 1 << 56
|
|
29
|
+
MAX_ENCODABLE_THRESHOLD = MAX_THRESHOLD - 1
|
|
30
|
+
UINT64_MODULO = 1 << 64
|
|
31
|
+
UINT64_MASK = UINT64_MODULO - 1
|
|
32
|
+
VALID_THRESHOLD = /\A[0-9a-f]{1,14}\z/
|
|
33
|
+
VALID_RANDOM_VALUE = /\A[0-9a-f]{14}\z/
|
|
34
|
+
NON_PROBABILITY_DECISIONS = Set[
|
|
35
|
+
Sampling::Ext::Decision::MANUAL,
|
|
36
|
+
Sampling::Ext::Decision::ASM,
|
|
37
|
+
Sampling::Ext::Decision::AI_GUARD,
|
|
38
|
+
].freeze
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|