| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- // Unless explicitly stated otherwise all files in this repository are licensed
- // under the Apache License Version 2.0.
- // This product includes software developed at Datadog (https://www.datadoghq.com/).
- // Copyright 2016 Datadog, Inc.
- package tracer
- import (
- "bytes"
- "fmt"
- "io"
- "net"
- "net/http"
- "os"
- "runtime"
- "strconv"
- "strings"
- "sync/atomic"
- "time"
- traceinternal "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal"
- "gopkg.in/DataDog/dd-trace-go.v1/internal"
- "gopkg.in/DataDog/dd-trace-go.v1/internal/version"
- "github.com/tinylib/msgp/msgp"
- )
- const (
- // headerComputedTopLevel specifies that the client has marked top-level spans, when set.
- // Any non-empty value will mean 'yes'.
- headerComputedTopLevel = "Datadog-Client-Computed-Top-Level"
- )
- var defaultDialer = &net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 30 * time.Second,
- DualStack: true,
- }
- var defaultClient = &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: defaultDialer.DialContext,
- MaxIdleConns: 100,
- IdleConnTimeout: 90 * time.Second,
- TLSHandshakeTimeout: 10 * time.Second,
- ExpectContinueTimeout: 1 * time.Second,
- },
- Timeout: defaultHTTPTimeout,
- }
- const (
- defaultHostname = "localhost"
- defaultPort = "8126"
- defaultAddress = defaultHostname + ":" + defaultPort
- defaultURL = "http://" + defaultAddress
- defaultHTTPTimeout = 2 * 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 communicating data to the agent.
- type transport interface {
- // send sends the payload p to the agent using the transport set up.
- // It returns a non-nil response body when no error occurred.
- send(p *payload) (body io.ReadCloser, err error)
- // sendStats sends the given stats payload to the agent.
- sendStats(s *statsPayload) error
- // endpoint returns the URL to which the transport will send traces.
- endpoint() string
- }
- type httpTransport struct {
- traceURL string // the delivery URL for traces
- statsURL string // the delivery URL for stats
- client *http.Client // the HTTP client used in the POST
- headers map[string]string // the Transport headers
- }
- // newTransport returns a new Transport implementation that sends traces to a
- // trace agent at the given url, using a given *http.Client.
- //
- // In general, using this method is only necessary if you have a trace agent
- // running on a non-default port, if it's located on another machine, or when
- // otherwise needing to customize the transport layer, for instance when using
- // a unix domain socket.
- func newHTTPTransport(url string, client *http.Client) *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": version.Tag,
- "Content-Type": "application/msgpack",
- }
- if cid := internal.ContainerID(); cid != "" {
- defaultHeaders["Datadog-Container-ID"] = cid
- }
- return &httpTransport{
- traceURL: fmt.Sprintf("%s/v0.4/traces", url),
- statsURL: fmt.Sprintf("%s/v0.6/stats", url),
- client: client,
- headers: defaultHeaders,
- }
- }
- func (t *httpTransport) sendStats(p *statsPayload) error {
- var buf bytes.Buffer
- if err := msgp.Encode(&buf, p); err != nil {
- return err
- }
- req, err := http.NewRequest("POST", t.statsURL, &buf)
- if err != nil {
- return err
- }
- resp, err := t.client.Do(req)
- if err != nil {
- return err
- }
- if code := resp.StatusCode; code >= 400 {
- // error, check the body for context information and
- // return a nice error.
- msg := make([]byte, 1000)
- n, _ := resp.Body.Read(msg)
- resp.Body.Close()
- txt := http.StatusText(code)
- if n > 0 {
- return fmt.Errorf("%s (Status: %s)", msg[:n], txt)
- }
- return fmt.Errorf("%s", txt)
- }
- return nil
- }
- func (t *httpTransport) send(p *payload) (body io.ReadCloser, err error) {
- req, err := http.NewRequest("POST", t.traceURL, p)
- if err != nil {
- return nil, 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()))
- req.Header.Set(headerComputedTopLevel, "yes")
- if t, ok := traceinternal.GetGlobalTracer().(*tracer); ok {
- if t.config.canComputeStats() {
- req.Header.Set("Datadog-Client-Computed-Stats", "yes")
- }
- droppedTraces := int(atomic.SwapUint32(&t.droppedP0Traces, 0))
- partialTraces := int(atomic.SwapUint32(&t.partialTraces, 0))
- droppedSpans := int(atomic.SwapUint32(&t.droppedP0Spans, 0))
- if stats := t.statsd; stats != nil {
- stats.Count("datadog.tracer.dropped_p0_traces", int64(droppedTraces),
- []string{fmt.Sprintf("partial:%s", strconv.FormatBool(partialTraces > 0))}, 1)
- stats.Count("datadog.tracer.dropped_p0_spans", int64(droppedSpans), nil, 1)
- }
- req.Header.Set("Datadog-Client-Dropped-P0-Traces", strconv.Itoa(droppedTraces))
- req.Header.Set("Datadog-Client-Dropped-P0-Spans", strconv.Itoa(droppedSpans))
- }
- response, err := t.client.Do(req)
- if err != nil {
- return nil, err
- }
- 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)
- response.Body.Close()
- txt := http.StatusText(code)
- if n > 0 {
- return nil, fmt.Errorf("%s (Status: %s)", msg[:n], txt)
- }
- return nil, fmt.Errorf("%s", txt)
- }
- return response.Body, nil
- }
- func (t *httpTransport) endpoint() string {
- return t.traceURL
- }
- // resolveAgentAddr resolves the given agent address and fills in any missing host
- // and port using the defaults. Some environment variable settings will
- // take precedence over configuration.
- func resolveAgentAddr() string {
- var host, port string
- if v := os.Getenv("DD_AGENT_HOST"); v != "" {
- host = v
- }
- if v := os.Getenv("DD_TRACE_AGENT_PORT"); v != "" {
- port = v
- }
- if host == "" {
- host = defaultHostname
- }
- if port == "" {
- port = defaultPort
- }
- return fmt.Sprintf("%s:%s", host, port)
- }
|