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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
class GuardrailsTelemetry
|
|
6
|
+
# Canonical guardrails reason codes. Codes
|
|
7
|
+
# shared with the other Datadog tracers keep the exact shared
|
|
8
|
+
# spelling; codes with no shared counterpart use the Ruby tracer's
|
|
9
|
+
# own name for the same decision.
|
|
10
|
+
module Reason
|
|
11
|
+
# Skip reason emitted when a probe firing is rejected by the
|
|
12
|
+
# probe's own rate limiter.
|
|
13
|
+
RATE_LIMIT_PROBE = "rateLimitProbe"
|
|
14
|
+
|
|
15
|
+
# Skip reason emitted when a probe firing is rejected by the
|
|
16
|
+
# process-wide rate limiter.
|
|
17
|
+
RATE_LIMIT_GLOBAL = "rateLimitGlobal"
|
|
18
|
+
|
|
19
|
+
# Skip reason emitted when a capture expression evaluation exceeds
|
|
20
|
+
# the capture time budget.
|
|
21
|
+
EVALUATION_TIMEOUT = "evaluationTimeout"
|
|
22
|
+
|
|
23
|
+
# Skip reason named in the +di: debug log when a condition-evaluation
|
|
24
|
+
# failure notification is rejected by the per-probe limiter.
|
|
25
|
+
EVALUATION_ERROR_THROTTLED = "evaluationErrorThrottled"
|
|
26
|
+
|
|
27
|
+
# Skip reason naming the per-invocation DI time budget guardrail
|
|
28
|
+
# decision.
|
|
29
|
+
BUDGET_EXCEEDED_INVOCATION = "budgetExceededInvocation"
|
|
30
|
+
|
|
31
|
+
# Skip reason naming the process-wide DI time budget guardrail
|
|
32
|
+
# decision.
|
|
33
|
+
BUDGET_EXCEEDED_GLOBAL = "budgetExceededGlobal"
|
|
34
|
+
|
|
35
|
+
# Skip and drop reason emitted when an event is discarded at a
|
|
36
|
+
# full worker queue.
|
|
37
|
+
QUEUE_FULL = "queueFull"
|
|
38
|
+
|
|
39
|
+
# Skip and drop reason naming the queue high-watermark guardrail
|
|
40
|
+
# decision.
|
|
41
|
+
QUEUE_HIGH_WATERMARK = "queueHighWatermark"
|
|
42
|
+
|
|
43
|
+
# Drop reason emitted when a snapshot exceeds the serialized
|
|
44
|
+
# payload limit.
|
|
45
|
+
PAYLOAD_TOO_LARGE = "payloadTooLarge"
|
|
46
|
+
|
|
47
|
+
# Drop reason naming the batch byte-cap guardrail decision.
|
|
48
|
+
BATCH_BYTES_EXCEEDED = "batchBytesExceeded"
|
|
49
|
+
|
|
50
|
+
# Capture-incomplete reason emitted when a capture or
|
|
51
|
+
# serialization runtime exception ends the capture of a value
|
|
52
|
+
# early.
|
|
53
|
+
RUNTIME_ERROR = "runtimeError"
|
|
54
|
+
|
|
55
|
+
# Capture-incomplete reason emitted when a value is not captured
|
|
56
|
+
# because the capture time budget expired while it was being
|
|
57
|
+
# serialized.
|
|
58
|
+
CAPTURE_TIMEOUT = "timeout"
|
|
59
|
+
|
|
60
|
+
# Capture-incomplete reason emitted when a value at the maximum
|
|
61
|
+
# capture depth is not captured.
|
|
62
|
+
DEPTH = "depth"
|
|
63
|
+
|
|
64
|
+
# Capture-incomplete reason emitted when an object with more
|
|
65
|
+
# fields than the capture limit is partially captured.
|
|
66
|
+
FIELD_COUNT = "fieldCount"
|
|
67
|
+
|
|
68
|
+
# Capture-incomplete reason emitted when a collection with more
|
|
69
|
+
# items than the capture limit is partially captured.
|
|
70
|
+
COLLECTION_SIZE = "collectionSize"
|
|
71
|
+
|
|
72
|
+
# Capture-incomplete reason emitted when a captured string is
|
|
73
|
+
# trimmed to the capture limit.
|
|
74
|
+
STRING_LENGTH = "stringLength"
|
|
75
|
+
|
|
76
|
+
# Capture-incomplete reason for an early-capture cause with no
|
|
77
|
+
# more specific code.
|
|
78
|
+
OTHER = "other"
|
|
79
|
+
|
|
80
|
+
# The skip reasons the events.skipped metric tags with the firing
|
|
81
|
+
# probe's id; every other skip reason names a process-wide
|
|
82
|
+
# decision and carries no probe id.
|
|
83
|
+
PROBE_SCOPED_REASONS = [RATE_LIMIT_PROBE, EVALUATION_TIMEOUT].freeze
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "fatal_exceptions"
|
|
4
|
+
require_relative "guardrails_telemetry/reason"
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module DI
|
|
8
|
+
# Telemetry namespace for DI metrics, shared with the other tracers.
|
|
9
|
+
# The telemetry wire name is
|
|
10
|
+
# +dd.instrumentation_telemetry_data.debugger.*+.
|
|
11
|
+
TELEMETRY_NAMESPACE = "debugger"
|
|
12
|
+
|
|
13
|
+
# Telemetry reporter for the DI guardrails: the reason codes and the
|
|
14
|
+
# metric emitters.
|
|
15
|
+
class GuardrailsTelemetry
|
|
16
|
+
# Skip count metric name, emitted under +DI::TELEMETRY_NAMESPACE+.
|
|
17
|
+
EVENTS_SKIPPED = "events.skipped"
|
|
18
|
+
# Drop count metric name, emitted under +DI::TELEMETRY_NAMESPACE+.
|
|
19
|
+
EVENTS_DROPPED = "events.dropped"
|
|
20
|
+
# Incomplete-capture count metric name, emitted under +DI::TELEMETRY_NAMESPACE+.
|
|
21
|
+
CAPTURE_INCOMPLETE = "capture.incomplete"
|
|
22
|
+
|
|
23
|
+
# Queue byte-count metric, emitted under +DI::TELEMETRY_NAMESPACE+.
|
|
24
|
+
QUEUE_DROPPED_BYTES = "guardrails.queue.dropped_bytes"
|
|
25
|
+
|
|
26
|
+
# Tag value for probes that capture a full snapshot.
|
|
27
|
+
EVENT_TYPE_SNAPSHOT = "snapshot"
|
|
28
|
+
# Tag value for probes that capture evaluated log output.
|
|
29
|
+
EVENT_TYPE_LOG = "log"
|
|
30
|
+
|
|
31
|
+
# Initializes the reporter.
|
|
32
|
+
#
|
|
33
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings the propagation escape hatch is read from
|
|
34
|
+
# @param logger [DI::Logger] logger for contained emission failures
|
|
35
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component the guardrails metrics are emitted through
|
|
36
|
+
def initialize(settings:, logger:, telemetry:)
|
|
37
|
+
@settings = settings
|
|
38
|
+
@logger = logger
|
|
39
|
+
@telemetry = telemetry
|
|
40
|
+
@skipped_tags_memo = {}
|
|
41
|
+
@dropped_tags_memo = {}
|
|
42
|
+
@capture_incomplete_tags_memo = {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The tracer settings.
|
|
46
|
+
# @return [Datadog::Core::Configuration::Settings]
|
|
47
|
+
attr_reader :settings
|
|
48
|
+
# The logger.
|
|
49
|
+
# @return [DI::Logger]
|
|
50
|
+
attr_reader :logger
|
|
51
|
+
# The component the guardrails metrics are emitted through.
|
|
52
|
+
# @return [Datadog::Core::Telemetry::Component, nil]
|
|
53
|
+
attr_reader :telemetry
|
|
54
|
+
# The memo of frozen tag hashes for emitted skip metrics without
|
|
55
|
+
# +probe_id+, keyed by reason and event type.
|
|
56
|
+
# @return [Hash{String => Hash{String => Hash{Symbol => String}}}]
|
|
57
|
+
attr_reader :skipped_tags_memo
|
|
58
|
+
# The memo of frozen tag hashes for emitted drop metrics, keyed by
|
|
59
|
+
# reason and event type.
|
|
60
|
+
# @return [Hash{String => Hash{String => Hash{Symbol => String}}}]
|
|
61
|
+
attr_reader :dropped_tags_memo
|
|
62
|
+
# The memo of frozen tag hashes for emitted incomplete-capture
|
|
63
|
+
# metrics, keyed by reason and event type.
|
|
64
|
+
# @return [Hash{String => Hash{String => Hash{Symbol => String}}}]
|
|
65
|
+
attr_reader :capture_incomplete_tags_memo
|
|
66
|
+
|
|
67
|
+
# Returns the canonical event_type tag for a probe, derived from
|
|
68
|
+
# whether the probe captures a full snapshot.
|
|
69
|
+
#
|
|
70
|
+
# @param probe [Probe] the probe whose firing is being tagged
|
|
71
|
+
# @return [String] EVENT_TYPE_SNAPSHOT or EVENT_TYPE_LOG
|
|
72
|
+
def self.probe_event_type_tag(probe)
|
|
73
|
+
probe.capture_snapshot? ? EVENT_TYPE_SNAPSHOT : EVENT_TYPE_LOG
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Emits the canonical +debugger.events.skipped+ count metric for a
|
|
77
|
+
# probe firing a guardrail rejected.
|
|
78
|
+
#
|
|
79
|
+
# @param reason [String] a GuardrailsTelemetry::Reason constant naming the skip cause
|
|
80
|
+
# @param probe [Probe] the probe whose firing was rejected
|
|
81
|
+
# @return [void]
|
|
82
|
+
def skipped(reason:, probe:)
|
|
83
|
+
probe_id = probe.id if Reason::PROBE_SCOPED_REASONS.include?(reason)
|
|
84
|
+
inc_metric(
|
|
85
|
+
DI::TELEMETRY_NAMESPACE, EVENTS_SKIPPED, 1,
|
|
86
|
+
tags: skipped_tags(reason, self.class.probe_event_type_tag(probe), probe_id),
|
|
87
|
+
)
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Emits the canonical +debugger.events.dropped+ count metric, and,
|
|
92
|
+
# when +bytes+ is provided, the +guardrails.queue.dropped_bytes+ count
|
|
93
|
+
# metric.
|
|
94
|
+
#
|
|
95
|
+
# @param reason [String] a GuardrailsTelemetry::Reason constant naming the drop cause
|
|
96
|
+
# @param event_type [String] the canonical event_type tag value
|
|
97
|
+
# @param bytes [Integer, nil] encoded bytes discarded, when the drop site knows the count
|
|
98
|
+
# @return [void]
|
|
99
|
+
def dropped(reason:, event_type:, bytes: nil)
|
|
100
|
+
tags = dropped_tags(reason, event_type)
|
|
101
|
+
inc_metric(DI::TELEMETRY_NAMESPACE, EVENTS_DROPPED, 1, tags: tags)
|
|
102
|
+
inc_metric(DI::TELEMETRY_NAMESPACE, QUEUE_DROPPED_BYTES, bytes, tags: tags) if bytes
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Emits the canonical +debugger.capture.incomplete+ count metric for
|
|
107
|
+
# a capture limit enforced on a snapshot that is still sent.
|
|
108
|
+
#
|
|
109
|
+
# @param reason [String] a GuardrailsTelemetry::Reason constant naming the enforced capture limit
|
|
110
|
+
# @param event_type [String] the canonical event_type tag value
|
|
111
|
+
# @return [void]
|
|
112
|
+
def capture_incomplete(reason:, event_type:)
|
|
113
|
+
inc_metric(
|
|
114
|
+
DI::TELEMETRY_NAMESPACE, CAPTURE_INCOMPLETE, 1,
|
|
115
|
+
tags: capture_incomplete_tags(reason, event_type),
|
|
116
|
+
)
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Emits a guardrails count metric.
|
|
123
|
+
#
|
|
124
|
+
# @param namespace [String] telemetry namespace the metric is emitted under
|
|
125
|
+
# @param metric_name [String] guardrails metric name
|
|
126
|
+
# @param value [Integer] increment count, or the discarded byte count
|
|
127
|
+
# @param tags [Hash{Symbol => String}] frozen metric tags for the emission
|
|
128
|
+
# @return [void]
|
|
129
|
+
def inc_metric(namespace, metric_name, value, tags:)
|
|
130
|
+
telemetry&.inc(namespace, metric_name, value, tags: tags)
|
|
131
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
132
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
133
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
134
|
+
report_emission_failure(namespace, metric_name, exc)
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def report_emission_failure(namespace, metric_name, exc)
|
|
139
|
+
logger.debug { "di: error emitting #{namespace}.#{metric_name} metric: #{exc.class}: #{exc.message}" }
|
|
140
|
+
begin
|
|
141
|
+
telemetry&.report(exc, description: "Error emitting #{namespace}.#{metric_name} metric")
|
|
142
|
+
rescue Exception => nested_exc # standard:disable Lint/RescueException
|
|
143
|
+
Datadog::DI.reraise_if_fatal(nested_exc)
|
|
144
|
+
logger.debug do
|
|
145
|
+
"di: error reporting #{namespace}.#{metric_name} metric emission failure: " \
|
|
146
|
+
"#{nested_exc.class}: #{nested_exc.message}"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns the frozen tags for a skip emission. Emissions without
|
|
153
|
+
# +probe_id+ are memoized; probe-scoped emissions allocate one
|
|
154
|
+
# frozen hash per emission: remote configuration adds and removes
|
|
155
|
+
# probes frequently, and a per-probe-id memo would grow without
|
|
156
|
+
# bound over the process lifetime.
|
|
157
|
+
#
|
|
158
|
+
# @param reason [String] the skip reason
|
|
159
|
+
# @param event_type [String] the canonical event_type tag value
|
|
160
|
+
# @param probe_id [String, nil] the rejected probe's id
|
|
161
|
+
# @return [Hash{Symbol => String}] the frozen skip-metric tags
|
|
162
|
+
def skipped_tags(reason, event_type, probe_id)
|
|
163
|
+
return {reason: reason, event_type: event_type, probe_id: probe_id}.freeze if probe_id
|
|
164
|
+
|
|
165
|
+
memoized_tags(skipped_tags_memo, reason, event_type)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns the frozen tags for a drop emission, memoized per
|
|
169
|
+
# (reason, event_type) pair.
|
|
170
|
+
#
|
|
171
|
+
# @param reason [String] the drop reason
|
|
172
|
+
# @param event_type [String] the canonical event_type tag value
|
|
173
|
+
# @return [Hash{Symbol => String}] the frozen drop-metric tags
|
|
174
|
+
def dropped_tags(reason, event_type)
|
|
175
|
+
memoized_tags(dropped_tags_memo, reason, event_type)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns the frozen tags for an incomplete-capture emission,
|
|
179
|
+
# memoized per (reason, event_type) pair.
|
|
180
|
+
#
|
|
181
|
+
# @param reason [String] the enforced capture limit
|
|
182
|
+
# @param event_type [String] the canonical event_type tag value
|
|
183
|
+
# @return [Hash{Symbol => String}] the frozen incomplete-capture metric tags
|
|
184
|
+
def capture_incomplete_tags(reason, event_type)
|
|
185
|
+
memoized_tags(capture_incomplete_tags_memo, reason, event_type)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Returns the frozen tags memoized per (reason, event_type) pair,
|
|
189
|
+
# so a repeated emission allocates no tag hash. The memo is
|
|
190
|
+
# lock-free: a racy first write can only discard a duplicate
|
|
191
|
+
# frozen hash.
|
|
192
|
+
#
|
|
193
|
+
# @param memo [Hash{String => Hash{String => Hash{Symbol => String}}}] the emission-kind memo to index
|
|
194
|
+
# @param reason [String] the emission reason
|
|
195
|
+
# @param event_type [String] the canonical event_type tag value
|
|
196
|
+
# @return [Hash{Symbol => String}] the frozen metric tags
|
|
197
|
+
def memoized_tags(memo, reason, event_type)
|
|
198
|
+
reason_memos = memo[reason] ||= {}
|
|
199
|
+
reason_memos[event_type] ||= {reason: reason, event_type: event_type}.freeze
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -4,7 +4,7 @@ require_relative "../core/rate_limiter"
|
|
|
4
4
|
require_relative "../core/utils/time"
|
|
5
5
|
require_relative "../ruby_version"
|
|
6
6
|
require_relative "fatal_exceptions"
|
|
7
|
-
require_relative "
|
|
7
|
+
require_relative "guardrails_telemetry"
|
|
8
8
|
|
|
9
9
|
# rubocop:disable Lint/AssignmentInCondition
|
|
10
10
|
# rubocop:disable Style/AndOr
|
|
@@ -78,11 +78,24 @@ module Datadog
|
|
|
78
78
|
# the whole process.
|
|
79
79
|
GLOBAL_LOG_RATE_LIMIT = 5000
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
# Initializes the instrumenter.
|
|
82
|
+
#
|
|
83
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings
|
|
84
|
+
# @param serializer [Serializer] serializer for captured values
|
|
85
|
+
# @param logger [DI::Logger] logger for instrumenter diagnostics
|
|
86
|
+
# @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical rate-limit skip metric
|
|
87
|
+
# @param capture_expression_evaluator [CaptureExpressionEvaluator] evaluator for the probe's capture expressions
|
|
88
|
+
# @param code_tracker [CodeTracker, nil] code tracker for line probe installation
|
|
89
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component instrumenter errors are reported through
|
|
90
|
+
# @return [void]
|
|
91
|
+
def initialize(settings, serializer, logger, guardrails_telemetry:, capture_expression_evaluator:,
|
|
92
|
+
code_tracker: nil, telemetry: nil)
|
|
82
93
|
@settings = settings
|
|
83
94
|
@serializer = serializer
|
|
84
95
|
@logger = logger
|
|
85
96
|
@telemetry = telemetry
|
|
97
|
+
@guardrails_telemetry = guardrails_telemetry
|
|
98
|
+
@capture_expression_evaluator = capture_expression_evaluator
|
|
86
99
|
@code_tracker = code_tracker
|
|
87
100
|
@global_snapshot_rate_limiter = Datadog::Core::TokenBucket.new(GLOBAL_SNAPSHOT_RATE_LIMIT)
|
|
88
101
|
@global_log_rate_limiter = Datadog::Core::TokenBucket.new(GLOBAL_LOG_RATE_LIMIT)
|
|
@@ -94,6 +107,12 @@ module Datadog
|
|
|
94
107
|
attr_reader :serializer
|
|
95
108
|
attr_reader :logger
|
|
96
109
|
attr_reader :telemetry
|
|
110
|
+
# The guardrails metric emitter.
|
|
111
|
+
# @return [GuardrailsTelemetry]
|
|
112
|
+
attr_reader :guardrails_telemetry
|
|
113
|
+
# The capture expression evaluator.
|
|
114
|
+
# @return [CaptureExpressionEvaluator]
|
|
115
|
+
attr_reader :capture_expression_evaluator
|
|
97
116
|
attr_reader :code_tracker
|
|
98
117
|
|
|
99
118
|
# The code tracker is a global singleton created lazily by
|
|
@@ -109,12 +128,6 @@ module Datadog
|
|
|
109
128
|
# @return [Datadog::Core::TokenBucket]
|
|
110
129
|
attr_reader :global_log_rate_limiter
|
|
111
130
|
|
|
112
|
-
def capture_expression_evaluator
|
|
113
|
-
@capture_expression_evaluator ||= CaptureExpressionEvaluator.new(
|
|
114
|
-
settings: settings, serializer: serializer, logger: logger, telemetry: telemetry,
|
|
115
|
-
)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
131
|
# Returns the process-wide rate limiter for the probe.
|
|
119
132
|
#
|
|
120
133
|
# @param probe [Probe] the probe whose invocation is being rate limited
|
|
@@ -127,6 +140,24 @@ module Datadog
|
|
|
127
140
|
end
|
|
128
141
|
end
|
|
129
142
|
|
|
143
|
+
# Emits the canonical rate-limit skip metric for a probe.
|
|
144
|
+
#
|
|
145
|
+
# @param probe [Probe] the probe being skipped
|
|
146
|
+
# @param reason [String] a GuardrailsTelemetry::Reason constant for the rejecting limit
|
|
147
|
+
# @return [void]
|
|
148
|
+
def record_rate_limit_skip(reason, probe)
|
|
149
|
+
logger.trace do
|
|
150
|
+
"di: #{probe.type} probe #{probe.id}: skipping due to " \
|
|
151
|
+
"#{(reason == GuardrailsTelemetry::Reason::RATE_LIMIT_PROBE) ? "per-probe" : "global"} rate limit" \
|
|
152
|
+
" (#{reason})"
|
|
153
|
+
end
|
|
154
|
+
guardrails_telemetry.skipped(
|
|
155
|
+
reason: reason,
|
|
156
|
+
probe: probe,
|
|
157
|
+
)
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
130
161
|
# This is a substitute for Thread::Backtrace::Location
|
|
131
162
|
# which does not have a public constructor.
|
|
132
163
|
# Used for the fabricated stack frame for the method itself
|
|
@@ -559,17 +590,22 @@ module Datadog
|
|
|
559
590
|
end
|
|
560
591
|
|
|
561
592
|
rate_limiter = probe.rate_limiter
|
|
562
|
-
admitted = continue
|
|
593
|
+
admitted = continue
|
|
594
|
+
if continue && rate_limiter && !rate_limiter.allow?
|
|
595
|
+
admitted = false
|
|
596
|
+
record_rate_limit_skip(GuardrailsTelemetry::Reason::RATE_LIMIT_PROBE, probe)
|
|
597
|
+
end
|
|
563
598
|
if admitted && !probe_global_rate_limiter(probe).allow?
|
|
564
599
|
admitted = false
|
|
565
|
-
|
|
600
|
+
record_rate_limit_skip(GuardrailsTelemetry::Reason::RATE_LIMIT_GLOBAL, probe)
|
|
566
601
|
end
|
|
567
602
|
if admitted
|
|
568
603
|
# Arguments may be mutated by the method, therefore
|
|
569
604
|
# they need to be serialized prior to method invocation.
|
|
570
605
|
serialized_entry_args = if probe.capture_snapshot?
|
|
571
606
|
serializer.serialize_args(args, kwargs, target_self,
|
|
572
|
-
|
|
607
|
+
deadline: serializer.serialization_deadline,
|
|
608
|
+
**probe.snapshot_serializer_limits(settings),)
|
|
573
609
|
end
|
|
574
610
|
|
|
575
611
|
entry_capture_expressions = nil
|
|
@@ -581,8 +617,9 @@ module Datadog
|
|
|
581
617
|
target_self: target_self,
|
|
582
618
|
locals: serializer.combine_args(args, kwargs, target_self),
|
|
583
619
|
)
|
|
584
|
-
|
|
585
|
-
|
|
620
|
+
entry_capture_result = capture_expression_evaluator.evaluate(probe, entry_context)
|
|
621
|
+
entry_capture_expressions = entry_capture_result.serialized_values
|
|
622
|
+
entry_capture_evaluation_errors = entry_capture_result.errors
|
|
586
623
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
587
624
|
Datadog::DI.reraise_if_fatal(exc)
|
|
588
625
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
@@ -751,6 +788,13 @@ module Datadog
|
|
|
751
788
|
public :kwargs_from_splat
|
|
752
789
|
end
|
|
753
790
|
|
|
791
|
+
# Trace point callback for a line probe.
|
|
792
|
+
#
|
|
793
|
+
# @param probe [Probe] the probe installed on this line
|
|
794
|
+
# @param iseq [RubyVM::InstructionSequence, nil] instruction sequence the trace point is targeted at, or nil when untargeted
|
|
795
|
+
# @param responder [#probe_executed_callback, #probe_condition_evaluation_failed_callback] callback target invoked with the built Context
|
|
796
|
+
# @param tp [TracePoint] the firing trace point
|
|
797
|
+
# @return [void]
|
|
754
798
|
def line_trace_point_callback(probe, iseq, responder, tp)
|
|
755
799
|
di_start_time = Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID)
|
|
756
800
|
|
|
@@ -827,10 +871,13 @@ module Datadog
|
|
|
827
871
|
|
|
828
872
|
# In practice we should always have a rate limiter, but be safe
|
|
829
873
|
# and check that it is in fact set.
|
|
830
|
-
|
|
874
|
+
if probe.rate_limiter && !probe.rate_limiter.allow?
|
|
875
|
+
record_rate_limit_skip(GuardrailsTelemetry::Reason::RATE_LIMIT_PROBE, probe)
|
|
876
|
+
return
|
|
877
|
+
end
|
|
831
878
|
|
|
832
879
|
unless probe_global_rate_limiter(probe).allow?
|
|
833
|
-
|
|
880
|
+
record_rate_limit_skip(GuardrailsTelemetry::Reason::RATE_LIMIT_GLOBAL, probe)
|
|
834
881
|
return
|
|
835
882
|
end
|
|
836
883
|
|
|
@@ -843,12 +890,14 @@ module Datadog
|
|
|
843
890
|
responder.probe_executed_callback(context)
|
|
844
891
|
|
|
845
892
|
check_and_disable_if_exceeded(probe, responder, di_start_time)
|
|
893
|
+
nil
|
|
846
894
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
847
895
|
Datadog::DI.reraise_if_fatal(exc)
|
|
848
896
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
849
897
|
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc.message}" }
|
|
850
898
|
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
|
851
899
|
# TODO test this path
|
|
900
|
+
nil
|
|
852
901
|
end
|
|
853
902
|
|
|
854
903
|
def build_trace_point_context(probe, tp)
|
|
@@ -881,6 +930,7 @@ module Datadog
|
|
|
881
930
|
# avoid a dependency on ProbeManager from Instrumenter.
|
|
882
931
|
probe.disable!
|
|
883
932
|
responder.probe_disabled_callback(probe, di_duration)
|
|
933
|
+
nil
|
|
884
934
|
end
|
|
885
935
|
end
|
|
886
936
|
|
data/lib/datadog/di/logger.rb
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "forwardable"
|
|
4
|
-
|
|
5
3
|
module Datadog
|
|
6
4
|
module DI
|
|
7
5
|
# Logger facade to add the +trace+ method.
|
|
8
6
|
#
|
|
9
7
|
# @api private
|
|
10
8
|
class Logger
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
# Initializes the logger facade with the trace-logging gate resolved
|
|
10
|
+
# once.
|
|
11
|
+
# Resolving the gate at construction keeps the per-call settings
|
|
12
|
+
# traversal off the probe hot path. Components are rebuilt on
|
|
13
|
+
# reconfiguration, so the cached value tracks configuration
|
|
14
|
+
# changes.
|
|
15
|
+
#
|
|
16
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings the gate is resolved from
|
|
17
|
+
# @param target [::Logger] logger the facade delegates to
|
|
13
18
|
def initialize(settings, target)
|
|
14
|
-
@
|
|
19
|
+
@trace_logging = settings.dynamic_instrumentation.internal.trace_logging
|
|
15
20
|
@target = target
|
|
16
21
|
end
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
# Whether trace logging is enabled.
|
|
24
|
+
# @return [Boolean]
|
|
25
|
+
attr_reader :trace_logging
|
|
26
|
+
# The target logger.
|
|
27
|
+
# @return [::Logger]
|
|
19
28
|
attr_reader :target
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
# Writes the given message or the block's message to the target
|
|
31
|
+
# logger.
|
|
32
|
+
#
|
|
33
|
+
# @param message [String, nil] message written when no block is given
|
|
34
|
+
# @return [void]
|
|
35
|
+
def debug(message = nil, &block)
|
|
36
|
+
target.debug(message, &block)
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
22
39
|
|
|
40
|
+
# Writes the block's message to the target logger at debug level when
|
|
41
|
+
# the trace-logging gate resolved at construction is enabled.
|
|
42
|
+
#
|
|
43
|
+
# @return [void]
|
|
23
44
|
def trace
|
|
24
|
-
if
|
|
45
|
+
if trace_logging
|
|
25
46
|
debug { yield }
|
|
26
47
|
end
|
|
48
|
+
nil
|
|
27
49
|
end
|
|
28
50
|
end
|
|
29
51
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# rubocop:disable Lint/AssignmentInCondition
|
|
4
4
|
|
|
5
5
|
require_relative "fatal_exceptions"
|
|
6
|
+
require_relative "guardrails_telemetry"
|
|
6
7
|
|
|
7
8
|
module Datadog
|
|
8
9
|
module DI
|
|
@@ -18,14 +19,26 @@ module Datadog
|
|
|
18
19
|
#
|
|
19
20
|
# @api private
|
|
20
21
|
class ProbeManager
|
|
22
|
+
# Initializes the probe manager.
|
|
23
|
+
#
|
|
24
|
+
# @param settings [Datadog::Core::Configuration::Settings] tracer settings
|
|
25
|
+
# @param instrumenter [Instrumenter] instrumenter that executes installed probes
|
|
26
|
+
# @param probe_notification_builder [ProbeNotificationBuilder] builder for status and snapshot payloads
|
|
27
|
+
# @param probe_notifier_worker [ProbeNotifierWorker] worker that sends the built payloads
|
|
28
|
+
# @param logger [DI::Logger] logger for probe manager diagnostics
|
|
29
|
+
# @param probe_repository [ProbeRepository] repository of installed probes
|
|
30
|
+
# @param guardrails_telemetry [GuardrailsTelemetry] emitter for the canonical condition-error skip metric
|
|
31
|
+
# @param telemetry [Datadog::Core::Telemetry::Component, nil] component probe manager errors are reported through
|
|
32
|
+
# @return [void]
|
|
21
33
|
def initialize(settings, instrumenter, probe_notification_builder,
|
|
22
|
-
probe_notifier_worker, logger, probe_repository, telemetry: nil)
|
|
34
|
+
probe_notifier_worker, logger, probe_repository, guardrails_telemetry:, telemetry: nil)
|
|
23
35
|
@settings = settings
|
|
24
36
|
@instrumenter = instrumenter
|
|
25
37
|
@probe_notification_builder = probe_notification_builder
|
|
26
38
|
@probe_notifier_worker = probe_notifier_worker
|
|
27
39
|
@logger = logger
|
|
28
40
|
@telemetry = telemetry
|
|
41
|
+
@guardrails_telemetry = guardrails_telemetry
|
|
29
42
|
@probe_repository = probe_repository
|
|
30
43
|
|
|
31
44
|
@definition_trace_point = TracePoint.new(:end) do |tp|
|
|
@@ -40,6 +53,9 @@ module Datadog
|
|
|
40
53
|
|
|
41
54
|
attr_reader :logger
|
|
42
55
|
attr_reader :telemetry
|
|
56
|
+
# The guardrails metric emitter.
|
|
57
|
+
# @return [GuardrailsTelemetry]
|
|
58
|
+
attr_reader :guardrails_telemetry
|
|
43
59
|
attr_reader :probe_repository
|
|
44
60
|
|
|
45
61
|
# Stops the probe manager without permanently releasing resources.
|
|
@@ -303,7 +319,7 @@ module Datadog
|
|
|
303
319
|
end
|
|
304
320
|
|
|
305
321
|
payload = probe_notification_builder.build_executed(context)
|
|
306
|
-
probe_notifier_worker.add_snapshot(payload)
|
|
322
|
+
probe_notifier_worker.add_snapshot(payload, probe: probe)
|
|
307
323
|
end
|
|
308
324
|
|
|
309
325
|
# Callback invoked when a probe's condition expression fails to evaluate.
|
|
@@ -317,16 +333,29 @@ module Datadog
|
|
|
317
333
|
# @param context [Context] The execution context containing probe and captured data
|
|
318
334
|
# @param expr [EL::Expression] The condition expression that failed
|
|
319
335
|
# @param exc [Exception] The exception raised during condition evaluation
|
|
336
|
+
# @return [void]
|
|
320
337
|
def probe_condition_evaluation_failed_callback(context, expr, exc)
|
|
321
338
|
probe = context.probe
|
|
322
|
-
|
|
339
|
+
rate_limiter = probe.condition_evaluation_failed_rate_limiter
|
|
340
|
+
if rate_limiter&.allow?
|
|
323
341
|
if instrumenter.global_snapshot_rate_limiter.allow?
|
|
324
342
|
payload = probe_notification_builder.build_condition_evaluation_failed(context, expr, exc)
|
|
325
|
-
probe_notifier_worker.add_snapshot(payload)
|
|
343
|
+
probe_notifier_worker.add_snapshot(payload, probe: probe)
|
|
326
344
|
else
|
|
327
|
-
logger.trace
|
|
345
|
+
logger.trace do
|
|
346
|
+
"di: #{probe.type} probe #{probe.id}: skipping condition error snapshot due to global rate limit" \
|
|
347
|
+
" (#{GuardrailsTelemetry::Reason::RATE_LIMIT_GLOBAL})"
|
|
348
|
+
end
|
|
349
|
+
record_condition_error_skip(GuardrailsTelemetry::Reason::RATE_LIMIT_GLOBAL, probe)
|
|
350
|
+
end
|
|
351
|
+
elsif rate_limiter
|
|
352
|
+
logger.trace do
|
|
353
|
+
"di: #{probe.type} probe #{probe.id}: skipping condition evaluation failure" \
|
|
354
|
+
" notification due to per-probe rate limit" \
|
|
355
|
+
" (#{GuardrailsTelemetry::Reason::EVALUATION_ERROR_THROTTLED})"
|
|
328
356
|
end
|
|
329
357
|
end
|
|
358
|
+
nil
|
|
330
359
|
end
|
|
331
360
|
|
|
332
361
|
# Callback invoked when a probe is disabled, for example due to
|
|
@@ -341,6 +370,20 @@ module Datadog
|
|
|
341
370
|
probe_notifier_worker.add_status(payload, probe: probe)
|
|
342
371
|
end
|
|
343
372
|
|
|
373
|
+
# Emits the canonical skip metric for a condition evaluation failure
|
|
374
|
+
# notification rejected by a rate limiter.
|
|
375
|
+
#
|
|
376
|
+
# @param probe [Probe] the probe whose condition evaluation failed
|
|
377
|
+
# @param reason [String] the GuardrailsTelemetry::Reason constant for the rejecting limit
|
|
378
|
+
# @return [void]
|
|
379
|
+
def record_condition_error_skip(reason, probe)
|
|
380
|
+
guardrails_telemetry.skipped(
|
|
381
|
+
reason: reason,
|
|
382
|
+
probe: probe,
|
|
383
|
+
)
|
|
384
|
+
nil
|
|
385
|
+
end
|
|
386
|
+
|
|
344
387
|
# Class/module definition trace point (:end type).
|
|
345
388
|
# Used to install hooks when the target classes/modules aren't yet
|
|
346
389
|
# defined when the hook request is received.
|