Scheduled workflow traces combined

Hello!

I am working on instrumenting our Temporal workflows with OpenTelemetry, and I am running into an issue with scheduled workflows. When the spans come in from a scheduled workflow, they are grouped into one gigantic trace instead of being their own distinct trace. This makes looking those traces up nearly impossible. Is there a way to separate these traces?

Here is a sample of the code I am using to set up the tracing interceptor:

import "go.temporal.io/sdk/client"

func GetClient() (client.Client, error) {
    tracingInterceptor, err := opentelemetry.NewTracingInterceptor(
        opentelemetry.TracerOptions{Tracer: otel.Tracer("temporal")}
    )

    config := client.Options{
	    HostPort:     "<port>",
	    Interceptors: []interceptor.ClientInterceptor{tracingInterceptor},
    }

    return client.Dial(config)
}

I managed to prevent the propagation of trace data to the schedule by implementing az interceptor, that explicitly removes the _tracer-data header from the context when creating a schedule, right after the tracing interceptor has placed it there.

Here’s how I did it:

// Implements interceptor.ClientInterceptor
type scheduleTraceRemoverInterceptor struct {
	interceptor.ClientInterceptorBase
}

func NewScheduleTraceRemoverInterceptor() interceptor.ClientInterceptor {
	return &scheduleTraceRemoverInterceptor{}
}

func (c *scheduleTraceRemoverInterceptor) InterceptClient(
	next interceptor.ClientOutboundInterceptor,
) interceptor.ClientOutboundInterceptor {
	i := &scheduleTraceRemoverClientOutboundInterceptor{}
	i.Next = next

	return i
}

// Implements interceptor.ClientOutboundInterceptor
type scheduleTraceRemoverClientOutboundInterceptor struct {
	interceptor.ClientOutboundInterceptorBase
}

func (i *scheduleTraceRemoverClientOutboundInterceptor) CreateSchedule(
	ctx context.Context,
	input *interceptor.ScheduleClientCreateInput,
) (client.ScheduleHandle, error) {
	header := interceptor.Header(ctx)
	delete(header, "_tracer-data")

	return i.Next.CreateSchedule(ctx, input)
}

To use this in your GetClient function you need to just add it to the interceptors after the tracing interceptor:

func GetClient() (client.Client, error) {
    tracingInterceptor, err := opentelemetry.NewTracingInterceptor(
        opentelemetry.TracerOptions{Tracer: otel.Tracer("temporal")}
    )

    scheduleTraceRemoverInterceptor := telemetry.NewScheduleTraceRemoverInterceptor()

    config := client.Options{
	    HostPort:     "<port>",
	    Interceptors: []interceptor.ClientInterceptor{
            tracingInterceptor,
            scheduleTraceRemoverInterceptor,
        },
    }

    return client.Dial(config)
}