Skip to content
This repository was archived by the owner on Mar 19, 2025. It is now read-only.

Commit a68016b

Browse files
cube2222peterdeme
authored andcommitted
- Hook into complete instead of complete attempt.
- Add custom tracer. - Improve http tracing. - Retry failed trace sends. - Set trace on retries as well. Signed-off-by: Jakub Martin <[email protected]>
1 parent 2664189 commit a68016b

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

contrib/internal/httputil/trace.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016 Datadog, Inc.
5+
6+
package httputil // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httputil"
7+
8+
//go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go"
9+
10+
import (
11+
"fmt"
12+
"net/http"
13+
"strconv"
14+
15+
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
16+
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
17+
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
18+
)
19+
20+
// TraceConfig defines the configuration for request tracing.
21+
type TraceConfig struct {
22+
ResponseWriter http.ResponseWriter // response writer
23+
Request *http.Request // request that is traced
24+
Service string // service name
25+
Resource string // resource name
26+
QueryParams bool // specifies that request query parameters should be appended to http.url tag
27+
FinishOpts []ddtrace.FinishOption // span finish options to be applied
28+
SpanOpts []ddtrace.StartSpanOption // additional span options to be applied
29+
}
30+
31+
// TraceAndServe will apply tracing to the given http.Handler using the passed tracer under the given service and resource.
32+
func TraceAndServe(h http.Handler, cfg *TraceConfig) {
33+
path := cfg.Request.URL.Path
34+
if cfg.QueryParams {
35+
path += "?" + cfg.Request.URL.RawQuery
36+
}
37+
opts := append([]ddtrace.StartSpanOption{
38+
tracer.SpanType(ext.SpanTypeWeb),
39+
tracer.ServiceName(cfg.Service),
40+
tracer.ResourceName(cfg.Resource),
41+
tracer.Tag(ext.HTTPMethod, cfg.Request.Method),
42+
tracer.Tag(ext.HTTPURL, path),
43+
}, cfg.SpanOpts...)
44+
if cfg.Request.URL.Host != "" {
45+
opts = append([]ddtrace.StartSpanOption{
46+
tracer.Tag("http.host", cfg.Request.URL.Host),
47+
tracer.Tag("http.content-length", cfg.Request.ContentLength),
48+
}, opts...)
49+
}
50+
if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(cfg.Request.Header)); err == nil {
51+
opts = append(opts, tracer.ChildOf(spanctx))
52+
}
53+
span, ctx := tracer.StartSpanFromContext(cfg.Request.Context(), "http.request", opts...)
54+
defer span.Finish(cfg.FinishOpts...)
55+
56+
cfg.ResponseWriter = wrapResponseWriter(cfg.ResponseWriter, span)
57+
58+
h.ServeHTTP(cfg.ResponseWriter, cfg.Request.WithContext(ctx))
59+
}
60+
61+
// responseWriter is a small wrapper around an http response writer that will
62+
// intercept and store the status of a request.
63+
type responseWriter struct {
64+
http.ResponseWriter
65+
span ddtrace.Span
66+
status int
67+
}
68+
69+
func newResponseWriter(w http.ResponseWriter, span ddtrace.Span) *responseWriter {
70+
return &responseWriter{w, span, 0}
71+
}
72+
73+
// Write writes the data to the connection as part of an HTTP reply.
74+
// We explicitely call WriteHeader with the 200 status code
75+
// in order to get it reported into the span.
76+
func (w *responseWriter) Write(b []byte) (int, error) {
77+
if w.status == 0 {
78+
w.WriteHeader(http.StatusOK)
79+
}
80+
return w.ResponseWriter.Write(b)
81+
}
82+
83+
// WriteHeader sends an HTTP response header with status code.
84+
// It also sets the status code to the span.
85+
func (w *responseWriter) WriteHeader(status int) {
86+
if w.status != 0 {
87+
return
88+
}
89+
w.ResponseWriter.WriteHeader(status)
90+
w.status = status
91+
w.span.SetTag(ext.HTTPCode, strconv.Itoa(status))
92+
if status >= 500 && status < 600 {
93+
w.span.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status)))
94+
}
95+
}

ddtrace/tracer/custom_trace_writer.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-2020 Datadog, Inc.
5+
// Copyright 2021 Spacelift, Inc.
6+
package tracer
7+
8+
import (
9+
"sync"
10+
"time"
11+
12+
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
13+
)
14+
15+
type customTraceWriter struct {
16+
spans []*span
17+
18+
// climit limits the number of concurrent outgoing connections
19+
climit chan struct{}
20+
21+
sendSpans func(spans []*CustomSpan) error
22+
23+
// wg waits for all uploads to finish
24+
wg sync.WaitGroup
25+
}
26+
27+
func NewCustomTraceWriter(sendSpans func(spans []*CustomSpan) error) *customTraceWriter {
28+
return &customTraceWriter{
29+
climit: make(chan struct{}, concurrentConnectionLimit),
30+
sendSpans: sendSpans,
31+
}
32+
}
33+
34+
func (h *customTraceWriter) add(trace []*span) {
35+
h.spans = append(h.spans, trace...)
36+
if len(h.spans) > payloadSizeLimit {
37+
h.flush()
38+
}
39+
}
40+
41+
func (h *customTraceWriter) stop() {
42+
h.flush()
43+
h.wg.Wait()
44+
}
45+
46+
// flush will push any currently buffered traces to the server.
47+
func (h *customTraceWriter) flush() {
48+
if len(h.spans) == 0 {
49+
return
50+
}
51+
h.wg.Add(1)
52+
h.climit <- struct{}{}
53+
go func(spans []*span) {
54+
defer func(start time.Time) {
55+
<-h.climit
56+
h.wg.Done()
57+
}(time.Now())
58+
59+
customSpans := make([]*CustomSpan, len(spans))
60+
for i := range spans {
61+
customSpans[i] = &CustomSpan{
62+
Name: spans[i].Name,
63+
Service: spans[i].Service,
64+
Resource: spans[i].Resource,
65+
Type: spans[i].Type,
66+
Start: spans[i].Start,
67+
Duration: spans[i].Duration,
68+
Meta: spans[i].Meta,
69+
Metrics: spans[i].Metrics,
70+
SpanID: spans[i].SpanID,
71+
TraceID: spans[i].TraceID,
72+
ParentID: spans[i].ParentID,
73+
Error: spans[i].Error,
74+
}
75+
}
76+
77+
if err := h.sendSpans(customSpans); err != nil {
78+
log.Error("lost %d spans: %v", len(spans), err)
79+
}
80+
}(h.spans)
81+
h.spans = nil
82+
}
83+
84+
type CustomSpan struct {
85+
Name string `json:"name"` // operation name
86+
Service string `json:"service"` // service name (i.e. "grpc.server", "http.request")
87+
Resource string `json:"resource"` // resource name (i.e. "/user?id=123", "SELECT * FROM users")
88+
Type string `json:"type"` // protocol associated with the span (i.e. "web", "db", "cache")
89+
Start int64 `json:"start"` // span start time expressed in nanoseconds since epoch
90+
Duration int64 `json:"duration"` // duration of the span expressed in nanoseconds
91+
Meta map[string]string `json:"meta"` // arbitrary map of metadata
92+
Metrics map[string]float64 `json:"metrics"` // arbitrary map of numeric metrics
93+
SpanID uint64 `json:"span_id"` // identifier of this span
94+
TraceID uint64 `json:"trace_id"` // identifier of the root span
95+
ParentID uint64 `json:"parent_id"` // identifier of the span's direct parent
96+
Error int32 `json:"error"` // error status of the span; 0 means no errors
97+
}
98+
99+
type multiTraceWriter struct {
100+
ws []traceWriter
101+
}
102+
103+
func (m *multiTraceWriter) add(spans []*span) {
104+
for i := range m.ws {
105+
m.ws[i].add(spans)
106+
}
107+
}
108+
109+
func (m *multiTraceWriter) flush() {
110+
for i := range m.ws {
111+
m.ws[i].flush()
112+
}
113+
}
114+
115+
func (m *multiTraceWriter) stop() {
116+
for i := range m.ws {
117+
m.ws[i].stop()
118+
}
119+
}

0 commit comments

Comments
 (0)