datadog 2.3.0 → 2.5.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 +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
|