datadog 2.3.0 → 2.4.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 (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