datadog 2.45.0 → 2.47.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +31 -14
  4. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -1
  5. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +5 -0
  6. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  7. data/ext/libdatadog_api/di.c +30 -5
  8. data/ext/libdatadog_api/extconf.rb +2 -0
  9. data/ext/libdatadog_api/otel_thread_context.c +20 -14
  10. data/ext/libdatadog_api/process_discovery.c +15 -1
  11. data/ext/libdatadog_api/trace_exporter.c +105 -25
  12. data/ext/libdatadog_extconf_helpers.rb +1 -1
  13. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +29 -2
  14. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
  15. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -2
  16. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
  17. data/lib/datadog/appsec/security_engine/engine.rb +2 -1
  18. data/lib/datadog/core/configuration/components.rb +40 -18
  19. data/lib/datadog/core/configuration/components_state.rb +8 -1
  20. data/lib/datadog/core/configuration/supported_configurations.rb +7 -2
  21. data/lib/datadog/core/process_discovery.rb +5 -1
  22. data/lib/datadog/core/remote/client/capabilities.rb +34 -29
  23. data/lib/datadog/core/remote/client.rb +44 -3
  24. data/lib/datadog/core/remote/component.rb +28 -12
  25. data/lib/datadog/core/remote/dispatcher.rb +47 -6
  26. data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
  27. data/lib/datadog/di/boot.rb +1 -0
  28. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  29. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  30. data/lib/datadog/di/component.rb +108 -3
  31. data/lib/datadog/di/configuration.rb +53 -2
  32. data/lib/datadog/di/context.rb +7 -2
  33. data/lib/datadog/di/contrib/active_record.rb +8 -2
  34. data/lib/datadog/di/evaluation_failure.rb +28 -0
  35. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  36. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  37. data/lib/datadog/di/instrumenter.rb +65 -15
  38. data/lib/datadog/di/logger.rb +30 -8
  39. data/lib/datadog/di/probe_manager.rb +48 -5
  40. data/lib/datadog/di/probe_notification_builder.rb +28 -26
  41. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  42. data/lib/datadog/di/remote.rb +46 -2
  43. data/lib/datadog/di/serializer.rb +147 -55
  44. data/lib/datadog/di/snapshot_event.rb +38 -0
  45. data/lib/datadog/di/transport/http.rb +13 -3
  46. data/lib/datadog/di/transport/input.rb +32 -7
  47. data/lib/datadog/open_feature/activation.rb +268 -0
  48. data/lib/datadog/open_feature/component.rb +31 -15
  49. data/lib/datadog/open_feature/configuration.rb +6 -0
  50. data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
  51. data/lib/datadog/open_feature/provider.rb +304 -8
  52. data/lib/datadog/open_feature.rb +31 -1
  53. data/lib/datadog/profiling/collectors/info.rb +37 -6
  54. data/lib/datadog/profiling/component.rb +22 -20
  55. data/lib/datadog/profiling/exporter.rb +3 -5
  56. data/lib/datadog/tracing/component.rb +21 -2
  57. data/lib/datadog/tracing/configuration/ext.rb +2 -0
  58. data/lib/datadog/tracing/configuration/settings.rb +14 -1
  59. data/lib/datadog/tracing/context_provider.rb +8 -0
  60. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
  61. data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
  62. data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
  63. data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
  64. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
  65. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
  66. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
  67. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
  68. data/lib/datadog/tracing/otel_thread_context.rb +3 -5
  69. data/lib/datadog/tracing/remote.rb +50 -19
  70. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  71. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  72. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  73. data/lib/datadog/tracing/transport/native.rb +16 -1
  74. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  75. data/lib/datadog/tracing/transport/traces.rb +82 -15
  76. data/lib/datadog/version.rb +1 -1
  77. metadata +19 -10
  78. data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
@@ -21,6 +21,14 @@ module Datadog
21
21
  def context=(ctx)
22
22
  ctx.otel_thread_context = @otel_thread_context if ctx.respond_to?(:otel_thread_context=)
23
23
 
24
+ if (otel_thread_context = @otel_thread_context)
25
+ if (trace = ctx&.active_trace)
26
+ otel_thread_context.update_from_trace_op(trace)
27
+ else
28
+ otel_thread_context.clear
29
+ end
30
+ end
31
+
24
32
  @context.local = ctx
25
33
  end
26
34
 
@@ -7,6 +7,7 @@ require_relative "../../metadata/ext"
7
7
  require_relative "../http"
8
8
  require_relative "ext"
9
9
  require_relative "../http_annotation_helper"
10
+ require_relative "../utils/quantization/http"
10
11
 
11
12
  module Datadog
12
13
  module Tracing
@@ -111,7 +112,7 @@ module Datadog
111
112
  )
112
113
  datadog_trace = Tracing.active_trace
113
114
 
114
- datadog_tag_request
115
+ datadog_tag_request(uri)
115
116
 
116
117
  if Tracing::Distributed::PropagationPolicy.enabled?(
117
118
  global_config: datadog_configuration,
@@ -131,12 +132,17 @@ module Datadog
131
132
 
132
133
  attr_reader :datadog_configuration
133
134
 
134
- def datadog_tag_request
135
+ def datadog_tag_request(uri)
135
136
  span = @datadog_span
136
137
  span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
137
138
  method = Ext::NOT_APPLICABLE_METHOD
138
139
  method = @datadog_method.to_s if instance_variable_defined?(:@datadog_method) && !@datadog_method.nil?
139
- span.resource = method
140
+ path = uri&.path
141
+ span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
142
+ method,
143
+ path,
144
+ enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
145
+ )
140
146
 
141
147
  if datadog_configuration[:peer_service]
142
148
  span.set_tag(
@@ -153,10 +159,9 @@ module Datadog
153
159
 
154
160
  span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_CLIENT)
155
161
 
156
- uri = try_parse_uri
157
162
  return unless uri
158
163
 
159
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, uri.path)
164
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
160
165
  span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, method)
161
166
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, uri.host)
162
167
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, uri.port)
@@ -8,6 +8,7 @@ require_relative "../http"
8
8
  require_relative "../analytics"
9
9
  require_relative "ext"
10
10
  require_relative "../http_annotation_helper"
11
+ require_relative "../utils/quantization/http"
11
12
 
12
13
  module Datadog
13
14
  module Tracing
@@ -117,7 +118,13 @@ module Datadog
117
118
  end
118
119
 
119
120
  def annotate!(span, datum)
120
- span.resource = datum[:method].to_s.upcase
121
+ http_method = datum[:method].to_s.upcase
122
+ path = datum[:path]
123
+ span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
124
+ http_method,
125
+ path,
126
+ enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
127
+ )
121
128
  span.service = service_name(datum[:host], @options)
122
129
  span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
123
130
  span.type = Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND
@@ -139,8 +146,8 @@ module Datadog
139
146
  # Set analytics sample rate
140
147
  Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
141
148
 
142
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, datum[:path])
143
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, datum[:method].to_s.upcase)
149
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
150
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, http_method)
144
151
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, datum[:host])
145
152
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, datum[:port])
146
153
  span.set_tags(
@@ -7,6 +7,7 @@ require_relative "../http"
7
7
  require_relative "../analytics"
8
8
  require_relative "ext"
9
9
  require_relative "../http_annotation_helper"
10
+ require_relative "../utils/quantization/http"
10
11
  require_relative "../../../core/telemetry/logger"
11
12
 
12
13
  module Datadog
@@ -45,7 +46,8 @@ module Datadog
45
46
 
46
47
  # rubocop:disable Metrics/AbcSize
47
48
  def annotate!(span, env, options)
48
- span.resource = resource_name(env)
49
+ path = env[:url].path
50
+ span.resource = resource_name(env, path)
49
51
  span.service = service_name(env[:url].host, options)
50
52
  span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
51
53
  span.type = Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND
@@ -69,7 +71,7 @@ module Datadog
69
71
  Contrib::Analytics.set_sample_rate(span, options[:analytics_sample_rate])
70
72
  end
71
73
 
72
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, env[:url].path)
74
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
73
75
  span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, env[:method].to_s.upcase)
74
76
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, env[:url].host)
75
77
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, env[:url].port)
@@ -103,8 +105,12 @@ module Datadog
103
105
  Contrib::HTTP.inject(trace, env[:request_headers])
104
106
  end
105
107
 
106
- def resource_name(env)
107
- env[:method].to_s.upcase
108
+ def resource_name(env, path)
109
+ Contrib::Utils::Quantization::HTTP.client_resource(
110
+ env[:method].to_s.upcase,
111
+ path,
112
+ enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
113
+ )
108
114
  end
109
115
 
110
116
  def build_request_options!(env)
@@ -34,7 +34,12 @@ module Datadog
34
34
  span.service = service_name(host, request_options, client_config)
35
35
  span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
36
36
  span.type = Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND
37
- span.resource = req.method
37
+ quantize_resource = Datadog.configuration.tracing.http_client_resource_name_quantize
38
+ span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
39
+ req.method,
40
+ quantize_resource ? request_uri_path(req) : nil,
41
+ enabled: quantize_resource
42
+ )
38
43
 
39
44
  if Tracing::Distributed::PropagationPolicy.enabled?(
40
45
  pin_config: client_config,
@@ -116,6 +121,16 @@ module Datadog
116
121
 
117
122
  private
118
123
 
124
+ def request_uri_path(request)
125
+ uri = request.uri if request.respond_to?(:uri)
126
+ return uri.path if uri
127
+ return request.path.split(/[?#]/, 2).first if request.path.start_with?("/")
128
+
129
+ URI.parse(request.path).path
130
+ rescue URI::InvalidURIError
131
+ nil
132
+ end
133
+
119
134
  def host_and_port(request)
120
135
  if request.respond_to?(:uri) && request.uri
121
136
  [request.uri.host, request.uri.port]
@@ -4,6 +4,7 @@ require_relative "../../metadata/ext"
4
4
  require_relative "../http"
5
5
  require_relative "../analytics"
6
6
  require_relative "../http_annotation_helper"
7
+ require_relative "../utils/quantization/http"
7
8
  require_relative "../../../core/telemetry/logger"
8
9
 
9
10
  module Datadog
@@ -74,10 +75,15 @@ module Datadog
74
75
 
75
76
  http_method = req.header.request_method.upcase
76
77
  uri = req.header.request_uri
78
+ path = uri.path
77
79
 
78
- span.resource = http_method
80
+ span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
81
+ http_method,
82
+ path,
83
+ enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
84
+ )
79
85
  span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, http_method)
80
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, uri.path)
86
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
81
87
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, uri.host)
82
88
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, uri.port)
83
89
 
@@ -4,6 +4,7 @@ require_relative "../../metadata/ext"
4
4
  require_relative "../http"
5
5
  require_relative "../analytics"
6
6
  require_relative "../http_annotation_helper"
7
+ require_relative "../utils/quantization/http"
7
8
  require_relative "../../../core/telemetry/logger"
8
9
 
9
10
  module Datadog
@@ -70,17 +71,22 @@ module Datadog
70
71
  span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
71
72
  span.set_tag(Tracing::Metadata::Ext::TAG_OPERATION, Ext::TAG_OPERATION_REQUEST)
72
73
 
74
+ uri = req.uri
75
+ path = uri&.path
73
76
  if req.verb&.is_a?(String) || req.verb.is_a?(Symbol)
74
77
  http_method = req.verb.to_s.upcase
75
- span.resource = http_method
78
+ span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
79
+ http_method,
80
+ path,
81
+ enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
82
+ )
76
83
  span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, http_method)
77
84
  else
78
85
  logger.debug("service #{req_options[:service_name]} span #{Ext::SPAN_REQUEST} missing request verb")
79
86
  end
80
87
 
81
- if req.uri
82
- uri = req.uri
83
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, uri.path)
88
+ if uri
89
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
84
90
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, uri.host)
85
91
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, uri.port)
86
92
 
@@ -6,6 +6,7 @@ require_relative "../../metadata/ext"
6
6
  require_relative "../http"
7
7
  require_relative "../analytics"
8
8
  require_relative "ext"
9
+ require_relative "../utils/quantization/http"
9
10
 
10
11
  module Datadog
11
12
  module Tracing
@@ -38,7 +39,13 @@ module Datadog
38
39
 
39
40
  def datadog_tag_request(uri, span)
40
41
  span.set_tag(Tracing::Metadata::Ext::TAG_SVC_SRC, Ext::TAG_COMPONENT)
41
- span.resource = method.to_s.upcase
42
+ http_method = method.to_s.upcase
43
+ path = uri.path
44
+ span.resource = Contrib::Utils::Quantization::HTTP.client_resource(
45
+ http_method,
46
+ path,
47
+ enabled: Datadog.configuration.tracing.http_client_resource_name_quantize
48
+ )
42
49
 
43
50
  if datadog_configuration[:peer_service]
44
51
  span.set_tag(
@@ -57,8 +64,8 @@ module Datadog
57
64
  # Set analytics sample rate
58
65
  Contrib::Analytics.set_sample_rate(span, analytics_sample_rate) if analytics_enabled?
59
66
 
60
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, uri.path)
61
- span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, method.to_s.upcase)
67
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, path)
68
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, http_method)
62
69
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_HOST, uri.host)
63
70
  span.set_tag(Tracing::Metadata::Ext::NET::TAG_TARGET_PORT, uri.port)
64
71
  span.set_tags(
@@ -20,6 +20,96 @@ module Datadog
20
20
 
21
21
  module_function
22
22
 
23
+ def client_resource(method, path, enabled:)
24
+ return method unless enabled && path
25
+
26
+ resource = method.dup
27
+ resource << (+" ").force_encoding(resource.encoding)
28
+ quantized_path = quantize_path(decode_path(path))
29
+ resource << quantized_path.force_encoding(resource.encoding)
30
+ end
31
+
32
+ def decode_path(path)
33
+ decoded = String.new(encoding: Encoding::BINARY)
34
+ index = 0
35
+
36
+ while index < path.bytesize
37
+ byte = path.getbyte(index)
38
+ break unless byte
39
+
40
+ if byte == 37 && index + 2 < path.bytesize
41
+ high_byte = path.getbyte(index + 1)
42
+ low_byte = path.getbyte(index + 2)
43
+ if high_byte && low_byte
44
+ high = decode_hex_byte(high_byte)
45
+ low = decode_hex_byte(low_byte)
46
+ if high && low
47
+ decoded << high * 16 + low
48
+ index += 3
49
+ next
50
+ end
51
+ end
52
+ end
53
+
54
+ decoded << byte
55
+ index += 1
56
+ end
57
+
58
+ decoded.force_encoding(path.encoding)
59
+ end
60
+
61
+ def quantize_path(path)
62
+ slash = (+"/").force_encoding(path.encoding)
63
+ normalized = if path.getbyte(0) == 47
64
+ path
65
+ else
66
+ String.new(encoding: path.encoding) << slash << path
67
+ end
68
+
69
+ output = String.new(encoding: path.encoding)
70
+ index = 1
71
+ replacements = 0
72
+
73
+ while index < normalized.bytesize
74
+ segment_end = index
75
+ allowed = 0
76
+ digits = 0
77
+ specials = 0
78
+
79
+ while segment_end < normalized.bytesize && normalized.getbyte(segment_end) != 47
80
+ byte = normalized.getbyte(segment_end)
81
+ break unless byte
82
+
83
+ if byte.between?(48, 57)
84
+ digits += 1
85
+ elsif byte.between?(65, 90) || byte.between?(97, 122) || byte == 45 || byte == 95
86
+ allowed += 1
87
+ else
88
+ specials += 1
89
+ end
90
+ segment_end += 1
91
+ end
92
+
93
+ output << slash
94
+ segment = normalized.byteslice(index, segment_end - index) || String.new(encoding: path.encoding)
95
+ if normalized.getbyte(index) == 118 && allowed == 1 && digits > 0 && specials.zero?
96
+ output << segment
97
+ elsif specials > 0 || digits > 0
98
+ output << (+"*").force_encoding(path.encoding)
99
+ replacements += 1
100
+ else
101
+ output << segment
102
+ end
103
+
104
+ index = segment_end + 1
105
+ end
106
+
107
+ return normalized.dup if replacements.zero?
108
+
109
+ output << slash if normalized.getbyte(normalized.bytesize - 1) == 47
110
+ output
111
+ end
112
+
23
113
  def url(url, options = {})
24
114
  url!(url, options)
25
115
  rescue
@@ -118,6 +208,18 @@ module Datadog
118
208
 
119
209
  private_class_method :collect_query
120
210
 
211
+ def decode_hex_byte(byte)
212
+ if byte.between?(48, 57)
213
+ byte - 48
214
+ elsif byte.between?(65, 70)
215
+ byte - 55
216
+ elsif byte.between?(97, 102)
217
+ byte - 87
218
+ end
219
+ end
220
+
221
+ private_class_method :decode_hex_byte
222
+
121
223
  # Scans over the query string and obfuscates sensitive data by
122
224
  # replacing matches with an opaque value
123
225
  def obfuscate_query(query, options = {})
@@ -14,9 +14,11 @@ module Datadog
14
14
 
15
15
  def self.build(tracing_settings)
16
16
  return unless tracing_settings.otel_thread_context_enabled
17
+ return unless Datadog::Core::LIBDATADOG_API_FAILURE.nil?
17
18
 
18
19
  instance = new
19
- instance if instance.supported? && instance._native_enable
20
+ instance._native_enable
21
+ instance
20
22
  end
21
23
 
22
24
  def initialize
@@ -65,10 +67,6 @@ module Datadog
65
67
  false
66
68
  end
67
69
 
68
- def supported?
69
- Datadog::Core::LIBDATADOG_API_FAILURE.nil? && _native_supported?
70
- end
71
-
72
70
  def update_from_trace_op(trace_op)
73
71
  active_span = trace_op.active_span
74
72
 
@@ -62,6 +62,8 @@ module Datadog
62
62
 
63
63
  # @type var parsed: Array[[::Datadog::Core::Remote::Configuration::Content, ::Hash[::String, untyped]]]
64
64
  parsed = []
65
+ # @type var unparseable_contents: Array[Datadog::Core::Remote::Configuration::Content]
66
+ unparseable_contents = []
65
67
  repository.contents.each do |content|
66
68
  next unless content.path.product == PRODUCT
67
69
 
@@ -76,9 +78,8 @@ module Datadog
76
78
  # A present but non-object lib_config (e.g. {"lib_config": []}) is a
77
79
  # schema violation the customer can fix. Acknowledging it as applied
78
80
  # would tell the backend the config was successfully applied when it
79
- # was not, and an empty merge would revert active tracing overrides
80
- # to their non-RC values. Reject it per content so the malformed
81
- # payload is reported errored instead. A null or absent lib_config
81
+ # was not. Reject it per content so the malformed payload is
82
+ # reported errored instead. A null or absent lib_config
82
83
  # carries no overrides and is left as a no-op.
83
84
  lib_config = config["lib_config"]
84
85
  if !lib_config.nil? && !lib_config.is_a?(Hash)
@@ -92,6 +93,7 @@ module Datadog
92
93
  description: "Failed to parse APM_TRACING remote config",
93
94
  )
94
95
  content.errored("#{e.class}: #{e.message}: #{Array(e.backtrace).join("\n")}")
96
+ unparseable_contents << content
95
97
  end
96
98
  end
97
99
 
@@ -117,10 +119,26 @@ module Datadog
117
119
  # deterministic merge.
118
120
  ordered = applicable.sort_by { |content, config| [-config_priority(config), content.path.config_id] }
119
121
  merged = merge_lib_configs(ordered.map { |_content, config| config })
122
+ # The applicable configs carrying a non-null dynamic_instrumentation_enabled
123
+ # value: any of their paths can still supply the signal after a later
124
+ # replacement, so the DI component records them all.
125
+ # @type var enable_signal_paths: Array[Datadog::Core::Remote::Configuration::Path]
126
+ enable_signal_paths = Core::Utils::EnumerableCompat.filter_map(ordered) do |pair|
127
+ content, config = pair
128
+ lib_config = config["lib_config"]
129
+ if lib_config.is_a?(Hash) && !lib_config["dynamic_instrumentation_enabled"].nil?
130
+ content.path
131
+ end
132
+ end
120
133
 
121
134
  # Applied even when the set is empty: an emptied repository (last config
122
135
  # removed) reverts the tracing overrides to their non-RC values.
123
- apply_lib_config(merged, repository)
136
+ apply_lib_config(
137
+ merged,
138
+ repository,
139
+ unparseable_contents: unparseable_contents,
140
+ enable_signal_paths: enable_signal_paths,
141
+ )
124
142
 
125
143
  parsed.each { |content, _config| content.applied }
126
144
  nil
@@ -205,8 +223,10 @@ module Datadog
205
223
  #
206
224
  # @param lib_config [Hash[String, untyped]] the lib_config to apply
207
225
  # @param repository [Core::Remote::Configuration::Repository, nil] forwarded to DI enablement
226
+ # @param unparseable_contents [Array<Core::Remote::Configuration::Content>] the APM_TRACING contents that failed to parse
227
+ # @param enable_signal_paths [Array<Core::Remote::Configuration::Path>] the paths of the applicable configs carrying the field
208
228
  # @return [nil]
209
- def apply_lib_config(lib_config, repository)
229
+ def apply_lib_config(lib_config, repository, unparseable_contents:, enable_signal_paths:)
210
230
  env_vars = Datadog::Tracing::Configuration::Dynamic::OPTIONS.map do |name, env_var, option|
211
231
  value = lib_config[name]
212
232
 
@@ -218,13 +238,30 @@ module Datadog
218
238
  [env_var, telemetry_value]
219
239
  end
220
240
 
221
- if (di_enabled = lib_config["dynamic_instrumentation_enabled"]) != nil # rubocop:disable Style/NonNilCheck
222
- # repository is forwarded so that an enable signal can reconcile DI
223
- # against probes delivered in an earlier poll while DI was stopped
224
- # (see Datadog::DI::Remote.handle_rc_enablement).
241
+ di_enabled = lib_config["dynamic_instrumentation_enabled"]
242
+ # allow_initialization: false because this runs on the remote-config worker
243
+ # thread, and the default would build the component tree from that thread.
244
+ components = Datadog.send(:components, allow_initialization: false)
245
+ di_component = components&.dynamic_instrumentation
246
+ if di_enabled.nil? && di_component&.rc_enablement_signal_withdrawn?(unparseable_contents)
247
+ di_enabled = false
248
+ end
249
+
250
+ # Records the parsed carrier paths and the previously recorded paths whose
251
+ # replacements remain unparseable, on every merge including merges that keep
252
+ # the DI state.
253
+ if di_component
254
+ unparseable_recorded_paths = di_component.rc_enablement_signal_paths.select do |path|
255
+ unparseable_contents.any? { |content| content.path == path }
256
+ end
257
+ di_component.record_rc_enablement_signal_paths(enable_signal_paths + unparseable_recorded_paths)
258
+ end
259
+
260
+ unless di_enabled.nil?
261
+ # repository is forwarded for the stopped->started replay in
262
+ # Datadog::DI::Remote.handle_rc_enablement.
225
263
  Datadog::DI::Remote.handle_rc_enablement(di_enabled, repository)
226
264
 
227
- components = Datadog.send(:components, allow_initialization: false)
228
265
  di_products = Datadog::DI::Remote.products +
229
266
  Datadog::SymbolDatabase::Remote.deferred_products(Datadog.configuration)
230
267
 
@@ -236,7 +273,7 @@ module Datadog
236
273
  # by DD_DYNAMIC_INSTRUMENTATION_ENABLED=false. Advertising then would
237
274
  # report DI as in use when it is not and invite probe configs the
238
275
  # tracer must refuse; withdraw the products otherwise.
239
- if components&.dynamic_instrumentation&.started?
276
+ if di_component&.started?
240
277
  components&.remote&.add_products(*di_products)
241
278
  else
242
279
  components&.remote&.remove_products(*di_products)
@@ -249,13 +286,7 @@ module Datadog
249
286
  Datadog.logger.debug { "APM_TRACING RC: merged dynamic_instrumentation_enabled=#{di_enabled}" }
250
287
  end
251
288
 
252
- # allow_initialization: false because this runs on the remote-config
253
- # worker thread. If components haven't been built yet (e.g. during a
254
- # teardown/reset window), the default value would synchronously build
255
- # the entire component tree from this thread. The &. chain matches the
256
- # pattern used by DI::Remote.handle_rc_enablement in the same dispatch
257
- # path.
258
- Datadog.send(:components, allow_initialization: false)&.telemetry&.client_configuration_change!(env_vars)
289
+ components&.telemetry&.client_configuration_change!(env_vars)
259
290
  nil
260
291
  end
261
292
 
@@ -269,7 +300,7 @@ module Datadog
269
300
 
270
301
  def receiver(products = [PRODUCT], &block)
271
302
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(products)
272
- [Core::Remote::Dispatcher::Receiver.new(matcher, &block)]
303
+ [Core::Remote::Dispatcher::Receiver.new(matcher, initial_reconcile: true, &block)]
273
304
  end
274
305
 
275
306
  private
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require_relative "../telemetry"
5
+
6
+ module Datadog
7
+ module Tracing
8
+ module Transport
9
+ module HTTP
10
+ class Telemetry < Datadog::Tracing::Transport::Telemetry
11
+ def initialize(client)
12
+ super(client, source: "ruby")
13
+ end
14
+
15
+ def batch(traces)
16
+ return unless @client.enabled? && @client.metrics_manager.enabled
17
+
18
+ Batch.new(self, traces)
19
+ rescue
20
+ nil
21
+ end
22
+
23
+ class Batch
24
+ def initialize(reporter, traces)
25
+ @reporter = reporter
26
+ @remaining_chunks = traces.length
27
+ @remaining_spans = traces.sum(&:length)
28
+ @reporter.record(spans_enqueued_for_serialization: @remaining_spans)
29
+ end
30
+
31
+ def record_oversized_drop(span_count)
32
+ @remaining_chunks -= 1
33
+ @remaining_spans -= span_count
34
+ @reporter.record(chunks_dropped_payload_too_large: 1, spans_dropped_payload_too_large: span_count)
35
+ end
36
+
37
+ def serialization_failed
38
+ @reporter.record(chunks_dropped_serialization_error: @remaining_chunks,
39
+ spans_dropped_serialization_error: @remaining_spans,)
40
+ end
41
+
42
+ def sent(request, response, chunks, spans, bytes, fallback:)
43
+ @reporter.record(requests_count: 1)
44
+ return if fallback
45
+
46
+ @remaining_chunks -= chunks
47
+ @remaining_spans -= spans
48
+ observations = {} #: Hash[Symbol, Integer]
49
+ if (status = request.http_status)
50
+ observations[:responses_count] = 1
51
+ observations[:status_code] = status
52
+ end
53
+ success = status ? status.between?(200, 299) : request.http_success
54
+ if success
55
+ observations[:chunks_sent] = chunks
56
+ observations[:bytes_sent] = bytes
57
+ elsif status || success == false
58
+ observations[:errors_status_code] = 1 if status
59
+ observations[:chunks_dropped_send_failure] = chunks
60
+ observations[:spans_dropped_api_error] = spans
61
+ elsif response.is_a?(Core::Transport::InternalErrorResponse)
62
+ error = response.error
63
+ if error.is_a?(ArgumentError) || error.is_a?(TypeError)
64
+ observations[:chunks_dropped_serialization_error] = chunks
65
+ observations[:spans_dropped_serialization_error] = spans
66
+ else
67
+ observations[:chunks_dropped_send_failure] = chunks
68
+ observations[:spans_dropped_api_error] = spans
69
+ if error.is_a?(::Timeout::Error) || error.is_a?(Errno::ETIMEDOUT)
70
+ observations[:errors_timeout] = 1
71
+ elsif network_error?(error)
72
+ observations[:errors_network] = 1
73
+ end
74
+ end
75
+ end
76
+ @reporter.record(observations)
77
+ rescue
78
+ nil
79
+ end
80
+
81
+ private
82
+
83
+ def network_error?(error)
84
+ error.is_a?(SystemCallError) || error.is_a?(IOError) || error.is_a?(SocketError) ||
85
+ error.is_a?(::Net::ProtocolError) || error.is_a?(::Net::HTTPBadResponse) ||
86
+ error.is_a?(::Net::HTTPHeaderSyntaxError) ||
87
+ (defined?(::OpenSSL::SSL::SSLError) && error.is_a?(::OpenSSL::SSL::SSLError))
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -51,6 +51,8 @@ module Datadog
51
51
 
52
52
  # Query for response
53
53
  http_response = super
54
+ # Delivery succeeded or failed before service-rate parsing can raise.
55
+ env.request.capture_status(http_response)
54
56
 
55
57
  # Process the response
56
58
  response_options = {trace_count: env.request.parcel.trace_count}.tap do |options|
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../telemetry"
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Transport
8
+ module Native
9
+ class Telemetry < Datadog::Tracing::Transport::Telemetry
10
+ def initialize(client)
11
+ super(client, source: "libdatadog")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end