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.

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

Returns a new instance of HTTPTransport.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/ddtrace/transport.rb', line 22

def initialize(hostname, port, options = {})
  @hostname = hostname
  @port = port
  @traces_endpoint = '/v0.3/traces'.freeze
  @services_endpoint = '/v0.3/services'.freeze
  @compatibility_mode = false
  @encoder = options.fetch(:encoder, Datadog::Encoding::MsgpackEncoder.new())

  # 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.



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

def hostname
  @hostname
end

#portObject

Returns the value of attribute port.



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

def port
  @port
end

#services_endpointObject (readonly)

Returns the value of attribute services_endpoint.



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

def services_endpoint
  @services_endpoint
end

#traces_endpointObject (readonly)

Returns the value of attribute traces_endpoint.



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

def traces_endpoint
  @traces_endpoint
end

Instance Method Details

#client_error?(code) ⇒ Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/ddtrace/transport.rb', line 106

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.



86
87
88
89
90
91
92
# File 'lib/ddtrace/transport.rb', line 86

def downgrade!
  @compatibility_mode = true
  @traces_endpoint = '/v0.2/traces'.freeze
  @services_endpoint = '/v0.2/services'.freeze
  @encoder = Datadog::Encoding::JSONEncoder.new()
  @headers['Content-Type'] = @encoder.content_type
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:

  • (Boolean)


121
122
123
# File 'lib/ddtrace/transport.rb', line 121

def downgrade?(code)
  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.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ddtrace/transport.rb', line 128

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

Returns:

  • (Boolean)


94
95
96
# File 'lib/ddtrace/transport.rb', line 94

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



69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ddtrace/transport.rb', line 69

def post(url, data, count = nil)
  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

#redirect?(code) ⇒ Boolean

Returns:

  • (Boolean)


102
103
104
# File 'lib/ddtrace/transport.rb', line 102

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

#send(endpoint, data) ⇒ Object

route the send to the right endpoint



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

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

  return status_code unless downgrade?(status_code) && !@compatibility_mode

  # the API endpoint is not available so we should downgrade the connection and re-try the call
  downgrade!
  send(endpoint, data)
end

#server_error?(code) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/ddtrace/transport.rb', line 110

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

#statsObject



151
152
153
154
155
156
157
158
159
160
# File 'lib/ddtrace/transport.rb', line 151

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:

  • (Boolean)


98
99
100
# File 'lib/ddtrace/transport.rb', line 98

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