go-agent
go-agent copied to clipboard
Context propagation with B3 Headers + Gin
Summary
Hello 👋🏼
(Apologies if this feels less like a GH Issue and more like a GH Discussion).
I have a project that is using Hasura as a GraphQL federation layer as well as for inter-process communication.
The flow is as follows:
First, an external GraphQL client calls Hasura. Then...
- Hasura forwards the request to an internal Go server, sending along B3 headers
- The Go service runs on Gin HTTP and is instrumented with this NR Go Agent. This is the part I haven't figured out yet — Gin middleware needs to be able to extract the B3 headers to ascertain the trace ID and parent span.
- The Go service makes a B3 instrumented request to Hasura using the
nrb3
module.
Hasura only supports B3 headers for propagation, whereas New Relic (from what I gather) only supports W3C headers.
Desired Behaviour
The desired behavior would be to see hierarchical spans all under one trace.
Possible Solution
Unsure. I'm exploring a resolution on the Hasura side where we simply alias the b3 headers as w3c headers.
Additional context
I just discovered InsertDistributedTraceHeaders and AcceptDistributedTraceHeaders.
Should be possible to write some Gin middleware that parses B3 headers and "passes them in" as W3C headers:
// NewB3Handler creates a Gin middleware that links New Relic transactions by
// accepting distributed trace headers from another transaction.
func NewB3Handler() gin.HandlerFunc {
return func(c *gin.Context) {
txn := nrgin.Transaction(c)
if txn != nil {
traceID := c.GetHeader("X-B3-TraceId")
spanID := c.GetHeader("X-B3-SpanId")
sampled := c.GetHeader("X-B3-Sampled")
// w3c spec uses hex encoding: https://www.w3.org/TR/trace-context/#trace-flags
sampledHex := "00"
if sampled == "1" || sampled == "true" {
sampledHex = "01"
}
// https://www.w3.org/TR/trace-context/#traceparent-header
w3cTraceParent := fmt.Sprintf("00-%s-%s-%s", traceID, spanID, sampledHex)
// https://docs.newrelic.com/docs/distributed-tracing/concepts/how-new-relic-distributed-tracing-works/#headers
var hdrs http.Header = map[string][]string{
newrelic.DistributedTraceW3CTraceParentHeader: []string{w3cTraceParent},
// Leaving the tracestate header blank for now.
// Per the official W3C spec: failure to parse tracestate MUST NOT affect the parsing of traceparent.
// https://github.com/newrelic/go-agent/blob/v3.20.1/v3/newrelic/distributed_tracing_test.go#L249-L277
newrelic.DistributedTraceW3CTraceStateHeader: []string{""},
newrelic.DistributedTraceNewRelicHeader: []string{c.GetHeader(newrelic.DistributedTraceNewRelicHeader)},
}
// Links transactions by accepting distributed trace headers from
// another transaction.
txn.AcceptDistributedTraceHeaders(newrelic.TransportHTTP, hdrs)
txn.AcceptDistributedTraceHeaders(newrelic.TransportHTTPS, hdrs)
}
c.Next()
}
}