datadog 2.23.0 → 2.24.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -2
  3. data/ext/datadog_profiling_native_extension/collectors_stack.c +17 -5
  4. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  5. data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
  6. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  7. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  8. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  9. data/lib/datadog/appsec/context.rb +2 -1
  10. data/lib/datadog/appsec/remote.rb +1 -9
  11. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  12. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  13. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  14. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  15. data/lib/datadog/core/configuration/options.rb +8 -5
  16. data/lib/datadog/core/configuration/settings.rb +14 -3
  17. data/lib/datadog/core/configuration/supported_configurations.rb +2 -1
  18. data/lib/datadog/core/environment/cgroup.rb +52 -25
  19. data/lib/datadog/core/environment/container.rb +140 -46
  20. data/lib/datadog/core/environment/ext.rb +1 -0
  21. data/lib/datadog/core/environment/process.rb +9 -1
  22. data/lib/datadog/core/rate_limiter.rb +9 -1
  23. data/lib/datadog/core/remote/client.rb +14 -6
  24. data/lib/datadog/core/remote/component.rb +6 -4
  25. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  26. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  27. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  28. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  29. data/lib/datadog/core/remote/transport/config.rb +3 -16
  30. data/lib/datadog/core/remote/transport/http/config.rb +4 -44
  31. data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
  32. data/lib/datadog/core/remote/transport/http.rb +13 -24
  33. data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
  34. data/lib/datadog/core/telemetry/component.rb +52 -13
  35. data/lib/datadog/core/telemetry/event/app_started.rb +34 -0
  36. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  37. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  38. data/lib/datadog/core/telemetry/request.rb +17 -3
  39. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
  40. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  41. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
  42. data/lib/datadog/core/telemetry/worker.rb +88 -32
  43. data/lib/datadog/core/transport/ext.rb +2 -0
  44. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  45. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  46. data/lib/datadog/core/transport/http/builder.rb +9 -5
  47. data/lib/datadog/core/transport/http/client.rb +19 -8
  48. data/lib/datadog/core/transport/http.rb +22 -19
  49. data/lib/datadog/core/transport/response.rb +9 -0
  50. data/lib/datadog/core/transport/transport.rb +90 -0
  51. data/lib/datadog/core/utils/only_once_successful.rb +2 -0
  52. data/lib/datadog/core/utils/time.rb +1 -1
  53. data/lib/datadog/core/workers/async.rb +10 -1
  54. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  55. data/lib/datadog/core/workers/polling.rb +2 -0
  56. data/lib/datadog/core/workers/queue.rb +100 -1
  57. data/lib/datadog/data_streams/processor.rb +1 -1
  58. data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
  59. data/lib/datadog/data_streams/transport/http.rb +5 -6
  60. data/lib/datadog/data_streams/transport/stats.rb +3 -17
  61. data/lib/datadog/di/contrib/active_record.rb +31 -5
  62. data/lib/datadog/di/el/compiler.rb +8 -4
  63. data/lib/datadog/di/error.rb +5 -0
  64. data/lib/datadog/di/instrumenter.rb +17 -4
  65. data/lib/datadog/di/probe_builder.rb +2 -1
  66. data/lib/datadog/di/probe_manager.rb +37 -31
  67. data/lib/datadog/di/probe_notification_builder.rb +15 -2
  68. data/lib/datadog/di/remote.rb +89 -84
  69. data/lib/datadog/di/transport/diagnostics.rb +7 -35
  70. data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
  71. data/lib/datadog/di/transport/http/input.rb +1 -31
  72. data/lib/datadog/di/transport/http.rb +28 -17
  73. data/lib/datadog/di/transport/input.rb +7 -34
  74. data/lib/datadog/di.rb +61 -5
  75. data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
  76. data/lib/datadog/open_feature/remote.rb +3 -10
  77. data/lib/datadog/open_feature/transport.rb +9 -11
  78. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  79. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
  80. data/lib/datadog/opentelemetry/metrics.rb +21 -14
  81. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
  82. data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
  83. data/lib/datadog/profiling/collectors/info.rb +2 -1
  84. data/lib/datadog/profiling/component.rb +12 -11
  85. data/lib/datadog/profiling/http_transport.rb +4 -1
  86. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  87. data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
  88. data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
  89. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  90. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
  91. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  92. data/lib/datadog/tracing/remote.rb +1 -9
  93. data/lib/datadog/tracing/span_event.rb +2 -2
  94. data/lib/datadog/tracing/span_operation.rb +9 -4
  95. data/lib/datadog/tracing/trace_operation.rb +44 -6
  96. data/lib/datadog/tracing/tracer.rb +42 -16
  97. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  98. data/lib/datadog/tracing/transport/http.rb +15 -9
  99. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  100. data/lib/datadog/tracing/transport/traces.rb +6 -66
  101. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  102. data/lib/datadog/tracing/writer.rb +1 -0
  103. data/lib/datadog/version.rb +2 -2
  104. metadata +7 -13
  105. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  106. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  107. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  108. data/lib/datadog/data_streams/transport/http/api.rb +0 -33
  109. data/lib/datadog/data_streams/transport/http/client.rb +0 -21
  110. data/lib/datadog/di/transport/http/api.rb +0 -42
  111. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  112. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'telemetry'
4
- require_relative 'http/api'
3
+ require_relative '../../encoding'
5
4
  require_relative '../../transport/http'
5
+ require_relative 'telemetry'
6
6
 
7
7
  module Datadog
8
8
  module Core
@@ -10,6 +10,16 @@ module Datadog
10
10
  module Transport
11
11
  # Namespace for HTTP transport components
12
12
  module HTTP
13
+ AGENT_TELEMETRY = Telemetry::API::Endpoint.new(
14
+ '/telemetry/proxy/api/v2/apmtelemetry',
15
+ Core::Encoding::JSONEncoder,
16
+ )
17
+
18
+ AGENTLESS_TELEMETRY = Telemetry::API::Endpoint.new(
19
+ '/api/v2/apmtelemetry',
20
+ Core::Encoding::JSONEncoder,
21
+ )
22
+
13
23
  module_function
14
24
 
15
25
  # Builds a new Transport::HTTP::Client with default settings
@@ -18,18 +28,14 @@ module Datadog
18
28
  agent_settings:,
19
29
  logger:,
20
30
  api_key: nil,
21
- api_version: nil,
22
31
  headers: nil
23
32
  )
24
- Core::Transport::HTTP.build(api_instance_class: Telemetry::API::Instance,
33
+ Core::Transport::HTTP.build(
25
34
  logger: logger,
26
35
  agent_settings: agent_settings,
27
- api_version: api_version,
28
- headers: headers) do |transport|
29
- apis = API.defaults
30
-
31
- transport.api API::AGENTLESS_TELEMETRY, apis[API::AGENTLESS_TELEMETRY]
32
-
36
+ headers: headers
37
+ ) do |transport|
38
+ transport.api 'agentless_telemetry', AGENTLESS_TELEMETRY
33
39
  # Call block to apply any customization, if provided
34
40
  yield(transport) if block_given?
35
41
  end.to_transport(Core::Telemetry::Transport::Telemetry::Transport).tap do |transport|
@@ -42,15 +48,14 @@ module Datadog
42
48
  def agent_telemetry(
43
49
  agent_settings:,
44
50
  logger:,
45
- api_version: nil,
46
51
  headers: nil
47
52
  )
48
- Core::Transport::HTTP.build(api_instance_class: Telemetry::API::Instance,
53
+ Core::Transport::HTTP.build(
49
54
  logger: logger,
50
- agent_settings: agent_settings, api_version: api_version, headers: headers) do |transport|
51
- apis = API.defaults
52
-
53
- transport.api API::AGENT_TELEMETRY, apis[API::AGENT_TELEMETRY]
55
+ agent_settings: agent_settings,
56
+ headers: headers
57
+ ) do |transport|
58
+ transport.api 'agent_telemetry', AGENT_TELEMETRY
54
59
 
55
60
  # Call block to apply any customization, if provided
56
61
  yield(transport) if block_given?
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../transport/parcel'
4
+ require_relative '../../transport/transport'
4
5
  require_relative 'http/telemetry'
5
6
 
6
7
  module Datadog
@@ -23,23 +24,15 @@ module Datadog
23
24
  end
24
25
  end
25
26
 
26
- class Transport
27
- attr_reader :client, :apis, :default_api, :current_api_id, :logger
27
+ class Transport < Core::Transport::Transport
28
28
  attr_accessor :api_key
29
29
 
30
- def initialize(apis, default_api, logger:)
31
- @apis = apis
32
- @logger = logger
33
-
34
- @client = Core::Telemetry::Transport::HTTP::Telemetry::Client.new(@apis[default_api], logger: logger)
35
- end
36
-
37
30
  def send_telemetry(request_type:, payload:)
38
31
  json = JSON.dump(payload)
39
32
  parcel = EncodedParcel.new(json)
40
33
  request = Request.new(request_type, parcel, api_key)
41
34
 
42
- @client.send_telemetry_payload(request)
35
+ @client.send_request(:telemetry, request)
43
36
  # Perform no error checking here
44
37
  end
45
38
  end
@@ -9,7 +9,10 @@ require_relative '../workers/queue'
9
9
  module Datadog
10
10
  module Core
11
11
  module Telemetry
12
- # Accumulates events and sends them to the API at a regular interval, including heartbeat event.
12
+ # Accumulates events and sends them to the API at a regular interval,
13
+ # including heartbeat event.
14
+ #
15
+ # @api private
13
16
  class Worker
14
17
  include Core::Workers::Queue
15
18
  include Core::Workers::Polling
@@ -40,11 +43,23 @@ module Datadog
40
43
  self.enabled = enabled
41
44
  # Workers::IntervalLoop settings
42
45
  self.loop_base_interval = metrics_aggregation_interval_seconds
43
- self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
46
+ # We actually restart the worker after fork, but this is done
47
+ # via the AtForkMonkeyPatch rather than the worker fork policy
48
+ # because we also need to reset state outside of the worker
49
+ # (e.g. the metrics).
50
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
44
51
 
45
52
  @shutdown_timeout = shutdown_timeout
46
53
  @buffer_size = buffer_size
47
54
 
55
+ initialize_state
56
+ end
57
+
58
+ # To make the method calls clear, the initialization code is in this
59
+ # method called +initialize_state+ which is called from +after_fork+.
60
+ # This way users of this class (e.g. telemetry Component) do not
61
+ # need to invoke +initialize_state+ directly, which can be confusing.
62
+ private def initialize_state
48
63
  self.buffer = buffer_klass.new(@buffer_size)
49
64
 
50
65
  @initial_event_once = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
@@ -53,12 +68,13 @@ module Datadog
53
68
  attr_reader :logger
54
69
  attr_reader :initial_event_once
55
70
  attr_reader :initial_event
71
+ attr_reader :emitter
56
72
 
57
73
  # Returns true if worker thread is successfully started,
58
74
  # false if worker thread was not started but telemetry is enabled,
59
75
  # nil if telemetry is disabled.
60
76
  def start(initial_event)
61
- return if !enabled? || forked?
77
+ return unless enabled?
62
78
 
63
79
  @initial_event = initial_event
64
80
 
@@ -79,7 +95,7 @@ module Datadog
79
95
  # for not enqueueing event (presently) is that telemetry is disabled
80
96
  # altogether, and in this case other methods return nil.
81
97
  def enqueue(event)
82
- return if !enabled? || forked?
98
+ return unless enabled?
83
99
 
84
100
  buffer.push(event)
85
101
  true
@@ -102,38 +118,16 @@ module Datadog
102
118
  # been flushed.
103
119
  #
104
120
  # @api private
105
- def flush
106
- return true unless enabled? || !run_loop?
107
-
108
- started = Utils::Time.get_time
109
- loop do
110
- # The AppStarted event is triggered by the worker itself,
111
- # from the worker thread. As such the main thread has no way
112
- # to delay itself until that event is queued and we need some
113
- # way to wait until that event is sent out to assert on it in
114
- # the test suite. Check the run once flag which *should*
115
- # indicate the event has been queued (at which point our queue
116
- # depth check should waint until it's sent).
117
- # This is still a hack because the flag can be overridden
118
- # either way with or without the event being sent out.
119
- # Note that if the AppStarted sending fails, this check
120
- # will return false and flushing will be blocked until the
121
- # 15 second timeout.
122
- # Note that the first wait interval between telemetry event
123
- # sending is 10 seconds, the timeout needs to be strictly
124
- # greater than that.
125
- return true if buffer.empty? && !in_iteration? && sent_initial_event?
126
-
127
- sleep 0.5
128
-
129
- return false if Utils::Time.get_time - started > 15
130
- end
121
+ def flush(timeout: nil)
122
+ # Increase default timeout to 15 seconds - see the comment in
123
+ # +idle?+ for more details.
124
+ super(timeout: timeout || 15)
131
125
  end
132
126
 
133
127
  private
134
128
 
135
129
  def perform(*events)
136
- return if !enabled? || forked?
130
+ return unless enabled?
137
131
 
138
132
  if need_initial_event?
139
133
  started!
@@ -189,7 +183,9 @@ module Datadog
189
183
  # dependencies and send the new ones.
190
184
  # System tests demand only one instance of this event per
191
185
  # dependency.
192
- send_event(Event::AppDependenciesLoaded.new) if @dependency_collection && initial_event.class.eql?(Telemetry::Event::AppStarted) # standard:disable Style/ClassEqualityComparison:
186
+ if @dependency_collection && initial_event.app_started?
187
+ send_event(Event::AppDependenciesLoaded.new)
188
+ end
193
189
 
194
190
  true
195
191
  else
@@ -212,6 +208,8 @@ module Datadog
212
208
  res
213
209
  end
214
210
 
211
+ # This method overrides Queue's dequeue method and does LIFO instead
212
+ # of FIFO that Queue implements. Why?
215
213
  def dequeue
216
214
  buffer.pop
217
215
  end
@@ -240,6 +238,45 @@ module Datadog
240
238
  disable!
241
239
  end
242
240
 
241
+ # Call this method in a forked child to reset the state of this worker.
242
+ #
243
+ # Discard any accumulated events since they will be sent by
244
+ # the parent.
245
+ # Discard any accumulated metrics.
246
+ # Restart the worker thread, if it was running in the parent process.
247
+ #
248
+ # This method cannot be called +after_fork+ because workers define
249
+ # and call +after_fork+ which is supposed to do different things.
250
+ def after_fork_monkey_patched
251
+ # If telemetry is disabled, we still reset the state to avoid
252
+ # having wrong state. It is possible that in the future telemetry
253
+ # will be re-enabled after errors.
254
+ initialize_state
255
+ # In the child process, we get a new runtime_id.
256
+ # As such we need to send AppStarted event.
257
+ # In the parent process, the event may have been the
258
+ # SynthAppClientConfigurationChange instead of AppStarted,
259
+ # and in that case we need to convert it to the "regular"
260
+ # AppStarted event.
261
+ if defined?(@initial_event) && @initial_event.is_a?(Event::SynthAppClientConfigurationChange)
262
+ # It would be great to just replace the initial event in
263
+ # +initialize_state+ method. Unfortunately this event requires
264
+ # the entire component tree to build its payload, which we
265
+ # 1) don't currently have in telemetry and
266
+ # 2) don't want to keep a permanent reference to in any case.
267
+ # Therefore we have this +reset!+ method that changes the
268
+ # event type while keeping the payload.
269
+ @initial_event.reset! # steep:ignore NoMethod
270
+ end
271
+
272
+ if enabled? && !worker.nil?
273
+ # Start the background thread if it was started in the parent
274
+ # process (which requires telemetry to be enabled).
275
+ # This should be done after all of the state resets.
276
+ perform
277
+ end
278
+ end
279
+
243
280
  # Deduplicate logs by counting the number of repeated occurrences of the same log
244
281
  # entry and replacing them with a single entry with the calculated `count` value.
245
282
  # Non-log events are unchanged.
@@ -270,6 +307,25 @@ module Datadog
270
307
 
271
308
  other_events + uniq_logs
272
309
  end
310
+
311
+ def idle?
312
+ # The AppStarted event is triggered by the worker itself,
313
+ # from the worker thread. As such the main thread has no way
314
+ # to delay itself until that event is queued and we need some
315
+ # way to wait until that event is sent out to assert on it in
316
+ # the test suite. Check the run once flag which *should*
317
+ # indicate the event has been queued (at which point our queue
318
+ # depth check should wait until it's sent).
319
+ # This is still a hack because the flag can be overridden
320
+ # either way with or without the event being sent out.
321
+ # Note that if the AppStarted sending fails, this check
322
+ # will return false and flushing will be blocked until the
323
+ # 15 second timeout.
324
+ # Note that the first wait interval between telemetry event
325
+ # sending is 10 seconds, the timeout needs to be strictly
326
+ # greater than that.
327
+ super && sent_initial_event?
328
+ end
273
329
  end
274
330
  end
275
331
  end
@@ -8,6 +8,8 @@ module Datadog
8
8
  module Ext
9
9
  module HTTP
10
10
  HEADER_CONTAINER_ID = 'Datadog-Container-ID'
11
+ HEADER_ENTITY_ID = 'Datadog-Entity-ID'
12
+ HEADER_EXTERNAL_ENV = 'Datadog-External-Env'
11
13
  HEADER_DD_API_KEY = 'DD-API-KEY'
12
14
  # Tells agent that `_dd.top_level` metrics have been set by the tracer.
13
15
  # The agent will not calculate top-level spans but instead trust the tracer tagging.
@@ -9,13 +9,18 @@ module Datadog
9
9
  module API
10
10
  # Endpoint
11
11
  class Endpoint
12
- attr_reader \
13
- :verb,
14
- :path
12
+ attr_reader :verb
13
+ attr_reader :path
15
14
 
16
- def initialize(verb, path)
15
+ # TODO Currently only Traces transport specifies an encoder.
16
+ # Other transports perform encoding "inline" / ad-hoc.
17
+ # They should probably use this encoder field instead.
18
+ attr_reader :encoder
19
+
20
+ def initialize(verb, path, encoder: nil)
17
21
  @verb = verb
18
22
  @path = path
23
+ @encoder = encoder
19
24
  end
20
25
 
21
26
  def call(env)
@@ -7,36 +7,19 @@ module Datadog
7
7
  module API
8
8
  # An API configured with adapter and routes
9
9
  class Instance
10
- # Raised when an endpoint is invoked on an API that is not the
11
- # of expected API class for that endpoint.
12
- class EndpointNotSupportedError < StandardError
13
- attr_reader :spec, :endpoint_name
14
-
15
- def initialize(endpoint_name, spec)
16
- @spec = spec
17
- @endpoint_name = endpoint_name
18
-
19
- super(message)
20
- end
21
-
22
- def message
23
- "#{endpoint_name} not supported for this API!"
24
- end
25
- end
26
-
27
10
  attr_reader \
28
11
  :adapter,
29
12
  :headers,
30
- :spec
13
+ :endpoint
31
14
 
32
- def initialize(spec, adapter, options = {})
33
- @spec = spec
15
+ def initialize(endpoint, adapter, options = {})
16
+ @endpoint = endpoint
34
17
  @adapter = adapter
35
18
  @headers = options.fetch(:headers, {})
36
19
  end
37
20
 
38
21
  def encoder
39
- spec.encoder
22
+ endpoint.encoder
40
23
  end
41
24
 
42
25
  def call(env)
@@ -13,7 +13,6 @@ module Datadog
13
13
  REGISTRY = Datadog::Core::Transport::HTTP::Adapters::Registry.new
14
14
 
15
15
  attr_reader \
16
- :api_instance_class,
17
16
  :apis,
18
17
  :api_options,
19
18
  :default_adapter,
@@ -21,7 +20,7 @@ module Datadog
21
20
  :default_headers,
22
21
  :logger
23
22
 
24
- def initialize(api_instance_class:, logger: Datadog.logger)
23
+ def initialize(logger: Datadog.logger)
25
24
  # Global settings
26
25
  @default_adapter = nil
27
26
  @default_headers = {}
@@ -33,7 +32,6 @@ module Datadog
33
32
  # API settings
34
33
  @api_options = {}
35
34
 
36
- @api_instance_class = api_instance_class
37
35
  @logger = logger
38
36
 
39
37
  yield(self) if block_given?
@@ -73,6 +71,12 @@ module Datadog
73
71
  @apis[key] = spec
74
72
 
75
73
  # Apply as default API, if specified to do so.
74
+ #
75
+ # This code also sets the first defined API to be the default API.
76
+ # In APIs without fallbacks (currently, everything other than
77
+ # tracing's Traces, though DI Input will also have fallbacks soon)
78
+ # there is only one declaration of `transport.api`, and that
79
+ # API is set as the default API in the transport by this line.
76
80
  @default_api = key if options.delete(:default) || @default_api.nil?
77
81
 
78
82
  # Save all other settings for initialization
@@ -94,7 +98,7 @@ module Datadog
94
98
  def to_api_instances
95
99
  raise NoApisError if @apis.empty?
96
100
 
97
- @apis.inject(Datadog::Core::Transport::HTTP::API::Map.new) do |instances, (key, spec)|
101
+ @apis.inject(API::Map.new) do |instances, (key, spec)|
98
102
  instances.tap do
99
103
  api_options = @api_options[key].dup
100
104
 
@@ -107,7 +111,7 @@ module Datadog
107
111
  api_options[:headers] = @default_headers.merge((api_options[:headers] || {}))
108
112
 
109
113
  # Add API::Instance with all settings
110
- instances[key] = api_instance_class.new(
114
+ instances[key] = API::Instance.new(
111
115
  spec,
112
116
  adapter,
113
117
  api_options
@@ -11,21 +11,32 @@ module Datadog
11
11
  #
12
12
  # @api private
13
13
  class Client
14
- attr_reader :api, :logger
14
+ attr_reader :instance, :logger
15
15
 
16
- def initialize(api, logger:)
17
- @api = api
16
+ def initialize(instance, logger:)
17
+ @instance = instance
18
18
  @logger = logger
19
19
  end
20
20
 
21
- private
22
-
23
- def send_request(request, &block)
21
+ def send_request(action, request)
24
22
  # Build request into env
25
23
  env = build_env(request)
26
24
 
27
- # Get responses from API
28
- yield(api, env).tap do |response|
25
+ # Get responses from API.
26
+ # All of our APIs send only one type of request each.
27
+ instance.endpoint.call(env) do |request_env|
28
+ instance.call(request_env)
29
+ end.tap do |response|
30
+ unless response.ok?
31
+ # This logging is on debug level.
32
+ # To report the failed operations on lower levels,
33
+ # throttling needs to be implemented because
34
+ # agent unavailability can produce a lot of spam that would
35
+ # be not desired by customers.
36
+ # Some transports do report failed operations on warn level
37
+ # with such throttling.
38
+ logger.debug { "send_request #{action.inspect} failed: #{response.inspect}" }
39
+ end
29
40
  on_response(response)
30
41
  end
31
42
  rescue => exception
@@ -12,16 +12,16 @@ module Datadog
12
12
  module HTTP
13
13
  # Add adapters to registry
14
14
  Builder::REGISTRY.set(
15
- Transport::HTTP::Adapters::Net,
15
+ Core::Transport::HTTP::Adapters::Net,
16
16
  Core::Configuration::Ext::Agent::HTTP::ADAPTER
17
17
  )
18
18
  Builder::REGISTRY.set(
19
- Transport::HTTP::Adapters::Test,
20
- Transport::Ext::Test::ADAPTER
19
+ Core::Transport::HTTP::Adapters::Test,
20
+ Core::Transport::Ext::Test::ADAPTER
21
21
  )
22
22
  Builder::REGISTRY.set(
23
- Transport::HTTP::Adapters::UnixSocket,
24
- Transport::Ext::UnixSocket::ADAPTER
23
+ Core::Transport::HTTP::Adapters::UnixSocket,
24
+ Core::Transport::Ext::UnixSocket::ADAPTER
25
25
  )
26
26
 
27
27
  module_function
@@ -29,8 +29,13 @@ module Datadog
29
29
  # Helper function that delegates to Builder.new
30
30
  # but is under HTTP namespace so that client code requires this file
31
31
  # to get the adapters configured, and not the builder directly.
32
- def build(api_instance_class:, agent_settings:, logger: Datadog.logger, api_version: nil, headers: nil, &block)
33
- Builder.new(api_instance_class: api_instance_class, logger: logger) do |transport|
32
+ def build(
33
+ agent_settings:,
34
+ logger: Datadog.logger,
35
+ headers: nil,
36
+ &block
37
+ )
38
+ Builder.new(logger: logger) do |transport|
34
39
  transport.adapter(agent_settings)
35
40
  transport.headers(default_headers)
36
41
 
@@ -38,34 +43,32 @@ module Datadog
38
43
  yield transport
39
44
 
40
45
  # Apply any settings given by options
41
- transport.default_api = api_version if api_version
42
46
  transport.headers(headers) if headers
43
47
  end
44
48
  end
45
49
 
46
50
  def default_headers
47
51
  {
48
- Datadog::Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_TOP_LEVEL => '1',
49
- Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG =>
52
+ Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_TOP_LEVEL => '1',
53
+ Core::Transport::Ext::HTTP::HEADER_META_LANG =>
50
54
  Datadog::Core::Environment::Ext::LANG,
51
- Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG_VERSION =>
55
+ Core::Transport::Ext::HTTP::HEADER_META_LANG_VERSION =>
52
56
  Datadog::Core::Environment::Ext::LANG_VERSION,
53
- Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER =>
57
+ Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER =>
54
58
  Datadog::Core::Environment::Ext::LANG_INTERPRETER,
55
- Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER_VENDOR =>
59
+ Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER_VENDOR =>
56
60
  Core::Environment::Ext::LANG_ENGINE,
57
- Datadog::Core::Transport::Ext::HTTP::HEADER_META_TRACER_VERSION =>
61
+ Core::Transport::Ext::HTTP::HEADER_META_TRACER_VERSION =>
58
62
  Datadog::Core::Environment::Ext::GEM_DATADOG_VERSION
59
63
  }.tap do |headers|
60
- # Add container ID, if present.
61
- if (container_id = Datadog::Core::Environment::Container.container_id)
62
- headers[Datadog::Core::Transport::Ext::HTTP::HEADER_CONTAINER_ID] = container_id
63
- end
64
+ # Add application container info
65
+ headers.merge!(Core::Environment::Container.to_headers)
66
+
64
67
  # TODO: inject configuration rather than reading from global here
65
68
  unless Datadog.configuration.apm.tracing.enabled
66
69
  # Sending this header to the agent will disable metrics computation (and billing) on the agent side
67
70
  # by pretending it has already been done on the library side.
68
- headers[Datadog::Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_STATS] = 'yes'
71
+ headers[Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_STATS] = 'yes'
69
72
  end
70
73
  end
71
74
  end
@@ -37,6 +37,15 @@ module Datadog
37
37
  maybe_code = if respond_to?(:code)
38
38
  " code:#{code}," # steep:ignore
39
39
  end
40
+ payload = self.payload
41
+ # Truncation thresholds are arbitrary but we need to truncate the
42
+ # payload here because outputting multi-MB request body to the
43
+ # log is not useful.
44
+ #
45
+ # Note that payload can be nil here.
46
+ if payload && payload.length > 2000 # steep:ignore
47
+ payload = Utils::Truncation.truncate_in_middle(payload, 1500, 500) # steep:ignore
48
+ end
40
49
  "#{self.class} ok?:#{ok?},#{maybe_code} unsupported?:#{unsupported?}, " \
41
50
  "not_found?:#{not_found?}, client_error?:#{client_error?}, " \
42
51
  "server_error?:#{server_error?}, internal_error?:#{internal_error?}, " \
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'http/client'
4
+
5
+ module Datadog
6
+ module Core
7
+ module Transport
8
+ # Raised when configured with an unknown API version
9
+ class UnknownApiVersionError < StandardError
10
+ attr_reader :version
11
+
12
+ def initialize(version)
13
+ super
14
+
15
+ @version = version
16
+ end
17
+
18
+ def message
19
+ "No matching transport API for version #{version}!"
20
+ end
21
+ end
22
+
23
+ # Raised when the API verson to downgrade to does not map to a
24
+ # defined API.
25
+ class NoDowngradeAvailableError < StandardError
26
+ attr_reader :version
27
+
28
+ def initialize(version)
29
+ super
30
+
31
+ @version = version
32
+ end
33
+
34
+ def message
35
+ "No downgrade from transport API version #{version} is available!"
36
+ end
37
+ end
38
+
39
+ # Base class for transports.
40
+ class Transport
41
+ attr_reader :client, :apis, :default_api, :current_api_id, :logger
42
+
43
+ class << self
44
+ # The HTTP client class to use for requests, derived from
45
+ # Core::Transport::HTTP::Client.
46
+ #
47
+ # Important: this attribute is NOT inherited by derived classes -
48
+ # it must be set by every Transport class that wants to have a
49
+ # non-default HTTP::Client instance.
50
+ attr_accessor :http_client_class
51
+ end
52
+
53
+ def initialize(apis, default_api, logger:)
54
+ @apis = apis
55
+ @default_api = default_api
56
+ @logger = logger
57
+
58
+ set_api!(default_api)
59
+ end
60
+
61
+ def current_api
62
+ apis[current_api_id]
63
+ end
64
+
65
+ private
66
+
67
+ def set_api!(api_id)
68
+ raise UnknownApiVersionError, api_id unless apis.key?(api_id)
69
+
70
+ @current_api_id = api_id
71
+ client_class = self.class.http_client_class || Core::Transport::HTTP::Client
72
+ @client = client_class.new(current_api, logger: logger) # steep:ignore
73
+ end
74
+
75
+ def downgrade?(response)
76
+ return false unless apis.fallbacks.key?(current_api_id)
77
+
78
+ response.not_found? || response.unsupported?
79
+ end
80
+
81
+ def downgrade!
82
+ downgrade_api_id = apis.fallbacks[current_api_id]
83
+ raise NoDowngradeAvailableError, current_api_id if downgrade_api_id.nil?
84
+
85
+ set_api!(downgrade_api_id)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -67,7 +67,9 @@ module Datadog
67
67
 
68
68
  private
69
69
 
70
+ # Use this method only after checking that limit is not nil.
70
71
  def check_limit!
72
+ # @type ivar @limit: Integer
71
73
  if @retries >= @limit
72
74
  @failed = true
73
75
  @ran_once = true