Class: Datadog::HTTPTransport

Inherits:
Object
  • Object
show all
Defined in:
lib/ddtrace/transport.rb

Overview

Transport class that handles the spans delivery to the local trace-agent. The class wraps a Net:HTTP instance so that the Transport is thread-safe. rubocop:disable Metrics/ClassLength

Constant Summary collapse

DEFAULT_AGENT_HOST =
'127.0.0.1'.freeze
DEFAULT_TRACE_AGENT_PORT =
'8126'.freeze
DEPRECATION_WARNING =
%(
Datadog::HTTPTransport is deprecated as of version 0.24.0.
It has been replaced by Datadog::Transport:HTTP, which is the new default.
You can create one with `Datadog::Transport::HTTP.default`
Datadog::HTTPTransport will be REMOVED in version 0.25.0).freeze
TIMEOUT =

seconds before the transport timeout

1
TRACE_COUNT_HEADER =

header containing the number of traces in a payload

'X-Datadog-Trace-Count'.freeze
RUBY_INTERPRETER =
RUBY_VERSION > '1.9' ? RUBY_ENGINE + '-' + RUBY_PLATFORM : 'ruby-' + RUBY_PLATFORM

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ HTTPTransport

Returns a new instance of HTTPTransport.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ddtrace/transport.rb', line 54

def initialize(options = {})
  # Log deprecation warning
  Datadog::Tracer.log.warn(DEPRECATION_WARNING)

  api_version = options.fetch(:api_version, V3)

  @hostname = options[:hostname] || ENV['DD_AGENT_HOST'] || DEFAULT_AGENT_HOST
  @port = options[:port] || ENV['DD_TRACE_AGENT_PORT'] || DEFAULT_TRACE_AGENT_PORT
  @api = API.fetch(api_version)
  @encoder = options[:encoder] || @api[:encoder]
  @response_callback = options[:response_callback]

  # overwrite the Content-type with the one chosen in the Encoder
  @headers = options.fetch(:headers, {})
  @headers['Content-Type'] = @encoder.content_type
  @headers['Datadog-Meta-Lang'] = 'ruby'
  @headers['Datadog-Meta-Lang-Version'] = RUBY_VERSION
  @headers['Datadog-Meta-Lang-Interpreter'] = RUBY_INTERPRETER
  @headers['Datadog-Meta-Tracer-Version'] = Datadog::VERSION::STRING

  # stats
  @mutex = Mutex.new
  @count_success = 0
  @count_client_error = 0
  @count_server_error = 0
  @count_internal_error = 0
  @count_consecutive_errors = 0
end

Instance Attribute Details

#hostnameObject

Returns the value of attribute hostname.



13
14
15
# File 'lib/ddtrace/transport.rb', line 13

def hostname
  @hostname
end

#portObject

Returns the value of attribute port.



13
14
15
# File 'lib/ddtrace/transport.rb', line 13

def port
  @port
end

#traces_endpointObject (readonly)

Returns the value of attribute traces_endpoint.



14
15
16
# File 'lib/ddtrace/transport.rb', line 14

def traces_endpoint
  @traces_endpoint
end

Instance Method Details

#client_error?(code) ⇒ Boolean

Returns:



153
154
155
# File 'lib/ddtrace/transport.rb', line 153

def client_error?(code)
  code.between?(400, 499)
end

#downgrade!Object

Downgrade the connection to a compatibility version of the HTTPTransport; this method should target a stable API that works whatever is the agent or the tracing client versions.



131
132
133
134
135
136
137
138
139
# File 'lib/ddtrace/transport.rb', line 131

def downgrade!
  @mutex.synchronize do
    fallback_version = @api.fetch(:fallback)

    @api = API.fetch(fallback_version)
    @encoder = @api[:encoder]
    @headers['Content-Type'] = @encoder.content_type
  end
end

#downgrade?(code) ⇒ Boolean

receiving a 404 means that we’re targeting an endpoint that is not available in the trace agent. Usually this means that we’ve an up-to-date tracing client, while running an obsolete agent. receiving a 415 means that we’re using an unsupported content-type with an existing endpoint. Usually this means that we’re using a newer encoder with a previous endpoint. In both cases, we’re going to downgrade the transporter encoder so that it will target a stable API.

Returns:



168
169
170
171
172
# File 'lib/ddtrace/transport.rb', line 168

def downgrade?(code)
  return unless @api[:fallback]

  code == 404 || code == 415
end

#handle_response(response) ⇒ Object

handles the server response; here you can log the trace-agent response or do something more complex to recover from a possible error. This function is handled within the HTTP mutex.synchronize so it’s thread-safe.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/ddtrace/transport.rb', line 177

def handle_response(response)
  status_code = response.code.to_i

  if success?(status_code)
    Datadog::Tracer.log.debug('Payload correctly sent to the trace agent.')
    @mutex.synchronize { @count_consecutive_errors = 0 }
    @mutex.synchronize { @count_success += 1 }
  elsif downgrade?(status_code)
    Datadog::Tracer.log.debug("calling the endpoint but received #{status_code}; downgrading the API")
  elsif client_error?(status_code)
    log_error_once("Client error: #{response.message}")
    @mutex.synchronize { @count_client_error += 1 }
  elsif server_error?(status_code)
    log_error_once("Server error: #{response.message}")
  end

  status_code
rescue StandardError => e
  log_error_once(e.message)
  @mutex.synchronize { @count_internal_error += 1 }

  500
end

#informational?(code) ⇒ Boolean

Returns:



141
142
143
# File 'lib/ddtrace/transport.rb', line 141

def informational?(code)
  code.between?(100, 199)
end

#post(url, data, count = nil) ⇒ Object

send data to the trace-agent; the method is thread-safe



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/ddtrace/transport.rb', line 108

def post(url, data, count = nil)
  begin
    Datadog::Tracer.log.debug("Sending data from process: #{Process.pid}")
    headers = count.nil? ? {} : { TRACE_COUNT_HEADER => count.to_s }
    headers = headers.merge(@headers)
    request = Net::HTTP::Post.new(url, headers)
    request.body = data

    response = Net::HTTP.start(@hostname, @port, open_timeout: TIMEOUT, read_timeout: TIMEOUT) do |http|
      http.request(request)
    end
    handle_response(response)
  rescue StandardError => e
    log_error_once(e.message)
    500
  end.tap do
    yield(response) if block_given?
  end
end

#redirect?(code) ⇒ Boolean

Returns:



149
150
151
# File 'lib/ddtrace/transport.rb', line 149

def redirect?(code)
  code.between?(300, 399)
end

#send(endpoint, data) ⇒ Object

route the send to the right endpoint



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ddtrace/transport.rb', line 84

def send(endpoint, data)
  case endpoint
  when :services
    return nil
  when :traces
    count = data.length
    payload = @encoder.encode_traces(data)
    status_code = post(@api[:traces_endpoint], payload, count) do |response|
      process_callback(:traces, response)
    end
  else
    Datadog::Tracer.log.error("Unsupported endpoint: #{endpoint}")
    return nil
  end

  if downgrade?(status_code)
    downgrade!
    send(endpoint, data)
  else
    status_code
  end
end

#server_error?(code) ⇒ Boolean

Returns:



157
158
159
# File 'lib/ddtrace/transport.rb', line 157

def server_error?(code)
  code.between?(500, 599)
end

#statsObject



201
202
203
204
205
206
207
208
209
210
# File 'lib/ddtrace/transport.rb', line 201

def stats
  @mutex.synchronize do
    {
      success: @count_success,
      client_error: @count_client_error,
      server_error: @count_server_error,
      internal_error: @count_internal_error
    }
  end
end

#success?(code) ⇒ Boolean

Returns:



145
146
147
# File 'lib/ddtrace/transport.rb', line 145

def success?(code)
  code.between?(200, 299)
end