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

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(hostname, port, options = {}) ⇒ HTTPTransport



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ddtrace/transport.rb', line 48

def initialize(hostname, port, options = {})
  api_version = options.fetch(:api_version, V3)

  @hostname = hostname
  @port = port
  @api = API.fetch(api_version)
  @encoder = options[:encoder] || @api[:encoder].new
  @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
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

#services_endpointObject (readonly)

Returns the value of attribute services_endpoint.



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

def services_endpoint
  @services_endpoint
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



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

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.



122
123
124
125
126
127
128
129
130
# File 'lib/ddtrace/transport.rb', line 122

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

    @api = API.fetch(fallback_version)
    @encoder = @api[:encoder].new
    @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.



159
160
161
162
163
# File 'lib/ddtrace/transport.rb', line 159

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.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ddtrace/transport.rb', line 168

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_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)
    Datadog::Tracer.log.error("Client error: #{response.message}")
    @mutex.synchronize { @count_client_error += 1 }
  elsif server_error?(status_code)
    Datadog::Tracer.log.error("Server error: #{response.message}")
    @mutex.synchronize { @count_server_error += 1 }
  end

  status_code
rescue StandardError => e
  Datadog::Tracer.log.error(e.message)
  @mutex.synchronize { @count_internal_error += 1 }
  500
end

#informational?(code) ⇒ Boolean



132
133
134
# File 'lib/ddtrace/transport.rb', line 132

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



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ddtrace/transport.rb', line 101

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, read_timeout: TIMEOUT) { |http| http.request(request) }
    handle_response(response)
  rescue StandardError => e
    Datadog::Tracer.log.error(e.message)
    500
  end.tap do
    yield(response) if block_given?
  end
end

#redirect?(code) ⇒ Boolean



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

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

#send(endpoint, data) ⇒ Object

route the send to the right endpoint



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ddtrace/transport.rb', line 74

def send(endpoint, data)
  case endpoint
  when :services
    payload = @encoder.encode_services(data)
    status_code = post(@api[:services_endpoint], payload) do |response|
      process_callback(:services, response)
    end
  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



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

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

#statsObject



191
192
193
194
195
196
197
198
199
200
# File 'lib/ddtrace/transport.rb', line 191

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



136
137
138
# File 'lib/ddtrace/transport.rb', line 136

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