From 10121e15ec17ee88f74059896d6ca168923e7586 Mon Sep 17 00:00:00 2001 From: Robert Pickering Date: Tue, 12 Jul 2022 21:01:12 +0200 Subject: [PATCH] Allow user id to be propagated (#2968) * Allow user id to be propagated * Update tracer/src/Datadog.Trace/SpanExtensions.cs Co-authored-by: Andrew Lock * Don't add the tag to the span if it's too long * Update tracer/src/Datadog.Trace/UserDetails.cs Co-authored-by: Lucas Pimentel-Ordyna * Changes * fix unite tests Co-authored-by: Andrew Lock Co-authored-by: Lucas Pimentel-Ordyna --- tracer/src/Datadog.Trace/SpanExtensions.cs | 36 ++++++++++++++---- tracer/src/Datadog.Trace/UserDetails.cs | 15 +++++++- ....Trace.PublicApiHasNotChanged.verified.txt | 1 + .../test/Datadog.Trace.Tests/TracerTests.cs | 38 ++++++++++--------- 4 files changed, 62 insertions(+), 28 deletions(-) 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]