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.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -2
  3. data/ext/datadog_profiling_loader/datadog_profiling_loader.c +9 -1
  4. data/ext/datadog_profiling_loader/extconf.rb +10 -22
  5. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +3 -3
  6. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +198 -41
  7. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +4 -2
  8. data/ext/datadog_profiling_native_extension/collectors_stack.c +89 -46
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +645 -107
  10. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +15 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +0 -27
  12. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -4
  13. data/ext/datadog_profiling_native_extension/extconf.rb +42 -25
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +50 -0
  15. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +75 -0
  16. data/ext/datadog_profiling_native_extension/heap_recorder.c +194 -34
  17. data/ext/datadog_profiling_native_extension/heap_recorder.h +11 -0
  18. data/ext/datadog_profiling_native_extension/http_transport.c +38 -6
  19. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +1 -1
  20. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +53 -2
  21. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +3 -0
  22. data/ext/datadog_profiling_native_extension/profiling.c +1 -1
  23. data/ext/datadog_profiling_native_extension/ruby_helpers.c +14 -11
  24. data/ext/datadog_profiling_native_extension/stack_recorder.c +58 -22
  25. data/ext/datadog_profiling_native_extension/stack_recorder.h +2 -0
  26. data/ext/libdatadog_api/crashtracker.c +20 -18
  27. data/ext/libdatadog_api/datadog_ruby_common.c +0 -27
  28. data/ext/libdatadog_api/datadog_ruby_common.h +0 -4
  29. data/ext/libdatadog_extconf_helpers.rb +1 -1
  30. data/lib/datadog/appsec/assets/waf_rules/recommended.json +2184 -108
  31. data/lib/datadog/appsec/assets/waf_rules/strict.json +1430 -2
  32. data/lib/datadog/appsec/component.rb +29 -8
  33. data/lib/datadog/appsec/configuration/settings.rb +10 -2
  34. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +1 -0
  35. data/lib/datadog/appsec/contrib/devise/patcher/rememberable_patch.rb +21 -0
  36. data/lib/datadog/appsec/contrib/devise/patcher.rb +12 -2
  37. data/lib/datadog/appsec/contrib/graphql/appsec_trace.rb +0 -14
  38. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +67 -31
  39. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +14 -15
  40. data/lib/datadog/appsec/contrib/graphql/integration.rb +14 -1
  41. data/lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb +7 -20
  42. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +2 -5
  43. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +9 -15
  44. data/lib/datadog/appsec/contrib/rack/reactive/request.rb +6 -18
  45. data/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +7 -20
  46. data/lib/datadog/appsec/contrib/rack/reactive/response.rb +5 -18
  47. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +3 -1
  48. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +3 -5
  49. data/lib/datadog/appsec/contrib/rails/reactive/action.rb +5 -18
  50. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +6 -10
  51. data/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +7 -20
  52. data/lib/datadog/appsec/event.rb +25 -1
  53. data/lib/datadog/appsec/ext.rb +4 -0
  54. data/lib/datadog/appsec/monitor/gateway/watcher.rb +3 -5
  55. data/lib/datadog/appsec/monitor/reactive/set_user.rb +7 -20
  56. data/lib/datadog/appsec/processor/context.rb +109 -0
  57. data/lib/datadog/appsec/processor/rule_loader.rb +3 -1
  58. data/lib/datadog/appsec/processor/rule_merger.rb +33 -15
  59. data/lib/datadog/appsec/processor.rb +42 -107
  60. data/lib/datadog/appsec/rate_limiter.rb +25 -40
  61. data/lib/datadog/appsec/remote.rb +7 -3
  62. data/lib/datadog/appsec/scope.rb +1 -4
  63. data/lib/datadog/appsec/utils/trace_operation.rb +15 -0
  64. data/lib/datadog/appsec/utils.rb +2 -0
  65. data/lib/datadog/appsec.rb +3 -2
  66. data/lib/datadog/core/configuration/agent_settings_resolver.rb +26 -25
  67. data/lib/datadog/core/configuration/components.rb +4 -3
  68. data/lib/datadog/core/configuration/settings.rb +96 -5
  69. data/lib/datadog/core/configuration.rb +1 -3
  70. data/lib/datadog/core/crashtracking/component.rb +9 -6
  71. data/lib/datadog/core/environment/execution.rb +5 -5
  72. data/lib/datadog/core/environment/yjit.rb +5 -0
  73. data/lib/datadog/core/metrics/client.rb +7 -0
  74. data/lib/datadog/core/rate_limiter.rb +183 -0
  75. data/lib/datadog/core/remote/client/capabilities.rb +4 -3
  76. data/lib/datadog/core/remote/component.rb +4 -2
  77. data/lib/datadog/core/remote/negotiation.rb +4 -4
  78. data/lib/datadog/core/remote/tie.rb +2 -0
  79. data/lib/datadog/core/remote/transport/http.rb +5 -0
  80. data/lib/datadog/core/remote/worker.rb +1 -1
  81. data/lib/datadog/core/runtime/ext.rb +1 -0
  82. data/lib/datadog/core/runtime/metrics.rb +5 -1
  83. data/lib/datadog/core/semaphore.rb +35 -0
  84. data/lib/datadog/core/telemetry/component.rb +2 -0
  85. data/lib/datadog/core/telemetry/event.rb +12 -7
  86. data/lib/datadog/core/telemetry/logger.rb +51 -0
  87. data/lib/datadog/core/telemetry/logging.rb +50 -14
  88. data/lib/datadog/core/telemetry/request.rb +13 -1
  89. data/lib/datadog/core/transport/ext.rb +1 -0
  90. data/lib/datadog/core/utils/time.rb +12 -0
  91. data/lib/datadog/core/workers/async.rb +1 -1
  92. data/lib/datadog/di/code_tracker.rb +166 -0
  93. data/lib/datadog/di/configuration/settings.rb +163 -0
  94. data/lib/datadog/di/configuration.rb +11 -0
  95. data/lib/datadog/di/error.rb +31 -0
  96. data/lib/datadog/di/extensions.rb +16 -0
  97. data/lib/datadog/di/instrumenter.rb +301 -0
  98. data/lib/datadog/di/probe.rb +162 -0
  99. data/lib/datadog/di/probe_builder.rb +47 -0
  100. data/lib/datadog/di/probe_notification_builder.rb +207 -0
  101. data/lib/datadog/di/probe_notifier_worker.rb +244 -0
  102. data/lib/datadog/di/redactor.rb +188 -0
  103. data/lib/datadog/di/serializer.rb +215 -0
  104. data/lib/datadog/di/transport.rb +67 -0
  105. data/lib/datadog/di/utils.rb +39 -0
  106. data/lib/datadog/di.rb +57 -0
  107. data/lib/datadog/opentelemetry/sdk/propagator.rb +2 -0
  108. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +12 -10
  109. data/lib/datadog/profiling/collectors/info.rb +12 -3
  110. data/lib/datadog/profiling/collectors/thread_context.rb +32 -8
  111. data/lib/datadog/profiling/component.rb +21 -4
  112. data/lib/datadog/profiling/http_transport.rb +6 -1
  113. data/lib/datadog/profiling/scheduler.rb +2 -0
  114. data/lib/datadog/profiling/stack_recorder.rb +40 -9
  115. data/lib/datadog/single_step_instrument.rb +12 -0
  116. data/lib/datadog/tracing/component.rb +13 -0
  117. data/lib/datadog/tracing/contrib/action_cable/instrumentation.rb +8 -12
  118. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -0
  119. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +78 -0
  120. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +33 -0
  121. data/lib/datadog/tracing/contrib/action_pack/patcher.rb +2 -0
  122. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +4 -0
  123. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +3 -1
  124. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +3 -1
  125. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +5 -1
  126. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +5 -0
  127. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +6 -1
  128. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -0
  129. data/lib/datadog/tracing/contrib/excon/middleware.rb +3 -0
  130. data/lib/datadog/tracing/contrib/faraday/middleware.rb +12 -0
  131. data/lib/datadog/tracing/contrib/grape/endpoint.rb +24 -2
  132. data/lib/datadog/tracing/contrib/graphql/patcher.rb +9 -12
  133. data/lib/datadog/tracing/contrib/graphql/trace_patcher.rb +3 -3
  134. data/lib/datadog/tracing/contrib/graphql/tracing_patcher.rb +3 -3
  135. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +13 -9
  136. data/lib/datadog/tracing/contrib/graphql/unified_trace_patcher.rb +6 -3
  137. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +9 -0
  138. data/lib/datadog/tracing/contrib/http/instrumentation.rb +22 -15
  139. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +10 -5
  140. data/lib/datadog/tracing/contrib/httpclient/patcher.rb +1 -14
  141. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +9 -0
  142. data/lib/datadog/tracing/contrib/httprb/patcher.rb +1 -14
  143. data/lib/datadog/tracing/contrib/lograge/patcher.rb +1 -2
  144. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +2 -0
  145. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +13 -6
  146. data/lib/datadog/tracing/contrib/patcher.rb +2 -1
  147. data/lib/datadog/tracing/contrib/presto/patcher.rb +1 -13
  148. data/lib/datadog/tracing/contrib/rack/middlewares.rb +27 -0
  149. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  150. data/lib/datadog/tracing/contrib/redis/tags.rb +4 -0
  151. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +3 -0
  152. data/lib/datadog/tracing/contrib/sinatra/tracer.rb +4 -0
  153. data/lib/datadog/tracing/contrib/stripe/request.rb +3 -2
  154. data/lib/datadog/tracing/distributed/propagation.rb +7 -0
  155. data/lib/datadog/tracing/metadata/ext.rb +2 -0
  156. data/lib/datadog/tracing/remote.rb +5 -2
  157. data/lib/datadog/tracing/sampling/matcher.rb +6 -1
  158. data/lib/datadog/tracing/sampling/rate_sampler.rb +1 -1
  159. data/lib/datadog/tracing/sampling/rule.rb +2 -0
  160. data/lib/datadog/tracing/sampling/rule_sampler.rb +15 -9
  161. data/lib/datadog/tracing/sampling/span/ext.rb +1 -1
  162. data/lib/datadog/tracing/sampling/span/rule.rb +2 -2
  163. data/lib/datadog/tracing/trace_operation.rb +26 -2
  164. data/lib/datadog/tracing/tracer.rb +29 -22
  165. data/lib/datadog/tracing/transport/http/client.rb +1 -0
  166. data/lib/datadog/tracing/transport/http.rb +4 -0
  167. data/lib/datadog/tracing/transport/io/client.rb +1 -0
  168. data/lib/datadog/tracing/workers/trace_writer.rb +1 -1
  169. data/lib/datadog/tracing/workers.rb +2 -2
  170. data/lib/datadog/tracing/writer.rb +26 -28
  171. data/lib/datadog/version.rb +1 -1
  172. metadata +40 -15
  173. 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
- skip_idle_samples_for_testing,
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: "#{lib_datadog_gem.version}-#{lib_datadog_gem.platform}",
78
+ libdatadog: libdatadog_version,
70
79
  settings: collect_settings_recursively(settings.profiling),
71
80
  }.freeze
72
81
  end