datadog 2.3.0 → 2.4.0

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