131 lines
4 KiB
Go
131 lines
4 KiB
Go
package tracer
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// TODO(gbbr): find a more effective way to keep this up to date,
|
|
// e.g. via `go generate`
|
|
var tracerVersion = "v1.5.0"
|
|
|
|
const (
|
|
defaultHostname = "localhost"
|
|
defaultPort = "8126"
|
|
defaultAddress = defaultHostname + ":" + defaultPort
|
|
defaultHTTPTimeout = time.Second // defines the current timeout before giving up with the send process
|
|
traceCountHeader = "X-Datadog-Trace-Count" // header containing the number of traces in the payload
|
|
)
|
|
|
|
// Transport is an interface for span submission to the agent.
|
|
type transport interface {
|
|
send(p *payload) error
|
|
}
|
|
|
|
// newTransport returns a new Transport implementation that sends traces to a
|
|
// trace agent running on the given hostname and port. If the zero values for
|
|
// hostname and port are provided, the default values will be used ("localhost"
|
|
// for hostname, and "8126" for port).
|
|
//
|
|
// In general, using this method is only necessary if you have a trace agent
|
|
// running on a non-default port or if it's located on another machine.
|
|
func newTransport(addr string) transport {
|
|
return newHTTPTransport(addr)
|
|
}
|
|
|
|
// newDefaultTransport return a default transport for this tracing client
|
|
func newDefaultTransport() transport {
|
|
return newHTTPTransport(defaultAddress)
|
|
}
|
|
|
|
type httpTransport struct {
|
|
traceURL string // the delivery URL for traces
|
|
client *http.Client // the HTTP client used in the POST
|
|
headers map[string]string // the Transport headers
|
|
}
|
|
|
|
// newHTTPTransport returns an httpTransport for the given endpoint
|
|
func newHTTPTransport(addr string) *httpTransport {
|
|
// initialize the default EncoderPool with Encoder headers
|
|
defaultHeaders := map[string]string{
|
|
"Datadog-Meta-Lang": "go",
|
|
"Datadog-Meta-Lang-Version": strings.TrimPrefix(runtime.Version(), "go"),
|
|
"Datadog-Meta-Lang-Interpreter": runtime.Compiler + "-" + runtime.GOARCH + "-" + runtime.GOOS,
|
|
"Datadog-Meta-Tracer-Version": tracerVersion,
|
|
"Content-Type": "application/msgpack",
|
|
}
|
|
return &httpTransport{
|
|
traceURL: fmt.Sprintf("http://%s/v0.3/traces", resolveAddr(addr)),
|
|
client: &http.Client{
|
|
// We copy the transport to avoid using the default one, as it might be
|
|
// augmented with tracing and we don't want these calls to be recorded.
|
|
// See https://golang.org/pkg/net/http/#DefaultTransport .
|
|
Transport: &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
DialContext: (&net.Dialer{
|
|
Timeout: 30 * time.Second,
|
|
KeepAlive: 30 * time.Second,
|
|
DualStack: true,
|
|
}).DialContext,
|
|
MaxIdleConns: 100,
|
|
IdleConnTimeout: 90 * time.Second,
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
ExpectContinueTimeout: 1 * time.Second,
|
|
},
|
|
Timeout: defaultHTTPTimeout,
|
|
},
|
|
headers: defaultHeaders,
|
|
}
|
|
}
|
|
|
|
func (t *httpTransport) send(p *payload) error {
|
|
// prepare the client and send the payload
|
|
req, err := http.NewRequest("POST", t.traceURL, p)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot create http request: %v", err)
|
|
}
|
|
for header, value := range t.headers {
|
|
req.Header.Set(header, value)
|
|
}
|
|
req.Header.Set(traceCountHeader, strconv.Itoa(p.itemCount()))
|
|
req.Header.Set("Content-Length", strconv.Itoa(p.size()))
|
|
response, err := t.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer response.Body.Close()
|
|
if code := response.StatusCode; code >= 400 {
|
|
// error, check the body for context information and
|
|
// return a nice error.
|
|
msg := make([]byte, 1000)
|
|
n, _ := response.Body.Read(msg)
|
|
txt := http.StatusText(code)
|
|
if n > 0 {
|
|
return fmt.Errorf("%s (Status: %s)", msg[:n], txt)
|
|
}
|
|
return fmt.Errorf("%s", txt)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// resolveAddr resolves the given agent address and fills in any missing host
|
|
// and port using the defaults.
|
|
func resolveAddr(addr string) string {
|
|
host, port, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
// no port in addr
|
|
host = addr
|
|
}
|
|
if host == "" {
|
|
host = defaultHostname
|
|
}
|
|
if port == "" {
|
|
port = defaultPort
|
|
}
|
|
return fmt.Sprintf("%s:%s", host, port)
|
|
}
|