Skip to content
This repository was archived by the owner on Aug 27, 2024. It is now read-only.

Commit 5dcbc43

Browse files
feat: add w3c event correlation support (#251)
* feat: add w3c event correlation support * Update docs/preview/02-Features/publishing-events.md Co-authored-by: Frederik Gheysels <frederik.gheysels@telenet.be> Co-authored-by: Frederik Gheysels <frederik.gheysels@telenet.be>
1 parent 32271b7 commit 5dcbc43

9 files changed

+349
-193
lines changed

docs/preview/02-Features/publishing-events.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public void ConfigureServices(IServiceCollection services)
172172

173173
### Correlation tracking
174174
The library also provides several ways to influence the correlation tracking during the event publishing.
175-
By default, each event that gets published will be enriched with two correlation properties: transaction ID and the operation parent ID. These two properties should be configured as [custom delivery properties](https://docs.microsoft.com/en-us/azure/event-grid/delivery-properties) in Azure Event Grid so that the event processor can easily access them and continue to correlate the message.
175+
By default, each event that gets published will be enriched with the `traceparent` correlation property. This property should be configured as a [custom delivery property](https://docs.microsoft.com/en-us/azure/event-grid/delivery-properties) in Azure Event Grid so that the event processor can easily access them and continue to correlate the message.
176176

177177
> ⚠ The Arcus `EventGridPublisherClient` assumes that every event published is an JSON event with `data` JSON node.
178178
@@ -224,17 +224,18 @@ Then the Arcus `EventGridPublisherClient` will alter the event data as follows:
224224
"storageDiagnostics": {
225225
"batchId": "681fe319-3006-00a8-0022-9e7cde000000"
226226
},
227-
"transactionId": "<your-transaction-id>",
228-
"operationParentId": "<your-dependency-id>"
227+
"traceparent": "00-<your-transaction-id>-<your-parent-id>-00"
229228
}
230229
}
231230
```
232231

233-
These properties can be accessed with `data.transactionId` and `data.operationParentId` in your Azure Event Grid custom delivery properties of your event subscription.
234-
The example shows how these properties are transfered to application properties `Transaction-Id` and `Operation-Parent-Id`. These are the default correlation properties of the [Arcus message pump](https://messaging.arcus-azure.net/).
232+
This property can be accessed with `data.traceparent` in your Azure Event Grid custom delivery properties of your event subscription.
233+
The example shows how this property is transfered to an application property `Diagnostic-Id`. This is the default correlation property of the [Arcus message pump](https://messaging.arcus-azure.net/) using the W3C message correlation.
235234
Use the correct correlation properties of your event processor system to correctly correlate the event.
236235

237-
![Azure Event Grid custom delivery properties](/media/event-grid-delivery-properties.png)
236+
![Azure Event Grid custom delivery properties](/media/event-grid-traceparent-delivery-properties.png)
237+
238+
> ⚡ We also support Hierarchical event correlation, where the the event has a `data.transactionId` and `data.operationParentId` instead of a `data.traceparent` property. This can be configured using the `Format` option described below.
238239
239240
Several other options related to correlation can be configured on the client:
240241
```csharp
@@ -253,16 +254,25 @@ public void ConfigureServices(IServiceCollection services)
253254
"<my-authentication-secret-name>",
254255
options =>
255256
{
257+
// The function to generate the dependency ID used when tracking the event publishing.
258+
// This value corresponds with the operation parent ID on the receiver side, and is called the dependency ID on this side (sender).
259+
options.GenerateDependencyId = () => $"custom-dependency-id-{Guid.NewGuid()}";
260+
261+
// W3C event correlation
262+
// ---------------------
263+
264+
// The name of the JSON property that represents the 'traceparent' that will be added to the event data of the published event (default: traceparent).
265+
options.TraceParentEventPropertyName = "customTraceParent";
266+
267+
// Hierarhical event correlation
268+
// -----------------------------
269+
256270
// The name of the JSON property that represents the transaction ID that will be added to the event data of the published event (default: transactionId).
257271
options.TransactionIdEventDataPropertyName = "customTransactionId";
258272

259273
// The name of the JSON property that represents the operation parent ID that will be added to the event data of the published event (default: operationParentId).
260274
options.UpstreamServicePropertyName = "customOperationParentId";
261275

262-
// The function to generate the dependency ID used when tracking the event publishing.
263-
// This value corresponds with the operation parent ID on the receiver side, and is called the dependency ID on this side (sender).
264-
options.GenerateDependencyId = () => $"custom-dependency-id-{Guid.NewGuid()}";
265-
266276
// Adds a telemetry context while tracking the Azure Event Grid dependency.
267277
options.AddTelemetryContext(new Dictionary<string, object>
268278
{
29.6 KB
Loading
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Arcus.EventGrid.Core
2+
{
3+
/// <summary>
4+
/// Represents the event correlation format of the send-out events.
5+
/// </summary>
6+
public enum EventCorrelationFormat
7+
{
8+
/// <summary>
9+
/// Uses the W3C event correlation system with traceparent and tracestate to represent parent-child relationship.
10+
/// </summary>
11+
W3C,
12+
13+
/// <summary>
14+
/// Uses the hierarchical event correlation system with Root-Id and Request-Id to represent parent-child relationship.
15+
/// </summary>
16+
Hierarchical
17+
}
18+
}

src/Arcus.EventGrid.Core/Publishing/EventGridPublisherClientWithTracking.cs

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Text.Json.Nodes;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Arcus.EventGrid.Core;
910
using Arcus.Observability.Correlation;
1011
using Arcus.Observability.Telemetry.Core;
1112
using Arcus.Security.Core;
@@ -350,6 +351,7 @@ private async Task<Response> TrackPublishEventAsync<TEvent>(object oneOrManyEven
350351

351352
string dependencyId = Options.GenerateDependencyId();
352353
string transactionId = CorrelationAccessor.GetCorrelationInfo()?.TransactionId;
354+
353355
oneOrManyEvents = SetCorrelationPropertiesInEvent(oneOrManyEvents, dependencyId, transactionId);
354356

355357
bool isSuccessful = false;
@@ -410,47 +412,55 @@ private static string DetermineEventType(object @event)
410412

411413
private object SetCorrelationPropertiesInEvent(object @event, string dependencyId, string transactionId)
412414
{
413-
if (!Options.EnableDependencyTracking)
415+
if (Options.Format is EventCorrelationFormat.Hierarchical
416+
&& !Options.EnableDependencyTracking)
414417
{
415418
return @event;
416419
}
417420

418-
string upstreamServicePropertyName = Options.UpstreamServicePropertyName;
419-
string transactionIdPropertyName = Options.TransactionIdEventDataPropertyName;
421+
var properties = new Collection<(string propertyName, string propertyValue)>();
422+
if (Options.Format is EventCorrelationFormat.Hierarchical)
423+
{
424+
properties.Add((Options.TransactionIdEventDataPropertyName, transactionId));
425+
properties.Add((Options.UpstreamServicePropertyName, dependencyId));
426+
}
420427

421-
switch (@event)
428+
if (Options.Format is EventCorrelationFormat.W3C)
422429
{
423-
case BinaryData data:
424-
data = SetCorrelationPropertyInCustomEvent(data, upstreamServicePropertyName, dependencyId);
425-
data = SetCorrelationPropertyInCustomEvent(data, transactionIdPropertyName, transactionId);
426-
return data;
427-
case IEnumerable<BinaryData> datas:
428-
datas = SetCorrelationPropertyInCustomEvents(datas, upstreamServicePropertyName, dependencyId);
429-
datas = SetCorrelationPropertyInCustomEvents(datas, transactionIdPropertyName, transactionId);
430-
return datas;
431-
case EventGridEvent ev:
432-
ev = SetCorrelationPropertyInEventGridEvent(ev, upstreamServicePropertyName, dependencyId);
433-
ev = SetCorrelationPropertyInEventGridEvent(ev, transactionIdPropertyName, transactionId);
434-
return ev;
435-
case IEnumerable<EventGridEvent> events:
436-
events = SetCorrelationPropertyInEventGridEvents(events, upstreamServicePropertyName, dependencyId);
437-
events = SetCorrelationPropertyInEventGridEvents(events, transactionIdPropertyName, transactionId);
438-
return events;
439-
case CloudEvent ev:
440-
ev = SetCorrelationPropertyInCloudEvent(ev, upstreamServicePropertyName, dependencyId);
441-
ev = SetCorrelationPropertyInCloudEvent(ev, transactionIdPropertyName, transactionId);
442-
return ev;
443-
case IEnumerable<CloudEvent> events:
444-
events = SetCorrelationPropertyInCloudEvents(events, upstreamServicePropertyName, dependencyId);
445-
events = SetCorrelationPropertyInCloudEvents(events, transactionIdPropertyName, transactionId);
446-
return events;
447-
case ReadOnlyMemory<byte> encodedCloudEvents:
448-
encodedCloudEvents = SetCorrelationPropertyInEncodedCloudEvents(encodedCloudEvents, upstreamServicePropertyName, dependencyId);
449-
encodedCloudEvents = SetCorrelationPropertyInEncodedCloudEvents(encodedCloudEvents, transactionIdPropertyName, transactionId);
450-
return encodedCloudEvents;
451-
default:
452-
throw new ArgumentOutOfRangeException(nameof(@event), @event, "Unknown event type");
430+
properties.Add((Options.TraceParentPropertyName, $"00-{transactionId}-{dependencyId}-00"));
453431
}
432+
433+
foreach ((string propertyName, string propertyValue) in properties)
434+
{
435+
switch (@event)
436+
{
437+
case BinaryData data:
438+
@event = SetCorrelationPropertyInCustomEvent(data, propertyName, propertyValue);
439+
break;
440+
case IEnumerable<BinaryData> datas:
441+
@event = SetCorrelationPropertyInCustomEvents(datas, propertyName, propertyValue);
442+
break;
443+
case EventGridEvent ev:
444+
@event = SetCorrelationPropertyInEventGridEvent(ev, propertyName, propertyValue);
445+
break;
446+
case IEnumerable<EventGridEvent> events:
447+
@event = SetCorrelationPropertyInEventGridEvents(events, propertyName, propertyValue);
448+
break;
449+
case CloudEvent ev:
450+
@event = SetCorrelationPropertyInCloudEvent(ev, propertyName, propertyValue);
451+
break;
452+
case IEnumerable<CloudEvent> events:
453+
@event = SetCorrelationPropertyInCloudEvents(events, propertyName, propertyValue);
454+
break;
455+
case ReadOnlyMemory<byte> encodedCloudEvents:
456+
@event = SetCorrelationPropertyInEncodedCloudEvents(encodedCloudEvents, propertyName, propertyValue);
457+
break;
458+
default:
459+
throw new ArgumentOutOfRangeException(nameof(@event), @event, "Unknown event type");
460+
}
461+
}
462+
463+
return @event;
454464
}
455465

456466
private IEnumerable<EventGridEvent> SetCorrelationPropertyInEventGridEvents(IEnumerable<EventGridEvent> eventGridEvents, string propertyName, string dependencyId)
@@ -632,7 +642,8 @@ protected virtual BinaryData SetCorrelationPropertyInCustomEvent(BinaryData data
632642

633643
private void LogEventGridDependency(string eventType, bool isSuccessful, DurationMeasurement measurement, string dependencyId)
634644
{
635-
if (Options.EnableDependencyTracking)
645+
if (Options.Format is EventCorrelationFormat.Hierarchical
646+
&& Options.EnableDependencyTracking)
636647
{
637648
Logger.LogDependency(
638649
dependencyType: "Azure Event Grid",

src/Arcus.EventGrid.Core/Publishing/EventGridPublisherClientWithTrackingOptions.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using Arcus.EventGrid.Core;
34
using GuardNet;
45
using Microsoft.Extensions.Azure;
56
using Polly;
@@ -16,14 +17,37 @@ namespace Azure.Messaging.EventGrid
1617
/// </summary>
1718
public class EventGridPublisherClientWithTrackingOptions : EventGridPublisherClientOptions
1819
{
19-
private string _upstreamServicePropertyName = "operationParentId", _transactionIdPropertyName = "transactionId";
20+
private string _upstreamServicePropertyName = "operationParentId", _transactionIdPropertyName = "transactionId", _traceParentPropertyName = "traceparent";
2021
private Func<string> _generateDependencyId = () => Guid.NewGuid().ToString();
2122

23+
/// <summary>
24+
/// Gets or sets the format of the used event correlation when publishing events to an Azure EventGrid subscription (default: W3C).
25+
/// </summary>
26+
public EventCorrelationFormat Format { get; set; } = EventCorrelationFormat.W3C;
27+
28+
/// <summary>
29+
/// Gets or sets the name of the JSON property that represents the 'traceparent' that will be added to the event data of the published event (default: traceparent(.
30+
/// The 'traceparent' will be available as 'data.traceparent' (if default configured) and can be added as dynamic event delivery property: <a href="https://docs.microsoft.com/en-us/azure/event-grid/delivery-properties" />.
31+
/// </summary>
32+
/// <remarks>
33+
/// Only used when the <see cref="Format"/> is set to <see cref="EventCorrelationFormat.W3C"/>.
34+
/// </remarks>
35+
public string TraceParentPropertyName
36+
{
37+
get => _traceParentPropertyName;
38+
set
39+
{
40+
Guard.NotNullOrWhitespace(value, nameof(value), "Requires a non-blank JSON property name to add the 'traceparent' to the event data of the published event");
41+
_traceParentPropertyName = value;
42+
}
43+
}
44+
2245
/// <summary>
2346
/// Gets or sets the name of the JSON property that represents the transaction ID that will be added to the event data of the published event (default: transactionId).
2447
/// The transaction ID will be available as 'data.transactionId' (if default configured) and can be used as dynamic event delivery property: <a href="https://docs.microsoft.com/en-us/azure/event-grid/delivery-properties" />.
2548
/// </summary>
2649
/// <remarks>
50+
/// Only used when <see cref="Format"/> is set to <see cref="EventCorrelationFormat.Hierarchical"/>.
2751
/// Make sure that the system processing the event (Azure Service Bus, web hook, etc.) can retrieve this property so that the correlation holds.
2852
/// When using Arcus' messaging, the event header name for the dynamic event delivery property should be: 'Transaction-Id'.
2953
/// </remarks>
@@ -43,6 +67,7 @@ public string TransactionIdEventDataPropertyName
4367
/// The operation parent ID will be available as 'data.operationParentId' (if default configured) and can be used as dynamic event delivery property: <a href="https://docs.microsoft.com/en-us/azure/event-grid/delivery-properties" />.
4468
/// </summary>
4569
/// <remarks>
70+
/// Only used when <see cref="Format"/> is set to <see cref="EventCorrelationFormat.Hierarchical"/>.
4671
/// Make sure that the system processing the event (Azure Service Bus, web hook, etc.) can retrieve this property so that the correlation holds.
4772
/// When using Arcus' messaging, the event header name for the dynamic event delivery property should be: 'Operation-Parent-Id'.
4873
/// </remarks>
@@ -61,6 +86,9 @@ public string UpstreamServicePropertyName
6186
/// Gets or sets the function to generate the dependency ID used when tracking the event publishing.
6287
/// This value corresponds with the operation parent ID on the receiver side, and is called the dependency ID on this side (sender).
6388
/// </summary>
89+
/// <remarks>
90+
/// Can only be used when the <see cref="Format"/> is set to <see cref="EventCorrelationFormat.Hierarchical"/> as Microsoft dependencies are usually automatically tracked.
91+
/// </remarks>
6492
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="value"/> is <c>null</c>.</exception>
6593
public Func<string> GenerateDependencyId
6694
{
@@ -80,6 +108,9 @@ public Func<string> GenerateDependencyId
80108
/// <summary>
81109
/// Adds a telemetry context while tracking the Azure Event Grid dependency.
82110
/// </summary>
111+
/// <remarks>
112+
/// Can only be used when the <see cref="Format"/> is set to <see cref="EventCorrelationFormat.Hierarchical"/> as Microsoft dependencies are usually automatically tracked.
113+
/// </remarks>
83114
/// <param name="telemetryContext">The dictionary with the contextual information about the event publishing dependency tracking.</param>
84115
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="telemetryContext"/> is <c>null</c>.</exception>
85116
public void AddTelemetryContext(Dictionary<string, object> telemetryContext)
@@ -92,9 +123,13 @@ public void AddTelemetryContext(Dictionary<string, object> telemetryContext)
92123
}
93124

94125
/// <summary>
95-
/// <para>Gets or sets the flag indicating whether or not the <see cref="EventGridPublisherClient"/> should track the Azure Event Grid topic dependency.</para>
126+
/// <para>Gets or sets the flag indicating whether or not the <see cref="EventGridPublisherClient"/> should track the Azure Event Grid topic dependency (default: <c>false</c>).</para>
96127
/// <para>For more information about dependency tracking <see href="https://observability.arcus-azure.net/features/writing-different-telemetry-types#dependencies"/>.</para>
97128
/// </summary>
129+
/// <remarks>
130+
/// Note that Microsoft dependencies are automatically tracked when using the default Application Insights telemetry services.
131+
/// Consider using this only when <see cref="Format"/> is set to <see cref="EventCorrelationFormat.Hierarchical"/>.
132+
/// </remarks>
98133
public bool EnableDependencyTracking { get; set; } = true;
99134

100135
/// <summary>

0 commit comments

Comments
 (0)