datadog 2.3.0 → 2.5.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 +64 -2
- 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/NativeExtensionDesign.md +3 -3
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
- 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 +645 -107
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -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 +42 -25
- 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 +194 -34
- data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
- data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
- 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/ruby_helpers.c +14 -11
- data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
- data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -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 +10 -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 +14 -15
- data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
- data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
- data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
- data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
- data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
- data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
- data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
- data/lib/datadog/appsec/event.rb +25 -1
- data/lib/datadog/appsec/ext.rb +4 -0
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
- data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
- data/lib/datadog/appsec/processor/context.rb +109 -0
- 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 +42 -107
- data/lib/datadog/appsec/rate_limiter.rb +25 -40
- data/lib/datadog/appsec/remote.rb +7 -3
- data/lib/datadog/appsec/scope.rb +1 -4
- data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
- data/lib/datadog/appsec/utils.rb +2 -0
- data/lib/datadog/appsec.rb +3 -2
- data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
- data/lib/datadog/core/configuration/components.rb +4 -3
- data/lib/datadog/core/configuration/settings.rb +96 -5
- data/lib/datadog/core/configuration.rb +1 -3
- data/lib/datadog/core/crashtracking/component.rb +9 -6
- data/lib/datadog/core/environment/execution.rb +5 -5
- data/lib/datadog/core/environment/yjit.rb +5 -0
- 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/remote/transport/http.rb +5 -0
- data/lib/datadog/core/remote/worker.rb +1 -1
- data/lib/datadog/core/runtime/ext.rb +1 -0
- data/lib/datadog/core/runtime/metrics.rb +5 -1
- data/lib/datadog/core/semaphore.rb +35 -0
- 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/transport/ext.rb +1 -0
- data/lib/datadog/core/utils/time.rb +12 -0
- data/lib/datadog/core/workers/async.rb +1 -1
- data/lib/datadog/di/code_tracker.rb +166 -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/instrumenter.rb +301 -0
- data/lib/datadog/di/probe.rb +162 -0
- data/lib/datadog/di/probe_builder.rb +47 -0
- data/lib/datadog/di/probe_notification_builder.rb +207 -0
- data/lib/datadog/di/probe_notifier_worker.rb +244 -0
- data/lib/datadog/di/redactor.rb +188 -0
- data/lib/datadog/di/serializer.rb +215 -0
- data/lib/datadog/di/transport.rb +67 -0
- data/lib/datadog/di/utils.rb +39 -0
- data/lib/datadog/di.rb +57 -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 +32 -8
- data/lib/datadog/profiling/component.rb +21 -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 +40 -9
- data/lib/datadog/single_step_instrument.rb +12 -0
- data/lib/datadog/tracing/component.rb +13 -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/ethon/easy_patch.rb +4 -0
- data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
- 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/circuit_breaker.rb +9 -0
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
- data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -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/rails/runner.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -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 +15 -9
- 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 +29 -22
- data/lib/datadog/tracing/transport/http/client.rb +1 -0
- data/lib/datadog/tracing/transport/http.rb +4 -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 +2 -2
- data/lib/datadog/tracing/writer.rb +26 -28
- data/lib/datadog/version.rb +1 -1
- metadata +40 -15
- data/lib/datadog/tracing/sampling/rate_limiter.rb +0 -185
@@ -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,215 @@
|
|
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
|
+
# The result of serialization should not reference parameter values when
|
30
|
+
# the values are mutable (currently, this only applies to string values).
|
31
|
+
# Serializer will duplicate such mutable values, so that if method
|
32
|
+
# arguments are captured at entry and then modified during method execution,
|
33
|
+
# the serialized values from entry are correctly preserved.
|
34
|
+
# Alternatively, we could pass a parameter to the serialization methods
|
35
|
+
# which would control whether values are duplicated. This may be more
|
36
|
+
# efficient but there would be additional overhead from passing this
|
37
|
+
# parameter all the time and the API would get more complex.
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
class Serializer
|
41
|
+
def initialize(settings, redactor)
|
42
|
+
@settings = settings
|
43
|
+
@redactor = redactor
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :settings
|
47
|
+
attr_reader :redactor
|
48
|
+
|
49
|
+
# Serializes positional and keyword arguments to a method,
|
50
|
+
# as obtained by a method probe.
|
51
|
+
#
|
52
|
+
# UI supports a single argument list only and does not distinguish
|
53
|
+
# between positional and keyword arguments. We convert positional
|
54
|
+
# arguments to keyword arguments ("arg1", "arg2", ...) and ensure
|
55
|
+
# the positional arguments are listed first.
|
56
|
+
def serialize_args(args, kwargs)
|
57
|
+
counter = 0
|
58
|
+
combined = args.each_with_object({}) do |value, c|
|
59
|
+
counter += 1
|
60
|
+
# Conversion to symbol is needed here to put args ahead of
|
61
|
+
# kwargs when they are merged below.
|
62
|
+
c[:"arg#{counter}"] = value
|
63
|
+
end.update(kwargs)
|
64
|
+
serialize_vars(combined)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Serializes variables captured by a line probe.
|
68
|
+
#
|
69
|
+
# These are normally local variables that exist on a particular line
|
70
|
+
# of executed code.
|
71
|
+
def serialize_vars(vars)
|
72
|
+
vars.each_with_object({}) do |(k, v), agg|
|
73
|
+
agg[k] = serialize_value(v, name: k)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Serializes a single named value.
|
78
|
+
#
|
79
|
+
# The name is needed to perform sensitive data redaction.
|
80
|
+
#
|
81
|
+
# In some cases, the value being serialized does not have a name
|
82
|
+
# (for example, it is the return value of a method).
|
83
|
+
# In this case +name+ can be nil.
|
84
|
+
#
|
85
|
+
# Returns a data structure comprised of only values of basic types
|
86
|
+
# (integers, strings, arrays, hashes).
|
87
|
+
#
|
88
|
+
# Respects string length, collection size and traversal depth limits.
|
89
|
+
def serialize_value(value, name: nil, depth: settings.dynamic_instrumentation.max_capture_depth)
|
90
|
+
if redactor.redact_type?(value)
|
91
|
+
return {type: class_name(value.class), notCapturedReason: "redactedType"}
|
92
|
+
end
|
93
|
+
|
94
|
+
if name && redactor.redact_identifier?(name)
|
95
|
+
return {type: class_name(value.class), notCapturedReason: "redactedIdent"}
|
96
|
+
end
|
97
|
+
|
98
|
+
serialized = {type: class_name(value.class)}
|
99
|
+
case value
|
100
|
+
when NilClass
|
101
|
+
serialized.update(isNull: true)
|
102
|
+
when Integer, Float, TrueClass, FalseClass
|
103
|
+
serialized.update(value: value.to_s)
|
104
|
+
when String, Symbol
|
105
|
+
need_dup = false
|
106
|
+
value = if String === value
|
107
|
+
# This is the only place where we duplicate the value, currently.
|
108
|
+
# All other values are immutable primitives (e.g. numbers).
|
109
|
+
# However, do not duplicate if the string is frozen, or if
|
110
|
+
# it is later truncated.
|
111
|
+
need_dup = !value.frozen?
|
112
|
+
value
|
113
|
+
else
|
114
|
+
value.to_s
|
115
|
+
end
|
116
|
+
max = settings.dynamic_instrumentation.max_capture_string_length
|
117
|
+
if value.length > max
|
118
|
+
serialized.update(truncated: true, size: value.length)
|
119
|
+
value = value[0...max]
|
120
|
+
need_dup = false
|
121
|
+
end
|
122
|
+
value = value.dup if need_dup
|
123
|
+
serialized.update(value: value)
|
124
|
+
when Array
|
125
|
+
if depth < 0
|
126
|
+
serialized.update(notCapturedReason: "depth")
|
127
|
+
else
|
128
|
+
max = settings.dynamic_instrumentation.max_capture_collection_size
|
129
|
+
if max != 0 && value.length > max
|
130
|
+
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
131
|
+
# same steep failure with array slices.
|
132
|
+
# https://github.com/soutaro/steep/issues/1219
|
133
|
+
value = value[0...max] || []
|
134
|
+
end
|
135
|
+
entries = value.map do |elt|
|
136
|
+
serialize_value(elt, depth: depth - 1)
|
137
|
+
end
|
138
|
+
serialized.update(elements: entries)
|
139
|
+
end
|
140
|
+
when Hash
|
141
|
+
if depth < 0
|
142
|
+
serialized.update(notCapturedReason: "depth")
|
143
|
+
else
|
144
|
+
max = settings.dynamic_instrumentation.max_capture_collection_size
|
145
|
+
cur = 0
|
146
|
+
entries = []
|
147
|
+
value.each do |k, v|
|
148
|
+
if max != 0 && cur >= max
|
149
|
+
serialized.update(notCapturedReason: "collectionSize", size: value.length)
|
150
|
+
break
|
151
|
+
end
|
152
|
+
cur += 1
|
153
|
+
entries << [serialize_value(k, depth: depth - 1), serialize_value(v, name: k, depth: depth - 1)]
|
154
|
+
end
|
155
|
+
serialized.update(entries: entries)
|
156
|
+
end
|
157
|
+
else
|
158
|
+
if depth < 0
|
159
|
+
serialized.update(notCapturedReason: "depth")
|
160
|
+
else
|
161
|
+
fields = {}
|
162
|
+
max = settings.dynamic_instrumentation.max_capture_attribute_count
|
163
|
+
cur = 0
|
164
|
+
|
165
|
+
# MRI and JRuby 9.4.5+ preserve instance variable definition
|
166
|
+
# order when calling #instance_variables. Previous JRuby versions
|
167
|
+
# did not preserve order and returned the variables in arbitrary
|
168
|
+
# order.
|
169
|
+
#
|
170
|
+
# The arbitrary order is problematic because 1) when there are
|
171
|
+
# fewer instance variables than capture limit, the order in which
|
172
|
+
# the variables are shown in UI will change from one capture to
|
173
|
+
# the next and generally will be arbitrary to the user, and
|
174
|
+
# 2) when there are more instance variables than capture limit,
|
175
|
+
# *which* variables are captured will also change meaning user
|
176
|
+
# looking at the UI may have "new" instance variables appear and
|
177
|
+
# existing ones disappear as they are looking at multiple captures.
|
178
|
+
#
|
179
|
+
# For consistency, we should have some kind of stable order of
|
180
|
+
# instance variables on all supported Ruby runtimes, so that the UI
|
181
|
+
# stays consistent. Given that initial implementation of Ruby DI
|
182
|
+
# does not support JRuby, we don't handle JRuby's lack of ordering
|
183
|
+
# of #instance_variables here, but if JRuby is supported in the
|
184
|
+
# future this may need to be addressed.
|
185
|
+
ivars = value.instance_variables
|
186
|
+
|
187
|
+
ivars.each do |ivar|
|
188
|
+
if cur >= max
|
189
|
+
serialized.update(notCapturedReason: "fieldCount", fields: fields)
|
190
|
+
break
|
191
|
+
end
|
192
|
+
cur += 1
|
193
|
+
fields[ivar] = serialize_value(value.instance_variable_get(ivar), name: ivar, depth: depth - 1)
|
194
|
+
end
|
195
|
+
serialized.update(fields: fields)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
serialized
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
# Returns the name for the specified class object.
|
204
|
+
#
|
205
|
+
# Ruby can have nameless classes, e.g. Class.new is a class object
|
206
|
+
# with no name. We return a placeholder for such nameless classes.
|
207
|
+
def class_name(cls)
|
208
|
+
# We could call `cls.to_s` to get the "standard" Ruby inspection of
|
209
|
+
# the class, but it is likely that user code can override #to_s
|
210
|
+
# and we don't want to invoke user code.
|
211
|
+
cls.name || "[Unnamed class]"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'error'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
module DI
|
7
|
+
# Transport for sending probe statuses and snapshots to local agent.
|
8
|
+
#
|
9
|
+
# Handles encoding of the payloads into multipart posts if necessary,
|
10
|
+
# body formatting/encoding, setting correct headers, etc.
|
11
|
+
#
|
12
|
+
# The transport does not handle batching of statuses or snapshots -
|
13
|
+
# the batching should be implemented upstream of this class.
|
14
|
+
#
|
15
|
+
# Timeout settings are forwarded from agent settings to the Net adapter.
|
16
|
+
#
|
17
|
+
# The send_* methods raise Error::AgentCommunicationError on errors
|
18
|
+
# (network errors and HTTP protocol errors). It is the responsibility
|
19
|
+
# of upstream code to rescue these exceptions appropriately to prevent them
|
20
|
+
# from being propagated to the application.
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
class Transport
|
24
|
+
DIAGNOSTICS_PATH = '/debugger/v1/diagnostics'
|
25
|
+
INPUT_PATH = '/debugger/v1/input'
|
26
|
+
|
27
|
+
def initialize(agent_settings)
|
28
|
+
# Note that this uses host, port, timeout and TLS flag from
|
29
|
+
# agent settings.
|
30
|
+
@client = Core::Transport::HTTP::Adapters::Net.new(agent_settings)
|
31
|
+
end
|
32
|
+
|
33
|
+
def send_diagnostics(payload)
|
34
|
+
event_payload = Core::Vendor::Multipart::Post::UploadIO.new(
|
35
|
+
StringIO.new(JSON.dump(payload)), 'application/json', 'event.json'
|
36
|
+
)
|
37
|
+
payload = {'event' => event_payload}
|
38
|
+
send_request('Probe status submission', DIAGNOSTICS_PATH, payload)
|
39
|
+
end
|
40
|
+
|
41
|
+
def send_input(payload)
|
42
|
+
send_request('Probe snapshot submission', INPUT_PATH, payload,
|
43
|
+
headers: {'content-type' => 'application/json'},)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :client
|
49
|
+
|
50
|
+
def send_request(desc, path, payload, headers: {})
|
51
|
+
# steep:ignore:start
|
52
|
+
env = OpenStruct.new(
|
53
|
+
path: path,
|
54
|
+
form: payload,
|
55
|
+
headers: headers,
|
56
|
+
)
|
57
|
+
# steep:ignore:end
|
58
|
+
response = client.post(env)
|
59
|
+
unless response.ok?
|
60
|
+
raise Error::AgentCommunicationError, "#{desc} failed: #{response.code}: #{response.payload}"
|
61
|
+
end
|
62
|
+
rescue IOError, SystemCallError => exc
|
63
|
+
raise Error::AgentCommunicationError, "#{desc} failed: #{exc.class}: #{exc}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module DI
|
5
|
+
module Utils
|
6
|
+
# Returns whether the provided +path+ matches the user-designated
|
7
|
+
# file suffix (of a line probe).
|
8
|
+
#
|
9
|
+
# If suffix is an absolute path (i.e., it starts with a slash), the path
|
10
|
+
# must be identical for it to match.
|
11
|
+
#
|
12
|
+
# If suffix is not an absolute path, the path matches if its suffix is
|
13
|
+
# the provided suffix, at a path component boundary.
|
14
|
+
module_function def path_matches_suffix?(path, suffix)
|
15
|
+
if suffix.start_with?('/')
|
16
|
+
path == suffix
|
17
|
+
else
|
18
|
+
# Exact match is not possible here, meaning any matching path
|
19
|
+
# has to be longer than the suffix. Require full component matches,
|
20
|
+
# meaning either the first character of the suffix is a slash
|
21
|
+
# or the previous character in the path is a slash.
|
22
|
+
# For now only check for forward slashes for Unix-like OSes;
|
23
|
+
# backslash is a legitimate character of a file name in Unix
|
24
|
+
# therefore simply permitting forward or back slash is not
|
25
|
+
# sufficient, we need to perform an OS check to know which
|
26
|
+
# path separator to use.
|
27
|
+
!!
|
28
|
+
if path.length > suffix.length && path.end_with?(suffix)
|
29
|
+
previous_char = path[path.length - suffix.length - 1]
|
30
|
+
previous_char == "/" || suffix[0] == "/"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Alternative implementation using a regular expression:
|
34
|
+
# !!(path =~ %r,(/|\A)#{Regexp.quote(suffix)}\z,)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/datadog/di.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'di/error'
|
4
|
+
require_relative 'di/configuration'
|
5
|
+
require_relative 'di/code_tracker'
|
6
|
+
require_relative 'di/extensions'
|
7
|
+
require_relative 'di/instrumenter'
|
8
|
+
require_relative 'di/probe'
|
9
|
+
require_relative 'di/redactor'
|
10
|
+
require_relative 'di/serializer'
|
11
|
+
require_relative 'di/transport'
|
12
|
+
require_relative 'di/utils'
|
13
|
+
|
14
|
+
module Datadog
|
15
|
+
# Namespace for Datadog dynamic instrumentation.
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
module DI
|
19
|
+
# Expose DI to global shared objects
|
20
|
+
Extensions.activate!
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_reader :code_tracker
|
24
|
+
|
25
|
+
# Activates code tracking. Normally this method should be called
|
26
|
+
# when the application starts. If instrumenting third-party code,
|
27
|
+
# code tracking needs to be enabled before the third-party libraries
|
28
|
+
# are loaded. If you definitely will not be instrumenting
|
29
|
+
# third-party libraries, activating tracking after third-party libraries
|
30
|
+
# have been loaded may improve lookup performance.
|
31
|
+
#
|
32
|
+
# TODO test that activating tracker multiple times preserves
|
33
|
+
# existing mappings in the registry
|
34
|
+
def activate_tracking!
|
35
|
+
(@code_tracker ||= CodeTracker.new).start
|
36
|
+
end
|
37
|
+
|
38
|
+
# Deactivates code tracking. In normal usage of DI this method should
|
39
|
+
# never be called, however it is used by DI's test suite to reset
|
40
|
+
# state for individual tests.
|
41
|
+
#
|
42
|
+
# Note that deactivating tracking clears out the registry, losing
|
43
|
+
# the ability to look up files that have been loaded into the process
|
44
|
+
# already.
|
45
|
+
def deactivate_tracking!
|
46
|
+
code_tracker&.stop
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns whether code tracking is available.
|
50
|
+
# This method should be used instead of querying #code_tracker
|
51
|
+
# because the latter one may be nil.
|
52
|
+
def code_tracking_active?
|
53
|
+
code_tracker&.active? || false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
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
|