datadog 2.3.0 → 2.4.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 +37 -1
- data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
- data/ext/datadog_profiling_loader/extconf.rb +10 -22
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +148 -30
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
- data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +580 -29
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -1
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
- data/ext/datadog_profiling_native_extension/extconf.rb +38 -21
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
- data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
- data/ext/datadog_profiling_native_extension/heap_recorder.c +20 -6
- data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +52 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -1
- data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
- data/ext/libdatadog_api/crashtracker.c +20 -18
- data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
- data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
- data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
- data/lib/datadog/appsec/component.rb +29 -8
- data/lib/datadog/appsec/configuration/settings.rb +2 -2
- data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
- data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
- data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
- data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
- data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +18 -15
- data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
- data/lib/datadog/appsec/event.rb +1 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
- data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
- data/lib/datadog/appsec/processor.rb +36 -37
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec.rb +2 -2
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration/settings.rb +84 -5
- data/lib/datadog/core/crashtracking/component.rb +1 -1
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/metrics/client.rb +7 -0
- data/lib/datadog/core/rate_limiter.rb +183 -0
- data/lib/datadog/core/remote/client/capabilities.rb +4 -3
- data/lib/datadog/core/remote/component.rb +4 -2
- data/lib/datadog/core/remote/negotiation.rb +4 -4
- data/lib/datadog/core/remote/tie.rb +2 -0
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/telemetry/component.rb +2 -0
- data/lib/datadog/core/telemetry/event.rb +12 -7
- data/lib/datadog/core/telemetry/logger.rb +51 -0
- data/lib/datadog/core/telemetry/logging.rb +50 -14
- data/lib/datadog/core/telemetry/request.rb +13 -1
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/di/code_tracker.rb +168 -0
- data/lib/datadog/di/configuration/settings.rb +163 -0
- data/lib/datadog/di/configuration.rb +11 -0
- data/lib/datadog/di/error.rb +31 -0
- data/lib/datadog/di/extensions.rb +16 -0
- data/lib/datadog/di/probe.rb +133 -0
- data/lib/datadog/di/probe_builder.rb +41 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +193 -0
- data/lib/datadog/di.rb +14 -0
- data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
- data/lib/datadog/profiling/collectors/info.rb +12 -3
- data/lib/datadog/profiling/collectors/thread_context.rb +26 -0
- data/lib/datadog/profiling/component.rb +20 -4
- data/lib/datadog/profiling/http_transport.rb +6 -1
- data/lib/datadog/profiling/scheduler.rb +2 -0
- data/lib/datadog/profiling/stack_recorder.rb +3 -0
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
- data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +9 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +19 -0
- data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
- data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
- data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
- data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +18 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +5 -0
- data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
- data/lib/datadog/tracing/contrib/patcher.rb +2 -1
- data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
- data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
- data/lib/datadog/tracing/distributed/propagation.rb +7 -0
- data/lib/datadog/tracing/metadata/ext.rb +2 -0
- data/lib/datadog/tracing/remote.rb +5 -2
- data/lib/datadog/tracing/sampling/matcher.rb +6 -1
- data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
- data/lib/datadog/tracing/sampling/rule.rb +2 -0
- data/lib/datadog/tracing/sampling/rule_sampler.rb +9 -5
- data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
- data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
- data/lib/datadog/tracing/trace_operation.rb +26 -2
- data/lib/datadog/tracing/tracer.rb +14 -12
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/io/client.rb +1 -0
- data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
- data/lib/datadog/tracing/workers.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +25 -8
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "error"
|
4
|
+
require_relative "../core/rate_limiter"
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
module DI
|
8
|
+
# Encapsulates probe information (as received via remote config)
|
9
|
+
# and state (e.g. whether the probe was installed, or executed).
|
10
|
+
#
|
11
|
+
# It is possible that remote configuration will specify an unsupported
|
12
|
+
# probe type or attribute, due to new DI functionality being added
|
13
|
+
# over time. We want to have predictable behavior in such cases, and
|
14
|
+
# since we can't guarantee that there will be enough information in
|
15
|
+
# a remote config payload to construct a functional probe, ProbeBuilder
|
16
|
+
# and remote config code must be prepared to deal with exceptions
|
17
|
+
# raised by Probe constructor in particular. Therefore, Probe constructor
|
18
|
+
# will raise an exception if it determines that there is not enough
|
19
|
+
# information (or confilcting information) in the arguments to create a
|
20
|
+
# functional probe, and upstream code is tasked with not spamming logs
|
21
|
+
# with notifications of such errors (and potentially limiting the
|
22
|
+
# attempts to construct probe from a given payload).
|
23
|
+
#
|
24
|
+
# Note that, while remote configuration provides line numbers as an
|
25
|
+
# array, the only supported line number configuration is a single line
|
26
|
+
# (this is the case for all languages currently). Therefore Probe
|
27
|
+
# only supports one line number, and ProbeBuilder is responsible for
|
28
|
+
# extracting that one line number out of the array received from RC.
|
29
|
+
#
|
30
|
+
# Note: only some of the parameter/attribute values are currently validated.
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
class Probe
|
34
|
+
def initialize(id:, type:,
|
35
|
+
file: nil, line_no: nil, type_name: nil, method_name: nil,
|
36
|
+
template: nil, capture_snapshot: false, max_capture_depth: nil, rate_limit: nil)
|
37
|
+
# Perform some sanity checks here to detect unexpected attribute
|
38
|
+
# combinations, in order to not do them in subsequent code.
|
39
|
+
if line_no && method_name
|
40
|
+
raise ArgumentError, "Probe contains both line number and method name: #{id}"
|
41
|
+
end
|
42
|
+
|
43
|
+
if type_name && !method_name || method_name && !type_name
|
44
|
+
raise ArgumentError, "Partial method probe definition: #{id}"
|
45
|
+
end
|
46
|
+
|
47
|
+
@id = id
|
48
|
+
@type = type
|
49
|
+
@file = file
|
50
|
+
@line_no = line_no
|
51
|
+
@type_name = type_name
|
52
|
+
@method_name = method_name
|
53
|
+
@template = template
|
54
|
+
@capture_snapshot = !!capture_snapshot
|
55
|
+
@max_capture_depth = max_capture_depth
|
56
|
+
|
57
|
+
# These checks use instance methods that have more complex logic
|
58
|
+
# than checking a single argument value. To avoid duplicating
|
59
|
+
# the logic here, use the methods and perform these checks after
|
60
|
+
# instance variable assignment.
|
61
|
+
unless method? || line?
|
62
|
+
raise ArgumentError, "Unhandled probe type: neither method nor line probe: #{id}"
|
63
|
+
end
|
64
|
+
|
65
|
+
@rate_limit = rate_limit || (@capture_snapshot ? 1 : 5000)
|
66
|
+
@rate_limiter = Datadog::Core::TokenBucket.new(@rate_limit)
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :id
|
70
|
+
attr_reader :type
|
71
|
+
attr_reader :file
|
72
|
+
attr_reader :line_no
|
73
|
+
attr_reader :type_name
|
74
|
+
attr_reader :method_name
|
75
|
+
attr_reader :template
|
76
|
+
|
77
|
+
# Configured maximum capture depth. Can be nil in which case
|
78
|
+
# the global default will be used.
|
79
|
+
attr_reader :max_capture_depth
|
80
|
+
|
81
|
+
# Rate limit in effect, in invocations per second. Always present.
|
82
|
+
attr_reader :rate_limit
|
83
|
+
|
84
|
+
# Rate limiter object. For internal DI use only.
|
85
|
+
attr_reader :rate_limiter
|
86
|
+
|
87
|
+
def capture_snapshot?
|
88
|
+
@capture_snapshot
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns whether the probe is a line probe.
|
92
|
+
#
|
93
|
+
# Method probes may still specify a file name (to aid in locating the
|
94
|
+
# method or for stack traversal purposes?), therefore we do not check
|
95
|
+
# for file name/path presence here and just consider the line number.
|
96
|
+
def line?
|
97
|
+
!line_no.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns whether the probe is a method probe.
|
101
|
+
def method?
|
102
|
+
!!(type_name && method_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the line number associated with the probe, raising
|
106
|
+
# Error::MissingLineNumber if the probe does not have a line number
|
107
|
+
# associated with it.
|
108
|
+
#
|
109
|
+
# This method is used by instrumentation driver to ensure a line number
|
110
|
+
# that is passed into the instrumentation logic is actually a line number
|
111
|
+
# and not nil.
|
112
|
+
def line_no!
|
113
|
+
if line_no.nil?
|
114
|
+
raise Error::MissingLineNumber, "Probe #{id} does not have a line number associated with it"
|
115
|
+
end
|
116
|
+
line_no
|
117
|
+
end
|
118
|
+
|
119
|
+
# Source code location of the probe, for diagnostic reporting.
|
120
|
+
def location
|
121
|
+
if method?
|
122
|
+
"#{type_name}.#{method_name}"
|
123
|
+
elsif line?
|
124
|
+
"#{file}:#{line_no}"
|
125
|
+
else
|
126
|
+
# This case should not be possible because constructor verifies that
|
127
|
+
# the probe is a method or a line probe.
|
128
|
+
raise NotImplementedError
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "probe"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module DI
|
7
|
+
# Creates Probe instances from remote configuration payloads.
|
8
|
+
#
|
9
|
+
# Due to the dynamic instrumentation product evolving over time,
|
10
|
+
# it is possible that the payload corresponds to a type of probe that the
|
11
|
+
# current version of the library does not handle.
|
12
|
+
# For now ArgumentError is raised in such cases (by ProbeBuilder or
|
13
|
+
# Probe constructor), since generally DI is meant to rescue all exceptions
|
14
|
+
# internally and not propagate any exceptions to applications.
|
15
|
+
# A dedicated exception could be added in the future if there is a use case
|
16
|
+
# for it.
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
module ProbeBuilder
|
20
|
+
module_function def build_from_remote_config(config)
|
21
|
+
# The validations here are not yet comprehensive.
|
22
|
+
Probe.new(
|
23
|
+
id: config.fetch("id"),
|
24
|
+
type: config.fetch("type"),
|
25
|
+
file: config["where"]&.[]("sourceFile"),
|
26
|
+
# Sometimes lines are sometimes received as an array of nil
|
27
|
+
# for some reason.
|
28
|
+
line_no: config["where"]&.[]("lines")&.compact&.map(&:to_i)&.first,
|
29
|
+
type_name: config["where"]&.[]("typeName"),
|
30
|
+
method_name: config["where"]&.[]("methodName"),
|
31
|
+
template: config["template"],
|
32
|
+
capture_snapshot: !!config["captureSnapshot"],
|
33
|
+
max_capture_depth: config["capture"]&.[]("maxReferenceDepth"),
|
34
|
+
rate_limit: config["sampling"]&.[]("snapshotsPerSecond"),
|
35
|
+
)
|
36
|
+
rescue KeyError => exc
|
37
|
+
raise ArgumentError, "Malformed remote configuration entry for probe: #{exc.class}: #{exc}: #{config}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
# Provides logic to identify sensitive information in snapshots captured
|
6
|
+
# by dynamic instrumentation.
|
7
|
+
#
|
8
|
+
# Redaction can be performed based on identifier or attribute name,
|
9
|
+
# or class name of said identifier or attribute. Redaction does not take
|
10
|
+
# into account variable values.
|
11
|
+
#
|
12
|
+
# There is a built-in list of identifier names which will be subject to
|
13
|
+
# redaction. Additional names can be provided by the user via the
|
14
|
+
# settings.dynamic_instrumentation.redacted_identifiers setting or
|
15
|
+
# the DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS environment
|
16
|
+
# variable. Currently no class names are subject to redaction by default;
|
17
|
+
# class names can be provided via the
|
18
|
+
# settings.dynamic_instrumentation.redacted_type_names setting or
|
19
|
+
# DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES environment variable.
|
20
|
+
#
|
21
|
+
# Redacted identifiers must match exactly to an attribute name, a key
|
22
|
+
# in a hash or a variable name. Redacted types can either be matched
|
23
|
+
# exactly or, if the name is suffixed with an asterisk (*), any class
|
24
|
+
# whose name contains the specified prefix will be subject to redaction.
|
25
|
+
#
|
26
|
+
# When specifying class (type) names to be redacted, user must specify
|
27
|
+
# fully-qualified names. For example, if `Token` or `Token*` are
|
28
|
+
# specified to be redacted, instances of ::Token will be redacted
|
29
|
+
# but instances of ::Foo::Token will not be. To redact the latter,
|
30
|
+
# specify `Foo::Token` or `::Foo::Token` as redacted types.
|
31
|
+
#
|
32
|
+
# This class does not perform redaction itself (i.e., value replacement
|
33
|
+
# with a placeholder). This replacement is performed by Serializer.
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
class Redactor
|
37
|
+
def initialize(settings)
|
38
|
+
@settings = settings
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :settings
|
42
|
+
|
43
|
+
def redact_identifier?(name)
|
44
|
+
redacted_identifiers.include?(normalize(name))
|
45
|
+
end
|
46
|
+
|
47
|
+
def redact_type?(value)
|
48
|
+
# Classses can be nameless, do not attempt to redact in that case.
|
49
|
+
if (cls_name = value.class.name)
|
50
|
+
redacted_type_names_regexp.match?(cls_name)
|
51
|
+
else
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def redacted_identifiers
|
59
|
+
@redacted_identifiers ||= begin
|
60
|
+
names = DEFAULT_REDACTED_IDENTIFIERS + settings.dynamic_instrumentation.redacted_identifiers
|
61
|
+
names.map! do |name|
|
62
|
+
normalize(name)
|
63
|
+
end
|
64
|
+
Set.new(names)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def redacted_type_names_regexp
|
69
|
+
@redacted_type_names_regexp ||= begin
|
70
|
+
names = settings.dynamic_instrumentation.redacted_type_names
|
71
|
+
names = names.map do |name|
|
72
|
+
if name.start_with?("::")
|
73
|
+
# :: prefix is redundant, all names are expected to be
|
74
|
+
# fully-qualified.
|
75
|
+
#
|
76
|
+
# Defaulting to empty string is for steep.
|
77
|
+
name = name[2...name.length] || ""
|
78
|
+
end
|
79
|
+
if name.end_with?("*")
|
80
|
+
# Defaulting to empty string is for steep.
|
81
|
+
name = name[0..-2] || ""
|
82
|
+
suffix = ".*"
|
83
|
+
else
|
84
|
+
suffix = ""
|
85
|
+
end
|
86
|
+
Regexp.escape(name) + suffix
|
87
|
+
end.join("|")
|
88
|
+
Regexp.new("\\A(?:#{names})\\z")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Copied from dd-trace-py
|
93
|
+
DEFAULT_REDACTED_IDENTIFIERS = [
|
94
|
+
"2fa",
|
95
|
+
"accesstoken",
|
96
|
+
"aiohttpsession",
|
97
|
+
"apikey",
|
98
|
+
"apisecret",
|
99
|
+
"apisignature",
|
100
|
+
"appkey",
|
101
|
+
"applicationkey",
|
102
|
+
"auth",
|
103
|
+
"authorization",
|
104
|
+
"authtoken",
|
105
|
+
"ccnumber",
|
106
|
+
"certificatepin",
|
107
|
+
"cipher",
|
108
|
+
"clientid",
|
109
|
+
"clientsecret",
|
110
|
+
"connectionstring",
|
111
|
+
"connectsid",
|
112
|
+
"cookie",
|
113
|
+
"credentials",
|
114
|
+
"creditcard",
|
115
|
+
"csrf",
|
116
|
+
"csrftoken",
|
117
|
+
"cvv",
|
118
|
+
"databaseurl",
|
119
|
+
"dburl",
|
120
|
+
"encryptionkey",
|
121
|
+
"encryptionkeyid",
|
122
|
+
"env",
|
123
|
+
"geolocation",
|
124
|
+
"gpgkey",
|
125
|
+
"ipaddress",
|
126
|
+
"jti",
|
127
|
+
"jwt",
|
128
|
+
"licensekey",
|
129
|
+
"masterkey",
|
130
|
+
"mysqlpwd",
|
131
|
+
"nonce",
|
132
|
+
"oauth",
|
133
|
+
"oauthtoken",
|
134
|
+
"otp",
|
135
|
+
"passhash",
|
136
|
+
"passwd",
|
137
|
+
"password",
|
138
|
+
"passwordb",
|
139
|
+
"pemfile",
|
140
|
+
"pgpkey",
|
141
|
+
"phpsessid",
|
142
|
+
"pin",
|
143
|
+
"pincode",
|
144
|
+
"pkcs8",
|
145
|
+
"privatekey",
|
146
|
+
"publickey",
|
147
|
+
"pwd",
|
148
|
+
"recaptchakey",
|
149
|
+
"refreshtoken",
|
150
|
+
"routingnumber",
|
151
|
+
"salt",
|
152
|
+
"secret",
|
153
|
+
"secretkey",
|
154
|
+
"secrettoken",
|
155
|
+
"securityanswer",
|
156
|
+
"securitycode",
|
157
|
+
"securityquestion",
|
158
|
+
"serviceaccountcredentials",
|
159
|
+
"session",
|
160
|
+
"sessionid",
|
161
|
+
"sessionkey",
|
162
|
+
"setcookie",
|
163
|
+
"signature",
|
164
|
+
"signaturekey",
|
165
|
+
"sshkey",
|
166
|
+
"ssn",
|
167
|
+
"symfony",
|
168
|
+
"token",
|
169
|
+
"transactionid",
|
170
|
+
"twiliotoken",
|
171
|
+
"usersession",
|
172
|
+
"voterid",
|
173
|
+
"xapikey",
|
174
|
+
"xauthtoken",
|
175
|
+
"xcsrftoken",
|
176
|
+
"xforwardedfor",
|
177
|
+
"xrealip",
|
178
|
+
"xsrf",
|
179
|
+
"xsrftoken",
|
180
|
+
]
|
181
|
+
|
182
|
+
# Input can be a string or a symbol.
|
183
|
+
def normalize(str)
|
184
|
+
str.to_s.strip.downcase.gsub(/[-_$@]/, "")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "redactor"
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module DI
|
7
|
+
# Serializes captured snapshot to primitive types, which are subsequently
|
8
|
+
# serialized to JSON and sent to the backend.
|
9
|
+
#
|
10
|
+
# This class performs actual removal of sensitive values from the
|
11
|
+
# snapshots. It uses Redactor to determine which values are sensitive
|
12
|
+
# and need to be removed.
|
13
|
+
#
|
14
|
+
# Serializer normally ought not to invoke user (application) code,
|
15
|
+
# to guarantee predictable performance. However, objects like ActiveRecord
|
16
|
+
# models cannot be usefully serialized into primitive types without
|
17
|
+
# custom logic (for example, the attributes are more than 3 levels
|
18
|
+
# down from the top-level object which is the default capture depth,
|
19
|
+
# thus they won't be captured at all). To accommodate complex objects,
|
20
|
+
# there is an extension mechanism implemented permitting registration
|
21
|
+
# of serializer callbacks for arbitrary types. Applications and libraries
|
22
|
+
# definining such serializer callbacks should be very careful to
|
23
|
+
# have predictable performance and avoid exceptions and infinite loops
|
24
|
+
# and other such issues.
|
25
|
+
#
|
26
|
+
# All serialization methods take the names of the variables being
|
27
|
+
# serialized in order to be able to redact values.
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
class Serializer
|
31
|
+
def initialize(settings, redactor)
|
32
|
+
@settings = settings
|
33
|
+
@redactor = redactor
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :settings
|
37
|
+
attr_reader :redactor
|
38
|
+
|
39
|
+
# Serializes positional and keyword arguments to a method,
|
40
|
+
# as obtained by a method probe.
|
41
|
+
#
|
42
|
+
# UI supports a single argument list only and does not distinguish
|
43
|
+
# between positional and keyword arguments. We convert positional
|
44
|
+
# arguments to keyword arguments ("arg1", "arg2", ...) and ensure
|
45
|
+
# the positional arguments are listed first.
|
46
|
+
def serialize_args(args, kwargs)
|
47
|
+
counter = 0
|
48
|
+
combined = args.each_with_object({}) do |value, c|
|
49
|
+
counter += 1
|
50
|
+
# Conversion to symbol is needed here to put args ahead of
|
51
|
+
# kwargs when they are merged below.
|
52
|
+
c[:"arg#{counter}"] = value
|
53
|
+
end.update(kwargs)
|
54
|
+
serialize_vars(combined)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Serializes variables captured by a line probe.
|
58
|
+
#
|
59
|
+
# These are normally local variables that exist on a particular line
|
60
|
+
# of executed code.
|
61
|
+
def serialize_vars(vars)
|
62
|
+
vars.each_with_object({}) do |(k, v), agg|
|
63
|
+
agg[k] = serialize_value(v, name: k)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Serializes a single named value.
|
68
|
+
#
|
69
|
+
# The name is needed to perform sensitive data redaction.
|
70
|
+
#
|
71
|
+
# In some cases, the value being serialized does not have a name
|
72
|
+
# (for example, it is the return value of a method).
|
73
|
+
# In this case +name+ can be nil.
|
74
|
+
#
|
75
|
+
# Returns a data structure comprised of only values of basic types
|
76
|
+
# (integers, strings, arrays, hashes).
|
77
|
+
#
|
78
|
+
# Respects string length, collection size and traversal depth limits.
|
79
|
+
def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.max_capture_depth)
|
80
|
+
if redactor.redact_type?(value)
|
81
|
+
return {type: class_name(value.class), notCapturedReason: "redactedType"}
|
82
|
+
end
|
83
|
+
|
84
|
+
if name && redactor.redact_identifier?(name)
|
85
|
+
return {type: class_name(value.class), notCapturedReason: "redactedIdent"}
|
86
|
+
end
|
87
|
+
|
88
|
+
serialized = {type: class_name(value.class)}
|
89
|
+
case value
|
90
|
+
when NilClass
|
91
|
+
serialized.update(isNull: true)
|
92
|
+
when Integer, Float, TrueClass, FalseClass
|
93
|
+
serialized.update(value: value.to_s)
|
94
|
+
when String, Symbol
|
95
|
+
value = value.to_s
|
96
|
+
max = settings.dynamic_instrumentation.max_capture_string_length
|
97
|
+
if value.length > max
|
98
|
+
serialized.update(truncated: true, size: value.length)
|
99
|
+
value = value[0...max]
|
100
|
+
end
|
101
|
+
serialized.update(value: value)
|
102
|
+
when Array
|
103
|
+
if depth < 0
|
104
|
+
serialized.update(notCapturedReason: "depth")
|
105
|
+
else
|
106
|
+
max = settings.dynamic_instrumentation.max_capture_collection_size
|
107
|
+
if max != 0 && value.length > max
|
108
|
+
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
109
|
+
# same steep failure with array slices.
|
110
|
+
# https://github.com/soutaro/steep/issues/1219
|
111
|
+
value = value[0...max] || []
|
112
|
+
end
|
113
|
+
entries = value.map do |elt|
|
114
|
+
serialize_value(elt, depth: depth - 1)
|
115
|
+
end
|
116
|
+
serialized.update(elements: entries)
|
117
|
+
end
|
118
|
+
when Hash
|
119
|
+
if depth < 0
|
120
|
+
serialized.update(notCapturedReason: "depth")
|
121
|
+
else
|
122
|
+
max = settings.dynamic_instrumentation.max_capture_collection_size
|
123
|
+
cur = 0
|
124
|
+
entries = []
|
125
|
+
value.each do |k, v|
|
126
|
+
if max != 0 && cur >= max
|
127
|
+
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
128
|
+
break
|
129
|
+
end
|
130
|
+
cur += 1
|
131
|
+
entries << [serialize_value(k, depth: depth - 1), serialize_value(v, name: k, depth: depth - 1)]
|
132
|
+
end
|
133
|
+
serialized.update(entries: entries)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
if depth < 0
|
137
|
+
serialized.update(notCapturedReason: "depth")
|
138
|
+
else
|
139
|
+
fields = {}
|
140
|
+
max = settings.dynamic_instrumentation.max_capture_attribute_count
|
141
|
+
cur = 0
|
142
|
+
|
143
|
+
# MRI and JRuby 9.4.5+ preserve instance variable definition
|
144
|
+
# order when calling #instance_variables. Previous JRuby versions
|
145
|
+
# did not preserve order and returned the variables in arbitrary
|
146
|
+
# order.
|
147
|
+
#
|
148
|
+
# The arbitrary order is problematic because 1) when there are
|
149
|
+
# fewer instance variables than capture limit, the order in which
|
150
|
+
# the variables are shown in UI will change from one capture to
|
151
|
+
# the next and generally will be arbitrary to the user, and
|
152
|
+
# 2) when there are more instance variables than capture limit,
|
153
|
+
# *which* variables are captured will also change meaning user
|
154
|
+
# looking at the UI may have "new" instance variables appear and
|
155
|
+
# existing ones disappear as they are looking at multiple captures.
|
156
|
+
#
|
157
|
+
# For consistency, we should have some kind of stable order of
|
158
|
+
# instance variables on all supported Ruby runtimes, so that the UI
|
159
|
+
# stays consistent. Given that initial implementation of Ruby DI
|
160
|
+
# does not support JRuby, we don't handle JRuby's lack of ordering
|
161
|
+
# of #instance_variables here, but if JRuby is supported in the
|
162
|
+
# future this may need to be addressed.
|
163
|
+
ivars = value.instance_variables
|
164
|
+
|
165
|
+
ivars.each do |ivar|
|
166
|
+
if cur >= max
|
167
|
+
serialized.update(notCapturedReason: "fieldCount", fields: fields)
|
168
|
+
break
|
169
|
+
end
|
170
|
+
cur += 1
|
171
|
+
fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1)
|
172
|
+
end
|
173
|
+
serialized.update(fields: fields)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
serialized
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# Returns the name for the specified class object.
|
182
|
+
#
|
183
|
+
# Ruby can have nameless classes, e.g. Class.new is a class object
|
184
|
+
# with no name. We return a placeholder for such nameless classes.
|
185
|
+
def class_name(cls)
|
186
|
+
# We could call `cls.to_s` to get the "standard" Ruby inspection of
|
187
|
+
# the class, but it is likely that user code can override #to_s
|
188
|
+
# and we don't want to invoke user code.
|
189
|
+
cls.name || "[Unnamed class]"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/lib/datadog/di.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'di/configuration'
|
4
|
+
require_relative 'di/extensions'
|
5
|
+
|
6
|
+
module Datadog
|
7
|
+
# Namespace for Datadog dynamic instrumentation.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
module DI
|
11
|
+
# Expose DI to global shared objects
|
12
|
+
Extensions.activate!
|
13
|
+
end
|
14
|
+
end
|
@@ -15,6 +15,7 @@ module Datadog
|
|
15
15
|
setter: ::OpenTelemetry::Context::Propagation.text_map_setter
|
16
16
|
)
|
17
17
|
unless setter == ::OpenTelemetry::Context::Propagation.text_map_setter
|
18
|
+
# PENDING: Not to report telemetry logs for now
|
18
19
|
Datadog.logger.error(
|
19
20
|
'Custom setter is not supported. Please inform the `datadog` team at ' \
|
20
21
|
' https://github.com/DataDog/dd-trace-rb of your use case so we can best support you. Using the default ' \
|
@@ -31,6 +32,7 @@ module Datadog
|
|
31
32
|
)
|
32
33
|
if getter != ::OpenTelemetry::Context::Propagation.text_map_getter &&
|
33
34
|
getter != ::OpenTelemetry::Common::Propagation.rack_env_getter
|
35
|
+
# PENDING: Not to report telemetry logs for now
|
34
36
|
Datadog.logger.error(
|
35
37
|
"Custom getter #{getter} is not supported. Please inform the `datadog` team at " \
|
36
38
|
' https://github.com/DataDog/dd-trace-rb of your use case so we can best support you. Using the default ' \
|
@@ -22,6 +22,7 @@ module Datadog
|
|
22
22
|
dynamic_sampling_rate_overhead_target_percentage:,
|
23
23
|
allocation_profiling_enabled:,
|
24
24
|
allocation_counting_enabled:,
|
25
|
+
gvl_profiling_enabled:,
|
25
26
|
# **NOTE**: This should only be used for testing; disabling the dynamic sampling rate will increase the
|
26
27
|
# profiler overhead!
|
27
28
|
dynamic_sampling_rate_enabled: true,
|
@@ -35,16 +36,17 @@ module Datadog
|
|
35
36
|
end
|
36
37
|
|
37
38
|
self.class._native_initialize(
|
38
|
-
self,
|
39
|
-
thread_context_collector,
|
40
|
-
gc_profiling_enabled,
|
41
|
-
idle_sampling_helper,
|
42
|
-
no_signals_workaround_enabled,
|
43
|
-
dynamic_sampling_rate_enabled,
|
44
|
-
dynamic_sampling_rate_overhead_target_percentage,
|
45
|
-
allocation_profiling_enabled,
|
46
|
-
allocation_counting_enabled,
|
47
|
-
|
39
|
+
self_instance: self,
|
40
|
+
thread_context_collector: thread_context_collector,
|
41
|
+
gc_profiling_enabled: gc_profiling_enabled,
|
42
|
+
idle_sampling_helper: idle_sampling_helper,
|
43
|
+
no_signals_workaround_enabled: no_signals_workaround_enabled,
|
44
|
+
dynamic_sampling_rate_enabled: dynamic_sampling_rate_enabled,
|
45
|
+
dynamic_sampling_rate_overhead_target_percentage: dynamic_sampling_rate_overhead_target_percentage,
|
46
|
+
allocation_profiling_enabled: allocation_profiling_enabled,
|
47
|
+
allocation_counting_enabled: allocation_counting_enabled,
|
48
|
+
gvl_profiling_enabled: gvl_profiling_enabled,
|
49
|
+
skip_idle_samples_for_testing: skip_idle_samples_for_testing,
|
48
50
|
)
|
49
51
|
@worker_thread = nil
|
50
52
|
@failure_exception = nil
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "set"
|
4
4
|
require "time"
|
5
|
+
require "libdatadog"
|
5
6
|
|
6
7
|
module Datadog
|
7
8
|
module Profiling
|
@@ -62,11 +63,19 @@ module Datadog
|
|
62
63
|
def collect_profiler_info(settings)
|
63
64
|
unless @profiler_info
|
64
65
|
lib_datadog_gem = ::Gem.loaded_specs["libdatadog"]
|
66
|
+
|
67
|
+
libdatadog_version =
|
68
|
+
if lib_datadog_gem
|
69
|
+
"#{lib_datadog_gem.version}-#{lib_datadog_gem.platform}"
|
70
|
+
else
|
71
|
+
# In some cases, Gem.loaded_specs may not be available, as in
|
72
|
+
# https://github.com/DataDog/dd-trace-rb/pull/1506; let's use the version directly
|
73
|
+
"#{Libdatadog::VERSION}-(unknown)"
|
74
|
+
end
|
75
|
+
|
65
76
|
@profiler_info = {
|
66
|
-
# TODO: If profiling is extracted and its version diverges from the datadog gem, this is inaccurate.
|
67
|
-
# Update if this ever occurs.
|
68
77
|
version: Datadog::Core::Environment::Identity.gem_datadog_version,
|
69
|
-
libdatadog:
|
78
|
+
libdatadog: libdatadog_version,
|
70
79
|
settings: collect_settings_recursively(settings.profiling),
|
71
80
|
}.freeze
|
72
81
|
end
|