Skip to content

Commit 6dde60e

Browse files
author
Liudmila Molkova
authored
Enable e2e tracing in EventGrid WebJobs extension (Azure#25944)
* Enable tracing in EventGrid WebJobs extension
1 parent f4ec14d commit 6dde60e

File tree

12 files changed

+416
-49
lines changed

12 files changed

+416
-49
lines changed

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public void OnNext(KeyValuePair<string, object> value)
6767
{
6868
Name = name,
6969
Activity = Activity.Current,
70-
Links = links.Select(a => a.ParentId).ToList(),
70+
Links = links.Select(a => new ProducedLink(a.ParentId, a.TraceStateString)).ToList(),
7171
LinkedActivities = links.ToList()
7272
};
7373

@@ -234,13 +234,31 @@ public class ProducedDiagnosticScope
234234
public bool IsCompleted { get; set; }
235235
public bool IsFailed => Exception != null;
236236
public Exception Exception { get; set; }
237-
public List<string> Links { get; set; } = new List<string>();
237+
public List<ProducedLink> Links { get; set; } = new List<ProducedLink>();
238238
public List<Activity> LinkedActivities { get; set; } = new List<Activity>();
239239

240240
public override string ToString()
241241
{
242242
return Name;
243243
}
244244
}
245+
246+
public struct ProducedLink
247+
{
248+
public ProducedLink(string id)
249+
{
250+
Traceparent = id;
251+
Tracestate = null;
252+
}
253+
254+
public ProducedLink(string traceparent, string tracestate)
255+
{
256+
Traceparent = traceparent;
257+
Tracestate = tracestate;
258+
}
259+
260+
public string Traceparent { get; set; }
261+
public string Tracestate { get; set; }
262+
}
245263
}
246264
}

sdk/core/Azure.Core/src/Shared/DiagnosticScope.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ public void AddAttribute<T>(string name, T value, Func<T, string> format)
8787
}
8888
}
8989

90-
public void AddLink(string id, IDictionary<string, string>? attributes = null)
90+
public void AddLink(string traceparent, string tracestate, IDictionary<string, string>? attributes = null)
9191
{
92-
_activityAdapter?.AddLink(id, attributes);
92+
_activityAdapter?.AddLink(traceparent, tracestate, attributes);
9393
}
9494

9595
public void Start()
@@ -236,7 +236,7 @@ public void AddTag(string name, string value)
236236
}
237237
}
238238

239-
var link = ActivityExtensions.CreateActivityLink(activity.ParentId!, linkTagsCollection);
239+
var link = ActivityExtensions.CreateActivityLink(activity.ParentId!, activity.TraceStateString, linkTagsCollection);
240240
if (link != null)
241241
{
242242
linkCollection.Add(link);
@@ -246,11 +246,12 @@ public void AddTag(string name, string value)
246246
return linkCollection;
247247
}
248248

249-
public void AddLink(string id, IDictionary<string, string>? attributes)
249+
public void AddLink(string traceparent, string tracestate, IDictionary<string, string>? attributes)
250250
{
251251
var linkedActivity = new Activity("LinkedActivity");
252252
linkedActivity.SetW3CFormat();
253-
linkedActivity.SetParentId(id);
253+
linkedActivity.SetParentId(traceparent);
254+
linkedActivity.TraceStateString = tracestate;
254255

255256
if (attributes != null)
256257
{
@@ -367,7 +368,7 @@ static ActivityExtensions()
367368
private static Action<Activity, string, object?>? ActivityAddTagMethod;
368369
private static Func<object, string, int, ICollection<KeyValuePair<string, object>>?, IList?, DateTimeOffset, Activity?>? ActivitySourceStartActivityMethod;
369370
private static Func<object, bool>? ActivitySourceHasListenersMethod;
370-
private static Func<string, ICollection<KeyValuePair<string, object>>?, object?>? CreateActivityLinkMethod;
371+
private static Func<string, string?, ICollection<KeyValuePair<string, object>>?, object?>? CreateActivityLinkMethod;
371372
private static Func<ICollection<KeyValuePair<string,object>>?>? CreateTagsCollectionMethod;
372373

373374
private static readonly ParameterExpression ActivityParameter = Expression.Parameter(typeof(Activity));
@@ -490,7 +491,7 @@ public static bool SupportsActivitySource()
490491
return CreateTagsCollectionMethod();
491492
}
492493

493-
public static object? CreateActivityLink(string id, ICollection<KeyValuePair<string,object>>? tags)
494+
public static object? CreateActivityLink(string traceparent, string? tracestate, ICollection<KeyValuePair<string,object>>? tags)
494495
{
495496
if (ActivityLinkType == null)
496497
{
@@ -507,24 +508,25 @@ public static bool SupportsActivitySource()
507508
ActivityTagsCollectionType == null ||
508509
ActivityContextType == null)
509510
{
510-
CreateActivityLinkMethod = (_, _) => null;
511+
CreateActivityLinkMethod = (_, _, _) => null;
511512
}
512513
else
513514
{
514-
var nameParameter = Expression.Parameter(typeof(string));
515+
var traceparentParameter = Expression.Parameter(typeof(string));
516+
var tracestateParameter = Expression.Parameter(typeof(string));
515517
var tagsParameter = Expression.Parameter(typeof(ICollection<KeyValuePair<string,object>>));
516518

517-
CreateActivityLinkMethod = Expression.Lambda<Func<string, ICollection<KeyValuePair<string, object>>?, object?>>(
519+
CreateActivityLinkMethod = Expression.Lambda<Func<string, string?, ICollection<KeyValuePair<string, object>>?, object?>>(
518520
Expression.TryCatch(
519521
Expression.Convert(Expression.New(ctor,
520-
Expression.Call(parseMethod, nameParameter, Expression.Default(typeof(string))),
522+
Expression.Call(parseMethod, traceparentParameter, tracestateParameter),
521523
Expression.Convert(tagsParameter, ActivityTagsCollectionType)), typeof(object)),
522524
Expression.Catch(typeof(Exception), Expression.Default(typeof(object)))),
523-
nameParameter, tagsParameter).Compile();
525+
traceparentParameter, tracestateParameter, tagsParameter).Compile();
524526
}
525527
}
526528

527-
return CreateActivityLinkMethod(id, tags);
529+
return CreateActivityLinkMethod(traceparent, tracestate, tags);
528530
}
529531

530532
public static bool ActivitySourceHasListeners(object? activitySource)
@@ -657,4 +659,4 @@ public static void ResetFeatureSwitch()
657659
"AZURE_EXPERIMENTAL_ENABLE_ACTIVITY_SOURCE");
658660
}
659661
}
660-
}
662+
}

sdk/core/Azure.Core/tests/ClientDiagnosticsTests.Net50.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ public void StartsActivitySourceActivity()
6767
scope.AddAttribute("Attribute2", 2, i => i.ToString());
6868
scope.AddAttribute("Attribute3", 3);
6969

70-
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00");
71-
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", new Dictionary<string, string>()
70+
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", "foo=bar");
71+
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", null, new Dictionary<string, string>()
7272
{
7373
{"linkAttribute", "linkAttributeValue"}
7474
});
@@ -88,7 +88,7 @@ public void StartsActivitySourceActivity()
8888

8989
var links = activity.Links.ToArray();
9090
Assert.AreEqual(2, links.Length);
91-
Assert.AreEqual(ActivityContext.Parse("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", null), links[0].Context);
91+
Assert.AreEqual(ActivityContext.Parse("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", "foo=bar"), links[0].Context);
9292
Assert.AreEqual(ActivityContext.Parse("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", null), links[1].Context);
9393

9494
Assert.AreEqual(ActivityIdFormat.W3C, activity.IdFormat);
@@ -174,7 +174,7 @@ public void StartActivitySourceActivityIgnoresInvalidLinkParent()
174174

175175
DiagnosticScope scope = clientDiagnostics.CreateScope("ClientName.ActivityName");
176176

177-
scope.AddLink("test");
177+
scope.AddLink("test", "ignored");
178178

179179
scope.Start();
180180
scope.Dispose();

sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ public void AddLinkCallsPassesLinksAsPartOfStartPayload()
102102

103103
DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
104104

105-
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00");
106-
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00");
105+
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", "foo=bar");
106+
scope.AddLink("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", null);
107107
scope.Start();
108108

109109
(string Key, object Value, DiagnosticListener) startEvent = testListener.Events.Dequeue();
@@ -131,9 +131,11 @@ public void AddLinkCallsPassesLinksAsPartOfStartPayload()
131131

132132
Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity1.IdFormat);
133133
Assert.AreEqual("00-6e76af18746bae4eadc3581338bbe8b1-2899ebfdbdce904b-00", linkedActivity1.ParentId);
134+
Assert.AreEqual("foo=bar", linkedActivity1.TraceStateString);
134135

135136
Assert.AreEqual(ActivityIdFormat.W3C, linkedActivity2.IdFormat);
136137
Assert.AreEqual("00-6e76af18746bae4eadc3581338bbe8b2-2899ebfdbdce904b-00", linkedActivity2.ParentId);
138+
Assert.Null(linkedActivity2.TraceStateString);
137139

138140
Assert.AreEqual(0, testListener.Events.Count);
139141
}
@@ -152,7 +154,7 @@ public void AddLinkCreatesLinkedActivityWithTags()
152154
{"key2", "value2"}
153155
};
154156

155-
scope.AddLink("id", expectedTags);
157+
scope.AddLink("id", null, expectedTags);
156158
scope.Start();
157159

158160
(string Key, object Value, DiagnosticListener) startEvent = testListener.Events.Dequeue();

sdk/eventgrid/Microsoft.Azure.WebJobs.Extensions.EventGrid/src/EventGridExtensionConfigProvider.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Threading.Tasks;
1111
using System.Web;
1212
using Azure;
13+
using Azure.Core.Pipeline;
1314
using Azure.Messaging;
1415
using Azure.Messaging.EventGrid;
1516
using Microsoft.Azure.WebJobs.Description;
@@ -34,6 +35,12 @@ internal class EventGridExtensionConfigProvider : IExtensionConfigProvider,
3435
private readonly ILoggerFactory _loggerFactory;
3536
private readonly Func<EventGridAttribute, IAsyncCollector<object>> _converter;
3637
private readonly HttpRequestProcessor _httpRequestProcessor;
38+
private readonly DiagnosticScopeFactory _diagnosticScopeFactory;
39+
40+
// ApplicationInsights SDK listens to all Azure SDK sources that look like 'Azure.*'
41+
private const string DiagnosticScopeNamespace = "Azure.Messaging.EventGrid";
42+
private const string ResourceProviderNamespace = "Microsoft.EventGrid";
43+
private const string DiagnosticScopeName = "EventGrid.Process";
3744

3845
// for end to end testing
3946
internal EventGridExtensionConfigProvider(
@@ -44,6 +51,7 @@ internal EventGridExtensionConfigProvider(
4451
_converter = converter;
4552
_httpRequestProcessor = httpRequestProcessor;
4653
_loggerFactory = loggerFactory;
54+
_diagnosticScopeFactory = new DiagnosticScopeFactory(DiagnosticScopeNamespace, ResourceProviderNamespace, true);
4755
}
4856

4957
// default constructor
@@ -52,6 +60,7 @@ public EventGridExtensionConfigProvider(HttpRequestProcessor httpRequestProcesso
5260
_converter = (attr => new EventGridAsyncCollector(new EventGridPublisherClient(new Uri(attr.TopicEndpointUri), new AzureKeyCredential(attr.TopicKeySetting))));
5361
_httpRequestProcessor = httpRequestProcessor;
5462
_loggerFactory = loggerFactory;
63+
_diagnosticScopeFactory = new DiagnosticScopeFactory(DiagnosticScopeNamespace, ResourceProviderNamespace, true);
5564
}
5665

5766
public void Initialize(ExtensionConfigContext context)
@@ -147,7 +156,7 @@ private async Task<HttpResponseMessage> ProcessEventsAsync(JArray events, string
147156
{
148157
TriggerValue = ev
149158
};
150-
executions.Add(_listeners[functionName].Executor.TryExecuteAsync(triggerData, CancellationToken.None));
159+
executions.Add(ExecuteWithTracingAsync(functionName, triggerData));
151160
}
152161
}
153162
// Batch Dispatch
@@ -157,8 +166,9 @@ private async Task<HttpResponseMessage> ProcessEventsAsync(JArray events, string
157166
{
158167
TriggerValue = events
159168
};
160-
executions.Add(_listeners[functionName].Executor.TryExecuteAsync(triggerData, CancellationToken.None));
169+
executions.Add(ExecuteWithTracingAsync(functionName, triggerData));
161170
}
171+
162172
await Task.WhenAll(executions).ConfigureAwait(false);
163173

164174
// FIXME without internal queuing, we are going to process all events in parallel
@@ -174,6 +184,50 @@ private async Task<HttpResponseMessage> ProcessEventsAsync(JArray events, string
174184
return new HttpResponseMessage(HttpStatusCode.Accepted);
175185
}
176186

187+
private async Task<FunctionResult> ExecuteWithTracingAsync(string functionName, TriggeredFunctionData triggerData)
188+
{
189+
using DiagnosticScope scope = _diagnosticScopeFactory.CreateScope(DiagnosticScopeName, DiagnosticScope.ActivityKind.Consumer);
190+
if (scope.IsEnabled)
191+
{
192+
if (triggerData.TriggerValue is JArray evntArray)
193+
{
194+
foreach (JToken eventToken in evntArray)
195+
{
196+
AddLinkIfEventHasContext(scope, eventToken);
197+
}
198+
}
199+
else if (triggerData.TriggerValue is JToken eventToken)
200+
{
201+
AddLinkIfEventHasContext(scope, eventToken);
202+
}
203+
}
204+
205+
scope.Start();
206+
207+
FunctionResult result = await _listeners[functionName].Executor.TryExecuteAsync(triggerData, CancellationToken.None).ConfigureAwait(false);
208+
if (result.Exception != null)
209+
{
210+
scope.Failed(result.Exception);
211+
}
212+
return result;
213+
}
214+
215+
private static void AddLinkIfEventHasContext(DiagnosticScope scope, JToken evnt)
216+
{
217+
if (evnt is JObject eventObj &&
218+
eventObj.TryGetValue("traceparent", out JToken traceparent) &&
219+
traceparent.Type == JTokenType.String)
220+
{
221+
string tracestateStr = null;
222+
if (eventObj.TryGetValue("tracestate", out JToken tracestate) &&
223+
tracestate.Type == JTokenType.String)
224+
{
225+
tracestateStr = tracestate.Value<string>();
226+
}
227+
scope.AddLink(traceparent.Value<string>(), tracestateStr);
228+
}
229+
}
230+
177231
private class JTokenToPocoConverter<T> : IConverter<JToken, T>
178232
{
179233
public T Convert(JToken input)

sdk/eventgrid/Microsoft.Azure.WebJobs.Extensions.EventGrid/src/Microsoft.Azure.WebJobs.Extensions.EventGrid.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,10 @@
1414
<PackageReference Include="Microsoft.Azure.WebJobs" />
1515
<PackageReference Include="Azure.Messaging.EventGrid" />
1616
</ItemGroup>
17+
18+
<ItemGroup>
19+
<Compile Include="$(AzureCoreSharedSources)AppContextSwitchHelper.cs" LinkBase="Shared" />
20+
<Compile Include="$(AzureCoreSharedSources)DiagnosticScope.cs" LinkBase="Shared" />
21+
<Compile Include="$(AzureCoreSharedSources)DiagnosticScopeFactory.cs" LinkBase="Shared" />
22+
</ItemGroup>
1723
</Project>

0 commit comments

Comments
 (0)