139 lines
4.0 KiB
Go
139 lines
4.0 KiB
Go
package tracing
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/rs/zerolog"
|
|
otelContrib "go.opentelemetry.io/contrib/propagators/Jaeger"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
const (
|
|
service = "cloudflared"
|
|
tracerInstrumentName = "origin"
|
|
|
|
tracerContextName = "cf-trace-id"
|
|
tracerContextNameOverride = "uber-trace-id"
|
|
|
|
IntCloudflaredTracingHeader = "cf-int-cloudflared-tracing"
|
|
)
|
|
|
|
var (
|
|
CanonicalCloudflaredTracingHeader = http.CanonicalHeaderKey(IntCloudflaredTracingHeader)
|
|
Http2TransportAttribute = trace.WithAttributes(TransportAttributeKey.String("http2"))
|
|
QuicTransportAttribute = trace.WithAttributes(TransportAttributeKey.String("quic"))
|
|
|
|
TransportAttributeKey = attribute.Key("transport")
|
|
TrafficAttributeKey = attribute.Key("traffic")
|
|
|
|
errNoopTracerProvider = errors.New("noop tracer provider records no spans")
|
|
)
|
|
|
|
func init() {
|
|
// Register the jaeger propagator globally.
|
|
otel.SetTextMapPropagator(otelContrib.Jaeger{})
|
|
}
|
|
|
|
type TracedRequest struct {
|
|
*http.Request
|
|
trace.TracerProvider
|
|
exporter InMemoryClient
|
|
}
|
|
|
|
// NewTracedRequest creates a new tracer for the current request context.
|
|
func NewTracedRequest(req *http.Request) *TracedRequest {
|
|
ctx, exists := extractTrace(req)
|
|
if !exists {
|
|
return &TracedRequest{req, trace.NewNoopTracerProvider(), &NoopOtlpClient{}}
|
|
}
|
|
mc := new(InMemoryOtlpClient)
|
|
exp, err := otlptrace.New(req.Context(), mc)
|
|
if err != nil {
|
|
return &TracedRequest{req, trace.NewNoopTracerProvider(), &NoopOtlpClient{}}
|
|
}
|
|
tp := tracesdk.NewTracerProvider(
|
|
// We want to dump to in-memory exporter immediately
|
|
tracesdk.WithSyncer(exp),
|
|
// Record information about this application in a Resource.
|
|
tracesdk.WithResource(resource.NewWithAttributes(
|
|
semconv.SchemaURL,
|
|
semconv.ServiceNameKey.String(service),
|
|
)),
|
|
)
|
|
|
|
return &TracedRequest{req.WithContext(ctx), tp, mc}
|
|
}
|
|
|
|
func (cft *TracedRequest) Tracer() trace.Tracer {
|
|
return cft.TracerProvider.Tracer(tracerInstrumentName)
|
|
}
|
|
|
|
// Spans returns the spans as base64 encoded protobuf otlp traces.
|
|
func (cft *TracedRequest) AddSpans(headers http.Header, log *zerolog.Logger) {
|
|
enc, err := cft.exporter.Spans()
|
|
switch err {
|
|
case nil:
|
|
break
|
|
case errNoTraces:
|
|
log.Error().Err(err).Msgf("expected traces to be available")
|
|
return
|
|
case errNoopTracer:
|
|
return // noop tracer has no traces
|
|
default:
|
|
log.Error().Err(err)
|
|
return
|
|
}
|
|
// No need to add header if no traces
|
|
if enc == "" {
|
|
log.Error().Msgf("no traces provided and no error from exporter")
|
|
return
|
|
}
|
|
headers[CanonicalCloudflaredTracingHeader] = []string{enc}
|
|
}
|
|
|
|
// EndWithStatus will set a status for the span and then end it.
|
|
func EndWithStatus(span trace.Span, code codes.Code, status string) {
|
|
if span == nil {
|
|
return
|
|
}
|
|
span.SetStatus(code, status)
|
|
span.End()
|
|
}
|
|
|
|
// extractTrace attempts to check for a cf-trace-id from a request header.
|
|
func extractTrace(req *http.Request) (context.Context, bool) {
|
|
// Only add tracing for requests with appropriately tagged headers
|
|
remoteTraces := req.Header.Values(tracerContextName)
|
|
if len(remoteTraces) <= 0 {
|
|
// Strip the cf-trace-id header
|
|
req.Header.Del(tracerContextName)
|
|
return nil, false
|
|
}
|
|
|
|
traceHeader := make(map[string]string, 1)
|
|
for _, t := range remoteTraces {
|
|
// Override the 'cf-trace-id' as 'uber-trace-id' so the jaeger propagator can extract it.
|
|
// Last entry wins if multiple provided
|
|
traceHeader[tracerContextNameOverride] = t
|
|
}
|
|
|
|
// Strip the cf-trace-id header
|
|
req.Header.Del(tracerContextName)
|
|
|
|
if traceHeader[tracerContextNameOverride] == "" {
|
|
return nil, false
|
|
}
|
|
remoteCtx := otel.GetTextMapPropagator().Extract(req.Context(), propagation.MapCarrier(traceHeader))
|
|
return remoteCtx, true
|
|
}
|