1+ using System . Collections . Concurrent ;
12using System . Diagnostics ;
23using System . Net ;
34using System . Net . Http ;
@@ -66,8 +67,6 @@ namespace Microsoft.Kiota.Http.HttpClientLibrary.Tests;
6667public sealed class HttpClientRequestAdapterObservabilityTests : IDisposable
6768{
6869 private readonly HttpClientRequestAdapter _requestAdapter ;
69- private readonly ActivityListener _activityListener ;
70- private readonly List < Activity > _capturedActivities ;
7170
7271 public HttpClientRequestAdapterObservabilityTests ( )
7372 {
@@ -77,30 +76,34 @@ public HttpClientRequestAdapterObservabilityTests()
7776
7877 var authProvider = new AnonymousAuthenticationProvider ( ) ;
7978 var observabilityOptions = new ObservabilityOptions ( ) ;
80- _requestAdapter = new HttpClientRequestAdapter ( authProvider , observabilityOptions : observabilityOptions ) ;
81- _requestAdapter . BaseUrl = "https://graph.microsoft.com/v1.0" ;
82-
83- // Setup activity listener to capture activities from all Kiota sources
84- _capturedActivities = new List < Activity > ( ) ;
85- _activityListener = new ActivityListener
79+ _requestAdapter = new HttpClientRequestAdapter ( authProvider , observabilityOptions : observabilityOptions )
8680 {
87- ShouldListenTo = source => source . Name . StartsWith ( "Microsoft.Kiota" , StringComparison . OrdinalIgnoreCase ) ,
88- Sample = ( ref ActivityCreationOptions < ActivityContext > _ ) => ActivitySamplingResult . AllDataAndRecorded ,
89- ActivityStarted = activity => _capturedActivities . Add ( activity ) ,
90- ActivityStopped = activity => { } // Capture stopped activities to ensure tags are set
81+ BaseUrl = "https://graph.microsoft.com/v1.0"
9182 } ;
92- ActivitySource . AddActivityListener ( _activityListener ) ;
9383 }
9484
9585 public void Dispose ( )
9686 {
97- _activityListener ? . Dispose ( ) ;
9887 _requestAdapter ? . Dispose ( ) ;
9988 }
10089
101- private KeyValuePair < string , string ? > GetTagFromActivities ( string tagKey , ActivityTraceId traceId )
90+ private static ( ActivityListener Listener , ConcurrentQueue < Activity > CapturedActivities ) CreateActivityCapture ( )
10291 {
103- return _capturedActivities
92+ var capturedActivities = new ConcurrentQueue < Activity > ( ) ;
93+ var activityListener = new ActivityListener
94+ {
95+ ShouldListenTo = source => source . Name . StartsWith ( "Microsoft.Kiota" , StringComparison . OrdinalIgnoreCase ) ,
96+ Sample = ( ref ActivityCreationOptions < ActivityContext > _ ) => ActivitySamplingResult . AllDataAndRecorded ,
97+ ActivityStarted = activity => capturedActivities . Enqueue ( activity ) ,
98+ ActivityStopped = activity => { } // Capture stopped activities to ensure tags are set
99+ } ;
100+ ActivitySource . AddActivityListener ( activityListener ) ;
101+ return ( activityListener , capturedActivities ) ;
102+ }
103+
104+ private static KeyValuePair < string , string ? > GetTagFromActivities ( IEnumerable < Activity > capturedActivities , string tagKey , ActivityTraceId traceId )
105+ {
106+ return capturedActivities
104107 . Where ( a => a . TraceId == traceId )
105108 . SelectMany ( a => a . Tags )
106109 . FirstOrDefault ( t => t . Key == tagKey ) ;
@@ -162,6 +165,8 @@ public void GetNormalizedHttpRoute_NormalizesCorrectly(string input, string expe
162165 public async Task SendAsync_CreatesActivityWithHttpRouteTag ( string urlTemplate , string ? pathParamKey , string ? pathParamValue , string expectedRoute )
163166 {
164167 // Arrange
168+ var ( activityListener , capturedActivities ) = CreateActivityCapture ( ) ;
169+ using var listener = activityListener ;
165170 var mockHandler = new Mock < HttpMessageHandler > ( ) ;
166171 mockHandler . Protected ( )
167172 . Setup < Task < HttpResponseMessage > > ( "SendAsync" ,
@@ -185,15 +190,13 @@ public async Task SendAsync_CreatesActivityWithHttpRouteTag(string urlTemplate,
185190 pathParameters . Add ( pathParamKey , pathParamValue ) ;
186191 }
187192 var requestInfo = new RequestInformation ( Method . GET , urlTemplate , pathParameters ) ;
188- // Clear any previously captured activities
189- _capturedActivities . Clear ( ) ;
190193 using var testRoot = StartTestRootActivity ( ) ;
191194
192195 // Act
193196 await adapter . SendAsync < MockEntity > ( requestInfo , MockEntity . Factory , cancellationToken : TestContext . Current . CancellationToken ) ;
194197
195198 // Assert
196- var activity = _capturedActivities . FirstOrDefault ( a => a . TraceId == testRoot . TraceId && a . OperationName . Contains ( "SendAsync" ) ) ;
199+ var activity = capturedActivities . FirstOrDefault ( a => a . TraceId == testRoot . TraceId && a . OperationName . Contains ( "SendAsync" ) ) ;
197200 Assert . NotNull ( activity ) ;
198201
199202 var httpRouteTag = activity . Tags . FirstOrDefault ( t => t . Key == "http.route" ) ;
@@ -216,6 +219,8 @@ public async Task SendAsync_CreatesActivityWithHttpRouteTag(string urlTemplate,
216219 public async Task SendAsync_CreatesActivityWithUriTemplateTag ( string urlTemplate , string ? pathParamKey , string ? pathParamValue , string expectedSubstring )
217220 {
218221 // Arrange
222+ var ( activityListener , capturedActivities ) = CreateActivityCapture ( ) ;
223+ using var listener = activityListener ;
219224 var mockHandler = new Mock < HttpMessageHandler > ( ) ;
220225 mockHandler . Protected ( )
221226 . Setup < Task < HttpResponseMessage > > ( "SendAsync" ,
@@ -239,16 +244,13 @@ public async Task SendAsync_CreatesActivityWithUriTemplateTag(string urlTemplate
239244 pathParameters . Add ( pathParamKey , pathParamValue ) ;
240245 }
241246 var requestInfo = new RequestInformation ( Method . GET , urlTemplate , pathParameters ) ;
242-
243- // Clear any previously captured activities
244- _capturedActivities . Clear ( ) ;
245247 using var testRoot = StartTestRootActivity ( ) ;
246248
247249 // Act
248250 await adapter . SendAsync < MockEntity > ( requestInfo , MockEntity . Factory , cancellationToken : TestContext . Current . CancellationToken ) ;
249251
250252 // Assert
251- var activity = _capturedActivities . FirstOrDefault ( a => a . TraceId == testRoot . TraceId && a . OperationName . Contains ( "SendAsync" ) ) ;
253+ var activity = capturedActivities . FirstOrDefault ( a => a . TraceId == testRoot . TraceId && a . OperationName . Contains ( "SendAsync" ) ) ;
252254 Assert . NotNull ( activity ) ;
253255
254256 var uriTemplateTag = activity . Tags . FirstOrDefault ( t => t . Key == "url.uri_template" ) ;
@@ -267,6 +269,8 @@ public async Task SendAsync_CreatesActivityWithUriTemplateTag(string urlTemplate
267269 public async Task SendAsync_SetsHttpRequestMethodTag ( Method httpMethod , string expectedMethodTag )
268270 {
269271 // Arrange
272+ var ( activityListener , capturedActivities ) = CreateActivityCapture ( ) ;
273+ using var listener = activityListener ;
270274 var mockHandler = new Mock < HttpMessageHandler > ( ) ;
271275 mockHandler . Protected ( )
272276 . Setup < Task < HttpResponseMessage > > ( "SendAsync" ,
@@ -290,17 +294,16 @@ public async Task SendAsync_SetsHttpRequestMethodTag(Method httpMethod, string e
290294 UrlTemplate = "{+baseurl}/users"
291295 } ;
292296
293- _capturedActivities . Clear ( ) ;
294297 using var testRoot = StartTestRootActivity ( ) ;
295298
296299 // Act
297300 await adapter . SendAsync < MockEntity > ( requestInfo , MockEntity . Factory , cancellationToken : TestContext . Current . CancellationToken ) ;
298301
299302 // Assert
300- var testActivities = _capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . ToList ( ) ;
303+ var testActivities = capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . ToList ( ) ;
301304 Assert . NotEmpty ( testActivities ) ;
302305
303- var methodTag = GetTagFromActivities ( "http.request.method" , testRoot . TraceId ) ;
306+ var methodTag = GetTagFromActivities ( capturedActivities , "http.request.method" , testRoot . TraceId ) ;
304307 Assert . NotNull ( methodTag . Key ) ;
305308 Assert . Equal ( expectedMethodTag , methodTag . Value ) ;
306309 }
@@ -309,6 +312,8 @@ public async Task SendAsync_SetsHttpRequestMethodTag(Method httpMethod, string e
309312 public async Task SendAsync_SetsUrlSchemeAndServerAddressTags ( )
310313 {
311314 // Arrange
315+ var ( activityListener , capturedActivities ) = CreateActivityCapture ( ) ;
316+ using var listener = activityListener ;
312317 var mockHandler = new Mock < HttpMessageHandler > ( ) ;
313318 mockHandler . Protected ( )
314319 . Setup < Task < HttpResponseMessage > > ( "SendAsync" ,
@@ -332,27 +337,28 @@ public async Task SendAsync_SetsUrlSchemeAndServerAddressTags()
332337 UrlTemplate = "{+baseurl}/users"
333338 } ;
334339
335- _capturedActivities . Clear ( ) ;
336340 using var testRoot = StartTestRootActivity ( ) ;
337341
338342 // Act
339343 await adapter . SendAsync < MockEntity > ( requestInfo , MockEntity . Factory , cancellationToken : TestContext . Current . CancellationToken ) ;
340344
341345 // Assert
342- var testActivities = _capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . ToList ( ) ;
346+ var testActivities = capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . ToList ( ) ;
343347 Assert . NotEmpty ( testActivities ) ;
344348
345- var schemeTag = GetTagFromActivities ( "url.scheme" , testRoot . TraceId ) ;
349+ var schemeTag = GetTagFromActivities ( capturedActivities , "url.scheme" , testRoot . TraceId ) ;
346350 Assert . Equal ( "https" , schemeTag . Value ) ;
347351
348- var serverTag = GetTagFromActivities ( "server.address" , testRoot . TraceId ) ;
352+ var serverTag = GetTagFromActivities ( capturedActivities , "server.address" , testRoot . TraceId ) ;
349353 Assert . Equal ( "graph.microsoft.com" , serverTag . Value ) ;
350354 }
351355
352356 [ Fact ]
353357 public async Task SendAsync_WithoutIncludeEUIIAttributes_DoesNotSetUrlFullTag ( )
354358 {
355359 // Arrange
360+ var ( activityListener , capturedActivities ) = CreateActivityCapture ( ) ;
361+ using var listener = activityListener ;
356362 var mockHandler = new Mock < HttpMessageHandler > ( ) ;
357363 mockHandler . Protected ( )
358364 . Setup < Task < HttpResponseMessage > > ( "SendAsync" ,
@@ -376,24 +382,25 @@ public async Task SendAsync_WithoutIncludeEUIIAttributes_DoesNotSetUrlFullTag()
376382 UrlTemplate = "{+baseurl}/users"
377383 } ;
378384
379- _capturedActivities . Clear ( ) ;
380385 using var testRoot = StartTestRootActivity ( ) ;
381386
382387 // Act
383388 await adapter . SendAsync < MockEntity > ( requestInfo , MockEntity . Factory , cancellationToken : TestContext . Current . CancellationToken ) ;
384389
385390 // Assert
386- var testActivities = _capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . ToList ( ) ;
391+ var testActivities = capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . ToList ( ) ;
387392 Assert . NotEmpty ( testActivities ) ;
388393
389- var urlFullTag = GetTagFromActivities ( "url.full" , testRoot . TraceId ) ;
394+ var urlFullTag = GetTagFromActivities ( capturedActivities , "url.full" , testRoot . TraceId ) ;
390395 Assert . Null ( urlFullTag . Key ) ;
391396 }
392397
393398 [ Fact ]
394399 public async Task SendAsync_CreatesNestedActivitySpans ( )
395400 {
396401 // Arrange
402+ var ( activityListener , capturedActivities ) = CreateActivityCapture ( ) ;
403+ using var listener = activityListener ;
397404 var mockHandler = new Mock < HttpMessageHandler > ( ) ;
398405 mockHandler . Protected ( )
399406 . Setup < Task < HttpResponseMessage > > ( "SendAsync" ,
@@ -417,15 +424,14 @@ public async Task SendAsync_CreatesNestedActivitySpans()
417424 UrlTemplate = "{+baseurl}/users"
418425 } ;
419426
420- _capturedActivities . Clear ( ) ;
421427 using var testRoot = StartTestRootActivity ( ) ;
422428
423429 // Act
424430 await adapter . SendAsync < MockEntity > ( requestInfo , MockEntity . Factory , cancellationToken : TestContext . Current . CancellationToken ) ;
425431
426432 // Assert - Verify various nested spans are created
427433 var resultingActivities = new HashSet < string > (
428- _capturedActivities . ToList ( ) . Where ( a => a . TraceId == testRoot . TraceId ) . Select ( static a => a . OperationName ) ,
434+ capturedActivities . Where ( a => a . TraceId == testRoot . TraceId ) . Select ( static a => a . OperationName ) ,
429435 StringComparer . Ordinal ) ;
430436 Assert . Contains ( "SendAsync - {+baseurl}/users" , resultingActivities ) ;
431437 Assert . Contains ( "GetHttpResponseMessageAsync" , resultingActivities ) ;
0 commit comments