datadog 2.3.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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