datadog 2.7.0 → 2.8.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 +43 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +47 -17
- data/ext/datadog_profiling_native_extension/extconf.rb +3 -8
- data/ext/datadog_profiling_native_extension/heap_recorder.c +11 -89
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +3 -9
- data/ext/datadog_profiling_native_extension/profiling.c +6 -0
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -4
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +0 -34
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/component.rb +1 -8
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +73 -0
- data/lib/datadog/appsec/contrib/active_record/integration.rb +41 -0
- data/lib/datadog/appsec/contrib/active_record/patcher.rb +53 -0
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/appsec/processor/context.rb +2 -2
- data/lib/datadog/appsec/remote.rb +1 -3
- data/lib/datadog/appsec/response.rb +7 -11
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/components.rb +17 -1
- data/lib/datadog/core/configuration/settings.rb +10 -0
- data/lib/datadog/core/configuration.rb +9 -1
- data/lib/datadog/core/remote/client/capabilities.rb +6 -0
- data/lib/datadog/core/remote/client.rb +65 -59
- data/lib/datadog/core/telemetry/component.rb +9 -3
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/di/code_tracker.rb +5 -4
- data/lib/datadog/di/component.rb +5 -1
- data/lib/datadog/di/contrib/active_record.rb +1 -0
- data/lib/datadog/di/init.rb +20 -0
- data/lib/datadog/di/instrumenter.rb +81 -11
- data/lib/datadog/di/probe.rb +11 -1
- data/lib/datadog/di/probe_builder.rb +1 -0
- data/lib/datadog/di/probe_manager.rb +4 -1
- data/lib/datadog/di/probe_notification_builder.rb +13 -7
- data/lib/datadog/di/remote.rb +124 -0
- data/lib/datadog/di/serializer.rb +14 -7
- data/lib/datadog/di/transport.rb +2 -1
- data/lib/datadog/di/utils.rb +7 -0
- data/lib/datadog/di.rb +84 -20
- data/lib/datadog/profiling/component.rb +4 -16
- data/lib/datadog/tracing/configuration/settings.rb +4 -8
- data/lib/datadog/tracing/contrib/active_support/cache/redis.rb +16 -4
- data/lib/datadog/tracing/contrib/elasticsearch/configuration/settings.rb +4 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/tracer.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- data/lib/datadog.rb +3 -0
- metadata +17 -13
- data/lib/datadog/appsec/processor/actions.rb +0 -49
data/lib/datadog/di/component.rb
CHANGED
@@ -24,7 +24,9 @@ module Datadog
|
|
24
24
|
|
25
25
|
return unless environment_supported?(settings)
|
26
26
|
|
27
|
-
new(settings, agent_settings, Datadog.logger, code_tracker: DI.code_tracker, telemetry: telemetry)
|
27
|
+
new(settings, agent_settings, Datadog.logger, code_tracker: DI.code_tracker, telemetry: telemetry).tap do |component|
|
28
|
+
DI.add_current_component(component)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
|
30
32
|
def build!(settings, agent_settings, telemetry: nil)
|
@@ -99,6 +101,8 @@ module Datadog
|
|
99
101
|
# was replaced by a new instance, the new instance of it wouldn't have
|
100
102
|
# any of the already loaded code tracked.
|
101
103
|
def shutdown!(replacement = nil)
|
104
|
+
DI.remove_current_component(self)
|
105
|
+
|
102
106
|
probe_manager.clear_hooks
|
103
107
|
probe_manager.close
|
104
108
|
probe_notifier_worker.stop
|
@@ -5,6 +5,7 @@ Datadog::DI::Serializer.register(condition: lambda { |value| ActiveRecord::Base
|
|
5
5
|
# steep:ignore:start
|
6
6
|
value_to_serialize = {
|
7
7
|
attributes: value.attributes,
|
8
|
+
new_record: value.new_record?,
|
8
9
|
}
|
9
10
|
serializer.serialize_value(value_to_serialize, depth: depth ? depth - 1 : nil, type: value.class)
|
10
11
|
# steep:ignore:end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Require 'datadog/di/init' early in the application boot process to
|
4
|
+
# enable dynamic instrumentation for third-party libraries used by the
|
5
|
+
# application.
|
6
|
+
|
7
|
+
require_relative '../tracing'
|
8
|
+
require_relative '../tracing/contrib'
|
9
|
+
require_relative '../di'
|
10
|
+
|
11
|
+
# Code tracking is required for line probes to work; see the comments
|
12
|
+
# on the activate_tracking methods in di.rb for further details.
|
13
|
+
#
|
14
|
+
# Unlike di.rb which conditionally activates tracking only if the
|
15
|
+
# DD_DYNAMIC_INSTRUMENTATION_ENABLED environment variable is set, this file
|
16
|
+
# always activates tracking. This is because this file is explicitly loaded
|
17
|
+
# by customer applications for the purpose of enabling code tracking
|
18
|
+
# early in application boot process (i.e., before datadog library itself
|
19
|
+
# is loaded).
|
20
|
+
Datadog::DI.activate_tracking
|
@@ -92,34 +92,89 @@ module Datadog
|
|
92
92
|
cls = symbolize_class_name(probe.type_name)
|
93
93
|
serializer = self.serializer
|
94
94
|
method_name = probe.method_name
|
95
|
-
|
96
|
-
|
95
|
+
loc = begin
|
96
|
+
cls.instance_method(method_name).source_location
|
97
|
+
rescue NameError
|
98
|
+
# The target method is not defined.
|
99
|
+
# This could be because it will be explicitly defined later
|
100
|
+
# (since classes can be reopened in Ruby)
|
101
|
+
# or the method is virtual (provided by a method_missing handler).
|
102
|
+
# In these cases we do not have a source location for the
|
103
|
+
# target method here.
|
104
|
+
end
|
97
105
|
rate_limiter = probe.rate_limiter
|
106
|
+
settings = self.settings
|
98
107
|
|
99
108
|
mod = Module.new do
|
100
|
-
define_method(method_name) do |*args, **kwargs| # steep:ignore
|
109
|
+
define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore
|
101
110
|
if rate_limiter.nil? || rate_limiter.allow?
|
102
111
|
# Arguments may be mutated by the method, therefore
|
103
112
|
# they need to be serialized prior to method invocation.
|
104
113
|
entry_args = if probe.capture_snapshot?
|
105
|
-
serializer.serialize_args(args, kwargs
|
114
|
+
serializer.serialize_args(args, kwargs,
|
115
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
116
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
106
117
|
end
|
107
118
|
rv = nil
|
119
|
+
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
|
120
|
+
# for methods defined via method_missing.
|
108
121
|
duration = Benchmark.realtime do # steep:ignore
|
109
|
-
rv =
|
122
|
+
rv = if args.any?
|
123
|
+
if kwargs.any?
|
124
|
+
super(*args, **kwargs, &target_block)
|
125
|
+
else
|
126
|
+
super(*args, &target_block)
|
127
|
+
end
|
128
|
+
elsif kwargs.any?
|
129
|
+
super(**kwargs, &target_block)
|
130
|
+
else
|
131
|
+
super(&target_block)
|
132
|
+
end
|
110
133
|
end
|
111
134
|
# The method itself is not part of the stack trace because
|
112
135
|
# we are getting the stack trace from outside of the method.
|
113
136
|
# Add the method in manually as the top frame.
|
114
|
-
method_frame =
|
115
|
-
|
137
|
+
method_frame = if loc
|
138
|
+
[Location.new(loc.first, loc.last, method_name)]
|
139
|
+
else
|
140
|
+
# For virtual and lazily-defined methods, we do not have
|
141
|
+
# the original source location here, and they won't be
|
142
|
+
# included in the stack trace currently.
|
143
|
+
# TODO when begin/end trace points are added for local
|
144
|
+
# variable capture in method probes, we should be able
|
145
|
+
# to obtain actual method execution location and use
|
146
|
+
# that location here.
|
147
|
+
[]
|
148
|
+
end
|
149
|
+
caller_locs = method_frame + caller_locations # steep:ignore
|
116
150
|
# TODO capture arguments at exit
|
117
151
|
# & is to stop steep complaints, block is always present here.
|
118
152
|
block&.call(probe: probe, rv: rv, duration: duration, caller_locations: caller_locs,
|
119
153
|
serialized_entry_args: entry_args)
|
120
154
|
rv
|
121
155
|
else
|
122
|
-
|
156
|
+
# stop standard from trying to mess up my code
|
157
|
+
_ = 42
|
158
|
+
|
159
|
+
# The necessity to invoke super in each of these specific
|
160
|
+
# ways is very difficult to test.
|
161
|
+
# Existing tests, even though I wrote many, still don't
|
162
|
+
# cause a failure if I replace all of the below with a
|
163
|
+
# simple super(*args, **kwargs, &target_block).
|
164
|
+
# But, let's be safe and go through the motions in case
|
165
|
+
# there is actually a legitimate need for the breakdown.
|
166
|
+
# TODO figure out how to test this properly.
|
167
|
+
if args.any?
|
168
|
+
if kwargs.any?
|
169
|
+
super(*args, **kwargs, &target_block)
|
170
|
+
else
|
171
|
+
super(*args, &target_block)
|
172
|
+
end
|
173
|
+
elsif kwargs.any?
|
174
|
+
super(**kwargs, &target_block)
|
175
|
+
else
|
176
|
+
super(&target_block)
|
177
|
+
end
|
123
178
|
end
|
124
179
|
end
|
125
180
|
end
|
@@ -222,12 +277,25 @@ module Datadog
|
|
222
277
|
# overhead of targeted trace points is minimal, don't worry about
|
223
278
|
# this optimization just yet and create a trace point for each probe.
|
224
279
|
|
225
|
-
|
280
|
+
types = if iseq
|
281
|
+
# When targeting trace points we can target the 'end' line of a method.
|
282
|
+
# However, by adding the :return trace point we lose diagnostics
|
283
|
+
# for lines that contain no executable code (e.g. comments only)
|
284
|
+
# and thus cannot actually be instrumented.
|
285
|
+
[:line, :return, :b_return]
|
286
|
+
else
|
287
|
+
[:line]
|
288
|
+
end
|
289
|
+
tp = TracePoint.new(*types) do |tp|
|
226
290
|
begin
|
227
291
|
# If trace point is not targeted, we must verify that the invocation
|
228
292
|
# is the file & line that we want, because untargeted trace points
|
229
293
|
# are invoked for *each* line of Ruby executed.
|
230
|
-
|
294
|
+
# TODO find out exactly when the path in trace point is relative.
|
295
|
+
# Looks like this is the case when line trace point is not targeted?
|
296
|
+
if iseq || tp.lineno == probe.line_no && (
|
297
|
+
probe.file == tp.path || probe.file_matches?(tp.path)
|
298
|
+
)
|
231
299
|
if rate_limiter.nil? || rate_limiter.allow?
|
232
300
|
# & is to stop steep complaints, block is always present here.
|
233
301
|
block&.call(probe: probe, trace_point: tp, caller_locations: caller_locations)
|
@@ -240,7 +308,7 @@ module Datadog
|
|
240
308
|
# TODO test this path
|
241
309
|
end
|
242
310
|
rescue => exc
|
243
|
-
raise if settings.dynamic_instrumentation.propagate_all_exceptions
|
311
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
244
312
|
logger.warn("Unhandled exception in line trace point: #{exc.class}: #{exc}")
|
245
313
|
telemetry&.report(exc, description: "Unhandled exception in line trace point")
|
246
314
|
# TODO test this path
|
@@ -266,7 +334,9 @@ module Datadog
|
|
266
334
|
else
|
267
335
|
tp.enable
|
268
336
|
end
|
337
|
+
# TracePoint#enable returns false when it succeeds.
|
269
338
|
end
|
339
|
+
true
|
270
340
|
end
|
271
341
|
|
272
342
|
def unhook_line(probe)
|
data/lib/datadog/di/probe.rb
CHANGED
@@ -36,7 +36,9 @@ module Datadog
|
|
36
36
|
|
37
37
|
def initialize(id:, type:,
|
38
38
|
file: nil, line_no: nil, type_name: nil, method_name: nil,
|
39
|
-
template: nil, capture_snapshot: false, max_capture_depth: nil,
|
39
|
+
template: nil, capture_snapshot: false, max_capture_depth: nil,
|
40
|
+
max_capture_attribute_count: nil,
|
41
|
+
rate_limit: nil)
|
40
42
|
# Perform some sanity checks here to detect unexpected attribute
|
41
43
|
# combinations, in order to not do them in subsequent code.
|
42
44
|
unless KNOWN_TYPES.include?(type)
|
@@ -64,6 +66,7 @@ module Datadog
|
|
64
66
|
@template = template
|
65
67
|
@capture_snapshot = !!capture_snapshot
|
66
68
|
@max_capture_depth = max_capture_depth
|
69
|
+
@max_capture_attribute_count = max_capture_attribute_count
|
67
70
|
|
68
71
|
# These checks use instance methods that have more complex logic
|
69
72
|
# than checking a single argument value. To avoid duplicating
|
@@ -91,6 +94,10 @@ module Datadog
|
|
91
94
|
# the global default will be used.
|
92
95
|
attr_reader :max_capture_depth
|
93
96
|
|
97
|
+
# Configured maximum capture attribute count. Can be nil in which case
|
98
|
+
# the global default will be used.
|
99
|
+
attr_reader :max_capture_attribute_count
|
100
|
+
|
94
101
|
# Rate limit in effect, in invocations per second. Always present.
|
95
102
|
attr_reader :rate_limit
|
96
103
|
|
@@ -154,6 +161,9 @@ module Datadog
|
|
154
161
|
# If file is not an absolute path, the path matches if the file is its suffix,
|
155
162
|
# at a path component boundary.
|
156
163
|
def file_matches?(path)
|
164
|
+
if path.nil?
|
165
|
+
raise ArgumentError, "Cannot match against a nil path"
|
166
|
+
end
|
157
167
|
unless file
|
158
168
|
raise ArgumentError, "Probe does not have a file to match against"
|
159
169
|
end
|
@@ -37,6 +37,7 @@ module Datadog
|
|
37
37
|
template: config["template"],
|
38
38
|
capture_snapshot: !!config["captureSnapshot"],
|
39
39
|
max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
|
40
|
+
max_capture_attribute_count: config["capture"]&.[]("maxFieldCount"),
|
40
41
|
rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
|
41
42
|
)
|
42
43
|
rescue KeyError => exc
|
@@ -161,7 +161,7 @@ module Datadog
|
|
161
161
|
# Silence all exceptions?
|
162
162
|
# TODO should we propagate here and rescue upstream?
|
163
163
|
logger.warn("Error removing probe #{probe.id}: #{exc.class}: #{exc}")
|
164
|
-
telemetry&.report(exc, description: "Error removing probe
|
164
|
+
telemetry&.report(exc, description: "Error removing probe")
|
165
165
|
end
|
166
166
|
end
|
167
167
|
end
|
@@ -206,6 +206,9 @@ module Datadog
|
|
206
206
|
# point, which is invoked for each required or loaded file
|
207
207
|
# (and also for eval'd code, but those invocations are filtered out).
|
208
208
|
def install_pending_line_probes(path)
|
209
|
+
if path.nil?
|
210
|
+
raise ArgumentError, "path must not be nil"
|
211
|
+
end
|
209
212
|
@lock.synchronize do
|
210
213
|
@pending_probes.values.each do |probe|
|
211
214
|
if probe.line?
|
@@ -65,14 +65,18 @@ module Datadog
|
|
65
65
|
arguments: if serialized_entry_args
|
66
66
|
serialized_entry_args
|
67
67
|
else
|
68
|
-
(args || kwargs) && serializer.serialize_args(args, kwargs
|
68
|
+
(args || kwargs) && serializer.serialize_args(args, kwargs,
|
69
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
70
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
69
71
|
end,
|
70
72
|
throwable: nil,
|
71
73
|
# standard:enable all
|
72
74
|
},
|
73
75
|
return: {
|
74
76
|
arguments: {
|
75
|
-
"@return": serializer.serialize_value(rv
|
77
|
+
"@return": serializer.serialize_value(rv,
|
78
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
79
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
|
76
80
|
},
|
77
81
|
throwable: nil,
|
78
82
|
},
|
@@ -80,7 +84,9 @@ module Datadog
|
|
80
84
|
elsif probe.line?
|
81
85
|
{
|
82
86
|
lines: snapshot && {
|
83
|
-
probe.line_no => {locals: serializer.serialize_vars(snapshot
|
87
|
+
probe.line_no => {locals: serializer.serialize_vars(snapshot,
|
88
|
+
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
89
|
+
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)},
|
84
90
|
},
|
85
91
|
}
|
86
92
|
end
|
@@ -121,7 +127,7 @@ module Datadog
|
|
121
127
|
},
|
122
128
|
# In python tracer duration is under debugger.snapshot,
|
123
129
|
# but UI appears to expect it here at top level.
|
124
|
-
duration: duration ? (duration * 10**9).to_i :
|
130
|
+
duration: duration ? (duration * 10**9).to_i : 0,
|
125
131
|
host: nil,
|
126
132
|
logger: {
|
127
133
|
name: probe.file,
|
@@ -135,11 +141,11 @@ module Datadog
|
|
135
141
|
version: 2,
|
136
142
|
},
|
137
143
|
# TODO add tests that the trace/span id is correctly propagated
|
138
|
-
"dd.trace_id": Datadog::Tracing.active_trace&.id,
|
139
|
-
"dd.span_id": Datadog::Tracing.active_span&.id,
|
144
|
+
"dd.trace_id": Datadog::Tracing.active_trace&.id&.to_s,
|
145
|
+
"dd.span_id": Datadog::Tracing.active_span&.id&.to_s,
|
140
146
|
ddsource: 'dd_debugger',
|
141
147
|
message: probe.template && evaluate_template(probe.template,
|
142
|
-
duration: duration ? duration * 1000 :
|
148
|
+
duration: duration ? duration * 1000 : 0),
|
143
149
|
timestamp: timestamp,
|
144
150
|
}
|
145
151
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
# Provides an interface expected by the core Remote subsystem to
|
6
|
+
# receive DI-specific remote configuration.
|
7
|
+
#
|
8
|
+
# In order to apply (i.e., act on) the configuration, we need the
|
9
|
+
# state stored under DI Component. Thus, this module forwards actual
|
10
|
+
# configuration application to the ProbeManager associated with the
|
11
|
+
# global DI Component.
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
module Remote
|
15
|
+
class ReadError < StandardError; end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
PRODUCT = 'LIVE_DEBUGGING'
|
19
|
+
|
20
|
+
def products
|
21
|
+
[PRODUCT]
|
22
|
+
end
|
23
|
+
|
24
|
+
def capabilities
|
25
|
+
[]
|
26
|
+
end
|
27
|
+
|
28
|
+
def receivers(telemetry)
|
29
|
+
receiver do |repository, _changes|
|
30
|
+
# DEV: Filter our by product. Given it will be very common
|
31
|
+
# DEV: we can filter this out before we receive the data in this method.
|
32
|
+
# DEV: Apply this refactor to AppSec as well if implemented.
|
33
|
+
|
34
|
+
component = DI.component
|
35
|
+
# We should always have a non-nil DI component here, because we
|
36
|
+
# only add DI product to remote config request if DI is enabled.
|
37
|
+
# Ideally, we should be injected with the DI component here
|
38
|
+
# rather than having to retrieve it from global state.
|
39
|
+
# If the component is nil for some reason, we also don't have a
|
40
|
+
# logger instance to report the issue.
|
41
|
+
if component
|
42
|
+
|
43
|
+
probe_manager = component.probe_manager
|
44
|
+
|
45
|
+
current_probe_ids = {}
|
46
|
+
repository.contents.each do |content|
|
47
|
+
case content.path.product
|
48
|
+
when PRODUCT
|
49
|
+
begin
|
50
|
+
probe_spec = parse_content(content)
|
51
|
+
probe = ProbeBuilder.build_from_remote_config(probe_spec)
|
52
|
+
payload = component.probe_notification_builder.build_received(probe)
|
53
|
+
component.probe_notifier_worker.add_status(payload)
|
54
|
+
component.logger.info("Received probe from RC: #{probe.type} #{probe.location}")
|
55
|
+
|
56
|
+
begin
|
57
|
+
# TODO test exception capture
|
58
|
+
probe_manager.add_probe(probe)
|
59
|
+
content.applied
|
60
|
+
rescue => exc
|
61
|
+
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
62
|
+
|
63
|
+
component.logger.warn("Unhandled exception adding probe in DI remote receiver: #{exc.class}: #{exc}")
|
64
|
+
component.telemetry&.report(exc, description: "Unhandled exception adding probe in DI remote receiver")
|
65
|
+
|
66
|
+
# If a probe fails to install, we will mark the content
|
67
|
+
# as errored. On subsequent remote configuration application
|
68
|
+
# attemps, probe manager will raise the "previously errored"
|
69
|
+
# exception and we'll rescue it here, again marking the
|
70
|
+
# content as errored but with a somewhat different exception
|
71
|
+
# message.
|
72
|
+
# TODO stack trace must be redacted or not sent at all
|
73
|
+
content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}: #{Array(exc.backtrace).join("\n")}")
|
74
|
+
end
|
75
|
+
|
76
|
+
# Important: even if processing fails for this probe config,
|
77
|
+
# we need to note it as being current so that we do not
|
78
|
+
# try to remove instrumentation that is still supposed to be
|
79
|
+
# active.
|
80
|
+
current_probe_ids[probe_spec.fetch('id')] = true
|
81
|
+
rescue => exc
|
82
|
+
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
83
|
+
|
84
|
+
component.logger.warn("Unhandled exception handling probe in DI remote receiver: #{exc.class}: #{exc}")
|
85
|
+
component.telemetry&.report(exc, description: "Unhandled exception handling probe in DI remote receiver")
|
86
|
+
|
87
|
+
content.errored("Error applying dynamic instrumentation configuration: #{exc.class.name} #{exc.message}: #{Array(exc.backtrace).join("\n")}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
# TODO test exception capture
|
94
|
+
probe_manager.remove_other_probes(current_probe_ids.keys)
|
95
|
+
rescue => exc
|
96
|
+
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
97
|
+
|
98
|
+
component.logger.warn("Unhandled exception removing probes in DI remote receiver: #{exc.class}: #{exc}")
|
99
|
+
component.telemetry&.report(exc, description: "Unhandled exception removing probes in DI remote receiver")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def receiver(products = [PRODUCT], &block)
|
106
|
+
matcher = Core::Remote::Dispatcher::Matcher::Product.new(products)
|
107
|
+
[Core::Remote::Dispatcher::Receiver.new(matcher, &block)]
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def parse_content(content)
|
113
|
+
data = content.data.read
|
114
|
+
|
115
|
+
content.data.rewind
|
116
|
+
|
117
|
+
raise ReadError, 'EOF reached' if data.nil?
|
118
|
+
|
119
|
+
JSON.parse(data)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -82,7 +82,9 @@ module Datadog
|
|
82
82
|
# between positional and keyword arguments. We convert positional
|
83
83
|
# arguments to keyword arguments ("arg1", "arg2", ...) and ensure
|
84
84
|
# the positional arguments are listed first.
|
85
|
-
def serialize_args(args, kwargs
|
85
|
+
def serialize_args(args, kwargs,
|
86
|
+
depth: settings.dynamic_instrumentation.max_capture_depth,
|
87
|
+
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
|
86
88
|
counter = 0
|
87
89
|
combined = args.each_with_object({}) do |value, c|
|
88
90
|
counter += 1
|
@@ -90,16 +92,18 @@ module Datadog
|
|
90
92
|
# kwargs when they are merged below.
|
91
93
|
c[:"arg#{counter}"] = value
|
92
94
|
end.update(kwargs)
|
93
|
-
serialize_vars(combined)
|
95
|
+
serialize_vars(combined, depth: depth, attribute_count: attribute_count)
|
94
96
|
end
|
95
97
|
|
96
98
|
# Serializes variables captured by a line probe.
|
97
99
|
#
|
98
100
|
# These are normally local variables that exist on a particular line
|
99
101
|
# of executed code.
|
100
|
-
def serialize_vars(vars
|
102
|
+
def serialize_vars(vars,
|
103
|
+
depth: settings.dynamic_instrumentation.max_capture_depth,
|
104
|
+
attribute_count: settings.dynamic_instrumentation.max_capture_attribute_count)
|
101
105
|
vars.each_with_object({}) do |(k, v), agg|
|
102
|
-
agg[k] = serialize_value(v, name: k)
|
106
|
+
agg[k] = serialize_value(v, name: k, depth: depth, attribute_count: attribute_count)
|
103
107
|
end
|
104
108
|
end
|
105
109
|
|
@@ -115,7 +119,11 @@ module Datadog
|
|
115
119
|
# (integers, strings, arrays, hashes).
|
116
120
|
#
|
117
121
|
# Respects string length, collection size and traversal depth limits.
|
118
|
-
def serialize_value(value, name: nil,
|
122
|
+
def serialize_value(value, name: nil,
|
123
|
+
depth: settings.dynamic_instrumentation.max_capture_depth,
|
124
|
+
attribute_count: nil,
|
125
|
+
type: nil)
|
126
|
+
attribute_count ||= settings.dynamic_instrumentation.max_capture_attribute_count
|
119
127
|
cls = type || value.class
|
120
128
|
begin
|
121
129
|
if redactor.redact_type?(value)
|
@@ -203,7 +211,6 @@ module Datadog
|
|
203
211
|
serialized.update(notCapturedReason: "depth")
|
204
212
|
else
|
205
213
|
fields = {}
|
206
|
-
max = settings.dynamic_instrumentation.max_capture_attribute_count
|
207
214
|
cur = 0
|
208
215
|
|
209
216
|
# MRI and JRuby 9.4.5+ preserve instance variable definition
|
@@ -229,7 +236,7 @@ module Datadog
|
|
229
236
|
ivars = value.instance_variables
|
230
237
|
|
231
238
|
ivars.each do |ivar|
|
232
|
-
if cur >=
|
239
|
+
if cur >= attribute_count
|
233
240
|
serialized.update(notCapturedReason: "fieldCount", fields: fields)
|
234
241
|
break
|
235
242
|
end
|
data/lib/datadog/di/transport.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ostruct'
|
3
4
|
require_relative 'error'
|
4
5
|
|
5
6
|
module Datadog
|
@@ -44,7 +45,7 @@ module Datadog
|
|
44
45
|
|
45
46
|
def send_input(payload)
|
46
47
|
send_request('Probe snapshot submission',
|
47
|
-
path: INPUT_PATH, body: payload.
|
48
|
+
path: INPUT_PATH, body: payload.to_json,
|
48
49
|
headers: {'content-type' => 'application/json'},)
|
49
50
|
end
|
50
51
|
|
data/lib/datadog/di/utils.rb
CHANGED
@@ -12,6 +12,13 @@ module Datadog
|
|
12
12
|
# If suffix is not an absolute path, the path matches if its suffix is
|
13
13
|
# the provided suffix, at a path component boundary.
|
14
14
|
module_function def path_matches_suffix?(path, suffix)
|
15
|
+
if path.nil?
|
16
|
+
raise ArgumentError, "nil path passed"
|
17
|
+
end
|
18
|
+
if suffix.nil?
|
19
|
+
raise ArgumentError, "nil suffix passed"
|
20
|
+
end
|
21
|
+
|
15
22
|
if suffix.start_with?('/')
|
16
23
|
path == suffix
|
17
24
|
else
|