Skip to content

Commit

Permalink
refactor(trace): remove unused code and simplify carrier interface
Browse files Browse the repository at this point in the history
feat(trace): add support for binary trace context in carrier interface to enable cross-language tracing
fix(trace): fix header names to conform to W3C Trace Context specification

feat(trace/w3c.go): add functions to convert SpanContext and TraceState to and from W3C format to enable cross-language tracing
  • Loading branch information
shumkovdenis committed May 16, 2023
1 parent 665df36 commit 0707149
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 102 deletions.
22 changes: 8 additions & 14 deletions connect/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ package connect

import (
"context"
"net/http"

"github.com/bufbuild/connect-go"
"github.com/shumkovdenis/bl/logger"
"github.com/shumkovdenis/bl/trace"
"go.opentelemetry.io/otel/propagation"
)

type ConnectHeaderCarrier propagation.HeaderCarrier
type ConnectHeaderCarrier http.Header

func (c ConnectHeaderCarrier) Get(key string) string {
value := propagation.HeaderCarrier(c).Get(key)
value := http.Header(c).Get(key)
if key == trace.GrpcTraceBinHeader {
b, _ := connect.DecodeBinaryHeader(value)
return string(b)
Expand All @@ -24,11 +24,7 @@ func (c ConnectHeaderCarrier) Set(key, value string) {
if key == trace.GrpcTraceBinHeader {
value = connect.EncodeBinaryHeader([]byte(value))
}
propagation.HeaderCarrier(c).Set(key, value)
}

func (c ConnectHeaderCarrier) Keys() []string {
return propagation.HeaderCarrier(c).Keys()
http.Header(c).Set(key, value)
}

func InjectTraceContext() connect.UnaryInterceptorFunc {
Expand All @@ -38,8 +34,8 @@ func InjectTraceContext() connect.UnaryInterceptorFunc {
req connect.AnyRequest,
) (connect.AnyResponse, error) {
if !req.Spec().IsClient {
carrier := trace.GRPCHeaderCarrier(ConnectHeaderCarrier(req.Header()))
ctx = trace.WithTraceContext(ctx, carrier)
carrier := ConnectHeaderCarrier(req.Header())
ctx = trace.ExtractBinaryTraceContext(ctx, carrier)
}
return next(ctx, req)
})
Expand Down Expand Up @@ -88,10 +84,8 @@ func AddTraceContextHeader() connect.UnaryInterceptorFunc {
req connect.AnyRequest,
) (connect.AnyResponse, error) {
if req.Spec().IsClient {
header := req.Header()
carrier := trace.GRPCHeaderCarrier(
ConnectHeaderCarrier(header))
trace.InjectTraceContext(ctx, carrier)
carrier := ConnectHeaderCarrier(req.Header())
trace.InjectBinaryTraceContext(ctx, carrier)
}
return next(ctx, req)
})
Expand Down
3 changes: 1 addition & 2 deletions http/client/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"net/http"

"github.com/shumkovdenis/bl/trace"
"go.opentelemetry.io/otel/propagation"
)

// https://jonfriesen.ca/articles/go-http-client-middleware
Expand Down Expand Up @@ -65,7 +64,7 @@ func AddTraceContextHeader() Middleware {
header = make(http.Header)
}

trace.InjectTraceContext(ctx, propagation.HeaderCarrier(header))
trace.InjectTraceContext(ctx, header)

return rt.RoundTrip(req)
})
Expand Down
4 changes: 2 additions & 2 deletions http/server/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
func InjectTraceContext() func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
ctx := c.UserContext()
headers := c.GetReqHeaders()
ctx = trace.WithTraceContext(ctx, trace.CanonicalMapCarrier(headers))
carrier := trace.MapCarrier(c.GetReqHeaders())
ctx = trace.ExtractTraceContext(ctx, carrier)
c.SetUserContext(ctx)
return c.Next()
}
Expand Down
48 changes: 48 additions & 0 deletions trace/binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package trace

import "go.opentelemetry.io/otel/trace"

// BinaryFromSpanContext returns the binary format representation of a SpanContext.
//
// If sc is the zero value, Binary returns nil.
func BinaryFromSpanContext(sc trace.SpanContext) []byte {
traceID := sc.TraceID()
spanID := sc.SpanID()
traceFlags := sc.TraceFlags()
if sc.Equal(emptySpanContext) {
return nil
}
var b [29]byte
copy(b[2:18], traceID[:])
b[18] = 1
copy(b[19:27], spanID[:])
b[27] = 2
b[28] = uint8(traceFlags)
return b[:]
}

// SpanContextFromBinary returns the SpanContext represented by b.
//
// If b has an unsupported version ID or contains no TraceID, SpanContextFromBinary returns with ok==false.
func SpanContextFromBinary(b []byte) (sc trace.SpanContext, ok bool) {
var scConfig trace.SpanContextConfig
if len(b) == 0 || b[0] != 0 {
return trace.SpanContext{}, false
}
b = b[1:]
if len(b) >= 17 && b[0] == 0 {
copy(scConfig.TraceID[:], b[1:17])
b = b[17:]
} else {
return trace.SpanContext{}, false
}
if len(b) >= 9 && b[0] == 1 {
copy(scConfig.SpanID[:], b[1:9])
b = b[9:]
}
if len(b) >= 2 && b[0] == 2 {
scConfig.TraceFlags = trace.TraceFlags(b[1])
}
sc = trace.NewSpanContext(scConfig)
return sc, true
}
16 changes: 16 additions & 0 deletions trace/carrier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package trace

type Carrier interface {
Get(key string) string
Set(key, value string)
}

type MapCarrier map[string]string

func (c MapCarrier) Get(key string) string {
return c[key]
}

func (c MapCarrier) Set(key, value string) {
c[key] = value
}
120 changes: 81 additions & 39 deletions trace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,109 @@ package trace

import (
"context"
"net/http"

"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)

const (
TraceparentHeader = "traceparent"
TracestateHeader = "tracestate"
TraceparentHeader = "Traceparent"
TracestateHeader = "Tracestate"
GrpcTraceBinHeader = "Grpc-Trace-Bin"
)

var (
traceContext propagation.TraceContext
)
// var (
// traceContext propagation.TraceContext
// )

type CanonicalMapCarrier propagation.MapCarrier
// type CanonicalMapCarrier propagation.MapCarrier

func (c CanonicalMapCarrier) Get(key string) string {
return propagation.MapCarrier(c).Get(http.CanonicalHeaderKey(key))
}
// func (c CanonicalMapCarrier) Get(key string) string {
// return propagation.MapCarrier(c).Get(http.CanonicalHeaderKey(key))
// }

func (c CanonicalMapCarrier) Set(key, value string) {
propagation.MapCarrier(c).Set(http.CanonicalHeaderKey(key), value)
}
// func (c CanonicalMapCarrier) Set(key, value string) {
// propagation.MapCarrier(c).Set(http.CanonicalHeaderKey(key), value)
// }

func (c CanonicalMapCarrier) Keys() []string {
return propagation.MapCarrier(c).Keys()
}
// func (c CanonicalMapCarrier) Keys() []string {
// return propagation.MapCarrier(c).Keys()
// }

type GRPCHeaderCarrier propagation.HeaderCarrier
// type GRPCHeaderCarrier propagation.HeaderCarrier

func (c GRPCHeaderCarrier) Get(key string) string {
if key == TraceparentHeader {
grpcTraceBin := propagation.HeaderCarrier(c).Get(GrpcTraceBinHeader)
sc, _ := SpanContextFromBinary([]byte(grpcTraceBin))
return SpanContextToW3CString(sc)
}
return propagation.HeaderCarrier(c).Get(key)
}
// func (c GRPCHeaderCarrier) Get(key string) string {
// if key == TraceparentHeader {
// grpcTraceBin := propagation.HeaderCarrier(c).Get(GrpcTraceBinHeader)
// sc, _ := SpanContextFromBinary([]byte(grpcTraceBin))
// return SpanContextToW3CString(sc)
// }
// return propagation.HeaderCarrier(c).Get(key)
// }

// func (c GRPCHeaderCarrier) Set(key, value string) {
// if key == TraceparentHeader {
// sc, _ := SpanContextFromW3CString(value)
// grpcTraceBin := BinaryFromSpanContext(sc)
// propagation.HeaderCarrier(c).Set(GrpcTraceBinHeader, string(grpcTraceBin))
// } else {
// propagation.HeaderCarrier(c).Set(key, value)
// }
// }

// func (c GRPCHeaderCarrier) Keys() []string {
// return propagation.HeaderCarrier(c).Keys()
// }

// func InjectTraceContext(ctx context.Context, carrier propagation.TextMapCarrier) {
// traceContext.Inject(ctx, carrier)
// }

func (c GRPCHeaderCarrier) Set(key, value string) {
if key == TraceparentHeader {
sc, _ := SpanContextFromW3CString(value)
grpcTraceBin := BinaryFromSpanContext(sc)
propagation.HeaderCarrier(c).Set(GrpcTraceBinHeader, string(grpcTraceBin))
} else {
propagation.HeaderCarrier(c).Set(key, value)
// func WithTraceContext(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
// return traceContext.Extract(ctx, carrier)
// }

// func TraceContextFromContext(ctx context.Context) trace.SpanContext {
// return trace.SpanContextFromContext(ctx)
// }

func ExtractTraceContext(ctx context.Context, carrier Carrier) context.Context {
traceparent := carrier.Get(TraceparentHeader)
sc, _ := SpanContextFromW3CString(traceparent)
if !sc.IsValid() {
return ctx
}

tracestate := carrier.Get(TracestateHeader)
ts := TraceStateFromW3CString(tracestate)
sc = sc.WithTraceState(ts)

return trace.ContextWithRemoteSpanContext(ctx, sc)
}

func (c GRPCHeaderCarrier) Keys() []string {
return propagation.HeaderCarrier(c).Keys()
func InjectTraceContext(ctx context.Context, carrier Carrier) {
sc := trace.SpanContextFromContext(ctx)

traceparent := SpanContextToW3CString(sc)
carrier.Set(TraceparentHeader, traceparent)

tracestate := TraceStateToW3CString(sc)
carrier.Set(TracestateHeader, tracestate)
}

func InjectTraceContext(ctx context.Context, carrier propagation.TextMapCarrier) {
traceContext.Inject(ctx, carrier)
func ExtractBinaryTraceContext(ctx context.Context, carrier Carrier) context.Context {
grpcTraceBin := carrier.Get(GrpcTraceBinHeader)
sc, _ := SpanContextFromBinary([]byte(grpcTraceBin))
if !sc.IsValid() {
return ctx
}

return trace.ContextWithRemoteSpanContext(ctx, sc)
}

func WithTraceContext(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
return traceContext.Extract(ctx, carrier)
func InjectBinaryTraceContext(ctx context.Context, carrier Carrier) {
sc := trace.SpanContextFromContext(ctx)
grpcTraceBin := BinaryFromSpanContext(sc)
carrier.Set(GrpcTraceBinHeader, string(grpcTraceBin))
}

func TraceContextFromContext(ctx context.Context) trace.SpanContext {
Expand Down
64 changes: 19 additions & 45 deletions trace/utils.go → trace/w3c.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,6 @@ const (

var emptySpanContext trace.SpanContext

// BinaryFromSpanContext returns the binary format representation of a SpanContext.
//
// If sc is the zero value, Binary returns nil.
func BinaryFromSpanContext(sc trace.SpanContext) []byte {
traceID := sc.TraceID()
spanID := sc.SpanID()
traceFlags := sc.TraceFlags()
if sc.Equal(emptySpanContext) {
return nil
}
var b [29]byte
copy(b[2:18], traceID[:])
b[18] = 1
copy(b[19:27], spanID[:])
b[27] = 2
b[28] = uint8(traceFlags)
return b[:]
}

// SpanContextFromBinary returns the SpanContext represented by b.
//
// If b has an unsupported version ID or contains no TraceID, SpanContextFromBinary returns with ok==false.
func SpanContextFromBinary(b []byte) (sc trace.SpanContext, ok bool) {
var scConfig trace.SpanContextConfig
if len(b) == 0 || b[0] != 0 {
return trace.SpanContext{}, false
}
b = b[1:]
if len(b) >= 17 && b[0] == 0 {
copy(scConfig.TraceID[:], b[1:17])
b = b[17:]
} else {
return trace.SpanContext{}, false
}
if len(b) >= 9 && b[0] == 1 {
copy(scConfig.SpanID[:], b[1:9])
b = b[9:]
}
if len(b) >= 2 && b[0] == 2 {
scConfig.TraceFlags = trace.TraceFlags(b[1])
}
sc = trace.NewSpanContext(scConfig)
return sc, true
}

// SpanContextToW3CString returns the SpanContext string representation.
func SpanContextToW3CString(sc trace.SpanContext) string {
traceID := sc.TraceID()
Expand Down Expand Up @@ -129,3 +84,22 @@ func SpanContextFromW3CString(h string) (sc trace.SpanContext, ok bool) {

return sc, true
}

// TraceStateFromW3CString extracts a span tracestate from given string which got earlier from TraceStateFromW3CString format.
func TraceStateFromW3CString(h string) trace.TraceState {
if h == "" {
return trace.TraceState{}
}

ts, err := trace.ParseTraceState(h)
if err != nil {
return trace.TraceState{}
}

return ts
}

// TraceStateToW3CString extracts the TraceState from given SpanContext and returns its string representation.
func TraceStateToW3CString(sc trace.SpanContext) string {
return sc.TraceState().String()
}

0 comments on commit 0707149

Please sign in to comment.