diff --git a/tracer/src/Datadog.Trace/SpanExtensions.cs b/tracer/src/Datadog.Trace/SpanExtensions.cs
index e447752cf567..a09ff31e9b46 100644
--- a/tracer/src/Datadog.Trace/SpanExtensions.cs
+++ b/tracer/src/Datadog.Trace/SpanExtensions.cs
@@ -3,6 +3,10 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
//
+using System;
+using System.Text;
+using Datadog.Trace.Logging;
+using Datadog.Trace.Tagging;
using Datadog.Trace.Util;
namespace Datadog.Trace
@@ -12,6 +16,8 @@ namespace Datadog.Trace
///
public static class SpanExtensions
{
+ private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanExtensions));
+
///
/// Sets the details of the user on the local root span
///
@@ -29,37 +35,51 @@ public static void SetUser(this ISpan span, UserDetails userDetails)
ThrowHelper.ThrowArgumentException(nameof(userDetails) + ".Id must be set to a value other than null or the empty string", nameof(userDetails));
}
- var localRootSpan = span;
+ TraceContext traceContext = null;
if (span is Span spanClass)
{
- localRootSpan = spanClass.Context.TraceContext?.RootSpan ?? span;
+ traceContext = spanClass.Context.TraceContext;
}
- localRootSpan.SetTag(Tags.User.Id, userDetails.Id);
+ Action setTag =
+ traceContext != null
+ ? (name, value) => traceContext.Tags.SetTag(name, value)
+ : (name, value) => span.SetTag(name, value);
+
+ if (userDetails.PropagateId)
+ {
+ var base64UserId = Convert.ToBase64String(Encoding.UTF8.GetBytes(userDetails.Id));
+ const string propagatedUserIdTag = TagPropagation.PropagatedTagPrefix + Tags.User.Id;
+ setTag(propagatedUserIdTag, base64UserId);
+ }
+ else
+ {
+ setTag(Tags.User.Id, userDetails.Id);
+ }
if (userDetails.Email is not null)
{
- localRootSpan.SetTag(Tags.User.Email, userDetails.Email);
+ setTag(Tags.User.Email, userDetails.Email);
}
if (userDetails.Name is not null)
{
- localRootSpan.SetTag(Tags.User.Name, userDetails.Name);
+ setTag(Tags.User.Name, userDetails.Name);
}
if (userDetails.SessionId is not null)
{
- localRootSpan.SetTag(Tags.User.SessionId, userDetails.SessionId);
+ setTag(Tags.User.SessionId, userDetails.SessionId);
}
if (userDetails.Role is not null)
{
- localRootSpan.SetTag(Tags.User.Role, userDetails.Role);
+ setTag(Tags.User.Role, userDetails.Role);
}
if (userDetails.Scope is not null)
{
- localRootSpan.SetTag(Tags.User.Scope, userDetails.Scope);
+ setTag(Tags.User.Scope, userDetails.Scope);
}
}
}
diff --git a/tracer/src/Datadog.Trace/UserDetails.cs b/tracer/src/Datadog.Trace/UserDetails.cs
index bbb117f4e7a7..e7c3d9803682 100644
--- a/tracer/src/Datadog.Trace/UserDetails.cs
+++ b/tracer/src/Datadog.Trace/UserDetails.cs
@@ -16,7 +16,7 @@ public struct UserDetails
///
/// Initializes a new instance of the struct.
///
- /// The unique identifier assoicated with the users
+ /// The unique identifier associated with the users
public UserDetails(string id)
{
if (string.IsNullOrEmpty(id))
@@ -25,6 +25,7 @@ public UserDetails(string id)
}
Id = id;
+ PropagateId = false;
Email = null;
Name = null;
SessionId = null;
@@ -43,10 +44,20 @@ public UserDetails(string id)
public string? Name { get; set; }
///
- /// Gets or sets the unique identifier assoicated with the users
+ /// Gets or sets the unique identifier associated with the user.
///
public string Id { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the Id field should be propagated to other services called.
+ /// NOTE: setting this value to true will automatically propagate the Id field to all downstream services
+ /// including any third-party services called by the application. Therefore this value should only be set
+ /// to true if the user is confident that it is safe to propagate the value to all downstream services
+ /// called by the application.
+ /// Default value is false.
+ ///
+ public bool PropagateId { get; set; }
+
///
/// Gets or sets the user's session unique identifier
///
diff --git a/tracer/test/Datadog.Trace.Tests/Snapshots/PublicApiTests.Datadog.Trace.PublicApiHasNotChanged.verified.txt b/tracer/test/Datadog.Trace.Tests/Snapshots/PublicApiTests.Datadog.Trace.PublicApiHasNotChanged.verified.txt
index b5bb01f09017..913e81c38bba 100644
--- a/tracer/test/Datadog.Trace.Tests/Snapshots/PublicApiTests.Datadog.Trace.PublicApiHasNotChanged.verified.txt
+++ b/tracer/test/Datadog.Trace.Tests/Snapshots/PublicApiTests.Datadog.Trace.PublicApiHasNotChanged.verified.txt
@@ -348,6 +348,7 @@ namespace Datadog.Trace
public string? Email { get; set; }
public string Id { get; set; }
public string? Name { get; set; }
+ public bool PropagateId { get; set; }
public string? Role { get; set; }
public string? Scope { get; set; }
public string? SessionId { get; set; }
diff --git a/tracer/test/Datadog.Trace.Tests/TracerTests.cs b/tracer/test/Datadog.Trace.Tests/TracerTests.cs
index c34636b8f680..0fbc1175e5a8 100644
--- a/tracer/test/Datadog.Trace.Tests/TracerTests.cs
+++ b/tracer/test/Datadog.Trace.Tests/TracerTests.cs
@@ -528,7 +528,7 @@ public async Task ForceFlush()
}
[Fact]
- public void SetUserOnRootSpanDirectly()
+ public void SetUserOnRootSpanDirectly_ShouldSetOnTrace()
{
var scopeManager = new AsyncLocalScopeManager();
@@ -538,8 +538,8 @@ public void SetUserOnRootSpanDirectly()
};
var tracer = new Tracer(settings, Mock.Of(), Mock.Of(), scopeManager, Mock.Of());
- var rootTestScope = tracer.StartActive("test.trace");
- var childTestScope = tracer.StartActive("test.trace.child");
+ var rootTestScope = (Scope)tracer.StartActive("test.trace");
+ var childTestScope = (Scope)tracer.StartActive("test.trace.child");
childTestScope.Dispose();
var email = "test@adventure-works.com";
@@ -560,16 +560,17 @@ public void SetUserOnRootSpanDirectly()
};
tracer.ActiveScope?.Span.SetUser(userDetails);
- Assert.Equal(email, rootTestScope.Span.GetTag(Tags.User.Email));
- Assert.Equal(name, rootTestScope.Span.GetTag(Tags.User.Name));
- Assert.Equal(id, rootTestScope.Span.GetTag(Tags.User.Id));
- Assert.Equal(sessionId, rootTestScope.Span.GetTag(Tags.User.SessionId));
- Assert.Equal(role, rootTestScope.Span.GetTag(Tags.User.Role));
- Assert.Equal(scope, rootTestScope.Span.GetTag(Tags.User.Scope));
+ var traceContext = rootTestScope.Span.Context.TraceContext;
+ Assert.Equal(email, traceContext.Tags.GetTag(Tags.User.Email));
+ Assert.Equal(name, traceContext.Tags.GetTag(Tags.User.Name));
+ Assert.Equal(id, traceContext.Tags.GetTag(Tags.User.Id));
+ Assert.Equal(sessionId, traceContext.Tags.GetTag(Tags.User.SessionId));
+ Assert.Equal(role, traceContext.Tags.GetTag(Tags.User.Role));
+ Assert.Equal(scope, traceContext.Tags.GetTag(Tags.User.Scope));
}
[Fact]
- public void SetUserOnChildChildSpan_ShouldAttachToRoot()
+ public void SetUserOnChildChildSpan_ShouldSetOnTrace()
{
var scopeManager = new AsyncLocalScopeManager();
@@ -579,8 +580,8 @@ public void SetUserOnChildChildSpan_ShouldAttachToRoot()
};
var tracer = new Tracer(settings, Mock.Of(), Mock.Of(), scopeManager, Mock.Of());
- var rootTestScope = tracer.StartActive("test.trace");
- var childTestScope = tracer.StartActive("test.trace.child");
+ var rootTestScope = (Scope)tracer.StartActive("test.trace");
+ var childTestScope = (Scope)tracer.StartActive("test.trace.child");
var email = "test@adventure-works.com";
var name = "Jane Doh";
@@ -602,12 +603,13 @@ public void SetUserOnChildChildSpan_ShouldAttachToRoot()
childTestScope.Dispose();
- Assert.Equal(email, rootTestScope.Span.GetTag(Tags.User.Email));
- Assert.Equal(name, rootTestScope.Span.GetTag(Tags.User.Name));
- Assert.Equal(id, rootTestScope.Span.GetTag(Tags.User.Id));
- Assert.Equal(sessionId, rootTestScope.Span.GetTag(Tags.User.SessionId));
- Assert.Equal(role, rootTestScope.Span.GetTag(Tags.User.Role));
- Assert.Equal(scope, rootTestScope.Span.GetTag(Tags.User.Scope));
+ var traceContext = rootTestScope.Span.Context.TraceContext;
+ Assert.Equal(email, traceContext.Tags.GetTag(Tags.User.Email));
+ Assert.Equal(name, traceContext.Tags.GetTag(Tags.User.Name));
+ Assert.Equal(id, traceContext.Tags.GetTag(Tags.User.Id));
+ Assert.Equal(sessionId, traceContext.Tags.GetTag(Tags.User.SessionId));
+ Assert.Equal(role, traceContext.Tags.GetTag(Tags.User.Role));
+ Assert.Equal(scope, traceContext.Tags.GetTag(Tags.User.Scope));
}
[Fact]