open-telemetry

Passing the traceid/context across page reloads?


Just a quick question/thought and I have a basic understanding of Open Telemetry.

Aare there a standard/recommendation for passing the traceId/context across page redirects in the browser? (like does the browser support passing the context across a 30x redirect?).

What I wanted to do was to visualize the authentication flow when using OpenID-Connect and I thought it would be very cool if I could pass the traceId across redirects to get a complete picture of all the page loads.

Assuming that I could get the auth service to support it).


Solution

  • I don't see what language/platform/framework you are using. So, I'll assume it's .NET.

    We do not have standard/recommendation for redirects. I had the same problem with redirects and this is what I came up with:

    Create new Propagator to check if request has trace in cookies

    public class CookiePropagator : TextMapPropagator
    {
        public const string IdpTraceid = "IdP-TraceId";
        public const string IdpTraceState = "Idp-TraceState";
        
        public  static readonly int TraceparentLengthV0 = "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-00".Length;
    
    
        public override void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
        {
            // We're ignoring setter because it is used for Headers in HTTP requests only.
        }
    
        public override PropagationContext Extract<T>(PropagationContext context, T carrier,
            Func<T, string, IEnumerable<string>> getter)
        {
            // We're ignoring getter because it is used for Headers only. It's the way OpenTelemetry works for now.
    
            if (carrier == null)
            {
                return context;
            }
    
            // Let's check if we have it in the cookie
            if (carrier is HttpRequest
                {
                    Path.Value: "/login1"
                    or "/login2"
                    or "/authorize"
                    or "/challenge" // all workflows you want to track with 302 cookie
                } request
                && request.Cookies[IdpTraceid] != null)
            {
                // You can use TryExtractTraceParent from 
                // https://github.com/open-telemetry/opentelemetry-dotnet/blob/ba8a0e4c131054217555aae5c8a210da3a4b7c39/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs
                var parsed = TryExtractTraceParent(request.Cookies[IdpTraceid], out var traceId, out var spanId, out var traceoptions); 
    
                if (!parsed)
                {
                    return context;
                }
    
                string tracestate = null;
                var traceStateFromCookie = request.Cookies[IdpTraceState];
                if (!string.IsNullOrWhiteSpace(traceStateFromCookie))
                {
                    // You can use TryExtractTraceState from 
                    // https://github.com/open-telemetry/opentelemetry-dotnet/blob/ba8a0e4c131054217555aae5c8a210da3a4b7c39/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs
                    TryExtractTraceState([traceStateFromCookie], out tracestate);
                }
                return new PropagationContext(
                    new ActivityContext(traceId, spanId, traceoptions, tracestate, isRemote: true),
                    context.Baggage);
            }
    
            return context;
        }
    }
    

    You will have to put traceId in cookie, so it could be done in middleware:

    app.Use(async (ctx, next) =>
        {
            await next.Invoke(ctx);
    
            if (ctx.Request.Path == "first/path/to/start/tracking" && Activity.Current != null)
            {
                var cookieOptions = new CookieOptions
                {
                    // Two minutes should suffice for most login use cases
                    Expires = DateTimeOffset.Now.AddMinutes(2)
                };
    
                // To make your WriteTraceParentIntoSpan you can check:
                // https://github.com/open-telemetry/opentelemetry-dotnet/blob/ba8a0e4c131054217555aae5c8a210da3a4b7c39/src/OpenTelemetry.Api/Context/Propagation/TraceContextPropagator.cs
                var traceParent = string.Create(CookiePropagator.TraceparentLengthV0, Activity.Current, CookiePropagator.WriteTraceParentIntoSpan);
                ctx.Response.Cookies.Append(CookiePropagator.IdpTraceid, traceParent, cookieOptions);
                ctx.Response.Cookies.Append(CookiePropagator.IdpTraceState, Activity.Current.TraceStateString ?? string.Empty, cookieOptions);
            }
    
            // Removing cookies after last redirect
            if (ctx.Request.Path == "/last/call/to/stop/tracking")
            {
                ctx.Response.Cookies.Delete(CookiePropagator.IdpTraceid);
                ctx.Response.Cookies.Delete(CookiePropagator.IdpTraceState);
            }
        });
    

    Don't forget to add new propagator to SDK!!!

    Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator(new TextMapPropagator[]
    {
        new TraceContextPropagator(),
        new CookiePropagator()
    }));