From f3dc3d2079625916a4c427130af2b59e29ae3cd2 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 25 Nov 2025 15:45:10 -0500 Subject: [PATCH 1/3] DRY transports by extracting base class with API functionality --- lib/datadog/core/remote/transport/config.rb | 19 +-- .../core/remote/transport/http/config.rb | 9 -- .../core/remote/transport/http/negotiation.rb | 9 -- .../core/remote/transport/negotiation.rb | 18 +-- .../telemetry/transport/http/telemetry.rb | 9 -- .../core/telemetry/transport/telemetry.rb | 13 +- lib/datadog/core/transport/http.rb | 26 ++-- lib/datadog/core/transport/http/builder.rb | 6 + lib/datadog/core/transport/http/client.rb | 8 +- lib/datadog/core/transport/transport.rb | 90 +++++++++++++ lib/datadog/data_streams/transport/http.rb | 1 - .../data_streams/transport/http/client.rb | 21 --- .../data_streams/transport/http/stats.rb | 1 - lib/datadog/data_streams/transport/stats.rb | 20 +-- lib/datadog/di/transport/diagnostics.rb | 18 +-- lib/datadog/di/transport/http/diagnostics.rb | 9 -- lib/datadog/di/transport/http/input.rb | 9 -- lib/datadog/di/transport/input.rb | 18 +-- lib/datadog/tracing/transport/http/traces.rb | 13 -- lib/datadog/tracing/transport/io/client.rb | 2 +- lib/datadog/tracing/transport/traces.rb | 70 +--------- sig/datadog/core/transport/http/client.rbs | 6 +- sig/datadog/core/transport/transport.rbs | 58 +++++++++ .../data_streams/transport/http/client.rbs | 11 -- sig/datadog/data_streams/transport/stats.rbs | 4 +- sig/datadog/tracing/transport/http/traces.rbs | 1 - sig/datadog/tracing/transport/traces.rbs | 49 +------ spec/datadog/core/telemetry/emitter_spec.rb | 3 +- .../core/transport/http/client_spec.rb | 14 +- spec/datadog/core/transport/transport_spec.rb | 123 ++++++++++++++++++ .../data_streams/transport/stats_spec.rb | 11 +- .../tracing/transport/http/client_spec.rb | 21 +-- .../tracing/transport/http/traces_spec.rb | 4 +- spec/datadog/tracing/transport/traces_spec.rb | 103 +-------------- 34 files changed, 364 insertions(+), 433 deletions(-) create mode 100644 lib/datadog/core/transport/transport.rb delete mode 100644 lib/datadog/data_streams/transport/http/client.rb create mode 100644 sig/datadog/core/transport/transport.rbs delete mode 100644 sig/datadog/data_streams/transport/http/client.rbs create mode 100644 spec/datadog/core/transport/transport_spec.rb diff --git a/lib/datadog/core/remote/transport/config.rb b/lib/datadog/core/remote/transport/config.rb index d21825ba520..34d4f76859c 100644 --- a/lib/datadog/core/remote/transport/config.rb +++ b/lib/datadog/core/remote/transport/config.rb @@ -2,6 +2,7 @@ require_relative '../../../core/transport/request' require_relative '../../../core/transport/parcel' +require_relative '../../../core/transport/transport' require_relative 'http/config' module Datadog @@ -23,27 +24,13 @@ class Request < Datadog::Core::Transport::Request end # Config transport - class Transport - attr_reader :client, :apis, :default_api, :current_api_id, :logger - - def initialize(apis, default_api, logger: Datadog.logger) - @apis = apis - @logger = logger - - @client = Remote::Transport::HTTP::Config::Client.new(current_api, logger: logger) - end - - ##### there is only one transport! it's negotiation! + class Transport < Core::Transport::Transport def send_config(payload) json = JSON.dump(payload) parcel = EncodedParcel.new(json) request = Request.new(parcel) - @client.send_config_payload(request) - end - - def current_api - @apis[HTTP::API::V7] + @client.send_request(:config, request) end end end diff --git a/lib/datadog/core/remote/transport/http/config.rb b/lib/datadog/core/remote/transport/http/config.rb index def76f36100..6256d1f3f90 100644 --- a/lib/datadog/core/remote/transport/http/config.rb +++ b/lib/datadog/core/remote/transport/http/config.rb @@ -178,15 +178,6 @@ def initialize(key, value) end end - # Remote transport HTTP client - class Client < Core::Transport::HTTP::Client - def send_config_payload(request) - send_request(request) do |api, env| - api.send_config(env) - end - end - end - module API # Extensions for HTTP API Spec module Spec diff --git a/lib/datadog/core/remote/transport/http/negotiation.rb b/lib/datadog/core/remote/transport/http/negotiation.rb index 2c9b2298c8e..0deb1da9728 100644 --- a/lib/datadog/core/remote/transport/http/negotiation.rb +++ b/lib/datadog/core/remote/transport/http/negotiation.rb @@ -43,15 +43,6 @@ def initialize(http_response, options = {}) attr_reader :version, :endpoints, :config, :span_events end - # Remote negotiation HTTP client - class Client < Core::Transport::HTTP::Client - def send_info_payload(request) - send_request(request) do |api, env| - api.send_info(env) - end - end - end - module API # Extensions for HTTP API Spec module Spec diff --git a/lib/datadog/core/remote/transport/negotiation.rb b/lib/datadog/core/remote/transport/negotiation.rb index 0973d552bac..8f6d37fa031 100644 --- a/lib/datadog/core/remote/transport/negotiation.rb +++ b/lib/datadog/core/remote/transport/negotiation.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative '../../../core/transport/request' +require_relative '../../../core/transport/transport' require_relative 'http/negotiation' # TODO: Resolve conceptual conundrum @@ -32,24 +33,11 @@ class Request < Datadog::Core::Transport::Request end # Negotiation transport - class Transport - attr_reader :client, :apis, :default_api, :current_api_id, :logger - - def initialize(apis, default_api, logger: Datadog.logger) - @apis = apis - @logger = logger - - @client = Remote::Transport::HTTP::Negotiation::Client.new(current_api, logger: logger) - end - + class Transport < Core::Transport::Transport def send_info request = Request.new - @client.send_info_payload(request) - end - - def current_api - @apis[HTTP::API::ROOT] + @client.send_request(:info, request) end end end diff --git a/lib/datadog/core/telemetry/transport/http/telemetry.rb b/lib/datadog/core/telemetry/transport/http/telemetry.rb index fab9b835bc1..159b2a8cc81 100644 --- a/lib/datadog/core/telemetry/transport/http/telemetry.rb +++ b/lib/datadog/core/telemetry/transport/http/telemetry.rb @@ -12,15 +12,6 @@ module Telemetry module Transport module HTTP module Telemetry - class Client < Core::Transport::HTTP::Client - def send_telemetry_payload(request) - send_request(request) do |api, env| - # TODO how to make api have the derived type for steep? - api.send_telemetry(env) # steep:ignore - end - end - end - module API class Instance < Core::Transport::HTTP::API::Instance def send_telemetry(env) diff --git a/lib/datadog/core/telemetry/transport/telemetry.rb b/lib/datadog/core/telemetry/transport/telemetry.rb index 81a68cd3895..152b328ffc2 100644 --- a/lib/datadog/core/telemetry/transport/telemetry.rb +++ b/lib/datadog/core/telemetry/transport/telemetry.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative '../../transport/parcel' +require_relative '../../transport/transport' require_relative 'http/telemetry' module Datadog @@ -23,23 +24,15 @@ def initialize(request_type, parcel, api_key) end end - class Transport - attr_reader :client, :apis, :default_api, :current_api_id, :logger + class Transport < Core::Transport::Transport attr_accessor :api_key - def initialize(apis, default_api, logger:) - @apis = apis - @logger = logger - - @client = Core::Telemetry::Transport::HTTP::Telemetry::Client.new(@apis[default_api], logger: logger) - end - def send_telemetry(request_type:, payload:) json = JSON.dump(payload) parcel = EncodedParcel.new(json) request = Request.new(request_type, parcel, api_key) - @client.send_telemetry_payload(request) + @client.send_request(:telemetry, request) # Perform no error checking here end end diff --git a/lib/datadog/core/transport/http.rb b/lib/datadog/core/transport/http.rb index b43eff70fa5..7c32a1d068f 100644 --- a/lib/datadog/core/transport/http.rb +++ b/lib/datadog/core/transport/http.rb @@ -12,16 +12,16 @@ module Transport module HTTP # Add adapters to registry Builder::REGISTRY.set( - Transport::HTTP::Adapters::Net, + Core::Transport::HTTP::Adapters::Net, Core::Configuration::Ext::Agent::HTTP::ADAPTER ) Builder::REGISTRY.set( - Transport::HTTP::Adapters::Test, - Transport::Ext::Test::ADAPTER + Core::Transport::HTTP::Adapters::Test, + Core::Transport::Ext::Test::ADAPTER ) Builder::REGISTRY.set( - Transport::HTTP::Adapters::UnixSocket, - Transport::Ext::UnixSocket::ADAPTER + Core::Transport::HTTP::Adapters::UnixSocket, + Core::Transport::Ext::UnixSocket::ADAPTER ) module_function @@ -45,27 +45,27 @@ def build(api_instance_class:, agent_settings:, logger: Datadog.logger, api_vers def default_headers { - Datadog::Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_TOP_LEVEL => '1', - Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG => + Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_TOP_LEVEL => '1', + Core::Transport::Ext::HTTP::HEADER_META_LANG => Datadog::Core::Environment::Ext::LANG, - Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG_VERSION => + Core::Transport::Ext::HTTP::HEADER_META_LANG_VERSION => Datadog::Core::Environment::Ext::LANG_VERSION, - Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER => + Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER => Datadog::Core::Environment::Ext::LANG_INTERPRETER, - Datadog::Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER_VENDOR => + Core::Transport::Ext::HTTP::HEADER_META_LANG_INTERPRETER_VENDOR => Core::Environment::Ext::LANG_ENGINE, - Datadog::Core::Transport::Ext::HTTP::HEADER_META_TRACER_VERSION => + Core::Transport::Ext::HTTP::HEADER_META_TRACER_VERSION => Datadog::Core::Environment::Ext::GEM_DATADOG_VERSION }.tap do |headers| # Add container ID, if present. if (container_id = Datadog::Core::Environment::Container.container_id) - headers[Datadog::Core::Transport::Ext::HTTP::HEADER_CONTAINER_ID] = container_id + headers[Core::Transport::Ext::HTTP::HEADER_CONTAINER_ID] = container_id end # TODO: inject configuration rather than reading from global here unless Datadog.configuration.apm.tracing.enabled # Sending this header to the agent will disable metrics computation (and billing) on the agent side # by pretending it has already been done on the library side. - headers[Datadog::Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_STATS] = 'yes' + headers[Core::Transport::Ext::HTTP::HEADER_CLIENT_COMPUTED_STATS] = 'yes' end end end diff --git a/lib/datadog/core/transport/http/builder.rb b/lib/datadog/core/transport/http/builder.rb index 9935c57488c..c653ff9bf27 100644 --- a/lib/datadog/core/transport/http/builder.rb +++ b/lib/datadog/core/transport/http/builder.rb @@ -73,6 +73,12 @@ def api(key, spec, options = {}) @apis[key] = spec # Apply as default API, if specified to do so. + # + # This code also sets the first defined API to be the default API. + # In APIs without fallbacks (currently, everything other than + # tracing's Traces, though DI Input will also have fallbacks soon) + # there is only one declaration of `transport.api`, and that + # API is set as the default API in the transport by this line. @default_api = key if options.delete(:default) || @default_api.nil? # Save all other settings for initialization diff --git a/lib/datadog/core/transport/http/client.rb b/lib/datadog/core/transport/http/client.rb index 25907de8c53..17f6c889697 100644 --- a/lib/datadog/core/transport/http/client.rb +++ b/lib/datadog/core/transport/http/client.rb @@ -18,9 +18,15 @@ def initialize(api, logger:) @logger = logger end + def send_request(action, request, &block) + send_request_impl(request) do |api, env| + api.public_send("send_#{action}", env) + end + end + private - def send_request(request, &block) + def send_request_impl(request, &block) # Build request into env env = build_env(request) diff --git a/lib/datadog/core/transport/transport.rb b/lib/datadog/core/transport/transport.rb new file mode 100644 index 00000000000..3c0831b9777 --- /dev/null +++ b/lib/datadog/core/transport/transport.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require_relative 'http/client' + +module Datadog + module Core + module Transport + # Raised when configured with an unknown API version + class UnknownApiVersionError < StandardError + attr_reader :version + + def initialize(version) + super + + @version = version + end + + def message + "No matching transport API for version #{version}!" + end + end + + # Raised when the API verson to downgrade to does not map to a + # defined API. + class NoDowngradeAvailableError < StandardError + attr_reader :version + + def initialize(version) + super + + @version = version + end + + def message + "No downgrade from transport API version #{version} is available!" + end + end + + # Base class for transports. + class Transport + attr_reader :client, :apis, :default_api, :current_api_id, :logger + + class << self + # The HTTP client class to use for requests, derived from + # Core::Transport::HTTP::Client. + # + # Important: this attribute is NOT inherited by derived classes - + # it must be set by every Transport class that wants to have a + # non-default HTTP::Client instance. + attr_accessor :http_client_class + end + + def initialize(apis, default_api, logger:) + @apis = apis + @default_api = default_api + @logger = logger + + set_api!(default_api) + end + + def current_api + apis[current_api_id] + end + + private + + def set_api!(api_id) + raise UnknownApiVersionError, api_id unless apis.key?(api_id) + + @current_api_id = api_id + client_class = self.class.http_client_class || Core::Transport::HTTP::Client + @client = client_class.new(current_api, logger: logger) + end + + def downgrade?(response) + return false unless apis.fallbacks.key?(current_api_id) + + response.not_found? || response.unsupported? + end + + def downgrade! + downgrade_api_id = apis.fallbacks[current_api_id] + raise NoDowngradeAvailableError, current_api_id if downgrade_api_id.nil? + + set_api!(downgrade_api_id) + end + end + end + end +end diff --git a/lib/datadog/data_streams/transport/http.rb b/lib/datadog/data_streams/transport/http.rb index 26eacc14fd9..98679e09673 100644 --- a/lib/datadog/data_streams/transport/http.rb +++ b/lib/datadog/data_streams/transport/http.rb @@ -2,7 +2,6 @@ require_relative '../../core/transport/http' require_relative 'http/api' -require_relative 'http/client' require_relative 'http/stats' require_relative 'stats' diff --git a/lib/datadog/data_streams/transport/http/client.rb b/lib/datadog/data_streams/transport/http/client.rb deleted file mode 100644 index c6e5b2679e0..00000000000 --- a/lib/datadog/data_streams/transport/http/client.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../core/transport/http/client' - -module Datadog - module DataStreams - module Transport - module HTTP - # HTTP client for Data Streams Monitoring - class Client < Core::Transport::HTTP::Client - def send_stats_payload(request) - send_request(request) do |api, env| - # TODO how to make api have the derived type for steep? - api.send_stats(env) # steep:ignore - end - end - end - end - end - end -end diff --git a/lib/datadog/data_streams/transport/http/stats.rb b/lib/datadog/data_streams/transport/http/stats.rb index 151f7acb8f8..f313fe113c2 100644 --- a/lib/datadog/data_streams/transport/http/stats.rb +++ b/lib/datadog/data_streams/transport/http/stats.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative '../stats' -require_relative 'client' require_relative '../../../core/transport/http/response' require_relative '../../../core/transport/http/api/endpoint' require_relative '../../../core/transport/http/api/spec' diff --git a/lib/datadog/data_streams/transport/stats.rb b/lib/datadog/data_streams/transport/stats.rb index c497bccf635..69699ca69ae 100644 --- a/lib/datadog/data_streams/transport/stats.rb +++ b/lib/datadog/data_streams/transport/stats.rb @@ -4,6 +4,7 @@ require 'zlib' require_relative '../../core/transport/parcel' require_relative '../../core/transport/request' +require_relative '../../core/transport/transport' module Datadog module DataStreams @@ -25,18 +26,7 @@ class Request < Datadog::Core::Transport::Request end # Transport for Data Streams Monitoring stats - class Transport - attr_reader :client, :apis, :current_api_id, :logger - - def initialize(apis, default_api, logger:) - @apis = apis - @logger = logger - @default_api = default_api - @current_api_id = default_api - - @client = DataStreams::Transport::HTTP::Client.new(current_api, logger: @logger) - end - + class Transport < Core::Transport::Transport def send_stats(payload) # MessagePack encode and gzip compress the payload msgpack_data = MessagePack.pack(payload) @@ -47,11 +37,7 @@ def send_stats(payload) request = Request.new(parcel) # Send to agent - client.send_stats_payload(request) - end - - def current_api - apis[@current_api_id] + client.send_request(:stats, request) end end end diff --git a/lib/datadog/di/transport/diagnostics.rb b/lib/datadog/di/transport/diagnostics.rb index c2389728d7d..90337710a54 100644 --- a/lib/datadog/di/transport/diagnostics.rb +++ b/lib/datadog/di/transport/diagnostics.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative '../../core/transport/parcel' +require_relative '../../core/transport/transport' require_relative 'http/diagnostics' module Datadog @@ -14,26 +15,13 @@ class EncodedParcel class Request < Datadog::Core::Transport::Request end - class Transport - attr_reader :client, :apis, :default_api, :current_api_id, :logger - - def initialize(apis, default_api, logger:) - @apis = apis - @logger = logger - - @client = DI::Transport::HTTP::Diagnostics::Client.new(current_api, logger: logger) - end - - def current_api - @apis[HTTP::API::DIAGNOSTICS] - end - + class Transport < Core::Transport::Transport def send_diagnostics(payload) json = JSON.dump(payload) parcel = EncodedParcel.new(json) request = Request.new(parcel) - response = @client.send_diagnostics_payload(request) + response = @client.send_request(:diagnostics, request) unless response.ok? # TODO Datadog::Core::Transport::InternalErrorResponse # does not have +code+ method, what is the actual API of diff --git a/lib/datadog/di/transport/http/diagnostics.rb b/lib/datadog/di/transport/http/diagnostics.rb index 0243840b2e1..1a07696c840 100644 --- a/lib/datadog/di/transport/http/diagnostics.rb +++ b/lib/datadog/di/transport/http/diagnostics.rb @@ -2,21 +2,12 @@ require_relative '../../../core/transport/http/api/instance' require_relative '../../../core/transport/http/api/spec' -require_relative '../../../core/transport/http/client' module Datadog module DI module Transport module HTTP module Diagnostics - class Client < Core::Transport::HTTP::Client - def send_diagnostics_payload(request) - send_request(request) do |api, env| - api.send_diagnostics(env) - end - end - end - module API class Instance < Core::Transport::HTTP::API::Instance def send_diagnostics(env) diff --git a/lib/datadog/di/transport/http/input.rb b/lib/datadog/di/transport/http/input.rb index 0f1d8ef8610..6176e8b324b 100644 --- a/lib/datadog/di/transport/http/input.rb +++ b/lib/datadog/di/transport/http/input.rb @@ -2,21 +2,12 @@ require_relative '../../../core/transport/http/api/instance' require_relative '../../../core/transport/http/api/spec' -require_relative '../../../core/transport/http/client' module Datadog module DI module Transport module HTTP module Input - class Client < Core::Transport::HTTP::Client - def send_input_payload(request) - send_request(request) do |api, env| - api.send_input(env) - end - end - end - module API class Instance < Core::Transport::HTTP::API::Instance def send_input(env) diff --git a/lib/datadog/di/transport/input.rb b/lib/datadog/di/transport/input.rb index b559f97e120..6e829b73f07 100644 --- a/lib/datadog/di/transport/input.rb +++ b/lib/datadog/di/transport/input.rb @@ -5,6 +5,7 @@ require_relative '../../core/tag_builder' require_relative '../../core/transport/parcel' require_relative '../../core/transport/request' +require_relative '../../core/transport/transport' require_relative '../error' require_relative 'http/input' @@ -26,9 +27,7 @@ def initialize(parcel, serialized_tags) end end - class Transport - attr_reader :client, :apis, :default_api, :current_api_id, :logger - + class Transport < Core::Transport::Transport # The limit on an individual snapshot payload, aka "log line", # is 1 MB. # @@ -48,17 +47,6 @@ class Transport # max chunk size, it will still get sent out. DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024 - def initialize(apis, default_api, logger:) - @apis = apis - @logger = logger - - @client = DI::Transport::HTTP::Input::Client.new(current_api, logger: logger) - end - - def current_api - @apis[HTTP::API::INPUT] - end - def send_input(payload, tags) # Tags are the same for all chunks, serialize them one time. serialized_tags = Core::TagBuilder.serialize_tags(tags) @@ -101,7 +89,7 @@ def send_input_chunk(chunked_payload, serialized_tags) parcel = EncodedParcel.new(chunked_payload) request = Request.new(parcel, serialized_tags) - response = @client.send_input_payload(request) + response = @client.send_request(:input, request) unless response.ok? # TODO Datadog::Core::Transport::InternalErrorResponse # does not have +code+ method, what is the actual API of diff --git a/lib/datadog/tracing/transport/http/traces.rb b/lib/datadog/tracing/transport/http/traces.rb index 5f19b9995cd..41fc4ec217d 100644 --- a/lib/datadog/tracing/transport/http/traces.rb +++ b/lib/datadog/tracing/transport/http/traces.rb @@ -3,7 +3,6 @@ require 'json' require_relative '../traces' -require_relative 'client' require_relative '../../../core/transport/http/response' require_relative '../../../core/transport/http/api/endpoint' require_relative '../../../core/transport/http/api/spec' @@ -27,15 +26,6 @@ def initialize(http_response, options = {}) end end - # Extensions for HTTP client - module Client - def send_traces_payload(request) - send_request(request) do |api, env| - api.send_traces(env) - end - end - end - module API # HTTP API Spec class Spec < Core::Transport::HTTP::API::Spec @@ -111,9 +101,6 @@ def call(env, &block) end end end - - # Add traces behavior to transport components - HTTP::Client.include(Traces::Client) end end end diff --git a/lib/datadog/tracing/transport/io/client.rb b/lib/datadog/tracing/transport/io/client.rb index 906e023122f..39dfdfc2b5d 100644 --- a/lib/datadog/tracing/transport/io/client.rb +++ b/lib/datadog/tracing/transport/io/client.rb @@ -42,7 +42,7 @@ def send_request(request) response rescue => e message = - "Internal error during IO transport request. Cause: #{e.class.name} #{e.message} " \ + "Internal error during IO transport request. Cause: #{e.class.name}: #{e.message} " \ "Location: #{Array(e.backtrace).first}" # Log error diff --git a/lib/datadog/tracing/transport/traces.rb b/lib/datadog/tracing/transport/traces.rb index 5eaabea27a6..0f946280399 100644 --- a/lib/datadog/tracing/transport/traces.rb +++ b/lib/datadog/tracing/transport/traces.rb @@ -3,7 +3,9 @@ require_relative '../../core/chunker' require_relative '../../core/transport/parcel' require_relative '../../core/transport/request' +require_relative '../../core/transport/transport' require_relative '../../core/utils/array' +require_relative 'http/client' require_relative 'serializable_trace' require_relative 'trace_formatter' @@ -121,16 +123,8 @@ def encode_trace(encoder, trace, logger:, native_events_supported:) # This class initializes the HTTP client, breaks down large # batches of traces into smaller chunks and handles # API version downgrade handshake. - class Transport - attr_reader :client, :apis, :default_api, :current_api_id, :logger - - def initialize(apis, default_api, logger: Datadog.logger) - @apis = apis - @default_api = default_api - @logger = logger - - change_api!(default_api) - end + class Transport < Core::Transport::Transport + self.http_client_class = Tracing::Transport::HTTP::Client def send_traces(traces) encoder = current_api.encoder @@ -143,7 +137,7 @@ def send_traces(traces) responses = chunker.encode_in_chunks(traces.lazy).map do |encoded_traces, trace_count| request = Request.new(EncodedParcel.new(encoded_traces, trace_count)) - client.send_traces_payload(request).tap do |response| + client.send_request(:traces, request).tap do |response| if downgrade?(response) downgrade! return send_traces(traces) @@ -174,32 +168,8 @@ def stats @client.stats end - def current_api - apis[@current_api_id] - end - private - def downgrade?(response) - return false unless apis.fallbacks.key?(@current_api_id) - - response.not_found? || response.unsupported? - end - - def downgrade! - downgrade_api_id = apis.fallbacks[@current_api_id] - raise NoDowngradeAvailableError, @current_api_id if downgrade_api_id.nil? - - change_api!(downgrade_api_id) - end - - def change_api!(api_id) - raise UnknownApiVersionError, api_id unless apis.key?(api_id) - - @current_api_id = api_id - @client = HTTP::Client.new(current_api, logger: logger) - end - # Queries the agent for native span events serialization support. # This changes how the serialization of span events performed. def native_events_supported? @@ -219,36 +189,6 @@ def native_events_supported? false end end - - # Raised when configured with an unknown API version - class UnknownApiVersionError < StandardError - attr_reader :version - - def initialize(version) - super - - @version = version - end - - def message - "No matching transport API for version #{version}!" - end - end - - # Raised when configured with an unknown API version - class NoDowngradeAvailableError < StandardError - attr_reader :version - - def initialize(version) - super - - @version = version - end - - def message - "No downgrade from transport API version #{version} is available!" - end - end end end end diff --git a/sig/datadog/core/transport/http/client.rbs b/sig/datadog/core/transport/http/client.rbs index a8668bb8c5e..065b1b79a56 100644 --- a/sig/datadog/core/transport/http/client.rbs +++ b/sig/datadog/core/transport/http/client.rbs @@ -11,11 +11,13 @@ module Datadog def initialize: (HTTP::API::Instance api, logger: Core::Logger) -> void + def send_request: (Symbol action, Core::Transport::Request request) -> (Core::Transport::HTTP::Response | Core::Transport::InternalErrorResponse) + private - def send_request: (Transport::Request request) { (HTTP::API::Instance, Core::Transport::HTTP::Env) -> Core::Transport::HTTP::Response } -> (Core::Transport::HTTP::Response | Core::Transport::InternalErrorResponse) + def send_request_impl: (Core::Transport::Request request) { (HTTP::API::Instance, Core::Transport::HTTP::Env) -> Core::Transport::HTTP::Response } -> (Core::Transport::HTTP::Response | Core::Transport::InternalErrorResponse) - def build_env: (Transport::Request request) -> Core::Transport::HTTP::Env + def build_env: (Core::Transport::Request request) -> Core::Transport::HTTP::Env def on_response: (Core::Transport::HTTP::Response response) -> void def on_exception: (Exception exception) -> void diff --git a/sig/datadog/core/transport/transport.rbs b/sig/datadog/core/transport/transport.rbs new file mode 100644 index 00000000000..1360a6a194c --- /dev/null +++ b/sig/datadog/core/transport/transport.rbs @@ -0,0 +1,58 @@ +module Datadog + module Core + module Transport + class UnknownApiVersionError < StandardError + @version: untyped + + attr_reader version: untyped + + def initialize: (untyped version) -> void + + def message: () -> ::String + end + class NoDowngradeAvailableError < StandardError + @version: untyped + + attr_reader version: untyped + + def initialize: (untyped version) -> void + + def message: () -> ::String + end + class Transport + @apis: untyped + + @default_api: untyped + + @logger: untyped + + @current_api_id: untyped + + @client: untyped + + attr_reader client: untyped + + attr_reader apis: untyped + + attr_reader default_api: untyped + + attr_reader current_api_id: untyped + + attr_reader logger: untyped + attr_accessor self.http_client_class: untyped + + def initialize: (untyped apis, untyped default_api, logger: untyped) -> void + + def current_api: () -> untyped + + private + + def set_api!: (untyped api_id) -> untyped + + def downgrade?: (untyped response) -> (false | untyped) + + def downgrade!: () -> untyped + end + end + end +end diff --git a/sig/datadog/data_streams/transport/http/client.rbs b/sig/datadog/data_streams/transport/http/client.rbs deleted file mode 100644 index fd69f06df12..00000000000 --- a/sig/datadog/data_streams/transport/http/client.rbs +++ /dev/null @@ -1,11 +0,0 @@ -module Datadog - module DataStreams - module Transport - module HTTP - class Client < Core::Transport::HTTP::Client - def send_stats_payload: (Transport::Stats::Request request) -> (Core::Transport::HTTP::Response | Core::Transport::InternalErrorResponse) - end - end - end - end -end diff --git a/sig/datadog/data_streams/transport/stats.rbs b/sig/datadog/data_streams/transport/stats.rbs index 2d7a94c4c8e..8a3126d89f4 100644 --- a/sig/datadog/data_streams/transport/stats.rbs +++ b/sig/datadog/data_streams/transport/stats.rbs @@ -20,9 +20,9 @@ module Datadog @logger: Core::Logger @default_api: ::String @current_api_id: ::String - @client: HTTP::Client + @client: Core::Transport::HTTP::Client - attr_reader client: HTTP::Client + attr_reader client: Core::Transport::HTTP::Client attr_reader apis: ::Hash[::String, HTTP::Stats::API::Instance] attr_reader current_api_id: ::String attr_reader logger: Core::Logger diff --git a/sig/datadog/tracing/transport/http/traces.rbs b/sig/datadog/tracing/transport/http/traces.rbs index 996dbcb6136..63058a82b47 100644 --- a/sig/datadog/tracing/transport/http/traces.rbs +++ b/sig/datadog/tracing/transport/http/traces.rbs @@ -15,7 +15,6 @@ module Datadog def initialize: (untyped http_response, ?::Hash[untyped, untyped] options) -> void end class Client < Core::Transport::HTTP::Client - def send_traces_payload: (untyped request) -> untyped end module API diff --git a/sig/datadog/tracing/transport/traces.rbs b/sig/datadog/tracing/transport/traces.rbs index 221bf1e5737..6f1796add8e 100644 --- a/sig/datadog/tracing/transport/traces.rbs +++ b/sig/datadog/tracing/transport/traces.rbs @@ -45,63 +45,16 @@ module Datadog module Encoder def self?.encode_trace: (untyped encoder, untyped trace, logger: untyped, native_events_supported: untyped) -> untyped end - class Transport - @apis: untyped - - @default_api: untyped - - @logger: untyped - - @current_api_id: untyped - - @client: untyped - + class Transport < Core::Transport::Transport @native_events_supported: bool - attr_reader client: untyped - - attr_reader apis: untyped - - attr_reader default_api: untyped - - attr_reader current_api_id: untyped - - attr_reader logger: untyped - - def initialize: (untyped apis, untyped default_api, ?logger: untyped) -> void - def send_traces: (Array[Tracing::TraceOperation] traces) -> untyped def stats: () -> untyped - def current_api: () -> untyped - private - def downgrade?: (untyped response) -> (false | untyped) - - def downgrade!: () -> untyped - - def change_api!: (untyped api_id) -> untyped def native_events_supported?: () -> bool - class UnknownApiVersionError < StandardError - @version: untyped - - attr_reader version: untyped - - def initialize: (untyped version) -> void - - def message: () -> ::String - end - class NoDowngradeAvailableError < StandardError - @version: untyped - - attr_reader version: untyped - - def initialize: (untyped version) -> void - - def message: () -> ::String - end end end end diff --git a/spec/datadog/core/telemetry/emitter_spec.rb b/spec/datadog/core/telemetry/emitter_spec.rb index 193ebc08703..38475ba6d05 100644 --- a/spec/datadog/core/telemetry/emitter_spec.rb +++ b/spec/datadog/core/telemetry/emitter_spec.rb @@ -7,7 +7,8 @@ RSpec.describe Datadog::Core::Telemetry::Emitter do subject(:emitter) { described_class.new(transport, logger: logger) } let(:logger) { logger_allowing_debug } - let(:transport) { double(Datadog::Core::Telemetry::Transport::HTTP::Telemetry::Client) } + # TODO Why is a Client instance assigned to a transport? + let(:transport) { double(Datadog::Core::Transport::HTTP::Client) } let(:response) do double(Datadog::Core::Transport::HTTP::Adapters::Net::Response, ok?: response_ok) diff --git a/spec/datadog/core/transport/http/client_spec.rb b/spec/datadog/core/transport/http/client_spec.rb index 745e0658741..4c63c92719a 100644 --- a/spec/datadog/core/transport/http/client_spec.rb +++ b/spec/datadog/core/transport/http/client_spec.rb @@ -11,8 +11,8 @@ it { is_expected.to have_attributes(api: api) } end - describe '#send_request' do - subject(:send_request) { client.send(:send_request, request, &block) } + describe '#send_request_impl' do + subject(:send_request_impl) { client.send(:send_request_impl, request, &block) } let(:request) { instance_double(Datadog::Core::Transport::Request) } let(:response_class) { stub_const('TestResponse', Class.new { include Datadog::Core::Transport::HTTP::Response }) } @@ -61,7 +61,7 @@ it 'makes only one attempt and returns an internal error response' do is_expected.to be_a_kind_of(Datadog::Core::Transport::InternalErrorResponse) - expect(send_request.error).to be_a_kind_of(error_class) + expect(send_request_impl.error).to be_a_kind_of(error_class) expect(handler).to have_received(:api).with(api).once end end @@ -73,14 +73,14 @@ expect(logger).to receive(:debug).twice end - subject(:send_request) do - client.send(:send_request, request, &block) - client.send(:send_request, request, &block) + subject(:send_request_impl) do + client.send(:send_request_impl, request, &block) + client.send(:send_request_impl, request, &block) end it 'makes only one attempt per request and returns an internal error response' do is_expected.to be_a_kind_of(Datadog::Core::Transport::InternalErrorResponse) - expect(send_request.error).to be_a_kind_of(error_class) + expect(send_request_impl.error).to be_a_kind_of(error_class) expect(handler).to have_received(:api).with(api).twice end end diff --git a/spec/datadog/core/transport/transport_spec.rb b/spec/datadog/core/transport/transport_spec.rb new file mode 100644 index 00000000000..8001f37847a --- /dev/null +++ b/spec/datadog/core/transport/transport_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +require 'datadog/core/transport/transport' + +# Some of the tests from spec/datadog/tracing/transport/traces_spec.rb +# could be ported to this file, if they are adjusted to not reference the +# Tracing::Traces transport but instead use a mock transport. + +RSpec.describe Datadog::Core::Transport::Transport do + let(:logger) { logger_allowing_debug } + + subject(:transport) { described_class.new(apis, current_api_id, logger: logger) } + + shared_context 'APIs with fallbacks' do + let(:current_api_id) { :v2 } + let(:apis) do + Datadog::Core::Transport::HTTP::API::Map[ + v2: api_v2, + v1: api_v1 + ].with_fallbacks(v2: :v1) + end + + let(:api_v1) { instance_double(Datadog::Core::Transport::HTTP::API::Instance, 'v1', encoder: encoder_v1) } + let(:api_v2) { instance_double(Datadog::Core::Transport::HTTP::API::Instance, 'v2', encoder: encoder_v2) } + let(:encoder_v1) { instance_double(Datadog::Core::Encoding::Encoder, 'v1', content_type: 'text/plain') } + let(:encoder_v2) { instance_double(Datadog::Core::Encoding::Encoder, 'v2', content_type: 'text/csv') } + end + + describe '#initialize' do + include_context 'APIs with fallbacks' + + it { is_expected.to have_attributes(apis: apis, current_api_id: current_api_id) } + end + + describe '#downgrade?' do + include_context 'APIs with fallbacks' + + subject(:downgrade?) { transport.send(:downgrade?, response) } + + let(:response) { instance_double(Datadog::Core::Transport::Response) } + + context 'when there is no fallback' do + let(:current_api_id) { :v1 } + + it { is_expected.to be false } + end + + context 'when a fallback is available' do + let(:current_api_id) { :v2 } + + context 'and the response isn\'t \'not found\' or \'unsupported\'' do + before do + allow(response).to receive(:not_found?).and_return(false) + allow(response).to receive(:unsupported?).and_return(false) + end + + it { is_expected.to be false } + end + + context 'and the response is \'not found\'' do + before do + allow(response).to receive(:not_found?).and_return(true) + allow(response).to receive(:unsupported?).and_return(false) + end + + it { is_expected.to be true } + end + + context 'and the response is \'unsupported\'' do + before do + allow(response).to receive(:not_found?).and_return(false) + allow(response).to receive(:unsupported?).and_return(true) + end + + it { is_expected.to be true } + end + end + end + + describe '#current_api' do + include_context 'APIs with fallbacks' + + subject(:current_api) { transport.current_api } + + it { is_expected.to be(api_v2) } + end + + describe '#set_api!' do + include_context 'APIs with fallbacks' + + subject(:set_api!) { transport.send(:set_api!, api_id) } + + context 'when the API ID does not match an API' do + let(:api_id) { :v99 } + + it { expect { set_api! }.to raise_error(Datadog::Core::Transport::UnknownApiVersionError) } + end + + context 'when the API ID matches an API' do + let(:api_id) { :v1 } + + it { expect { set_api! }.to change { transport.current_api }.from(api_v2).to(api_v1) } + end + end + + describe '#downgrade!' do + include_context 'APIs with fallbacks' + + subject(:downgrade!) { transport.send(:downgrade!) } + + context 'when the API has no fallback' do + let(:current_api_id) { :v1 } + + it { expect { downgrade! }.to raise_error(Datadog::Core::Transport::NoDowngradeAvailableError) } + end + + context 'when the API has fallback' do + let(:current_api_id) { :v2 } + + it { expect { downgrade! }.to change { transport.current_api }.from(api_v2).to(api_v1) } + end + end +end diff --git a/spec/datadog/data_streams/transport/stats_spec.rb b/spec/datadog/data_streams/transport/stats_spec.rb index 7969b6d11a8..e2e36885e16 100644 --- a/spec/datadog/data_streams/transport/stats_spec.rb +++ b/spec/datadog/data_streams/transport/stats_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'datadog/data_streams/transport/stats' -require 'datadog/data_streams/transport/http/client' +require 'datadog/core/transport/http/client' RSpec.describe Datadog::DataStreams::Transport::Stats do let(:logger) { logger_allowing_debug } @@ -12,10 +12,10 @@ let(:default_api) { :v01 } let(:apis) { {v01: api_instance} } let(:api_instance) { instance_double(Datadog::Core::Transport::HTTP::API::Instance) } - let(:client) { instance_double(Datadog::DataStreams::Transport::HTTP::Client) } + let(:client) { instance_double(Datadog::Core::Transport::HTTP::Client) } before do - allow(Datadog::DataStreams::Transport::HTTP::Client).to receive(:new) + allow(Datadog::Core::Transport::HTTP::Client).to receive(:new) .with(api_instance, logger: logger) .and_return(client) end @@ -42,7 +42,7 @@ let(:response) { instance_double(Datadog::Core::Transport::HTTP::Response, ok?: true) } before do - allow(client).to receive(:send_stats_payload).and_return(response) + allow(client).to receive(:send_request).and_return(response) end it 'encodes payload with MessagePack' do @@ -56,7 +56,8 @@ end it 'sends the compressed data via client' do - expect(client).to receive(:send_stats_payload) do |request| + expect(client).to receive(:send_request) do |action, request| + expect(action).to eq(:stats) expect(request).to be_a(Datadog::DataStreams::Transport::Stats::Request) expect(request.parcel).to be_a(Datadog::DataStreams::Transport::Stats::EncodedParcel) diff --git a/spec/datadog/tracing/transport/http/client_spec.rb b/spec/datadog/tracing/transport/http/client_spec.rb index d7a1853ed70..fb2d4d00631 100644 --- a/spec/datadog/tracing/transport/http/client_spec.rb +++ b/spec/datadog/tracing/transport/http/client_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' -require 'datadog' require 'datadog/tracing/transport/http/client' RSpec.describe Datadog::Tracing::Transport::HTTP::Client do @@ -13,11 +12,15 @@ it { is_expected.to have_attributes(api: api) } end - describe '#send_request' do - subject(:send_request) { client.send(:send_request, request, &block) } + describe '#send_request_impl' do + subject(:send_request_impl) { client.send(:send_request_impl, request, &block) } let(:request) { instance_double(Datadog::Core::Transport::Request) } - let(:response_class) { stub_const('TestResponse', Class.new { include Datadog::Core::Transport::HTTP::Response }) } + let(:response_class) do + stub_const('TestResponse', Class.new do + include Datadog::Core::Transport::HTTP::Response + end) + end let(:response) { instance_double(response_class, code: double('status code')) } before { allow(Datadog.health_metrics).to receive(:send_metrics) } @@ -74,7 +77,7 @@ .with(kind_of(error_class)) is_expected.to be_a_kind_of(Datadog::Core::Transport::InternalErrorResponse) - expect(send_request.error).to be_a_kind_of(error_class) + expect(send_request_impl.error).to be_a_kind_of(error_class) expect(handler).to have_received(:api).with(api).once # Check log was written to appropriately @@ -90,9 +93,9 @@ allow(logger).to receive(:error) end - subject(:send_request) do - client.send(:send_request, request, &block) - client.send(:send_request, request, &block) + subject(:send_request_impl) do + client.send(:send_request_impl, request, &block) + client.send(:send_request_impl, request, &block) end before do @@ -108,7 +111,7 @@ it 'makes only one attempt per request and returns an internal error response' do is_expected.to be_a_kind_of(Datadog::Core::Transport::InternalErrorResponse) - expect(send_request.error).to be_a_kind_of(error_class) + expect(send_request_impl.error).to be_a_kind_of(error_class) expect(handler).to have_received(:api).with(api).twice # Check log was written to appropriately diff --git a/spec/datadog/tracing/transport/http/traces_spec.rb b/spec/datadog/tracing/transport/http/traces_spec.rb index eef02e29ceb..32d172d194a 100644 --- a/spec/datadog/tracing/transport/http/traces_spec.rb +++ b/spec/datadog/tracing/transport/http/traces_spec.rb @@ -29,8 +29,8 @@ subject(:client) { described_class.new(api, logger: logger) } - describe '#send_traces_payload' do - subject(:send_traces_payload) { client.send_traces_payload(request) } + describe '#send_request' do + subject(:send_request_traces) { client.send_request(:traces, request) } let(:request) { instance_double(Datadog::Tracing::Transport::Traces::Request) } let(:response) { instance_double(Datadog::Tracing::Transport::HTTP::Traces::Response) } diff --git a/spec/datadog/tracing/transport/traces_spec.rb b/spec/datadog/tracing/transport/traces_spec.rb index 8bba3cacb65..ff853c6a8ec 100644 --- a/spec/datadog/tracing/transport/traces_spec.rb +++ b/spec/datadog/tracing/transport/traces_spec.rb @@ -213,8 +213,8 @@ allow(Datadog::Tracing::Transport::HTTP::Client).to receive(:new).with(api_v1, logger: logger).and_return(client_v1) allow(Datadog::Tracing::Transport::HTTP::Client).to receive(:new).with(api_v2, logger: logger).and_return(client_v2) - allow(client_v1).to receive(:send_traces_payload).with(request).and_return(response) - allow(client_v2).to receive(:send_traces_payload).with(request).and_return(response) + allow(client_v1).to receive(:send_request).with(:traces, request).and_return(response) + allow(client_v2).to receive(:send_request).with(:traces, request).and_return(response) allow(Datadog::Tracing::Transport::Traces::Request).to receive(:new).and_return(request) @@ -227,7 +227,7 @@ it 'sends to only the current API once' do is_expected.to eq(responses) - expect(client_v2).to have_received(:send_traces_payload).with(request).once + expect(client_v2).to have_received(:send_request).with(:traces, request).once expect(health_metrics).to have_received(:transport_chunked).with(1) end @@ -269,8 +269,8 @@ it 'attempts each API once as it falls back after each failure' do is_expected.to eq(responses) - expect(client_v2).to have_received(:send_traces_payload).with(request).once - expect(client_v1).to have_received(:send_traces_payload).with(request).once + expect(client_v2).to have_received(:send_request).with(:traces, request).once + expect(client_v1).to have_received(:send_request).with(:traces, request).once expect(health_metrics).to have_received(:transport_chunked).with(1) end @@ -285,8 +285,8 @@ it 'attempts each API once as it falls back after each failure' do is_expected.to eq(responses) - expect(client_v2).to have_received(:send_traces_payload).with(request).once - expect(client_v1).to have_received(:send_traces_payload).with(request).once + expect(client_v2).to have_received(:send_request).with(:traces, request).once + expect(client_v1).to have_received(:send_request).with(:traces, request).once expect(health_metrics).to have_received(:transport_chunked).with(1) end @@ -409,93 +409,4 @@ end end end - - describe '#downgrade?' do - include_context 'APIs with fallbacks' - - subject(:downgrade?) { transport.send(:downgrade?, response) } - - let(:response) { instance_double(Datadog::Core::Transport::Response) } - - context 'when there is no fallback' do - let(:current_api_id) { :v1 } - - it { is_expected.to be false } - end - - context 'when a fallback is available' do - let(:current_api_id) { :v2 } - - context 'and the response isn\'t \'not found\' or \'unsupported\'' do - before do - allow(response).to receive(:not_found?).and_return(false) - allow(response).to receive(:unsupported?).and_return(false) - end - - it { is_expected.to be false } - end - - context 'and the response is \'not found\'' do - before do - allow(response).to receive(:not_found?).and_return(true) - allow(response).to receive(:unsupported?).and_return(false) - end - - it { is_expected.to be true } - end - - context 'and the response is \'unsupported\'' do - before do - allow(response).to receive(:not_found?).and_return(false) - allow(response).to receive(:unsupported?).and_return(true) - end - - it { is_expected.to be true } - end - end - end - - describe '#current_api' do - include_context 'APIs with fallbacks' - - subject(:current_api) { transport.current_api } - - it { is_expected.to be(api_v2) } - end - - describe '#change_api!' do - include_context 'APIs with fallbacks' - - subject(:change_api!) { transport.send(:change_api!, api_id) } - - context 'when the API ID does not match an API' do - let(:api_id) { :v99 } - - it { expect { change_api! }.to raise_error(described_class::UnknownApiVersionError) } - end - - context 'when the API ID matches an API' do - let(:api_id) { :v1 } - - it { expect { change_api! }.to change { transport.current_api }.from(api_v2).to(api_v1) } - end - end - - describe '#downgrade!' do - include_context 'APIs with fallbacks' - - subject(:downgrade!) { transport.send(:downgrade!) } - - context 'when the API has no fallback' do - let(:current_api_id) { :v1 } - - it { expect { downgrade! }.to raise_error(described_class::NoDowngradeAvailableError) } - end - - context 'when the API has fallback' do - let(:current_api_id) { :v2 } - - it { expect { downgrade! }.to change { transport.current_api }.from(api_v2).to(api_v1) } - end - end end From c87e32bb5fac208e98f9d2309cef8a8821795940 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 3 Dec 2025 15:07:49 -0500 Subject: [PATCH 2/3] remove send_request_impl --- lib/datadog/core/transport/http/client.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/datadog/core/transport/http/client.rb b/lib/datadog/core/transport/http/client.rb index 17f6c889697..8885f149976 100644 --- a/lib/datadog/core/transport/http/client.rb +++ b/lib/datadog/core/transport/http/client.rb @@ -18,20 +18,12 @@ def initialize(api, logger:) @logger = logger end - def send_request(action, request, &block) - send_request_impl(request) do |api, env| - api.public_send("send_#{action}", env) - end - end - - private - - def send_request_impl(request, &block) + def send_request(action, request) # Build request into env env = build_env(request) # Get responses from API - yield(api, env).tap do |response| + api.public_send("send_#{action}", env).tap do |response| on_response(response) end rescue => exception From 20fab872b4366c98c6acbd5a99f0a925064ae089 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 9 Dec 2025 08:41:53 -0500 Subject: [PATCH 3/3] Revert "remove send_request_impl" This reverts commit c87e32bb5fac208e98f9d2309cef8a8821795940. --- lib/datadog/core/transport/http/client.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/datadog/core/transport/http/client.rb b/lib/datadog/core/transport/http/client.rb index 8885f149976..17f6c889697 100644 --- a/lib/datadog/core/transport/http/client.rb +++ b/lib/datadog/core/transport/http/client.rb @@ -18,12 +18,20 @@ def initialize(api, logger:) @logger = logger end - def send_request(action, request) + def send_request(action, request, &block) + send_request_impl(request) do |api, env| + api.public_send("send_#{action}", env) + end + end + + private + + def send_request_impl(request, &block) # Build request into env env = build_env(request) # Get responses from API - api.public_send("send_#{action}", env).tap do |response| + yield(api, env).tap do |response| on_response(response) end rescue => exception