Skip to content

Commit b20c241

Browse files
Store Traceparent in CloudEvent attributes (Azure#15515)
1 parent 0796e89 commit b20c241

File tree

35 files changed

+790
-122
lines changed

35 files changed

+790
-122
lines changed

sdk/core/Azure.Core.TestFramework/src/RecordedTestSanitizer.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ public class RecordedTestSanitizer
1515
public const string SanitizeValue = "Sanitized";
1616
public List<string> JsonPathSanitizers { get; } = new List<string>();
1717

18+
/// <summary>
19+
/// This is just a temporary workaround to avoid breaking tests that need to be re-recorded
20+
// when updating the JsonPathSanitizer logic to avoid changing date formats when deserializing requests.
21+
// this property will be removed in the future.
22+
/// </summary>
23+
public bool DoNotConvertJsonDateTokens { get; set; }
24+
1825
private static readonly string[] s_sanitizeValueArray = { SanitizeValue };
1926

2027
public List<string> SanitizedHeaders { get; } = new List<string> { "Authorization" };
@@ -41,15 +48,31 @@ public virtual string SanitizeTextBody(string contentType, string body)
4148
return body;
4249
try
4350
{
44-
var jsonO = JToken.Parse(body);
51+
var settings = new JsonSerializerSettings
52+
{
53+
DateParseHandling = DateParseHandling.None
54+
};
55+
56+
JToken jsonO;
57+
// Prevent default behavior where JSON.NET will convert DateTimeOffset
58+
// into a DateTime.
59+
if (DoNotConvertJsonDateTokens)
60+
{
61+
jsonO = JsonConvert.DeserializeObject<JToken>(body, settings);
62+
}
63+
else
64+
{
65+
jsonO = JToken.Parse(body);
66+
}
67+
4568
foreach (string jsonPath in JsonPathSanitizers)
4669
{
4770
foreach (JToken token in jsonO.SelectTokens(jsonPath))
4871
{
4972
token.Replace(JToken.FromObject(SanitizeValue));
5073
}
5174
}
52-
return JsonConvert.SerializeObject(jsonO);
75+
return JsonConvert.SerializeObject(jsonO, settings);
5376
}
5477
catch
5578
{

sdk/eventgrid/Azure.Messaging.EventGrid/api/Azure.Messaging.EventGrid.netstandard2.0.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public EventGridPublisherClient(System.Uri endpoint, Azure.Messaging.EventGrid.E
5959
public partial class EventGridPublisherClientOptions : Azure.Core.ClientOptions
6060
{
6161
public EventGridPublisherClientOptions(Azure.Messaging.EventGrid.EventGridPublisherClientOptions.ServiceVersion version = Azure.Messaging.EventGrid.EventGridPublisherClientOptions.ServiceVersion.V2018_01_01) { }
62-
public Azure.Core.Serialization.ObjectSerializer DataSerializer { get { throw null; } set { } }
62+
public Azure.Core.Serialization.ObjectSerializer Serializer { get { throw null; } set { } }
6363
public enum ServiceVersion
6464
{
6565
V2018_01_01 = 1,

sdk/eventgrid/Azure.Messaging.EventGrid/samples/Sample1_PublishEventsToTopic.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ EventGridPublisherClient client = new EventGridPublisherClient(
1616
```C# Snippet:CreateClientWithOptions
1717
EventGridPublisherClientOptions clientOptions = new EventGridPublisherClientOptions()
1818
{
19-
DataSerializer = myCustomDataSerializer
19+
Serializer = myCustomDataSerializer
2020
};
2121

2222
EventGridPublisherClient client = new EventGridPublisherClient(

sdk/eventgrid/Azure.Messaging.EventGrid/src/Customization/EventGridEvent.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ public static EventGridEvent[] Parse(string requestContent)
117117
egEventInternal.EventType,
118118
egEventInternal.DataVersion,
119119
egEventInternal.EventTime,
120-
egEventInternal.Id);
120+
egEventInternal.Id)
121+
{
122+
Topic = egEventInternal.Topic
123+
};
121124

122125
egEvents.Add(egEvent);
123126
}

sdk/eventgrid/Azure.Messaging.EventGrid/src/Customization/EventGridPublisherClient.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Globalization;
78
using System.IO;
8-
using System.Linq;
99
using System.Security.Cryptography;
1010
using System.Text;
1111
using System.Text.Json;
@@ -32,6 +32,9 @@ public class EventGridPublisherClient
3232
private readonly string _apiVersion;
3333
private readonly ObjectSerializer _dataSerializer;
3434

35+
private const string TraceParentHeaderName = "traceparent";
36+
private const string TraceStateHeaderName = "tracestate";
37+
3538
/// <summary>Initalizes an instance of EventGridClient.</summary>
3639
protected EventGridPublisherClient()
3740
{
@@ -62,7 +65,7 @@ public EventGridPublisherClient(Uri endpoint, AzureKeyCredential credential, Eve
6265
Argument.AssertNotNull(credential, nameof(credential));
6366
options ??= new EventGridPublisherClientOptions();
6467
_apiVersion = options.Version.GetVersionString();
65-
_dataSerializer = options.DataSerializer ?? new JsonObjectSerializer();
68+
_dataSerializer = options.Serializer ?? new JsonObjectSerializer();
6669
_endpoint = endpoint;
6770
_key = credential;
6871
HttpPipeline pipeline = HttpPipelineBuilder.Build(options, new AzureKeyCredentialPolicy(credential, Constants.SasKeyName));
@@ -80,7 +83,7 @@ public EventGridPublisherClient(Uri endpoint, EventGridSharedAccessSignatureCred
8083
{
8184
Argument.AssertNotNull(credential, nameof(credential));
8285
options ??= new EventGridPublisherClientOptions();
83-
_dataSerializer = options.DataSerializer ?? new JsonObjectSerializer();
86+
_dataSerializer = options.Serializer ?? new JsonObjectSerializer();
8487
_endpoint = endpoint;
8588
HttpPipeline pipeline = HttpPipelineBuilder.Build(options, new EventGridSharedAccessSignatureCredentialPolicy(credential));
8689
_serviceRestClient = new EventGridRestClient(new ClientDiagnostics(options), pipeline, options.Version.GetVersionString());
@@ -198,6 +201,15 @@ private async Task<Response> SendCloudEventsInternal(IEnumerable<CloudEvent> eve
198201
// List of events cannot be null
199202
Argument.AssertNotNull(events, nameof(events));
200203

204+
string activityId = null;
205+
string traceState = null;
206+
Activity currentActivity = Activity.Current;
207+
if (currentActivity != null && currentActivity.IsW3CFormat())
208+
{
209+
activityId = currentActivity.Id;
210+
currentActivity.TryGetTraceState(out traceState);
211+
}
212+
201213
List<CloudEventInternal> eventsWithSerializedPayloads = new List<CloudEventInternal>();
202214
foreach (CloudEvent cloudEvent in events)
203215
{
@@ -222,6 +234,17 @@ private async Task<Response> SendCloudEventsInternal(IEnumerable<CloudEvent> eve
222234
newCloudEvent.Add(kvp.Key, new CustomModelSerializer(kvp.Value, _dataSerializer, cancellationToken));
223235
}
224236

237+
if (activityId != null &&
238+
!cloudEvent.ExtensionAttributes.ContainsKey(TraceParentHeaderName) &&
239+
!cloudEvent.ExtensionAttributes.ContainsKey(TraceStateHeaderName))
240+
{
241+
newCloudEvent.Add(TraceParentHeaderName, activityId);
242+
if (traceState != null)
243+
{
244+
newCloudEvent.Add(TraceStateHeaderName, traceState);
245+
}
246+
}
247+
225248
// The 'Data' property is optional for CloudEvents
226249
// Additionally, if the type of data is binary, 'Data' will not be populated (data will be stored in 'DataBase64' instead)
227250
if (cloudEvent.Data != null)

sdk/eventgrid/Azure.Messaging.EventGrid/src/Customization/EventGridPublisherClientOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public enum ServiceVersion
4040
/// <summary>
4141
/// Used to serialize the payloads of given events to UTF-8 encoded JSON.
4242
/// </summary>
43-
public ObjectSerializer DataSerializer { get; set; }
43+
public ObjectSerializer Serializer { get; set; }
4444

4545
/// <summary>
4646
/// Initializes a new instance of the <see cref="EventGridPublisherClientOptions"/>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Text.Json;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Azure.Core;
12+
using Azure.Core.Pipeline;
13+
using Azure.Core.TestFramework;
14+
using Azure.Messaging.EventGrid.Models;
15+
using NUnit.Framework;
16+
17+
namespace Azure.Messaging.EventGrid.Tests
18+
{
19+
public class CloudEventTests
20+
{
21+
22+
private const string TraceParentHeaderName = "traceparent";
23+
private const string TraceStateHeaderName = "tracestate";
24+
25+
[Test]
26+
[TestCase(false, false)]
27+
[TestCase(true, true)]
28+
[TestCase(true, false)]
29+
[TestCase(false, true)]
30+
public async Task SetsTraceParentExtension(bool inclTraceparent, bool inclTracestate)
31+
{
32+
var mockTransport = new MockTransport(new MockResponse(200));
33+
var options = new EventGridPublisherClientOptions
34+
{
35+
Transport = mockTransport
36+
};
37+
EventGridPublisherClient client =
38+
new EventGridPublisherClient(
39+
new Uri("http://localHost"),
40+
new AzureKeyCredential("fakeKey"),
41+
options);
42+
var activity = new Activity($"{nameof(EventGridPublisherClient)}.{nameof(EventGridPublisherClient.SendEvents)}");
43+
activity.SetW3CFormat();
44+
activity.Start();
45+
List<CloudEvent> eventsList = new List<CloudEvent>();
46+
for (int i = 0; i < 10; i++)
47+
{
48+
CloudEvent cloudEvent = new CloudEvent(
49+
"record",
50+
"Microsoft.MockPublisher.TestEvent",
51+
JsonDocument.Parse("{\"property1\": \"abc\", \"property2\": 123}").RootElement)
52+
{
53+
Id = "id",
54+
Subject = $"Subject-{i}",
55+
Time = DateTimeOffset.UtcNow
56+
};
57+
if (inclTraceparent && i % 2 == 0)
58+
{
59+
cloudEvent.ExtensionAttributes.Add("traceparent", "traceparentValue");
60+
}
61+
if (inclTracestate && i % 2 == 0)
62+
{
63+
cloudEvent.ExtensionAttributes.Add("tracestate", "param:value");
64+
}
65+
eventsList.Add(cloudEvent);
66+
}
67+
await client.SendEventsAsync(eventsList);
68+
69+
activity.Stop();
70+
List<CloudEventInternal> cloudEvents = DeserializeRequest(mockTransport.SingleRequest);
71+
IEnumerator<CloudEvent> cloudEnum = eventsList.GetEnumerator();
72+
foreach (CloudEventInternal cloudInternal in cloudEvents)
73+
{
74+
cloudEnum.MoveNext();
75+
Dictionary<string, object> cloudEventAttr = cloudEnum.Current.ExtensionAttributes;
76+
if (cloudEventAttr.ContainsKey(TraceParentHeaderName) &&
77+
cloudEventAttr.ContainsKey(TraceStateHeaderName))
78+
{
79+
Assert.AreEqual(
80+
cloudEventAttr[TraceParentHeaderName],
81+
cloudInternal[TraceParentHeaderName]);
82+
83+
Assert.AreEqual(
84+
cloudEventAttr[TraceStateHeaderName],
85+
cloudInternal[TraceStateHeaderName]);
86+
}
87+
else if (cloudEventAttr.ContainsKey(TraceParentHeaderName))
88+
{
89+
Assert.AreEqual(
90+
cloudEventAttr[TraceParentHeaderName],
91+
cloudInternal[TraceParentHeaderName]);
92+
}
93+
else if (cloudEventAttr.ContainsKey(TraceStateHeaderName))
94+
{
95+
Assert.AreEqual(
96+
cloudEventAttr[TraceStateHeaderName],
97+
cloudInternal[TraceStateHeaderName]);
98+
}
99+
else
100+
{
101+
Assert.AreEqual(
102+
activity.Id,
103+
cloudInternal[TraceParentHeaderName]);
104+
}
105+
}
106+
}
107+
108+
private static List<CloudEventInternal> DeserializeRequest(Request request)
109+
{
110+
var content = request.Content as Utf8JsonRequestContent;
111+
var stream = new MemoryStream();
112+
content.WriteTo(stream, CancellationToken.None);
113+
stream.Position = 0;
114+
JsonDocument requestDocument = JsonDocument.Parse(stream);
115+
var cloudEvents = new List<CloudEventInternal>();
116+
// Parse JsonElement into separate events, deserialize event envelope properties
117+
if (requestDocument.RootElement.ValueKind == JsonValueKind.Object)
118+
{
119+
cloudEvents.Add(CloudEventInternal.DeserializeCloudEventInternal(requestDocument.RootElement));
120+
}
121+
else if (requestDocument.RootElement.ValueKind == JsonValueKind.Array)
122+
{
123+
foreach (JsonElement property in requestDocument.RootElement.EnumerateArray())
124+
{
125+
cloudEvents.Add(CloudEventInternal.DeserializeCloudEventInternal(property));
126+
}
127+
}
128+
return cloudEvents;
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)