Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context propagation with B3 Headers + Gin #608

Open
kevinmichaelchen opened this issue Nov 28, 2022 · 1 comment
Open

Context propagation with B3 Headers + Gin #608

kevinmichaelchen opened this issue Nov 28, 2022 · 1 comment

Comments

@kevinmichaelchen
Copy link

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.

2022-11-16-o11y-with-hasura

The flow is as follows:

First, an external GraphQL client calls Hasura. Then...

  1. Hasura forwards the request to an internal Go server, sending along B3 headers
  2. 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.
  3. 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

@kevinmichaelchen
Copy link
Author

kevinmichaelchen commented Nov 29, 2022

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()
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant