3

I've been reading the W3C Trace Context spec and I can't figure out how to get child-of relationships between spans from just looking at the headers in my requests.

Assuming I understand the processing model correctly, the first service generates a traceparent, while subsequent services replace the parentId within the traceparent for downstream requests.

So Service A generates a traceparent and sends a request to Service B:

host: service-b
traceparent: 00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01

Service B receives the requests and does two downstream requests to Service C and Service D each time changing the parentId:

host: service-c
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
host: service-d
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01

At the end I have three traceparents in my requests

00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01
00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01

It's easy to see that they all belong to the trace 0af7651916cd43dd8448eb211c80319c, but how do I figure out the parent child relationship between them?

My initial thought was that this is done using tracestate, but the spec states:

tracestate extends traceparent with vendor-specific data represented by a set of name/value pairs. Storing information in tracestate is optional.

n_l
  • 844
  • 1
  • 6
  • 19

1 Answers1

1

As you said before, the tracestate field is responsible for propagate only the vendor-specific data. Moreover, the traceparent itself doesn't carry enough upstream-downstream relationship information. The trace context solves three problems: provide an unique identifier for individual traces, provide an agreed-upon mechanism to forward vendor-specific trace data (tracestate) and provide an industry standard that intermediaries, platforms, and hardware providers can support. If you want to build the upstream-downstream (or parent-child) relationship you'll need the traceparent of the current span scope and also the upstream's traceparent. Take a look at an example in c# .NET 5:

using System;
using System.Diagnostics;

var upstreamActivity = new Activity("Upstream");

upstreamActivity.Start();
Console.WriteLine(upstreamActivity.OperationName);
Console.WriteLine("traceparent: {0}", upstreamActivity.Id);
Console.WriteLine("upstream traceparent: {0}", upstreamActivity.ParentId);
CallChildActivity();
upstreamActivity.Stop();

void CallChildActivity()
{
    var downstreamActivity = new Activity("Downstream Dependency");

    downstreamActivity.Start();
    Console.WriteLine(downstreamActivity.OperationName);
    Console.WriteLine("traceparent: {0}", downstreamActivity.Id);
    Console.WriteLine("upstream traceparent: {0}", downstreamActivity.ParentId);
    downstreamActivity.Stop();
}

the stdout will show something like this:

Upstream
traceparent: 00-192400a99d30ee459dce14655b09bc41-587841dea0e59b47-00
upstream traceparent:

Downstream Dependency
traceparent: 00-192400a99d30ee459dce14655b09bc41-d95c56215cddd74c-00
upstream traceparent: 00-192400a99d30ee459dce14655b09bc41-587841dea0e59b47-00

the upstream traceparent of the first Activity is empty, this means: that's the first activity of the whole trace. On the other hand, the second method has a not empty ParentId, so that's the identification of the upstream activity. Note that the term ParentId is different from the parent-id of the w3c spec, in .NET 5 it represents the upstream identification. Besides that, the parent-id field is called span-id in .NET 5.

That's the same concept of the stack trace, you need both ids to build the parent-child relationship (I wrote about it in this article). If you're using a framework, middleware or library different from System.Diagnostics and it doesn't provide the upstream traceparent, keep in mind that you'll need to store it in the moment it arrives at the new span scope. In other words, before the parent-id/span-id traceparent's update.

Luiz Lelis
  • 468
  • 7
  • 11
  • 1
    Are you saying that one cannot build a trace graph without assistance from the individual components involved? Say, we have T-A and T-B (format: traceid-parentid). Only the system in between can tie A to B. To an external component (say, newrelic) which is observing, T is start of graph and everything else is a child. Do those tools use timestamp etc or I must be missing something big here given this is already adopted by many. – Chethan May 24 '22 at 09:59
  • Hi Chethan, actually what I'm saying is: W3C traceparent doesn't define a standard for chield-parent relationships. If you want to understand how child-parent relationships between spans are conceived, my recommendation is to see other references like OpenTelemetry. The parent-id/span-id is made as 8 randomly generated bytes (note: trace-id is the identifier for the whole trace, parent-child relationships only make sense for spans). – Luiz Lelis May 26 '22 at 15:33