datadog 2.21.0 → 2.22.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 +48 -1
- data/ext/LIBDATADOG_DEVELOPMENT.md +60 -0
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
- data/ext/libdatadog_api/ddsketch.c +106 -0
- data/ext/libdatadog_api/init.c +3 -0
- data/ext/libdatadog_api/library_config.c +35 -27
- data/ext/libdatadog_api/process_discovery.c +19 -13
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +59 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +29 -0
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +26 -0
- data/lib/datadog/appsec/api_security/endpoint_collection.rb +10 -0
- data/lib/datadog/appsec/assets/waf_rules/README.md +30 -36
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +359 -4
- data/lib/datadog/appsec/assets/waf_rules/strict.json +43 -2
- data/lib/datadog/appsec/compressed_json.rb +1 -1
- data/lib/datadog/appsec/configuration/settings.rb +9 -0
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +3 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +3 -2
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +3 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -4
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +5 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +7 -2
- data/lib/datadog/appsec/contrib/rails/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +3 -1
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +10 -4
- data/lib/datadog/appsec/event.rb +12 -14
- data/lib/datadog/appsec/metrics/collector.rb +19 -3
- data/lib/datadog/appsec/metrics/telemetry_exporter.rb +2 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +4 -4
- data/lib/datadog/appsec/remote.rb +25 -13
- data/lib/datadog/appsec/security_engine/result.rb +28 -9
- data/lib/datadog/appsec/security_engine/runner.rb +17 -7
- data/lib/datadog/appsec/security_event.rb +5 -7
- data/lib/datadog/core/configuration/components.rb +14 -6
- data/lib/datadog/core/configuration/stable_config.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/core/ddsketch.rb +21 -0
- data/lib/datadog/core/environment/yjit.rb +2 -1
- data/lib/datadog/core/pin.rb +4 -8
- data/lib/datadog/core/process_discovery.rb +4 -2
- data/lib/datadog/core/remote/component.rb +4 -6
- data/lib/datadog/core/telemetry/component.rb +11 -0
- data/lib/datadog/core/telemetry/emitter.rb +6 -6
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +30 -0
- data/lib/datadog/core/telemetry/event.rb +1 -0
- data/lib/datadog/core/transport/response.rb +4 -1
- data/lib/datadog/core/utils/network.rb +19 -0
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/component.rb +14 -0
- data/lib/datadog/di/context.rb +70 -0
- data/lib/datadog/di/el/compiler.rb +164 -0
- data/lib/datadog/di/el/evaluator.rb +159 -0
- data/lib/datadog/di/el/expression.rb +42 -0
- data/lib/datadog/di/el.rb +5 -0
- data/lib/datadog/di/error.rb +25 -0
- data/lib/datadog/di/instrumenter.rb +101 -32
- data/lib/datadog/di/probe.rb +35 -15
- data/lib/datadog/di/probe_builder.rb +39 -1
- data/lib/datadog/di/probe_manager.rb +3 -2
- data/lib/datadog/di/probe_notification_builder.rb +50 -51
- data/lib/datadog/di/serializer.rb +151 -7
- data/lib/datadog/tracing/component.rb +6 -17
- data/lib/datadog/tracing/configuration/dynamic.rb +2 -2
- data/lib/datadog/tracing/configuration/settings.rb +3 -3
- data/lib/datadog/tracing/contrib/component.rb +2 -2
- data/lib/datadog/tracing/contrib/graphql/configuration/settings.rb +7 -0
- data/lib/datadog/tracing/contrib/graphql/ext.rb +1 -0
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +53 -28
- data/lib/datadog/tracing/metadata/ext.rb +8 -0
- data/lib/datadog/version.rb +1 -1
- metadata +22 -9
- data/ext/libdatadog_api/macos_development.md +0 -26
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
module EL
|
6
|
+
# Evaluator for expression language.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Evaluator
|
10
|
+
def ref(var)
|
11
|
+
@context.fetch(var)
|
12
|
+
end
|
13
|
+
|
14
|
+
def iref(var)
|
15
|
+
@context.fetch_ivar(var)
|
16
|
+
end
|
17
|
+
|
18
|
+
def len(var, var_name)
|
19
|
+
case var
|
20
|
+
when Array, String
|
21
|
+
var.length
|
22
|
+
else
|
23
|
+
raise DI::Error::ExpressionEvaluationError, "Unsupported type for length: #{var.class}: #{var_name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_empty(var, var_name)
|
28
|
+
case var
|
29
|
+
when nil, Numeric
|
30
|
+
false
|
31
|
+
when Array, String
|
32
|
+
var.empty?
|
33
|
+
else
|
34
|
+
raise DI::Error::ExpressionEvaluationError, "Unsupported type for isEmpty: #{var.class}: #{var_name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_undefined(var, var_name)
|
39
|
+
var.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def contains(haystack, needle)
|
43
|
+
if String === haystack && String === needle or # standard:disable Style/AndOr
|
44
|
+
Array === haystack
|
45
|
+
haystack.include?(needle)
|
46
|
+
else
|
47
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid arguments for contains: #{haystack}, #{needle}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def matches(haystack, needle)
|
52
|
+
re = Regexp.compile(needle)
|
53
|
+
!!(haystack =~ re)
|
54
|
+
end
|
55
|
+
|
56
|
+
def getmember(object, field)
|
57
|
+
object.instance_variable_get("@#{field}")
|
58
|
+
end
|
59
|
+
|
60
|
+
def index(array_or_hash, index_or_key)
|
61
|
+
case array_or_hash
|
62
|
+
when Array
|
63
|
+
case index_or_key
|
64
|
+
when Integer
|
65
|
+
array_or_hash[index_or_key]
|
66
|
+
else
|
67
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid index value: #{index_or_key}"
|
68
|
+
end
|
69
|
+
when Hash
|
70
|
+
array_or_hash[index_or_key]
|
71
|
+
else
|
72
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid argument for index: #{array_or_hash}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def substring(object, from, to)
|
77
|
+
unless String === object
|
78
|
+
raise DI::Error::ExpressionEvaluationError, "Invalid type for substring: #{object}"
|
79
|
+
end
|
80
|
+
object[from...to]
|
81
|
+
end
|
82
|
+
|
83
|
+
def starts_with(haystack, needle)
|
84
|
+
# To guard against running arbitrary customer code, check that
|
85
|
+
# the haystack is a string. This does not help if customer
|
86
|
+
# overrode String#start_with? but at least it's better than nothing.
|
87
|
+
String === haystack && haystack.start_with?(needle)
|
88
|
+
end
|
89
|
+
|
90
|
+
def ends_with(haystack, needle)
|
91
|
+
String === haystack && haystack.end_with?(needle)
|
92
|
+
end
|
93
|
+
|
94
|
+
def all(collection, &block)
|
95
|
+
case collection
|
96
|
+
when Array
|
97
|
+
collection.all? do |item|
|
98
|
+
block.call(item)
|
99
|
+
end
|
100
|
+
when Hash
|
101
|
+
# For hashes, the expression language has both @it and
|
102
|
+
# @key/@value. Manufacture @it from the key and value.
|
103
|
+
collection.all? do |key, value|
|
104
|
+
block.call([key, value], key, value)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
raise DI::Error::ExpressionEvaluationError, "Bad collection type for all: #{collection.class}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def any(collection, &block)
|
112
|
+
case collection
|
113
|
+
when Array
|
114
|
+
collection.any? do |item|
|
115
|
+
block.call(item)
|
116
|
+
end
|
117
|
+
when Hash
|
118
|
+
collection.any? do |key, value|
|
119
|
+
# For hashes, the expression language has both @it and
|
120
|
+
# @key/@value. Manufacture @it from the key and value.
|
121
|
+
block.call([key, value], key, value)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
raise DI::Error::ExpressionEvaluationError, "Bad collection type for any: #{collection.class}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def filter(collection, &block)
|
129
|
+
case collection
|
130
|
+
when Array
|
131
|
+
collection.select do |item|
|
132
|
+
block.call(item)
|
133
|
+
end
|
134
|
+
when Hash
|
135
|
+
collection.select do |key, value|
|
136
|
+
block.call([key, value], key, value)
|
137
|
+
end.to_h
|
138
|
+
else
|
139
|
+
raise DI::Error::ExpressionEvaluationError, "Bad collection type for filter: #{collection.class}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def instanceof(object, cls_name)
|
144
|
+
cls = object.class
|
145
|
+
loop do
|
146
|
+
if cls.name == cls_name
|
147
|
+
return true
|
148
|
+
end
|
149
|
+
if supercls = cls.superclass # standard:disable Lint/AssignmentInCondition
|
150
|
+
cls = supercls
|
151
|
+
else
|
152
|
+
return false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
module EL
|
6
|
+
# Represents an Expression Language expression.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Expression
|
10
|
+
def initialize(dsl_expr, compiled_expr)
|
11
|
+
unless String === compiled_expr
|
12
|
+
raise ArgumentError, "compiled_expr must be a string"
|
13
|
+
end
|
14
|
+
|
15
|
+
@dsl_expr = dsl_expr
|
16
|
+
|
17
|
+
cls = Class.new(Evaluator)
|
18
|
+
cls.class_exec do
|
19
|
+
eval(<<-RUBY, Object.new.send(:binding), __FILE__, __LINE__ + 1) # standard:disable Security/Eval
|
20
|
+
def evaluate(context)
|
21
|
+
@context = context
|
22
|
+
#{compiled_expr}
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
end
|
26
|
+
@evaluator = cls.new
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :dsl_expr
|
30
|
+
attr_reader :evaluator
|
31
|
+
|
32
|
+
def evaluate(context)
|
33
|
+
@evaluator.evaluate(context)
|
34
|
+
end
|
35
|
+
|
36
|
+
def satisfied?(context)
|
37
|
+
!!evaluate(context)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/datadog/di/error.rb
CHANGED
@@ -48,6 +48,31 @@ module Datadog
|
|
48
48
|
# and the user will need to make their suffix more precise.
|
49
49
|
class MultiplePathsMatch < Error
|
50
50
|
end
|
51
|
+
|
52
|
+
# Base class for exceptions arising during expression language AST
|
53
|
+
# compilation into Ruby code.
|
54
|
+
#
|
55
|
+
# Expression language does not specify behavior in all cases,
|
56
|
+
# leaving some choices to the language implementation in the tracers.
|
57
|
+
# It is therefore possible that some technically valid expressions are
|
58
|
+
# prohibited by our implementation.
|
59
|
+
#
|
60
|
+
# It is also possible that the sanitizers/validators prohibit some
|
61
|
+
# esoteric constructs that are technically valid in Ruby,
|
62
|
+
# for example if instance variable name rules are relaxed to allow
|
63
|
+
# arbitrary characters in them as permitted in method names.
|
64
|
+
class InvalidExpression < Error
|
65
|
+
end
|
66
|
+
|
67
|
+
# Variable name with invalid characters in an expression language
|
68
|
+
# expression.
|
69
|
+
class BadVariableName < InvalidExpression
|
70
|
+
end
|
71
|
+
|
72
|
+
# Base class for exceptions arising when evaluating expression language
|
73
|
+
# expressions.
|
74
|
+
class ExpressionEvaluationError < Error
|
75
|
+
end
|
51
76
|
end
|
52
77
|
end
|
53
78
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative '../core/utils/time'
|
4
4
|
|
5
5
|
# rubocop:disable Lint/AssignmentInCondition
|
6
|
+
# rubocop:disable Style/AndOr
|
6
7
|
|
7
8
|
module Datadog
|
8
9
|
module DI
|
@@ -118,7 +119,26 @@ module Datadog
|
|
118
119
|
|
119
120
|
mod = Module.new do
|
120
121
|
define_method(method_name) do |*args, **kwargs, &target_block| # steep:ignore
|
121
|
-
|
122
|
+
continue = true
|
123
|
+
if condition = probe.condition
|
124
|
+
begin
|
125
|
+
# This context will be recreated later, unlike for line probes.
|
126
|
+
context = Context.new(
|
127
|
+
locals: serializer.combine_args(args, kwargs, self),
|
128
|
+
target_self: self,
|
129
|
+
probe: probe, settings: settings, serializer: serializer,
|
130
|
+
caller_locations: caller_locations,
|
131
|
+
)
|
132
|
+
continue = condition.satisfied?(context)
|
133
|
+
rescue
|
134
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
135
|
+
|
136
|
+
# TODO log / report via telemetry?
|
137
|
+
continue = false
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
if continue and rate_limiter.nil? || rate_limiter.allow?
|
122
142
|
# Arguments may be mutated by the method, therefore
|
123
143
|
# they need to be serialized prior to method invocation.
|
124
144
|
serialized_entry_args = if probe.capture_snapshot?
|
@@ -127,19 +147,29 @@ module Datadog
|
|
127
147
|
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count)
|
128
148
|
end
|
129
149
|
start_time = Core::Utils::Time.get_time
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
150
|
+
|
151
|
+
rv = nil
|
152
|
+
begin
|
153
|
+
# Under Ruby 2.6 we cannot just call super(*args, **kwargs)
|
154
|
+
# for methods defined via method_missing.
|
155
|
+
rv = if args.any?
|
156
|
+
if kwargs.any?
|
157
|
+
super(*args, **kwargs, &target_block)
|
158
|
+
else
|
159
|
+
super(*args, &target_block)
|
160
|
+
end
|
161
|
+
elsif kwargs.any?
|
162
|
+
super(**kwargs, &target_block)
|
135
163
|
else
|
136
|
-
super(
|
164
|
+
super(&target_block)
|
137
165
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
166
|
+
rescue NoMemoryError, Interrupt, SystemExit
|
167
|
+
raise
|
168
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
169
|
+
# We will raise the exception captured here later, after
|
170
|
+
# the instrumentation callback runs.
|
142
171
|
end
|
172
|
+
|
143
173
|
duration = Core::Utils::Time.get_time - start_time
|
144
174
|
# The method itself is not part of the stack trace because
|
145
175
|
# we are getting the stack trace from outside of the method.
|
@@ -158,12 +188,20 @@ module Datadog
|
|
158
188
|
end
|
159
189
|
caller_locs = method_frame + caller_locations # steep:ignore
|
160
190
|
# TODO capture arguments at exit
|
191
|
+
|
192
|
+
context = Context.new(locals: nil, target_self: self,
|
193
|
+
probe: probe, settings: settings, serializer: serializer,
|
194
|
+
serialized_entry_args: serialized_entry_args,
|
195
|
+
caller_locations: caller_locs,
|
196
|
+
return_value: rv, duration: duration, exception: exc,)
|
197
|
+
|
161
198
|
# & is to stop steep complaints, block is always present here.
|
162
|
-
block&.call(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
199
|
+
block&.call(context)
|
200
|
+
if exc
|
201
|
+
raise exc
|
202
|
+
else
|
203
|
+
rv
|
204
|
+
end
|
167
205
|
else
|
168
206
|
# stop standard from trying to mess up my code
|
169
207
|
_ = 42
|
@@ -307,27 +345,57 @@ module Datadog
|
|
307
345
|
# are invoked for *each* line of Ruby executed.
|
308
346
|
# TODO find out exactly when the path in trace point is relative.
|
309
347
|
# Looks like this is the case when line trace point is not targeted?
|
310
|
-
|
348
|
+
continue = iseq || tp.lineno == probe.line_no && (
|
311
349
|
probe.file == tp.path || probe.file_matches?(tp.path)
|
312
350
|
)
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
351
|
+
|
352
|
+
# We set the trace point on :return to be able to instrument
|
353
|
+
# 'end' lines. This also causes the trace point to be invoked on
|
354
|
+
# non-'end' lines when a line raises an exception, since the
|
355
|
+
# exception causes the method to stop executing and stack unwends.
|
356
|
+
# We do not want two invocations of the trace point.
|
357
|
+
# Therefore, if a trace point is invoked with a :line event,
|
358
|
+
# mark it as such and ignore subsequent :return events.
|
359
|
+
continue &&= if probe.executed_on_line?
|
360
|
+
tp.event == :line
|
361
|
+
else
|
362
|
+
if tp.event == :line
|
363
|
+
probe.executed_on_line!
|
364
|
+
end
|
365
|
+
true
|
366
|
+
end
|
367
|
+
|
368
|
+
if continue
|
369
|
+
if condition = probe.condition
|
370
|
+
context = Context.new(
|
371
|
+
locals: Instrumenter.get_local_variables(tp),
|
327
372
|
target_self: tp.self,
|
328
|
-
|
373
|
+
probe: probe, settings: settings, serializer: serializer,
|
374
|
+
path: tp.path,
|
375
|
+
caller_locations: caller_locations,
|
376
|
+
)
|
377
|
+
continue = condition.satisfied?(context)
|
329
378
|
end
|
330
379
|
end
|
380
|
+
|
381
|
+
continue &&= rate_limiter.nil? || rate_limiter.allow? # standard:disable Style/AndOr
|
382
|
+
|
383
|
+
if continue
|
384
|
+
# The context creation is relatively expensive and we don't
|
385
|
+
# want to run it if the callback won't be executed due to the
|
386
|
+
# rate limit.
|
387
|
+
# Thus the copy-paste of the creation call here.
|
388
|
+
context ||= Context.new(
|
389
|
+
locals: Instrumenter.get_local_variables(tp),
|
390
|
+
target_self: tp.self,
|
391
|
+
probe: probe, settings: settings, serializer: serializer,
|
392
|
+
path: tp.path,
|
393
|
+
caller_locations: caller_locations,
|
394
|
+
)
|
395
|
+
|
396
|
+
# & is to stop steep complaints, block is always present here.
|
397
|
+
block&.call(context)
|
398
|
+
end
|
331
399
|
rescue => exc
|
332
400
|
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
333
401
|
logger.debug { "di: unhandled exception in line trace point: #{exc.class}: #{exc}" }
|
@@ -449,3 +517,4 @@ module Datadog
|
|
449
517
|
end
|
450
518
|
|
451
519
|
# rubocop:enable Lint/AssignmentInCondition
|
520
|
+
# rubocop:enable Style/AndOr
|
data/lib/datadog/di/probe.rb
CHANGED
@@ -17,7 +17,7 @@ module Datadog
|
|
17
17
|
# and remote config code must be prepared to deal with exceptions
|
18
18
|
# raised by Probe constructor in particular. Therefore, Probe constructor
|
19
19
|
# will raise an exception if it determines that there is not enough
|
20
|
-
# information (or
|
20
|
+
# information (or conflicting information) in the arguments to create a
|
21
21
|
# functional probe, and upstream code is tasked with not spamming logs
|
22
22
|
# with notifications of such errors (and potentially limiting the
|
23
23
|
# attempts to construct probe from a given payload).
|
@@ -36,8 +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,
|
40
|
-
|
39
|
+
template: nil, template_segments: nil,
|
40
|
+
capture_snapshot: false, max_capture_depth: nil,
|
41
|
+
max_capture_attribute_count: nil, condition: nil,
|
41
42
|
rate_limit: nil)
|
42
43
|
# Perform some sanity checks here to detect unexpected attribute
|
43
44
|
# combinations, in order to not do them in subsequent code.
|
@@ -45,9 +46,17 @@ module Datadog
|
|
45
46
|
raise ArgumentError, "Unknown probe type: #{type}"
|
46
47
|
end
|
47
48
|
|
48
|
-
if
|
49
|
-
|
50
|
-
|
49
|
+
# Probe should be inferred to be a line probe if the specification
|
50
|
+
# contains a line number. This how Java tracer works and Go tracer
|
51
|
+
# is implementing the same behavior, and Go will have all 3 fields
|
52
|
+
# (file path, line number and method name) for line probes.
|
53
|
+
# Do not raise if line number and method name both exist - instead
|
54
|
+
# treat the probe as a line probe.
|
55
|
+
#
|
56
|
+
# In the future we want to provide type name and method name to line
|
57
|
+
# probes, so that the library can verify that the instrumented line
|
58
|
+
# is in the method that the frontend showed to the user when the
|
59
|
+
# user created the probe.
|
51
60
|
|
52
61
|
if line_no && !file
|
53
62
|
raise ArgumentError, "Probe contains line number but not file: #{id}"
|
@@ -57,6 +66,10 @@ module Datadog
|
|
57
66
|
raise ArgumentError, "Partial method probe definition: #{id}"
|
58
67
|
end
|
59
68
|
|
69
|
+
if line_no.nil? && method_name.nil?
|
70
|
+
raise ArgumentError, "Unhandled probe type: neither method nor line probe: #{id}"
|
71
|
+
end
|
72
|
+
|
60
73
|
@id = id
|
61
74
|
@type = type
|
62
75
|
@file = file
|
@@ -64,17 +77,11 @@ module Datadog
|
|
64
77
|
@type_name = type_name
|
65
78
|
@method_name = method_name
|
66
79
|
@template = template
|
80
|
+
@template_segments = template_segments
|
67
81
|
@capture_snapshot = !!capture_snapshot
|
68
82
|
@max_capture_depth = max_capture_depth
|
69
83
|
@max_capture_attribute_count = max_capture_attribute_count
|
70
|
-
|
71
|
-
# These checks use instance methods that have more complex logic
|
72
|
-
# than checking a single argument value. To avoid duplicating
|
73
|
-
# the logic here, use the methods and perform these checks after
|
74
|
-
# instance variable assignment.
|
75
|
-
unless method? || line?
|
76
|
-
raise ArgumentError, "Unhandled probe type: neither method nor line probe: #{id}"
|
77
|
-
end
|
84
|
+
@condition = condition
|
78
85
|
|
79
86
|
@rate_limit = rate_limit || (@capture_snapshot ? 1 : 5000)
|
80
87
|
@rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
|
@@ -89,6 +96,10 @@ module Datadog
|
|
89
96
|
attr_reader :type_name
|
90
97
|
attr_reader :method_name
|
91
98
|
attr_reader :template
|
99
|
+
attr_reader :template_segments
|
100
|
+
|
101
|
+
# The compiled condition for the probe, as a String.
|
102
|
+
attr_reader :condition
|
92
103
|
|
93
104
|
# Configured maximum capture depth. Can be nil in which case
|
94
105
|
# the global default will be used.
|
@@ -122,7 +133,7 @@ module Datadog
|
|
122
133
|
|
123
134
|
# Returns whether the probe is a method probe.
|
124
135
|
def method?
|
125
|
-
|
136
|
+
line_no.nil?
|
126
137
|
end
|
127
138
|
|
128
139
|
# Returns the line number associated with the probe, raising
|
@@ -186,6 +197,15 @@ module Datadog
|
|
186
197
|
def emitting_notified?
|
187
198
|
!!@emitting_notified
|
188
199
|
end
|
200
|
+
|
201
|
+
def executed_on_line?
|
202
|
+
!!@executed_on_line
|
203
|
+
end
|
204
|
+
|
205
|
+
def executed_on_line!
|
206
|
+
# TODO lock?
|
207
|
+
@executed_on_line = true
|
208
|
+
end
|
189
209
|
end
|
190
210
|
end
|
191
211
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Lint/AssignmentInCondition
|
4
|
+
|
3
5
|
require_relative "probe"
|
6
|
+
require_relative 'el'
|
4
7
|
|
5
8
|
module Datadog
|
6
9
|
module DI
|
@@ -21,10 +24,19 @@ module Datadog
|
|
21
24
|
'LOG_PROBE' => :log,
|
22
25
|
}.freeze
|
23
26
|
|
24
|
-
module_function
|
27
|
+
module_function
|
28
|
+
|
29
|
+
def build_from_remote_config(config)
|
25
30
|
# The validations here are not yet comprehensive.
|
26
31
|
type = config.fetch('type')
|
27
32
|
type_symbol = PROBE_TYPES[type] or raise ArgumentError, "Unrecognized probe type: #{type}"
|
33
|
+
cond = if cond_spec = config['when']
|
34
|
+
unless cond_spec['dsl'] && cond_spec['json']
|
35
|
+
raise ArgumentError, "Malformed condition specification for probe: #{config}"
|
36
|
+
end
|
37
|
+
compiled = EL::Compiler.new.compile(cond_spec['json'])
|
38
|
+
EL::Expression.new(cond_spec['dsl'], compiled)
|
39
|
+
end
|
28
40
|
Probe.new(
|
29
41
|
id: config.fetch("id"),
|
30
42
|
type: type_symbol,
|
@@ -34,15 +46,41 @@ module Datadog
|
|
34
46
|
line_no: config["where"]&.[]("lines")&.compact&.map(&:to_i)&.first,
|
35
47
|
type_name: config["where"]&.[]("typeName"),
|
36
48
|
method_name: config["where"]&.[]("methodName"),
|
49
|
+
# We should not be using the template for anything - we instead
|
50
|
+
# use +segments+ - but keep the template for debugging.
|
37
51
|
template: config["template"],
|
52
|
+
template_segments: build_template_segments(config['segments']),
|
38
53
|
capture_snapshot: !!config["captureSnapshot"],
|
39
54
|
max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
|
40
55
|
max_capture_attribute_count: config["capture"]&.[]("maxFieldCount"),
|
41
56
|
rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
|
57
|
+
condition: cond,
|
42
58
|
)
|
43
59
|
rescue KeyError => exc
|
44
60
|
raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc}: #{config}"
|
45
61
|
end
|
62
|
+
|
63
|
+
def build_template_segments(segments)
|
64
|
+
segments&.map do |segment|
|
65
|
+
if Hash === segment
|
66
|
+
if str = segment['str']
|
67
|
+
str
|
68
|
+
elsif ast = segment['json']
|
69
|
+
unless dsl = segment['dsl']
|
70
|
+
raise ArgumentError, "Missing dsl for json in segment: #{segment}"
|
71
|
+
end
|
72
|
+
compiled = EL::Compiler.new.compile(ast)
|
73
|
+
EL::Expression.new(dsl, compiled)
|
74
|
+
else
|
75
|
+
# TODO report to telemetry?
|
76
|
+
end
|
77
|
+
else
|
78
|
+
# TODO report to telemetry?
|
79
|
+
end
|
80
|
+
end&.compact
|
81
|
+
end
|
46
82
|
end
|
47
83
|
end
|
48
84
|
end
|
85
|
+
|
86
|
+
# rubocop:enable Lint/AssignmentInCondition
|
@@ -229,7 +229,8 @@ module Datadog
|
|
229
229
|
# This method is responsible for queueing probe status to be sent to the
|
230
230
|
# backend (once per the probe's lifetime) and a snapshot corresponding
|
231
231
|
# to the current invocation.
|
232
|
-
def probe_executed_callback(
|
232
|
+
def probe_executed_callback(context)
|
233
|
+
probe = context.probe
|
233
234
|
logger.trace { "di: executed #{probe.type} probe at #{probe.location} (#{probe.id})" }
|
234
235
|
unless probe.emitting_notified?
|
235
236
|
payload = probe_notification_builder.build_emitting(probe)
|
@@ -237,7 +238,7 @@ module Datadog
|
|
237
238
|
probe.emitting_notified = true
|
238
239
|
end
|
239
240
|
|
240
|
-
payload = probe_notification_builder.build_executed(
|
241
|
+
payload = probe_notification_builder.build_executed(context)
|
241
242
|
probe_notifier_worker.add_snapshot(payload)
|
242
243
|
end
|
243
244
|
|