Skip to content

Commit a97233b

Browse files
committed
TUN-6030: Add ttfb span for origin http request
1 parent 775c2bc commit a97233b

File tree

6 files changed

+57
-25
lines changed

6 files changed

+57
-25
lines changed

cmd/cloudflared/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/cloudflare/cloudflared/logger"
2222
"github.com/cloudflare/cloudflared/metrics"
2323
"github.com/cloudflare/cloudflared/overwatch"
24+
"github.com/cloudflare/cloudflared/tracing"
2425
"github.com/cloudflare/cloudflared/watcher"
2526
)
2627

@@ -86,6 +87,7 @@ func main() {
8687
tunnel.Init(bInfo, graceShutdownC) // we need this to support the tunnel sub command...
8788
access.Init(graceShutdownC)
8889
updater.Init(Version)
90+
tracing.Init(Version)
8991
runApp(app, graceShutdownC)
9092
}
9193

proxy/proxy.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"github.com/pkg/errors"
1212
"github.com/rs/zerolog"
1313
"go.opentelemetry.io/otel/attribute"
14+
"go.opentelemetry.io/otel/codes"
15+
"go.opentelemetry.io/otel/trace"
1416

1517
"github.com/cloudflare/cloudflared/carrier"
1618
"github.com/cloudflare/cloudflared/cfio"
@@ -72,7 +74,8 @@ func (p *Proxy) ProxyHTTP(
7274
lbProbe := connection.IsLBProbeRequest(req)
7375
p.appendTagHeaders(req)
7476

75-
_, ruleSpan := tr.Tracer().Start(req.Context(), "ingress_match")
77+
_, ruleSpan := tr.Tracer().Start(req.Context(), "ingress_match",
78+
trace.WithAttributes(attribute.String("req-host", req.Host)))
7679
rule, ruleNum := p.ingressRules.FindMatchingRule(req.Host, req.URL.Path)
7780
logFields := logFields{
7881
cfRay: cfRay,
@@ -167,7 +170,7 @@ func (p *Proxy) proxyHTTPRequest(
167170
) error {
168171
roundTripReq := tr.Request
169172
if isWebsocket {
170-
roundTripReq = tr.Request.Clone(tr.Request.Context())
173+
roundTripReq = tr.Clone(tr.Request.Context())
171174
roundTripReq.Header.Set("Connection", "Upgrade")
172175
roundTripReq.Header.Set("Upgrade", "websocket")
173176
roundTripReq.Header.Set("Sec-Websocket-Version", "13")
@@ -191,10 +194,13 @@ func (p *Proxy) proxyHTTPRequest(
191194
roundTripReq.Header.Set("User-Agent", "")
192195
}
193196

197+
_, ttfbSpan := tr.Tracer().Start(tr.Context(), "ttfb_origin")
194198
resp, err := httpService.RoundTrip(roundTripReq)
195199
if err != nil {
200+
tracing.EndWithStatus(ttfbSpan, codes.Error, "")
196201
return errors.Wrap(err, "Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared")
197202
}
203+
tracing.EndWithStatus(ttfbSpan, codes.Ok, resp.Status)
198204
defer resp.Body.Close()
199205

200206
// Add spans to response header (if available)

tracing/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
const (
15-
maxTraceAmount = 20
15+
MaxTraceAmount = 20
1616
)
1717

1818
var (
@@ -46,7 +46,7 @@ func (mc *InMemoryOtlpClient) UploadTraces(_ context.Context, protoSpans []*trac
4646
defer mc.mu.Unlock()
4747
// Catch to make sure too many traces aren't being added to response header.
4848
// Returning nil makes sure we don't fail to send the traces we already recorded.
49-
if len(mc.spans)+len(protoSpans) > maxTraceAmount {
49+
if len(mc.spans)+len(protoSpans) > MaxTraceAmount {
5050
return nil
5151
}
5252
mc.spans = append(mc.spans, protoSpans...)

tracing/client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ func TestSpansNil(t *testing.T) {
108108

109109
func TestSpansTooManySpans(t *testing.T) {
110110
client := &InMemoryOtlpClient{}
111-
for i := 0; i < maxTraceAmount+1; i++ {
111+
for i := 0; i < MaxTraceAmount+1; i++ {
112112
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
113113
err := client.UploadTraces(context.Background(), spans)
114114
assert.NoError(t, err)
115115
}
116-
assert.Len(t, client.spans, maxTraceAmount)
116+
assert.Len(t, client.spans, MaxTraceAmount)
117117
_, err := client.Spans()
118118
assert.NoError(t, err)
119119
}

tracing/tracing.go

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package tracing
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"net/http"
8+
"os"
9+
"runtime"
710

811
"github.com/rs/zerolog"
912
otelContrib "go.opentelemetry.io/contrib/propagators/Jaeger"
@@ -22,26 +25,41 @@ const (
2225
service = "cloudflared"
2326
tracerInstrumentName = "origin"
2427

25-
tracerContextName = "cf-trace-id"
26-
tracerContextNameOverride = "uber-trace-id"
28+
TracerContextName = "cf-trace-id"
29+
TracerContextNameOverride = "uber-trace-id"
2730

2831
IntCloudflaredTracingHeader = "cf-int-cloudflared-tracing"
2932
)
3033

3134
var (
3235
CanonicalCloudflaredTracingHeader = http.CanonicalHeaderKey(IntCloudflaredTracingHeader)
33-
Http2TransportAttribute = trace.WithAttributes(TransportAttributeKey.String("http2"))
34-
QuicTransportAttribute = trace.WithAttributes(TransportAttributeKey.String("quic"))
36+
Http2TransportAttribute = trace.WithAttributes(transportAttributeKey.String("http2"))
37+
QuicTransportAttribute = trace.WithAttributes(transportAttributeKey.String("quic"))
38+
HostOSAttribute = semconv.HostTypeKey.String(runtime.GOOS)
39+
HostArchAttribute = semconv.HostArchKey.String(runtime.GOARCH)
3540

36-
TransportAttributeKey = attribute.Key("transport")
37-
TrafficAttributeKey = attribute.Key("traffic")
41+
otelVersionAttribute attribute.KeyValue
42+
hostnameAttribute attribute.KeyValue
43+
cloudflaredVersionAttribute attribute.KeyValue
44+
serviceAttribute = semconv.ServiceNameKey.String(service)
45+
46+
transportAttributeKey = attribute.Key("transport")
47+
otelVersionAttributeKey = attribute.Key("jaeger.version")
3848

3949
errNoopTracerProvider = errors.New("noop tracer provider records no spans")
4050
)
4151

4252
func init() {
4353
// Register the jaeger propagator globally.
4454
otel.SetTextMapPropagator(otelContrib.Jaeger{})
55+
otelVersionAttribute = otelVersionAttributeKey.String(fmt.Sprintf("go-otel-%s", otel.Version()))
56+
if hostname, err := os.Hostname(); err == nil {
57+
hostnameAttribute = attribute.String("hostname", hostname)
58+
}
59+
}
60+
61+
func Init(version string) {
62+
cloudflaredVersionAttribute = semconv.ProcessRuntimeVersionKey.String(version)
4563
}
4664

4765
type TracedRequest struct {
@@ -67,7 +85,12 @@ func NewTracedRequest(req *http.Request) *TracedRequest {
6785
// Record information about this application in a Resource.
6886
tracesdk.WithResource(resource.NewWithAttributes(
6987
semconv.SchemaURL,
70-
semconv.ServiceNameKey.String(service),
88+
serviceAttribute,
89+
otelVersionAttribute,
90+
hostnameAttribute,
91+
cloudflaredVersionAttribute,
92+
HostOSAttribute,
93+
HostArchAttribute,
7194
)),
7295
)
7396

@@ -110,27 +133,28 @@ func EndWithStatus(span trace.Span, code codes.Code, status string) {
110133
span.End()
111134
}
112135

113-
// extractTrace attempts to check for a cf-trace-id from a request header.
136+
// extractTrace attempts to check for a cf-trace-id from a request and return the
137+
// trace context with the provided http.Request.
114138
func extractTrace(req *http.Request) (context.Context, bool) {
115139
// Only add tracing for requests with appropriately tagged headers
116-
remoteTraces := req.Header.Values(tracerContextName)
140+
remoteTraces := req.Header.Values(TracerContextName)
117141
if len(remoteTraces) <= 0 {
118142
// Strip the cf-trace-id header
119-
req.Header.Del(tracerContextName)
143+
req.Header.Del(TracerContextName)
120144
return nil, false
121145
}
122146

123-
traceHeader := make(map[string]string, 1)
147+
traceHeader := map[string]string{}
124148
for _, t := range remoteTraces {
125149
// Override the 'cf-trace-id' as 'uber-trace-id' so the jaeger propagator can extract it.
126150
// Last entry wins if multiple provided
127-
traceHeader[tracerContextNameOverride] = t
151+
traceHeader[TracerContextNameOverride] = t
128152
}
129153

130154
// Strip the cf-trace-id header
131-
req.Header.Del(tracerContextName)
155+
req.Header.Del(TracerContextName)
132156

133-
if traceHeader[tracerContextNameOverride] == "" {
157+
if traceHeader[TracerContextNameOverride] == "" {
134158
return nil, false
135159
}
136160
remoteCtx := otel.GetTextMapPropagator().Extract(req.Context(), propagation.MapCarrier(traceHeader))

tracing/tracing_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
func TestNewCfTracer(t *testing.T) {
1414
req := httptest.NewRequest("GET", "http://localhost", nil)
15-
req.Header.Add(tracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
15+
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
1616
tr := NewTracedRequest(req)
1717
assert.NotNil(t, tr)
1818
assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider)
@@ -21,8 +21,8 @@ func TestNewCfTracer(t *testing.T) {
2121

2222
func TestNewCfTracerMultiple(t *testing.T) {
2323
req := httptest.NewRequest("GET", "http://localhost", nil)
24-
req.Header.Add(tracerContextName, "1241ce3ecdefc68854e8514e69ba42ca:b38f1bf5eae406f3:0:1")
25-
req.Header.Add(tracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
24+
req.Header.Add(TracerContextName, "1241ce3ecdefc68854e8514e69ba42ca:b38f1bf5eae406f3:0:1")
25+
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
2626
tr := NewTracedRequest(req)
2727
assert.NotNil(t, tr)
2828
assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider)
@@ -31,7 +31,7 @@ func TestNewCfTracerMultiple(t *testing.T) {
3131

3232
func TestNewCfTracerNilHeader(t *testing.T) {
3333
req := httptest.NewRequest("GET", "http://localhost", nil)
34-
req.Header[http.CanonicalHeaderKey(tracerContextName)] = nil
34+
req.Header[http.CanonicalHeaderKey(TracerContextName)] = nil
3535
tr := NewTracedRequest(req)
3636
assert.NotNil(t, tr)
3737
assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider)
@@ -41,7 +41,7 @@ func TestNewCfTracerNilHeader(t *testing.T) {
4141
func TestNewCfTracerInvalidHeaders(t *testing.T) {
4242
req := httptest.NewRequest("GET", "http://localhost", nil)
4343
for _, test := range [][]string{nil, {""}} {
44-
req.Header[http.CanonicalHeaderKey(tracerContextName)] = test
44+
req.Header[http.CanonicalHeaderKey(TracerContextName)] = test
4545
tr := NewTracedRequest(req)
4646
assert.NotNil(t, tr)
4747
assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider)

0 commit comments

Comments
 (0)