datadog 2.45.0 → 2.46.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 +25 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
- data/ext/datadog_profiling_native_extension/profiling.c +6 -0
- data/ext/libdatadog_api/di.c +30 -5
- data/ext/libdatadog_api/extconf.rb +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +20 -14
- data/ext/libdatadog_api/process_discovery.c +15 -1
- data/ext/libdatadog_api/trace_exporter.c +105 -25
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/core/configuration/components.rb +29 -8
- data/lib/datadog/core/configuration/supported_configurations.rb +5 -2
- data/lib/datadog/core/process_discovery.rb +5 -1
- data/lib/datadog/core/remote/client/capabilities.rb +34 -29
- data/lib/datadog/core/remote/component.rb +28 -12
- data/lib/datadog/core/remote/dispatcher.rb +13 -3
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
- data/lib/datadog/di/component.rb +38 -3
- data/lib/datadog/di/configuration.rb +53 -2
- data/lib/datadog/di/context.rb +7 -2
- data/lib/datadog/di/contrib/active_record.rb +8 -2
- data/lib/datadog/di/evaluation_failure.rb +28 -0
- data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
- data/lib/datadog/di/guardrails_telemetry.rb +203 -0
- data/lib/datadog/di/instrumenter.rb +65 -15
- data/lib/datadog/di/logger.rb +30 -8
- data/lib/datadog/di/probe_manager.rb +48 -5
- data/lib/datadog/di/probe_notification_builder.rb +28 -26
- data/lib/datadog/di/probe_notifier_worker.rb +56 -6
- data/lib/datadog/di/serializer.rb +130 -54
- data/lib/datadog/di/snapshot_event.rb +38 -0
- data/lib/datadog/di/transport/http.rb +13 -3
- data/lib/datadog/di/transport/input.rb +32 -7
- data/lib/datadog/open_feature/activation.rb +268 -0
- data/lib/datadog/open_feature/component.rb +31 -15
- data/lib/datadog/open_feature/provider.rb +282 -5
- data/lib/datadog/open_feature.rb +31 -1
- data/lib/datadog/tracing/component.rb +14 -0
- data/lib/datadog/tracing/configuration/settings.rb +3 -1
- data/lib/datadog/tracing/otel_thread_context.rb +3 -5
- data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
- data/lib/datadog/tracing/transport/http/traces.rb +2 -0
- data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
- data/lib/datadog/tracing/transport/native.rb +16 -1
- data/lib/datadog/tracing/transport/telemetry.rb +62 -0
- data/lib/datadog/tracing/transport/traces.rb +82 -15
- data/lib/datadog/version.rb +1 -1
- metadata +16 -7
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "date"
|
|
3
4
|
require_relative "redactor"
|
|
5
|
+
require_relative "guardrails_telemetry"
|
|
6
|
+
require_relative "../core/utils/time"
|
|
4
7
|
|
|
5
8
|
module Datadog
|
|
6
9
|
module DI
|
|
7
|
-
# Serializes captured
|
|
10
|
+
# Serializes captured snapshots to primitive types, which are subsequently
|
|
8
11
|
# serialized to JSON and sent to the backend.
|
|
9
12
|
#
|
|
10
|
-
#
|
|
11
|
-
# snapshots. It uses Redactor to determine which values are sensitive
|
|
12
|
-
# and need to be removed.
|
|
13
|
+
# Sensitive values are removed from the snapshots.
|
|
13
14
|
#
|
|
14
15
|
# Serializer normally ought not to invoke user (application) code,
|
|
15
16
|
# to guarantee predictable performance. However, objects like ActiveRecord
|
|
@@ -19,7 +20,7 @@ module Datadog
|
|
|
19
20
|
# thus they won't be captured at all). To accommodate complex objects,
|
|
20
21
|
# there is an extension mechanism implemented permitting registration
|
|
21
22
|
# of serializer callbacks for arbitrary types. Applications and libraries
|
|
22
|
-
#
|
|
23
|
+
# defining such serializer callbacks should be very careful to
|
|
23
24
|
# have predictable performance and avoid exceptions and infinite loops
|
|
24
25
|
# and other such issues.
|
|
25
26
|
#
|
|
@@ -36,25 +37,21 @@ module Datadog
|
|
|
36
37
|
# efficient but there would be additional overhead from passing this
|
|
37
38
|
# parameter all the time and the API would get more complex.
|
|
38
39
|
#
|
|
39
|
-
# Note: "self" cannot be used as a parameter name in Ruby, therefore
|
|
40
|
-
# there should never be a conflict between instance variable
|
|
41
|
-
# serialization and method parameters.
|
|
42
|
-
#
|
|
43
40
|
# @api private
|
|
44
41
|
class Serializer
|
|
45
|
-
# Exception classes that
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
42
|
+
# Exception classes that must propagate to the caller during
|
|
43
|
+
# serialization: fatal conditions (signals, interrupts, system exit).
|
|
44
|
+
# NoMemoryError is deliberately excluded (unlike
|
|
45
|
+
# DI::FATAL_EXCEPTION_CLASSES): serialization of large or deeply
|
|
46
|
+
# recursive objects can exhaust memory, and the serializer must return
|
|
47
|
+
# a safe stub rather than terminate the process.
|
|
51
48
|
SERIALIZABLE_FATAL_EXCEPTION_CLASSES = [SystemExit, SignalException].freeze
|
|
52
49
|
|
|
53
50
|
# Placeholder emitted by #serialize_value_for_message in place of a value
|
|
54
|
-
# whose identifier or type matches the redaction configuration.
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
51
|
+
# whose identifier or type matches the redaction configuration. The
|
|
52
|
+
# message-path counterpart of the +notCapturedReason+
|
|
53
|
+
# "redactedIdent"/"redactedType" markers that #serialize_value produces
|
|
54
|
+
# on the snapshot path.
|
|
58
55
|
REDACTED_VALUE_FOR_MESSAGE = "[redacted]"
|
|
59
56
|
|
|
60
57
|
# Third-party library integration / custom serializers.
|
|
@@ -103,16 +100,42 @@ module Datadog
|
|
|
103
100
|
@@flat_registry << {condition: condition, proc: block}
|
|
104
101
|
end
|
|
105
102
|
|
|
106
|
-
|
|
103
|
+
# Initializes the serializer.
|
|
104
|
+
#
|
|
105
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings
|
|
106
|
+
# @param redactor [Redactor] redactor gating captured values
|
|
107
|
+
# @param logger [DI::Logger] logger for contained serialization failures
|
|
108
|
+
# @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical capture-incomplete metric
|
|
109
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component serializer errors are reported through
|
|
110
|
+
# @return [void]
|
|
111
|
+
def initialize(settings, redactor, logger:, guardrails_telemetry:, telemetry: nil)
|
|
107
112
|
@settings = settings
|
|
108
113
|
@redactor = redactor
|
|
114
|
+
@logger = logger
|
|
115
|
+
@guardrails_telemetry = guardrails_telemetry
|
|
109
116
|
@telemetry = telemetry
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
attr_reader :settings
|
|
113
120
|
attr_reader :redactor
|
|
121
|
+
# The logger.
|
|
122
|
+
# @return [DI::Logger]
|
|
123
|
+
attr_reader :logger
|
|
124
|
+
# The guardrails metric emitter.
|
|
125
|
+
# @return [GuardrailsTelemetry]
|
|
126
|
+
attr_reader :guardrails_telemetry
|
|
114
127
|
attr_reader :telemetry
|
|
115
128
|
|
|
129
|
+
# Emits the canonical capture-incomplete metric for a capture limit
|
|
130
|
+
# enforced while serializing a snapshot, with the snapshot event
|
|
131
|
+
# type.
|
|
132
|
+
#
|
|
133
|
+
# @param reason [String] a GuardrailsTelemetry::Reason constant naming the enforced limit
|
|
134
|
+
# @return [void]
|
|
135
|
+
def record_capture_incomplete(reason)
|
|
136
|
+
guardrails_telemetry.capture_incomplete(reason: reason, event_type: GuardrailsTelemetry::EVENT_TYPE_SNAPSHOT)
|
|
137
|
+
end
|
|
138
|
+
|
|
116
139
|
def combine_args(args, kwargs, target_self)
|
|
117
140
|
counter = 0
|
|
118
141
|
combined = args.each_with_object({}) do |value, c|
|
|
@@ -133,31 +156,39 @@ module Datadog
|
|
|
133
156
|
# arguments to keyword arguments ("arg1", "arg2", ...) and ensure
|
|
134
157
|
# the positional arguments are listed first.
|
|
135
158
|
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
159
|
+
# @param args [Array] positional arguments passed to the probed method
|
|
160
|
+
# @param kwargs [Hash{Symbol => Object}] keyword arguments passed to the probed method
|
|
161
|
+
# @param target_self [any] receiver of the probed method invocation
|
|
162
|
+
# @param deadline [Float] absolute capture deadline in monotonic clock seconds
|
|
163
|
+
# @return [Hash{Symbol => Hash}] serialized arguments keyed by argument name
|
|
139
164
|
def serialize_args(args, kwargs, target_self,
|
|
165
|
+
deadline:,
|
|
140
166
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
|
141
167
|
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count,
|
|
142
168
|
length: nil,
|
|
143
169
|
collection_size: nil)
|
|
144
170
|
combined = combine_args(args, kwargs, target_self)
|
|
145
171
|
serialize_vars(combined, depth: depth, attribute_count: attribute_count,
|
|
146
|
-
length: length, collection_size: collection_size)
|
|
172
|
+
length: length, collection_size: collection_size, deadline: deadline,)
|
|
147
173
|
end
|
|
148
174
|
|
|
149
175
|
# Serializes variables captured by a line probe.
|
|
150
176
|
#
|
|
151
177
|
# These are normally local variables that exist on a particular line
|
|
152
178
|
# of executed code.
|
|
179
|
+
#
|
|
180
|
+
# @param vars [Hash{Symbol => Object}] variables to serialize, keyed by variable name
|
|
181
|
+
# @param deadline [Float] absolute capture deadline in monotonic clock seconds
|
|
182
|
+
# @return [Hash{Symbol => Hash}] serialized variables keyed by variable name
|
|
153
183
|
def serialize_vars(vars,
|
|
184
|
+
deadline:,
|
|
154
185
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
|
155
186
|
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count,
|
|
156
187
|
length: nil,
|
|
157
188
|
collection_size: nil)
|
|
158
189
|
vars.each_with_object({}) do |(k, v), agg|
|
|
159
190
|
agg[k] = serialize_value(v, name: k, depth: depth, attribute_count: attribute_count,
|
|
160
|
-
length: length, collection_size: collection_size)
|
|
191
|
+
length: length, collection_size: collection_size, deadline: deadline,)
|
|
161
192
|
end
|
|
162
193
|
end
|
|
163
194
|
|
|
@@ -173,7 +204,13 @@ module Datadog
|
|
|
173
204
|
# (integers, strings, arrays, hashes).
|
|
174
205
|
#
|
|
175
206
|
# Respects string length, collection size and traversal depth limits.
|
|
176
|
-
|
|
207
|
+
#
|
|
208
|
+
# @param value [any] value to serialize
|
|
209
|
+
# @param name [Symbol, String, nil] name the value is bound to, used for redaction
|
|
210
|
+
# @param deadline [Float] absolute capture deadline in monotonic clock seconds
|
|
211
|
+
# @return [Hash{Symbol => Object}] serialized value; the {type:, notCapturedReason: "timeout"} stub when the deadline is exceeded
|
|
212
|
+
def serialize_value(value, deadline:,
|
|
213
|
+
name: nil,
|
|
177
214
|
depth: settings.dynamic_instrumentation.max_capture_depth,
|
|
178
215
|
attribute_count: nil,
|
|
179
216
|
length: nil,
|
|
@@ -182,6 +219,12 @@ module Datadog
|
|
|
182
219
|
attribute_count ||= settings.dynamic_instrumentation.max_capture_attribute_count
|
|
183
220
|
cls = type || value.class
|
|
184
221
|
begin
|
|
222
|
+
if deadline_exceeded?(deadline)
|
|
223
|
+
telemetry&.inc(DI::TELEMETRY_NAMESPACE, "serialized_values_skipped_by_timeout", 1)
|
|
224
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::CAPTURE_TIMEOUT)
|
|
225
|
+
return {type: class_name(cls), notCapturedReason: "timeout"}
|
|
226
|
+
end
|
|
227
|
+
|
|
185
228
|
if redactor.redact_type?(value)
|
|
186
229
|
return {type: class_name(cls), notCapturedReason: "redactedType"}
|
|
187
230
|
end
|
|
@@ -213,7 +256,7 @@ module Datadog
|
|
|
213
256
|
|
|
214
257
|
if condition_result
|
|
215
258
|
serializer_proc = entry.fetch(:proc)
|
|
216
|
-
return serializer_proc.call(self, value, name: nil, depth: depth)
|
|
259
|
+
return serializer_proc.call(self, value, name: nil, depth: depth, deadline: deadline)
|
|
217
260
|
end
|
|
218
261
|
end
|
|
219
262
|
end
|
|
@@ -227,8 +270,6 @@ module Datadog
|
|
|
227
270
|
when Integer, Float, TrueClass, FalseClass
|
|
228
271
|
serialized.update(value: value.to_s)
|
|
229
272
|
when Time
|
|
230
|
-
# This path also handles DateTime values although they do not need
|
|
231
|
-
# to be explicitly added to the case statement.
|
|
232
273
|
serialized.update(value: value.iso8601)
|
|
233
274
|
when Date
|
|
234
275
|
serialized.update(value: value.to_s)
|
|
@@ -246,27 +287,24 @@ module Datadog
|
|
|
246
287
|
end
|
|
247
288
|
|
|
248
289
|
# Handle binary strings and invalid UTF-8 by escaping to JSON-safe format.
|
|
249
|
-
# See escape_binary_string for details on the escaping format.
|
|
250
|
-
#
|
|
251
|
-
# Truncate binary data BEFORE escaping to avoid cutting mid-escape-sequence.
|
|
252
290
|
# For regular strings, the limit is applied to string length in characters.
|
|
253
291
|
length ||= settings.dynamic_instrumentation.max_capture_string_length
|
|
254
292
|
max = length
|
|
255
293
|
|
|
256
294
|
if value.encoding == Encoding::BINARY || !value.valid_encoding?
|
|
257
|
-
# Truncate binary data
|
|
295
|
+
# Truncate binary data before escaping to avoid cutting mid-escape-sequence
|
|
258
296
|
# For invalid encodings, use bytesize instead of length to avoid encoding errors
|
|
259
297
|
original_size = value.bytesize
|
|
260
298
|
if original_size > max
|
|
261
299
|
serialized.update(truncated: true, size: original_size)
|
|
300
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::STRING_LENGTH)
|
|
262
301
|
value = value.byteslice(0...max)
|
|
263
302
|
end
|
|
264
303
|
value = escape_binary_string(value) # steep:ignore ArgumentTypeMismatch
|
|
265
|
-
false # Already converted to a new string
|
|
266
304
|
else
|
|
267
|
-
# Truncate non-binary strings
|
|
268
305
|
if value.length > max
|
|
269
306
|
serialized.update(truncated: true, size: value.length)
|
|
307
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::STRING_LENGTH)
|
|
270
308
|
value = value[0...max]
|
|
271
309
|
need_dup = false
|
|
272
310
|
end
|
|
@@ -278,23 +316,32 @@ module Datadog
|
|
|
278
316
|
when Array
|
|
279
317
|
if depth <= 0
|
|
280
318
|
serialized.update(notCapturedReason: "depth")
|
|
319
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::DEPTH)
|
|
281
320
|
else
|
|
282
321
|
collection_size ||= settings.dynamic_instrumentation.max_capture_collection_size
|
|
283
322
|
max = collection_size
|
|
284
323
|
if max != 0 && value.length > max
|
|
285
324
|
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
|
325
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::COLLECTION_SIZE)
|
|
286
326
|
# same steep failure with array slices.
|
|
287
327
|
# https://github.com/soutaro/steep/issues/1219
|
|
288
328
|
value = value[0...max] || []
|
|
289
329
|
end
|
|
290
|
-
entries =
|
|
291
|
-
|
|
330
|
+
entries = []
|
|
331
|
+
value.each do |elt|
|
|
332
|
+
entry = serialize_value(elt, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
|
|
333
|
+
entries << entry
|
|
334
|
+
if entry[:notCapturedReason] == "timeout"
|
|
335
|
+
serialized.update(notCapturedReason: "timeout")
|
|
336
|
+
break
|
|
337
|
+
end
|
|
292
338
|
end
|
|
293
339
|
serialized.update(elements: entries)
|
|
294
340
|
end
|
|
295
341
|
when Hash
|
|
296
342
|
if depth <= 0
|
|
297
343
|
serialized.update(notCapturedReason: "depth")
|
|
344
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::DEPTH)
|
|
298
345
|
else
|
|
299
346
|
collection_size ||= settings.dynamic_instrumentation.max_capture_collection_size
|
|
300
347
|
max = collection_size
|
|
@@ -303,17 +350,28 @@ module Datadog
|
|
|
303
350
|
value.each do |k, v|
|
|
304
351
|
if max != 0 && cur >= max
|
|
305
352
|
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
|
353
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::COLLECTION_SIZE)
|
|
306
354
|
break
|
|
307
355
|
end
|
|
308
356
|
cur += 1
|
|
309
|
-
|
|
310
|
-
|
|
357
|
+
key_entry = serialize_value(k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
|
|
358
|
+
if key_entry[:notCapturedReason] == "timeout"
|
|
359
|
+
serialized.update(notCapturedReason: "timeout")
|
|
360
|
+
break
|
|
361
|
+
end
|
|
362
|
+
value_entry = serialize_value(v, name: k, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
|
|
363
|
+
entries << [key_entry, value_entry]
|
|
364
|
+
if value_entry[:notCapturedReason] == "timeout"
|
|
365
|
+
serialized.update(notCapturedReason: "timeout")
|
|
366
|
+
break
|
|
367
|
+
end
|
|
311
368
|
end
|
|
312
369
|
serialized.update(entries: entries)
|
|
313
370
|
end
|
|
314
371
|
else
|
|
315
372
|
if depth <= 0
|
|
316
373
|
serialized.update(notCapturedReason: "depth")
|
|
374
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::DEPTH)
|
|
317
375
|
else
|
|
318
376
|
fields = {}
|
|
319
377
|
cur = 0
|
|
@@ -334,40 +392,42 @@ module Datadog
|
|
|
334
392
|
#
|
|
335
393
|
# For consistency, we should have some kind of stable order of
|
|
336
394
|
# instance variables on all supported Ruby runtimes, so that the UI
|
|
337
|
-
# stays consistent.
|
|
338
|
-
#
|
|
339
|
-
# of #instance_variables here, but if JRuby is supported in the
|
|
340
|
-
# future this may need to be addressed.
|
|
395
|
+
# stays consistent. Ruby DI does not support JRuby, so JRuby's lack
|
|
396
|
+
# of ordering of #instance_variables is not handled here.
|
|
341
397
|
ivars = value.instance_variables
|
|
342
398
|
|
|
343
399
|
ivars.each do |ivar|
|
|
344
400
|
if cur >= attribute_count
|
|
345
401
|
serialized.update(notCapturedReason: "fieldCount", fields: fields)
|
|
402
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::FIELD_COUNT)
|
|
346
403
|
break
|
|
347
404
|
end
|
|
348
405
|
cur += 1
|
|
349
|
-
|
|
406
|
+
entry = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1, length: length, collection_size: collection_size, attribute_count: attribute_count, deadline: deadline)
|
|
407
|
+
fields[ivar] = entry
|
|
408
|
+
if entry[:notCapturedReason] == "timeout"
|
|
409
|
+
serialized.update(notCapturedReason: "timeout")
|
|
410
|
+
break
|
|
411
|
+
end
|
|
350
412
|
end
|
|
351
413
|
serialized.update(fields: fields)
|
|
352
414
|
end
|
|
353
415
|
end
|
|
354
416
|
serialized
|
|
355
417
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
356
|
-
# Re-raise fatal exceptions that should not be caught
|
|
357
|
-
# (signals, interrupts, system exit)
|
|
358
418
|
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
|
|
359
419
|
|
|
360
|
-
# Catch all
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
|
|
364
|
-
|
|
420
|
+
# Catch all remaining exceptions: SystemStackError and NoMemoryError can
|
|
421
|
+
# occur during serialization (e.g. a custom serializer recursing
|
|
422
|
+
# infinitely) and produce the safe stub.
|
|
423
|
+
record_capture_incomplete(GuardrailsTelemetry::Reason::RUNTIME_ERROR)
|
|
424
|
+
logger.debug { "di: error serializing value of #{class_name(cls)}: #{exc.class}: #{exc.message}" }
|
|
365
425
|
telemetry&.report(exc, description: "Error serializing")
|
|
366
426
|
{type: class_name(cls), notSerializedReason: exc.to_s}
|
|
367
427
|
end
|
|
368
428
|
end
|
|
369
429
|
|
|
370
|
-
#
|
|
430
|
+
# Serializes a value for interpolation into a log-probe message.
|
|
371
431
|
# Because the output is meant to be human-readable, we cannot use
|
|
372
432
|
# the "normal" serialization format which is meant to be machine-readable.
|
|
373
433
|
# Serialize objects with depth of 1 and include the class name.
|
|
@@ -386,6 +446,8 @@ module Datadog
|
|
|
386
446
|
# +name+, when given, is the identifier the template expression
|
|
387
447
|
# references at its top level; a redacted identifier yields the
|
|
388
448
|
# redaction placeholder, mirroring #serialize_value on the snapshot path.
|
|
449
|
+
# Message rendering is capped by depth (1) and
|
|
450
|
+
# MAX_MESSAGE_COLLECTION_SIZE / MAX_MESSAGE_ATTRIBUTE_COUNT.
|
|
389
451
|
def serialize_value_for_message(value, depth: 1, name: nil)
|
|
390
452
|
# This method is more verbose than "normal" Ruby code to avoid
|
|
391
453
|
# array allocations.
|
|
@@ -475,13 +537,27 @@ module Datadog
|
|
|
475
537
|
raise if SERIALIZABLE_FATAL_EXCEPTION_CLASSES.any? { |klass| exc.is_a?(klass) }
|
|
476
538
|
|
|
477
539
|
telemetry&.report(exc, description: "Error serializing for message")
|
|
478
|
-
# TODO class_name
|
|
479
|
-
# Telemetry reporting could potentially also fail?
|
|
540
|
+
# TODO handle class_name raising here as well.
|
|
480
541
|
"#<#{class_name(value.class)}: serialization error>"
|
|
481
542
|
end
|
|
482
543
|
|
|
544
|
+
# Computes the absolute monotonic deadline (in seconds) for a capture
|
|
545
|
+
# point from max_time_to_serialize_ms. Resolve once per capture point and
|
|
546
|
+
# share across all values serialized on the built-in paths so the
|
|
547
|
+
# budget is not exceeded.
|
|
548
|
+
#
|
|
549
|
+
# @return [Float] absolute monotonic clock reading, in seconds, at
|
|
550
|
+
# which the capture budget is exhausted
|
|
551
|
+
def serialization_deadline
|
|
552
|
+
Core::Utils::Time.get_time + settings.dynamic_instrumentation.max_time_to_serialize_ms / 1000.0
|
|
553
|
+
end
|
|
554
|
+
|
|
483
555
|
private
|
|
484
556
|
|
|
557
|
+
def deadline_exceeded?(deadline)
|
|
558
|
+
Core::Utils::Time.get_time >= deadline
|
|
559
|
+
end
|
|
560
|
+
|
|
485
561
|
MAX_MESSAGE_COLLECTION_SIZE = 3
|
|
486
562
|
MAX_MESSAGE_ATTRIBUTE_COUNT = 5
|
|
487
563
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
# A snapshot payload queued for sending to the agent, paired with
|
|
6
|
+
# the guardrails event type of the probe that produced it, so drop
|
|
7
|
+
# metrics can be tagged with the probe's classification.
|
|
8
|
+
# @api private
|
|
9
|
+
class SnapshotEvent
|
|
10
|
+
# Initializes the event.
|
|
11
|
+
#
|
|
12
|
+
# @param payload [Hash] the snapshot payload to send to the agent
|
|
13
|
+
# @param event_type [String] one of GuardrailsTelemetry::EVENT_TYPE_SNAPSHOT and EVENT_TYPE_LOG
|
|
14
|
+
# @return [void]
|
|
15
|
+
def initialize(payload:, event_type:)
|
|
16
|
+
@payload = payload
|
|
17
|
+
@event_type = event_type
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# The snapshot payload to send to the agent.
|
|
21
|
+
# @return [Hash]
|
|
22
|
+
attr_reader :payload
|
|
23
|
+
|
|
24
|
+
# The guardrails event type of the probe that produced the payload.
|
|
25
|
+
# @return [String] one of GuardrailsTelemetry::EVENT_TYPE_SNAPSHOT and EVENT_TYPE_LOG
|
|
26
|
+
attr_reader :event_type
|
|
27
|
+
|
|
28
|
+
# Compares two events by payload and event type.
|
|
29
|
+
#
|
|
30
|
+
# @param other [Object] the object to compare with
|
|
31
|
+
# @return [Boolean] whether the other object is an event with the
|
|
32
|
+
# same payload and event type
|
|
33
|
+
def ==(other)
|
|
34
|
+
other.is_a?(SnapshotEvent) && other.payload == payload && other.event_type == event_type
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -47,11 +47,20 @@ module Datadog
|
|
|
47
47
|
end.to_transport(DI::Transport::Diagnostics::Transport)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
# Builds
|
|
51
|
-
#
|
|
50
|
+
# Builds the snapshot input transport. Pass a block to override
|
|
51
|
+
# any transport settings.
|
|
52
|
+
#
|
|
53
|
+
# @param agent_settings [Datadog::Core::Configuration::AgentSettings] agent connection settings
|
|
54
|
+
# @param logger [DI::Logger] logger for transport diagnostics
|
|
55
|
+
# @param headers [Hash{String => String}, nil] headers to send with every request
|
|
56
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component transport errors are reported through
|
|
57
|
+
# @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical guardrails drop metric
|
|
58
|
+
# @return [DI::Transport::Input::Transport]
|
|
59
|
+
# @yield [builder] block overriding the default transport builder settings
|
|
52
60
|
def self.input(
|
|
53
61
|
agent_settings:,
|
|
54
62
|
logger:,
|
|
63
|
+
guardrails_telemetry:,
|
|
55
64
|
headers: nil,
|
|
56
65
|
telemetry: nil
|
|
57
66
|
)
|
|
@@ -72,7 +81,8 @@ module Datadog
|
|
|
72
81
|
builder.to_api_instances,
|
|
73
82
|
builder.default_api,
|
|
74
83
|
logger: logger,
|
|
75
|
-
telemetry: telemetry
|
|
84
|
+
telemetry: telemetry,
|
|
85
|
+
guardrails_telemetry: guardrails_telemetry,
|
|
76
86
|
)
|
|
77
87
|
end
|
|
78
88
|
end
|
|
@@ -8,6 +8,7 @@ require_relative "../../core/transport/request"
|
|
|
8
8
|
require_relative "../../core/transport/transport"
|
|
9
9
|
require_relative "../error"
|
|
10
10
|
require_relative "../fatal_exceptions"
|
|
11
|
+
require_relative "../guardrails_telemetry"
|
|
11
12
|
require_relative "http/input"
|
|
12
13
|
|
|
13
14
|
module Datadog
|
|
@@ -26,10 +27,22 @@ module Datadog
|
|
|
26
27
|
|
|
27
28
|
class Transport < Core::Transport::Transport
|
|
28
29
|
attr_reader :telemetry
|
|
30
|
+
# The guardrails metric emitter.
|
|
31
|
+
# @return [GuardrailsTelemetry]
|
|
32
|
+
attr_reader :guardrails_telemetry
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
# Initializes the transport.
|
|
35
|
+
#
|
|
36
|
+
# @param apis [Datadog::Core::Transport::HTTP::API::Map] API instances by name
|
|
37
|
+
# @param default_api [String] name of the API requests are sent through when unspecified
|
|
38
|
+
# @param logger [Datadog::Core::Logger] logger for transport diagnostics
|
|
39
|
+
# @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical payload-drop metric
|
|
40
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component transport errors are reported through
|
|
41
|
+
# @return [void]
|
|
42
|
+
def initialize(apis, default_api, logger:, guardrails_telemetry:, telemetry: nil)
|
|
31
43
|
super(apis, default_api, logger: logger)
|
|
32
44
|
@telemetry = telemetry
|
|
45
|
+
@guardrails_telemetry = guardrails_telemetry
|
|
33
46
|
end
|
|
34
47
|
|
|
35
48
|
# The limit on an individual snapshot payload, aka "log line",
|
|
@@ -62,19 +75,24 @@ module Datadog
|
|
|
62
75
|
# Large snapshots (> 1MB) are dropped. Batches are split into chunks
|
|
63
76
|
# of ~2MB each to avoid large network requests.
|
|
64
77
|
#
|
|
65
|
-
# @param payload [Array<
|
|
78
|
+
# @param payload [Array<SnapshotEvent>] the snapshot events to send, each paired with its probe's guardrails event type
|
|
66
79
|
# @param tags [Hash] Tags to send with the snapshots
|
|
67
80
|
# @param on_serialization_error [Proc] Called with (probe_id, exception)
|
|
68
81
|
# when a snapshot fails to serialize.
|
|
82
|
+
# @return [Array<SnapshotEvent>] the events that were sent
|
|
69
83
|
def send_input(payload, tags, on_serialization_error:)
|
|
70
84
|
serialized_tags = Core::TagBuilder.serialize_tags(tags)
|
|
71
85
|
|
|
72
86
|
# Serialize each snapshot individually to isolate failures
|
|
73
87
|
encoded_snapshots = []
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
dropped_events = []
|
|
89
|
+
payload.each do |event|
|
|
90
|
+
encoded = encoder.encode(event.payload)
|
|
91
|
+
if encoded.bytesize > MAX_SERIALIZED_SNAPSHOT_SIZE
|
|
92
|
+
logger.debug do
|
|
93
|
+
"di: dropping too big snapshot (#{GuardrailsTelemetry::Reason::PAYLOAD_TOO_LARGE})"
|
|
94
|
+
end
|
|
95
|
+
dropped_events << [event, encoded.bytesize]
|
|
78
96
|
next
|
|
79
97
|
end
|
|
80
98
|
encoded_snapshots << encoded
|
|
@@ -82,7 +100,7 @@ module Datadog
|
|
|
82
100
|
Datadog::DI.reraise_if_fatal(exc)
|
|
83
101
|
# Serialization failed for this snapshot - report via callback
|
|
84
102
|
# This catches JSON::GeneratorError, Encoding errors, TypeError, etc.
|
|
85
|
-
probe_id =
|
|
103
|
+
probe_id = event.payload.dig(:debugger, :snapshot, :probe, :id)
|
|
86
104
|
logger.debug { "di: JSON encoding failed for snapshot (probe #{probe_id}): #{exc.class}: #{exc.message}" }
|
|
87
105
|
telemetry&.report(exc, description: "JSON encoding failed for snapshot")
|
|
88
106
|
|
|
@@ -97,6 +115,13 @@ module Datadog
|
|
|
97
115
|
end
|
|
98
116
|
end
|
|
99
117
|
|
|
118
|
+
dropped_events.each do |event, bytes|
|
|
119
|
+
guardrails_telemetry.dropped(
|
|
120
|
+
reason: GuardrailsTelemetry::Reason::PAYLOAD_TOO_LARGE,
|
|
121
|
+
event_type: event.event_type, bytes: bytes,
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
100
125
|
return payload if encoded_snapshots.empty?
|
|
101
126
|
|
|
102
127
|
Datadog::Core::Chunker.chunk_by_size(
|