Skip to content

Commit f81b0ee

Browse files
committed
TUN-5990: Add otlp span export to response header
1 parent 8a07a90 commit f81b0ee

File tree

4 files changed

+44
-12
lines changed

4 files changed

+44
-12
lines changed

connection/http2.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
231231
dest[name] = values
232232
}
233233

234+
if h2name == tracing.IntCloudflaredTracingHeader {
235+
// Add cf-int-cloudflared-tracing header outside of serialized userHeaders
236+
rp.w.Header()[tracing.CanonicalCloudflaredTracingHeader] = values
237+
continue
238+
}
239+
234240
if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
235241
// User headers, on the other hand, must all be serialized so that
236242
// HTTP/2 header validation won't be applied to HTTP/1 header values

proxy/proxy.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (p *Proxy) ProxyHTTP(
8787
case ingress.HTTPOriginProxy:
8888
if err := p.proxyHTTPRequest(
8989
w,
90-
req,
90+
tr,
9191
originProxy,
9292
isWebsocket,
9393
rule.Config.DisableChunkedEncoding,
@@ -159,15 +159,15 @@ func ruleField(ing ingress.Ingress, ruleNum int) (ruleID string, srv string) {
159159
// ProxyHTTPRequest proxies requests of underlying type http and websocket to the origin service.
160160
func (p *Proxy) proxyHTTPRequest(
161161
w connection.ResponseWriter,
162-
req *http.Request,
162+
tr *tracing.TracedRequest,
163163
httpService ingress.HTTPOriginProxy,
164164
isWebsocket bool,
165165
disableChunkedEncoding bool,
166166
fields logFields,
167167
) error {
168-
roundTripReq := req
168+
roundTripReq := tr.Request
169169
if isWebsocket {
170-
roundTripReq = req.Clone(req.Context())
170+
roundTripReq = tr.Request.Clone(tr.Request.Context())
171171
roundTripReq.Header.Set("Connection", "Upgrade")
172172
roundTripReq.Header.Set("Upgrade", "websocket")
173173
roundTripReq.Header.Set("Sec-Websocket-Version", "13")
@@ -177,7 +177,7 @@ func (p *Proxy) proxyHTTPRequest(
177177
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
178178
if disableChunkedEncoding {
179179
roundTripReq.TransferEncoding = []string{"gzip", "deflate"}
180-
cLength, err := strconv.Atoi(req.Header.Get("Content-Length"))
180+
cLength, err := strconv.Atoi(tr.Request.Header.Get("Content-Length"))
181181
if err == nil {
182182
roundTripReq.ContentLength = int64(cLength)
183183
}
@@ -197,6 +197,9 @@ func (p *Proxy) proxyHTTPRequest(
197197
}
198198
defer resp.Body.Close()
199199

200+
// Add spans to response header (if available)
201+
tr.AddSpans(resp.Header, p.log)
202+
200203
err = w.WriteRespHeaders(resp.StatusCode, resp.Header)
201204
if err != nil {
202205
return errors.Wrap(err, "Error writing response header")
@@ -211,7 +214,7 @@ func (p *Proxy) proxyHTTPRequest(
211214

212215
eyeballStream := &bidirectionalStream{
213216
writer: w,
214-
reader: req.Body,
217+
reader: tr.Request.Body,
215218
}
216219

217220
websocket.Stream(eyeballStream, rwc, p.log)

tracing/client.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const (
1616
)
1717

1818
var (
19-
errNoTraces = errors.New("no traces recorded to be exported")
19+
errNoTraces = errors.New("no traces recorded to be exported")
20+
errNoopTracer = errors.New("noop tracer has no traces")
2021
)
2122

2223
type InMemoryClient interface {
@@ -86,5 +87,5 @@ func (mc *NoopOtlpClient) UploadTraces(_ context.Context, _ []*tracepb.ResourceS
8687

8788
// Spans always returns no traces error
8889
func (mc *NoopOtlpClient) Spans() (string, error) {
89-
return "", errNoTraces
90+
return "", errNoopTracer
9091
}

tracing/tracing.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"net/http"
77

8+
"github.com/rs/zerolog"
89
otelContrib "go.opentelemetry.io/contrib/propagators/Jaeger"
910
"go.opentelemetry.io/otel"
1011
"go.opentelemetry.io/otel/attribute"
@@ -23,11 +24,14 @@ const (
2324

2425
tracerContextName = "cf-trace-id"
2526
tracerContextNameOverride = "uber-trace-id"
27+
28+
IntCloudflaredTracingHeader = "cf-int-cloudflared-tracing"
2629
)
2730

2831
var (
29-
Http2TransportAttribute = trace.WithAttributes(TransportAttributeKey.String("http2"))
30-
QuicTransportAttribute = trace.WithAttributes(TransportAttributeKey.String("quic"))
32+
CanonicalCloudflaredTracingHeader = http.CanonicalHeaderKey(IntCloudflaredTracingHeader)
33+
Http2TransportAttribute = trace.WithAttributes(TransportAttributeKey.String("http2"))
34+
QuicTransportAttribute = trace.WithAttributes(TransportAttributeKey.String("quic"))
3135

3236
TransportAttributeKey = attribute.Key("transport")
3337
TrafficAttributeKey = attribute.Key("traffic")
@@ -75,8 +79,26 @@ func (cft *TracedRequest) Tracer() trace.Tracer {
7579
}
7680

7781
// Spans returns the spans as base64 encoded protobuf otlp traces.
78-
func (cft *TracedRequest) Spans() (string, error) {
79-
return cft.exporter.Spans()
82+
func (cft *TracedRequest) AddSpans(headers http.Header, log *zerolog.Logger) {
83+
enc, err := cft.exporter.Spans()
84+
switch err {
85+
case nil:
86+
break
87+
case errNoTraces:
88+
log.Error().Err(err).Msgf("expected traces to be available")
89+
return
90+
case errNoopTracer:
91+
return // noop tracer has no traces
92+
default:
93+
log.Error().Err(err)
94+
return
95+
}
96+
// No need to add header if no traces
97+
if enc == "" {
98+
log.Error().Msgf("no traces provided and no error from exporter")
99+
return
100+
}
101+
headers[CanonicalCloudflaredTracingHeader] = []string{enc}
80102
}
81103

82104
// EndWithStatus will set a status for the span and then end it.

0 commit comments

Comments
 (0)