datadog 2.37.0 → 2.38.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 +29 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +23 -15
- data/ext/datadog_profiling_native_extension/collectors_stack.c +19 -24
- data/ext/datadog_profiling_native_extension/collectors_stack.h +0 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +155 -141
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +1 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +0 -3
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
- data/ext/datadog_profiling_native_extension/time_helpers.h +12 -7
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +5 -0
- data/lib/datadog/appsec/contrib/rails/gateway/request.rb +5 -0
- data/lib/datadog/core/configuration/agentless_settings_resolver.rb +2 -2
- data/lib/datadog/core/configuration/settings.rb +4 -4
- data/lib/datadog/core/configuration/supported_configurations.rb +1 -0
- data/lib/datadog/core/knuth_sampler.rb +3 -1
- data/lib/datadog/core/metrics/client.rb +1 -1
- data/lib/datadog/core/transport/http/builder.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
- data/lib/datadog/core/utils/time.rb +5 -1
- data/lib/datadog/di/boot.rb +3 -0
- data/lib/datadog/di/capture_expression.rb +21 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +71 -0
- data/lib/datadog/di/capture_limits.rb +41 -0
- data/lib/datadog/di/component.rb +2 -2
- data/lib/datadog/di/configuration.rb +6 -0
- data/lib/datadog/di/context.rb +9 -3
- data/lib/datadog/di/el/compiler.rb +1 -1
- data/lib/datadog/di/instrumenter.rb +34 -3
- data/lib/datadog/di/probe.rb +47 -2
- data/lib/datadog/di/probe_builder.rb +83 -1
- data/lib/datadog/di/probe_notification_builder.rb +48 -7
- data/lib/datadog/di/remote.rb +1 -1
- data/lib/datadog/di/serializer.rb +22 -10
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +5 -0
- data/lib/datadog/profiling/collectors/thread_context.rb +9 -1
- data/lib/datadog/profiling/component.rb +17 -0
- data/lib/datadog/tracing/contrib/active_job/data_streams.rb +62 -0
- data/lib/datadog/tracing/contrib/active_job/patcher.rb +12 -0
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +2 -3
- data/lib/datadog/tracing/contrib/pg/instrumentation.rb +12 -12
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/sequel/utils.rb +2 -2
- data/lib/datadog/tracing/contrib/sidekiq/server_tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/framework.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +9 -5
|
@@ -32,7 +32,9 @@ module Datadog
|
|
|
32
32
|
@knuth_factor = knuth_factor
|
|
33
33
|
|
|
34
34
|
rate = rate.to_f
|
|
35
|
-
|
|
35
|
+
# Not using `between?` here: it raises on NaN instead of falling back to 1.0 below.
|
|
36
|
+
# No current caller passes NaN, but this guards against it defensively for future ones.
|
|
37
|
+
unless rate >= 0.0 && rate <= 1.0 # rubocop:disable Style/ComparableBetween
|
|
36
38
|
Datadog.logger.warn("Sample rate #{rate} is not between 0.0 and 1.0, falling back to 1.0")
|
|
37
39
|
rate = 1.0
|
|
38
40
|
end
|
|
@@ -157,7 +157,7 @@ module Datadog
|
|
|
157
157
|
begin
|
|
158
158
|
if send_stats? && !start.nil?
|
|
159
159
|
finished = Utils::Time.get_time
|
|
160
|
-
distribution(stat, (
|
|
160
|
+
distribution(stat, (finished - start) * 1000, options)
|
|
161
161
|
end
|
|
162
162
|
rescue => e
|
|
163
163
|
# TODO: Likely to be redundant, since `distribution` handles its own errors.
|
|
@@ -108,7 +108,7 @@ module Datadog
|
|
|
108
108
|
|
|
109
109
|
# Resolve fallback and merge headers
|
|
110
110
|
fallback = api_options.delete(:fallback)
|
|
111
|
-
api_options[:headers] = @default_headers.merge(
|
|
111
|
+
api_options[:headers] = @default_headers.merge(api_options[:headers] || {})
|
|
112
112
|
|
|
113
113
|
# Add API::Instance with all settings
|
|
114
114
|
instances[key] = API::Instance.new(
|
|
@@ -6,7 +6,7 @@ module Datadog
|
|
|
6
6
|
# Monkey patches `Kernel#fork` and similar functions, adding an `at_fork` callback mechanism which
|
|
7
7
|
# is used to restart observability after the VM forks (e.g. in multiprocess Ruby apps).
|
|
8
8
|
module AtForkMonkeyPatch
|
|
9
|
-
AT_FORK_CHILD_BLOCKS = [] # rubocop:disable Style/MutableConstant Used to store blocks to run, mutable by design.
|
|
9
|
+
AT_FORK_CHILD_BLOCKS = [] # rubocop:disable Style/MutableConstant -- Used to store blocks to run, mutable by design.
|
|
10
10
|
private_constant :AT_FORK_CHILD_BLOCKS
|
|
11
11
|
|
|
12
12
|
def self.supported?
|
|
@@ -8,11 +8,15 @@ module Datadog
|
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
10
|
# Current monotonic time
|
|
11
|
+
# On macOS, CLOCK_MONOTONIC only has microsecond precision,
|
|
12
|
+
# so we use CLOCK_MONOTONIC_RAW which has nanosecond precision instead.
|
|
11
13
|
#
|
|
12
14
|
# @param unit [Symbol] unit for the resulting value, same as ::Process#clock_gettime, defaults to :float_second
|
|
13
15
|
# @return [Float|Integer] timestamp in the requested unit, since some unspecified starting point
|
|
16
|
+
MONOTONIC_CLOCK_ID = RUBY_PLATFORM.include?("darwin") ? Process::CLOCK_MONOTONIC_RAW : Process::CLOCK_MONOTONIC
|
|
17
|
+
|
|
14
18
|
def get_time(unit = :float_second)
|
|
15
|
-
Process.clock_gettime(
|
|
19
|
+
Process.clock_gettime(MONOTONIC_CLOCK_ID, unit)
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
# Current wall time.
|
data/lib/datadog/di/boot.rb
CHANGED
|
@@ -6,6 +6,9 @@ require_relative 'error'
|
|
|
6
6
|
require_relative 'code_tracker'
|
|
7
7
|
require_relative 'component'
|
|
8
8
|
require_relative 'context'
|
|
9
|
+
require_relative 'capture_limits'
|
|
10
|
+
require_relative 'capture_expression'
|
|
11
|
+
require_relative 'capture_expression_evaluator'
|
|
9
12
|
require_relative 'instrumenter'
|
|
10
13
|
require_relative 'probe'
|
|
11
14
|
require_relative 'probe_builder'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "capture_limits"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module DI
|
|
7
|
+
class CaptureExpression
|
|
8
|
+
def initialize(name:, expr:, limits: nil)
|
|
9
|
+
@name = name
|
|
10
|
+
@expr = expr
|
|
11
|
+
@limits = limits
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :name
|
|
15
|
+
|
|
16
|
+
attr_reader :expr
|
|
17
|
+
|
|
18
|
+
attr_reader :limits
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "capture_expression"
|
|
4
|
+
require_relative "capture_limits"
|
|
5
|
+
require_relative "fatal_exceptions"
|
|
6
|
+
|
|
7
|
+
module Datadog
|
|
8
|
+
module DI
|
|
9
|
+
class CaptureExpressionEvaluator
|
|
10
|
+
TELEMETRY_NAMESPACE = "dynamic_instrumentation"
|
|
11
|
+
|
|
12
|
+
def initialize(settings:, serializer:, logger:, telemetry: nil)
|
|
13
|
+
@settings = settings
|
|
14
|
+
@serializer = serializer
|
|
15
|
+
@logger = logger
|
|
16
|
+
@telemetry = telemetry
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :settings
|
|
20
|
+
|
|
21
|
+
attr_reader :serializer
|
|
22
|
+
|
|
23
|
+
attr_reader :logger
|
|
24
|
+
|
|
25
|
+
attr_reader :telemetry
|
|
26
|
+
|
|
27
|
+
def evaluate(probe, context)
|
|
28
|
+
budget_ns = settings.dynamic_instrumentation.max_time_to_serialize_ms * 1_000_000
|
|
29
|
+
deadline_ns = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) + budget_ns
|
|
30
|
+
|
|
31
|
+
output = {}
|
|
32
|
+
evaluation_errors = []
|
|
33
|
+
|
|
34
|
+
probe.capture_expressions.each do |capture_expression|
|
|
35
|
+
name = capture_expression.name
|
|
36
|
+
|
|
37
|
+
if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :nanosecond) >= deadline_ns
|
|
38
|
+
output[name] = {notCapturedReason: "timeout"}
|
|
39
|
+
telemetry&.inc(TELEMETRY_NAMESPACE, "capture_expressions_skipped_by_timeout", 1)
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
value = capture_expression.expr.evaluate(context)
|
|
45
|
+
limits = CaptureLimits.resolve(
|
|
46
|
+
expr_limits: capture_expression.limits,
|
|
47
|
+
probe: probe,
|
|
48
|
+
settings: settings,
|
|
49
|
+
)
|
|
50
|
+
output[name] = serializer.serialize_value(
|
|
51
|
+
value, name: name,
|
|
52
|
+
depth: limits[:depth],
|
|
53
|
+
attribute_count: limits[:attribute_count],
|
|
54
|
+
length: limits[:length],
|
|
55
|
+
collection_size: limits[:collection_size],
|
|
56
|
+
)
|
|
57
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
58
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
59
|
+
evaluation_errors << {expr: name, message: "#{exc.class}: #{exc.message}"}
|
|
60
|
+
logger.debug do
|
|
61
|
+
"di: probe #{probe.id}: capture expression #{name}: evaluation failed: #{exc.class}: #{exc.message}"
|
|
62
|
+
end
|
|
63
|
+
telemetry&.report(exc, description: "DI capture-expression evaluation failed")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
[output, evaluation_errors]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module DI
|
|
5
|
+
class CaptureLimits
|
|
6
|
+
def initialize(max_reference_depth: nil, max_collection_size: nil,
|
|
7
|
+
max_length: nil, max_field_count: nil)
|
|
8
|
+
@max_reference_depth = max_reference_depth
|
|
9
|
+
@max_collection_size = max_collection_size
|
|
10
|
+
@max_length = max_length
|
|
11
|
+
@max_field_count = max_field_count
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :max_reference_depth
|
|
15
|
+
|
|
16
|
+
attr_reader :max_collection_size
|
|
17
|
+
|
|
18
|
+
attr_reader :max_length
|
|
19
|
+
|
|
20
|
+
attr_reader :max_field_count
|
|
21
|
+
|
|
22
|
+
def self.resolve(expr_limits:, probe:, settings:)
|
|
23
|
+
di = settings.dynamic_instrumentation
|
|
24
|
+
{
|
|
25
|
+
depth: expr_limits&.max_reference_depth ||
|
|
26
|
+
probe.max_capture_depth ||
|
|
27
|
+
di.max_capture_depth,
|
|
28
|
+
collection_size: expr_limits&.max_collection_size ||
|
|
29
|
+
probe.max_capture_collection_size ||
|
|
30
|
+
di.max_capture_collection_size,
|
|
31
|
+
length: expr_limits&.max_length ||
|
|
32
|
+
probe.max_capture_string_length ||
|
|
33
|
+
di.max_capture_string_length,
|
|
34
|
+
attribute_count: expr_limits&.max_field_count ||
|
|
35
|
+
probe.max_capture_attribute_count ||
|
|
36
|
+
di.max_capture_attribute_count,
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/datadog/di/component.rb
CHANGED
|
@@ -76,7 +76,7 @@ module Datadog
|
|
|
76
76
|
@serializer = Serializer.new(settings, redactor, telemetry: telemetry)
|
|
77
77
|
@instrumenter = Instrumenter.new(settings, serializer, logger, code_tracker: code_tracker, telemetry: telemetry)
|
|
78
78
|
@probe_repository = ProbeRepository.new
|
|
79
|
-
@probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer)
|
|
79
|
+
@probe_notification_builder = ProbeNotificationBuilder.new(settings, serializer, logger, telemetry: telemetry)
|
|
80
80
|
@probe_notifier_worker = ProbeNotifierWorker.new(
|
|
81
81
|
settings, logger,
|
|
82
82
|
agent_settings: agent_settings,
|
|
@@ -185,7 +185,7 @@ module Datadog
|
|
|
185
185
|
end
|
|
186
186
|
|
|
187
187
|
def parse_probe_spec_and_notify(probe_spec)
|
|
188
|
-
probe = ProbeBuilder.build_from_remote_config(probe_spec)
|
|
188
|
+
probe = ProbeBuilder.build_from_remote_config(probe_spec, logger: logger)
|
|
189
189
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
190
190
|
Datadog::DI.reraise_if_fatal(exc)
|
|
191
191
|
begin
|
|
@@ -134,6 +134,12 @@ module Datadog
|
|
|
134
134
|
o.default 20
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
option :max_time_to_serialize_ms do |o|
|
|
138
|
+
o.type :int
|
|
139
|
+
o.default 200
|
|
140
|
+
o.env 'DD_DYNAMIC_INSTRUMENTATION_MAX_TIME_TO_SERIALIZE'
|
|
141
|
+
end
|
|
142
|
+
|
|
137
143
|
# Settings in the 'internal' group are for internal Datadog
|
|
138
144
|
# use only, and are needed to test dynamic instrumentation or
|
|
139
145
|
# experiment with features not released to customers.
|
data/lib/datadog/di/context.rb
CHANGED
|
@@ -14,6 +14,8 @@ module Datadog
|
|
|
14
14
|
target_self: nil,
|
|
15
15
|
path: nil, caller_locations: nil,
|
|
16
16
|
serialized_entry_args: nil,
|
|
17
|
+
entry_capture_expressions: nil,
|
|
18
|
+
entry_capture_evaluation_errors: nil,
|
|
17
19
|
return_value: nil, duration: nil, exception: nil)
|
|
18
20
|
@probe = probe
|
|
19
21
|
@settings = settings
|
|
@@ -23,6 +25,8 @@ module Datadog
|
|
|
23
25
|
@path = path
|
|
24
26
|
@caller_locations = caller_locations
|
|
25
27
|
@serialized_entry_args = serialized_entry_args
|
|
28
|
+
@entry_capture_expressions = entry_capture_expressions
|
|
29
|
+
@entry_capture_evaluation_errors = entry_capture_evaluation_errors
|
|
26
30
|
@return_value = return_value
|
|
27
31
|
@duration = duration
|
|
28
32
|
@exception = exception
|
|
@@ -44,6 +48,10 @@ module Datadog
|
|
|
44
48
|
|
|
45
49
|
attr_reader :serialized_entry_args
|
|
46
50
|
|
|
51
|
+
attr_reader :entry_capture_expressions
|
|
52
|
+
|
|
53
|
+
attr_reader :entry_capture_evaluation_errors
|
|
54
|
+
|
|
47
55
|
# Return value for the method, for a method probe
|
|
48
56
|
attr_reader :return_value
|
|
49
57
|
|
|
@@ -55,9 +63,7 @@ module Datadog
|
|
|
55
63
|
|
|
56
64
|
def serialized_locals
|
|
57
65
|
# TODO cache?
|
|
58
|
-
locals && serializer.serialize_vars(locals,
|
|
59
|
-
depth: probe.max_capture_depth || settings.dynamic_instrumentation.max_capture_depth,
|
|
60
|
-
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count,)
|
|
66
|
+
locals && serializer.serialize_vars(locals, **probe.snapshot_serializer_limits(settings))
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def fetch(var_name)
|
|
@@ -78,7 +78,7 @@ module Datadog
|
|
|
78
78
|
# We could format to a string here but what if customer
|
|
79
79
|
# has @duration as part of an expression and wants
|
|
80
80
|
# to retain it as a number?
|
|
81
|
-
"(context.duration * 1000)"
|
|
81
|
+
"(context.duration && context.duration * 1000)"
|
|
82
82
|
when '@exception'
|
|
83
83
|
"context.exception"
|
|
84
84
|
else
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative '../core/utils/time'
|
|
4
4
|
require_relative '../ruby_version'
|
|
5
5
|
require_relative 'fatal_exceptions'
|
|
6
|
+
require_relative 'capture_expression_evaluator'
|
|
6
7
|
|
|
7
8
|
# rubocop:disable Lint/AssignmentInCondition
|
|
8
9
|
# rubocop:disable Style/AndOr
|
|
@@ -84,6 +85,12 @@ module Datadog
|
|
|
84
85
|
attr_reader :telemetry
|
|
85
86
|
attr_reader :code_tracker
|
|
86
87
|
|
|
88
|
+
def capture_expression_evaluator
|
|
89
|
+
@capture_expression_evaluator ||= CaptureExpressionEvaluator.new(
|
|
90
|
+
settings: settings, serializer: serializer, logger: logger, telemetry: telemetry,
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
87
94
|
# This is a substitute for Thread::Backtrace::Location
|
|
88
95
|
# which does not have a public constructor.
|
|
89
96
|
# Used for the fabricated stack frame for the method itself
|
|
@@ -520,8 +527,27 @@ module Datadog
|
|
|
520
527
|
# they need to be serialized prior to method invocation.
|
|
521
528
|
serialized_entry_args = if probe.capture_snapshot?
|
|
522
529
|
serializer.serialize_args(args, kwargs, target_self,
|
|
523
|
-
|
|
524
|
-
|
|
530
|
+
**probe.snapshot_serializer_limits(settings))
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
entry_capture_expressions = nil
|
|
534
|
+
entry_capture_evaluation_errors = nil
|
|
535
|
+
if probe.capture_entry_expressions?
|
|
536
|
+
begin
|
|
537
|
+
entry_context = Context.new(
|
|
538
|
+
probe: probe, settings: settings, serializer: serializer,
|
|
539
|
+
target_self: target_self,
|
|
540
|
+
locals: serializer.combine_args(args, kwargs, target_self),
|
|
541
|
+
)
|
|
542
|
+
entry_capture_expressions, entry_capture_evaluation_errors =
|
|
543
|
+
capture_expression_evaluator.evaluate(probe, entry_context)
|
|
544
|
+
rescue Exception => exc # standard:disable Lint/RescueException
|
|
545
|
+
Datadog::DI.reraise_if_fatal(exc)
|
|
546
|
+
raise if settings.dynamic_instrumentation.internal.propagate_all_exceptions
|
|
547
|
+
|
|
548
|
+
logger.debug { "di: error evaluating entry-time capture expressions: #{exc.class}: #{exc.message}" }
|
|
549
|
+
telemetry&.report(exc, description: "Error evaluating entry-time capture expressions")
|
|
550
|
+
end
|
|
525
551
|
end
|
|
526
552
|
# We intentionally do not use Core::Utils::Time.get_time
|
|
527
553
|
# here because the time provider may be overridden by the
|
|
@@ -584,9 +610,14 @@ module Datadog
|
|
|
584
610
|
caller_locs = method_frame + (caller_locations(2) || [])
|
|
585
611
|
# TODO capture arguments at exit
|
|
586
612
|
|
|
587
|
-
|
|
613
|
+
capture_expression_locals = if probe.capture_expressions_only?
|
|
614
|
+
serializer.combine_args(args, kwargs, target_self)
|
|
615
|
+
end
|
|
616
|
+
context = Context.new(locals: capture_expression_locals, target_self: target_self,
|
|
588
617
|
probe: probe, settings: settings, serializer: serializer,
|
|
589
618
|
serialized_entry_args: serialized_entry_args,
|
|
619
|
+
entry_capture_expressions: entry_capture_expressions,
|
|
620
|
+
entry_capture_evaluation_errors: entry_capture_evaluation_errors,
|
|
590
621
|
caller_locations: caller_locs,
|
|
591
622
|
return_value: rv, duration: duration, exception: exc,)
|
|
592
623
|
|
data/lib/datadog/di/probe.rb
CHANGED
|
@@ -34,11 +34,17 @@ module Datadog
|
|
|
34
34
|
class Probe
|
|
35
35
|
KNOWN_TYPES = %i[log].freeze
|
|
36
36
|
|
|
37
|
+
EVALUATE_AT_VALUES = %i[entry exit].freeze
|
|
38
|
+
|
|
37
39
|
def initialize(id:, type:,
|
|
38
40
|
file: nil, line_no: nil, type_name: nil, method_name: nil,
|
|
39
41
|
template: nil, template_segments: nil,
|
|
40
42
|
capture_snapshot: false, max_capture_depth: nil,
|
|
41
|
-
max_capture_attribute_count: nil,
|
|
43
|
+
max_capture_attribute_count: nil,
|
|
44
|
+
max_capture_collection_size: nil, max_capture_string_length: nil,
|
|
45
|
+
capture_expressions: [],
|
|
46
|
+
evaluate_at: nil,
|
|
47
|
+
condition: nil,
|
|
42
48
|
rate_limit: nil)
|
|
43
49
|
# Perform some sanity checks here to detect unexpected attribute
|
|
44
50
|
# combinations, in order to not do them in subsequent code.
|
|
@@ -81,9 +87,17 @@ module Datadog
|
|
|
81
87
|
@capture_snapshot = !!capture_snapshot
|
|
82
88
|
@max_capture_depth = max_capture_depth
|
|
83
89
|
@max_capture_attribute_count = max_capture_attribute_count
|
|
90
|
+
@max_capture_collection_size = max_capture_collection_size
|
|
91
|
+
@max_capture_string_length = max_capture_string_length
|
|
92
|
+
@capture_expressions = capture_expressions || []
|
|
93
|
+
evaluate_at = :exit if evaluate_at.nil?
|
|
94
|
+
unless EVALUATE_AT_VALUES.include?(evaluate_at)
|
|
95
|
+
raise ArgumentError, "Unknown evaluate_at value: #{evaluate_at.inspect} (expected one of #{EVALUATE_AT_VALUES.inspect})"
|
|
96
|
+
end
|
|
97
|
+
@evaluate_at = evaluate_at
|
|
84
98
|
@condition = condition
|
|
85
99
|
|
|
86
|
-
@rate_limit = rate_limit || (@capture_snapshot ? 1 : 5000)
|
|
100
|
+
@rate_limit = rate_limit || ((@capture_snapshot || !@capture_expressions.empty?) ? 1 : 5000)
|
|
87
101
|
@rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
|
|
88
102
|
|
|
89
103
|
# At most one report per second.
|
|
@@ -120,6 +134,14 @@ module Datadog
|
|
|
120
134
|
# the global default will be used.
|
|
121
135
|
attr_reader :max_capture_attribute_count
|
|
122
136
|
|
|
137
|
+
attr_reader :max_capture_collection_size
|
|
138
|
+
|
|
139
|
+
attr_reader :max_capture_string_length
|
|
140
|
+
|
|
141
|
+
attr_reader :capture_expressions
|
|
142
|
+
|
|
143
|
+
attr_reader :evaluate_at
|
|
144
|
+
|
|
123
145
|
# Rate limit in effect, in invocations per second. Always present.
|
|
124
146
|
attr_reader :rate_limit
|
|
125
147
|
|
|
@@ -140,6 +162,29 @@ module Datadog
|
|
|
140
162
|
@capture_snapshot
|
|
141
163
|
end
|
|
142
164
|
|
|
165
|
+
def capture_expressions?
|
|
166
|
+
!@capture_expressions.empty?
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def evaluate_at_entry?
|
|
170
|
+
evaluate_at == :entry
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Capture-expression mode: the probe captures expressions but not a full
|
|
174
|
+
# snapshot. Snapshot capture serializes its own values, so expression
|
|
175
|
+
# capture only runs when a snapshot is not being taken.
|
|
176
|
+
def capture_expressions_only?
|
|
177
|
+
capture_expressions? && !capture_snapshot?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def capture_entry_expressions?
|
|
181
|
+
capture_expressions_only? && evaluate_at_entry?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def snapshot_serializer_limits(settings)
|
|
185
|
+
CaptureLimits.resolve(expr_limits: nil, probe: self, settings: settings)
|
|
186
|
+
end
|
|
187
|
+
|
|
143
188
|
# Returns whether the probe is a line probe.
|
|
144
189
|
#
|
|
145
190
|
# Method probes may still specify a file name (to aid in locating the
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
# rubocop:disable Lint/AssignmentInCondition
|
|
4
4
|
|
|
5
5
|
require_relative "probe"
|
|
6
|
+
require_relative "capture_expression"
|
|
7
|
+
require_relative "capture_limits"
|
|
6
8
|
require_relative 'el'
|
|
7
9
|
|
|
8
10
|
module Datadog
|
|
@@ -27,7 +29,15 @@ module Datadog
|
|
|
27
29
|
|
|
28
30
|
module_function
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
CAPTURE_EXPRESSION_NAME_PATTERN = /\A[a-zA-Z0-9_?]+\z/
|
|
33
|
+
|
|
34
|
+
EVALUATE_AT_STRINGS = {
|
|
35
|
+
"ENTRY" => :entry,
|
|
36
|
+
"EXIT" => :exit,
|
|
37
|
+
"DEFAULT" => :exit,
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
def build_from_remote_config(config, logger:)
|
|
31
41
|
# The validations here are not yet comprehensive.
|
|
32
42
|
type = config.fetch('type')
|
|
33
43
|
type_symbol = PROBE_TYPES[type] or raise ArgumentError, "Unrecognized probe type: #{type}"
|
|
@@ -38,6 +48,13 @@ module Datadog
|
|
|
38
48
|
compiled = EL::Compiler.new.compile(cond_spec['json'])
|
|
39
49
|
EL::Expression.new(cond_spec['dsl'], compiled)
|
|
40
50
|
end
|
|
51
|
+
capture_expressions = build_capture_expressions(config['captureExpressions'])
|
|
52
|
+
capture_expressions = dedup_capture_expressions(capture_expressions, config["id"], logger)
|
|
53
|
+
if !!config["captureSnapshot"] && !capture_expressions.empty?
|
|
54
|
+
logger.debug do
|
|
55
|
+
"di: probe #{config["id"]}: captureSnapshot=true wins over captureExpressions (n=#{capture_expressions.size})"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
41
58
|
Probe.new(
|
|
42
59
|
id: config.fetch("id"),
|
|
43
60
|
type: type_symbol,
|
|
@@ -54,6 +71,10 @@ module Datadog
|
|
|
54
71
|
capture_snapshot: !!config["captureSnapshot"],
|
|
55
72
|
max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
|
|
56
73
|
max_capture_attribute_count: config["capture"]&.[]("maxFieldCount"),
|
|
74
|
+
max_capture_collection_size: config["capture"]&.[]("maxCollectionSize"),
|
|
75
|
+
max_capture_string_length: config["capture"]&.[]("maxLength"),
|
|
76
|
+
capture_expressions: capture_expressions,
|
|
77
|
+
evaluate_at: parse_evaluate_at(config["evaluateAt"], config["id"], logger),
|
|
57
78
|
rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
|
|
58
79
|
condition: cond,
|
|
59
80
|
)
|
|
@@ -61,6 +82,67 @@ module Datadog
|
|
|
61
82
|
raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc.message}: #{config}"
|
|
62
83
|
end
|
|
63
84
|
|
|
85
|
+
def build_capture_expressions(raw)
|
|
86
|
+
return [] if raw.nil?
|
|
87
|
+
unless Array === raw
|
|
88
|
+
raise ArgumentError, "captureExpressions must be an array, got: #{raw.class}"
|
|
89
|
+
end
|
|
90
|
+
return [] if raw.empty?
|
|
91
|
+
raw.map do |entry|
|
|
92
|
+
unless Hash === entry
|
|
93
|
+
raise ArgumentError, "captureExpressions entry must be a hash, got: #{entry.class}"
|
|
94
|
+
end
|
|
95
|
+
name = entry['name']
|
|
96
|
+
unless String === name && CAPTURE_EXPRESSION_NAME_PATTERN.match?(name)
|
|
97
|
+
raise ArgumentError, "captureExpressions entry name missing or invalid (must match #{CAPTURE_EXPRESSION_NAME_PATTERN.inspect}): #{name.inspect}"
|
|
98
|
+
end
|
|
99
|
+
expr_spec = entry['expr']
|
|
100
|
+
unless Hash === expr_spec && expr_spec['dsl'] && expr_spec['json']
|
|
101
|
+
raise ArgumentError, "captureExpressions entry #{name}: missing or malformed expr"
|
|
102
|
+
end
|
|
103
|
+
compiled = EL::Compiler.new.compile(expr_spec['json'])
|
|
104
|
+
expr = EL::Expression.new(expr_spec['dsl'], compiled)
|
|
105
|
+
limits = build_capture_limits(entry['capture'])
|
|
106
|
+
CaptureExpression.new(name: name, expr: expr, limits: limits)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def dedup_capture_expressions(capture_expressions, probe_id, logger)
|
|
111
|
+
return capture_expressions if capture_expressions.size < 2
|
|
112
|
+
by_name = {}
|
|
113
|
+
capture_expressions.each do |capture_expression|
|
|
114
|
+
by_name[capture_expression.name] = capture_expression
|
|
115
|
+
end
|
|
116
|
+
return capture_expressions if by_name.size == capture_expressions.size
|
|
117
|
+
logger.debug do
|
|
118
|
+
"di: probe #{probe_id}: collapsed duplicate captureExpressions names, kept last per name (#{capture_expressions.size} -> #{by_name.size})"
|
|
119
|
+
end
|
|
120
|
+
by_name.values
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def build_capture_limits(raw)
|
|
124
|
+
return nil if raw.nil?
|
|
125
|
+
unless Hash === raw
|
|
126
|
+
raise ArgumentError, "capture-expression entry capture must be a hash, got: #{raw.class}"
|
|
127
|
+
end
|
|
128
|
+
CaptureLimits.new(
|
|
129
|
+
max_reference_depth: raw['maxReferenceDepth'],
|
|
130
|
+
max_collection_size: raw['maxCollectionSize'],
|
|
131
|
+
max_length: raw['maxLength'],
|
|
132
|
+
max_field_count: raw['maxFieldCount'],
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def parse_evaluate_at(raw, probe_id, logger)
|
|
137
|
+
return :exit if raw.nil?
|
|
138
|
+
EVALUATE_AT_STRINGS[raw] || begin
|
|
139
|
+
logger.debug do
|
|
140
|
+
"di: probe #{probe_id}: unrecognized evaluateAt value #{raw.inspect}, defaulting to :exit"
|
|
141
|
+
end
|
|
142
|
+
:exit
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
64
146
|
def build_template_segments(segments)
|
|
65
147
|
segments&.map do |segment|
|
|
66
148
|
if Hash === segment
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# rubocop:disable Lint/AssignmentInCondition
|
|
4
4
|
|
|
5
5
|
require_relative 'fatal_exceptions'
|
|
6
|
+
require_relative 'capture_expression_evaluator'
|
|
6
7
|
|
|
7
8
|
module Datadog
|
|
8
9
|
module DI
|
|
@@ -10,14 +11,26 @@ module Datadog
|
|
|
10
11
|
#
|
|
11
12
|
# @api private
|
|
12
13
|
class ProbeNotificationBuilder
|
|
13
|
-
def initialize(settings, serializer)
|
|
14
|
+
def initialize(settings, serializer, logger, telemetry: nil)
|
|
14
15
|
@settings = settings
|
|
15
16
|
@serializer = serializer
|
|
17
|
+
@logger = logger
|
|
18
|
+
@telemetry = telemetry
|
|
19
|
+
@capture_expression_evaluator = CaptureExpressionEvaluator.new(
|
|
20
|
+
settings: settings, serializer: serializer, logger: logger, telemetry: telemetry,
|
|
21
|
+
)
|
|
16
22
|
end
|
|
17
23
|
|
|
18
24
|
attr_reader :settings
|
|
25
|
+
|
|
19
26
|
attr_reader :serializer
|
|
20
27
|
|
|
28
|
+
attr_reader :logger
|
|
29
|
+
|
|
30
|
+
attr_reader :telemetry
|
|
31
|
+
|
|
32
|
+
attr_reader :capture_expression_evaluator
|
|
33
|
+
|
|
21
34
|
def build_received(probe)
|
|
22
35
|
build_status(probe,
|
|
23
36
|
message: "Probe #{probe.id} has been received correctly",
|
|
@@ -67,13 +80,13 @@ module Datadog
|
|
|
67
80
|
|
|
68
81
|
# TODO also verify that non-capturing probe does not pass
|
|
69
82
|
# snapshot or vars/args into this method
|
|
83
|
+
capture_expression_evaluation_errors = []
|
|
70
84
|
captures = if probe.capture_snapshot?
|
|
85
|
+
snapshot_limits = probe.snapshot_serializer_limits(settings)
|
|
71
86
|
if probe.method?
|
|
72
87
|
return_arguments = {
|
|
73
|
-
"@return": serializer.serialize_value(context.return_value,
|
|
74
|
-
|
|
75
|
-
attribute_count: probe.max_capture_attribute_count || settings.dynamic_instrumentation.max_capture_attribute_count),
|
|
76
|
-
self: serializer.serialize_value(context.target_self),
|
|
88
|
+
"@return": serializer.serialize_value(context.return_value, **snapshot_limits),
|
|
89
|
+
self: serializer.serialize_value(context.target_self, **snapshot_limits),
|
|
77
90
|
}
|
|
78
91
|
{
|
|
79
92
|
entry: {
|
|
@@ -89,11 +102,38 @@ module Datadog
|
|
|
89
102
|
lines: (locals = context.serialized_locals) && {
|
|
90
103
|
probe.line_no => {
|
|
91
104
|
locals: locals,
|
|
92
|
-
arguments: {self: serializer.serialize_value(context.target_self)},
|
|
105
|
+
arguments: {self: serializer.serialize_value(context.target_self, **snapshot_limits)},
|
|
93
106
|
},
|
|
94
107
|
},
|
|
95
108
|
}
|
|
96
109
|
end
|
|
110
|
+
elsif probe.capture_expressions?
|
|
111
|
+
if probe.method?
|
|
112
|
+
if probe.evaluate_at_entry?
|
|
113
|
+
captured_block = context.entry_capture_expressions || {}
|
|
114
|
+
capture_expression_evaluation_errors = context.entry_capture_evaluation_errors || []
|
|
115
|
+
{
|
|
116
|
+
entry: {captureExpressions: captured_block},
|
|
117
|
+
}
|
|
118
|
+
else
|
|
119
|
+
captured_block, capture_expression_evaluation_errors =
|
|
120
|
+
capture_expression_evaluator.evaluate(probe, context)
|
|
121
|
+
{
|
|
122
|
+
return: {
|
|
123
|
+
captureExpressions: captured_block,
|
|
124
|
+
throwable: context.exception ? serialize_throwable(context.exception) : nil,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
elsif probe.line?
|
|
129
|
+
captured_block, capture_expression_evaluation_errors =
|
|
130
|
+
capture_expression_evaluator.evaluate(probe, context)
|
|
131
|
+
{
|
|
132
|
+
lines: {
|
|
133
|
+
probe.line_no => {captureExpressions: captured_block},
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
end
|
|
97
137
|
end
|
|
98
138
|
|
|
99
139
|
message = nil
|
|
@@ -101,6 +141,7 @@ module Datadog
|
|
|
101
141
|
if segments = probe.template_segments
|
|
102
142
|
message, evaluation_errors = evaluate_template(segments, context)
|
|
103
143
|
end
|
|
144
|
+
evaluation_errors.concat(capture_expression_evaluation_errors)
|
|
104
145
|
build_snapshot_base(context,
|
|
105
146
|
evaluation_errors: evaluation_errors, message: message,
|
|
106
147
|
captures: captures)
|
|
@@ -305,7 +346,7 @@ module Datadog
|
|
|
305
346
|
# except there is currently no consensus on said heuristics.
|
|
306
347
|
# .NET always sends ld, other languages send nothing at the moment.
|
|
307
348
|
# Don't send anything for the time being.
|
|
308
|
-
#product: 'di/ld',
|
|
349
|
+
# product: 'di/ld',
|
|
309
350
|
snapshot: {
|
|
310
351
|
id: SecureRandom.uuid,
|
|
311
352
|
timestamp: timestamp,
|
data/lib/datadog/di/remote.rb
CHANGED
|
@@ -228,7 +228,7 @@ module Datadog
|
|
|
228
228
|
# we need to note it as being current so that we do not
|
|
229
229
|
# try to remove instrumentation that is still supposed to be
|
|
230
230
|
# active.
|
|
231
|
-
#current_probe_ids[probe_spec.fetch('id')] = true
|
|
231
|
+
# current_probe_ids[probe_spec.fetch('id')] = true
|
|
232
232
|
rescue Exception => exc # standard:disable Lint/RescueException
|
|
233
233
|
Datadog::DI.reraise_if_fatal(exc)
|
|
234
234
|
raise if component.settings.dynamic_instrumentation.internal.propagate_all_exceptions
|