datadog 2.7.0 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|