5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
7
using System . Linq ;
8
+ using System . Reflection ;
9
+ using Azure . Identity ;
10
+ using Azure . Monitor . OpenTelemetry . Exporter ;
8
11
using FluentAssertions ;
9
12
using Microsoft . ApplicationInsights ;
10
13
using Microsoft . ApplicationInsights . Extensibility ;
@@ -231,6 +234,78 @@ public void ResourceDetectorLocalDevelopment()
231
234
Assert . Equal ( 4 , resource . Attributes . Count ( ) ) ;
232
235
}
233
236
237
+ [ Fact ]
238
+ public void ConfigureTelemetry_Should_UseOpenTelemetryWhenModeSetAndAppInsightsAuthStringClientIdPresent ( )
239
+ {
240
+ // Arrange
241
+ var clientId = Guid . NewGuid ( ) ;
242
+ IServiceCollection serviceCollection = default ;
243
+
244
+ var hostBuilder = new HostBuilder ( )
245
+ . ConfigureAppConfiguration ( config =>
246
+ {
247
+ config . AddInMemoryCollection ( new Dictionary < string , string >
248
+ {
249
+ { "APPLICATIONINSIGHTS_AUTHENTICATION_STRING" , $ "Authorization=AAD;ClientId={ clientId } " } ,
250
+ { "APPLICATIONINSIGHTS_CONNECTION_STRING" , "InstrumentationKey=key" } ,
251
+ { ConfigurationPath . Combine ( ConfigurationSectionNames . JobHost , "telemetryMode" ) , TelemetryMode . OpenTelemetry . ToString ( ) }
252
+ } ) ;
253
+ } )
254
+ . ConfigureDefaultTestWebScriptHost ( )
255
+ . ConfigureLogging ( ( context , loggingBuilder ) => loggingBuilder . ConfigureTelemetry ( context ) )
256
+ . ConfigureServices ( services => serviceCollection = services ) ;
257
+
258
+ using var host = hostBuilder . Build ( ) ;
259
+
260
+ // Act
261
+ var tracerProviderDescriptors = GetTracerProviderDescriptors ( serviceCollection ) ;
262
+ var resolvedClient = ExtractClientFromDescriptors ( tracerProviderDescriptors ) ;
263
+
264
+ // Extract the clientId from the client object
265
+ var clientIdValue = resolvedClient ? . GetType ( ) . GetProperty ( "ClientId" , BindingFlags . Instance | BindingFlags . NonPublic ) ? . GetValue ( resolvedClient ) ? . ToString ( ) ;
266
+
267
+ // Assert
268
+ serviceCollection . Should ( ) . NotBeNullOrEmpty ( ) ;
269
+ clientIdValue . Should ( ) . Be ( clientId . ToString ( ) ) ;
270
+ resolvedClient . GetType ( ) . Name . Should ( ) . Be ( "ManagedIdentityClient" ) ;
271
+ }
272
+
273
+ [ Fact ]
274
+ public void ConfigureTelemetry_Should_UseOpenTelemetryWhenModeSetAndAppInsightsAuthStringPresent ( )
275
+ {
276
+ // Arrange
277
+ IServiceCollection serviceCollection = default ;
278
+
279
+ var hostBuilder = new HostBuilder ( )
280
+ . ConfigureAppConfiguration ( config =>
281
+ {
282
+ config . AddInMemoryCollection ( new Dictionary < string , string >
283
+ {
284
+ { "APPLICATIONINSIGHTS_AUTHENTICATION_STRING" , $ "Authorization=AAD" } ,
285
+ { "APPLICATIONINSIGHTS_CONNECTION_STRING" , "InstrumentationKey=key" } ,
286
+ { ConfigurationPath . Combine ( ConfigurationSectionNames . JobHost , "telemetryMode" ) , TelemetryMode . OpenTelemetry . ToString ( ) }
287
+ } ) ;
288
+ } )
289
+ . ConfigureDefaultTestWebScriptHost ( )
290
+ . ConfigureLogging ( ( context , loggingBuilder ) => loggingBuilder . ConfigureTelemetry ( context ) )
291
+ . ConfigureServices ( services => serviceCollection = services ) ;
292
+
293
+ using var host = hostBuilder . Build ( ) ;
294
+
295
+ // Act
296
+ var tracerProviderDescriptors = GetTracerProviderDescriptors ( serviceCollection ) ;
297
+ var resolvedClient = ExtractClientFromDescriptors ( tracerProviderDescriptors ) ;
298
+
299
+ // Extract the clientId from the client object
300
+ var clientIdValue = resolvedClient ? . GetType ( ) . GetProperty ( "ClientId" , BindingFlags . Instance | BindingFlags . NonPublic ) ? . GetValue ( resolvedClient ) ? . ToString ( ) ;
301
+
302
+ // Assert
303
+ serviceCollection . Should ( ) . NotBeNullOrEmpty ( ) ;
304
+ // No clientId should be present as it was not provided
305
+ clientIdValue . Should ( ) . BeNull ( ) ;
306
+ resolvedClient . GetType ( ) . Name . Should ( ) . Be ( "ManagedIdentityClient" ) ;
307
+ }
308
+
234
309
// The OpenTelemetryEventListener is fine because it's a no-op if there are no otel events to listen to
235
310
private bool HasOtelServices ( IServiceCollection sc ) => sc . Any ( sd => sd . ServiceType != typeof ( OpenTelemetryEventListener ) && sd . ServiceType . FullName . Contains ( "OpenTelemetry" ) ) ;
236
311
@@ -244,5 +319,46 @@ private static IDisposable SetupDefaultEnvironmentVariables()
244
319
{ "REGION_NAME" , "EastUS" }
245
320
} ) ;
246
321
}
322
+
323
+ private static List < ServiceDescriptor > GetTracerProviderDescriptors ( IServiceCollection services )
324
+ {
325
+ return services
326
+ . Where ( descriptor =>
327
+ descriptor . Lifetime == ServiceLifetime . Singleton &&
328
+ descriptor . ServiceType . Name == "IConfigureTracerProviderBuilder" &&
329
+ descriptor . ImplementationInstance ? . GetType ( ) . Name == "ConfigureTracerProviderBuilderCallbackWrapper" )
330
+ . ToList ( ) ;
331
+ }
332
+
333
+ private static object ExtractClientFromDescriptors ( List < ServiceDescriptor > descriptors )
334
+ {
335
+ foreach ( var descriptor in descriptors )
336
+ {
337
+ var implementation = descriptor . ImplementationInstance ;
338
+ if ( implementation is null )
339
+ {
340
+ continue ;
341
+ }
342
+
343
+ // Reflection starts here
344
+ var configureField = implementation . GetType ( ) . GetField ( "configure" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
345
+ if ( configureField ? . GetValue ( implementation ) is Action < IServiceProvider , TracerProviderBuilder > configureDelegate )
346
+ {
347
+ var targetType = configureDelegate . Target . GetType ( ) ;
348
+ var configureDelegateTarget = targetType . GetField ( "configure" , BindingFlags . Instance | BindingFlags . Public ) ;
349
+
350
+ if ( configureDelegateTarget ? . GetValue ( configureDelegate . Target ) is Action < AzureMonitorExporterOptions > exporterOptionsDelegate )
351
+ {
352
+ var credentialField = exporterOptionsDelegate . Target . GetType ( ) . GetField ( "credential" , BindingFlags . Instance | BindingFlags . Public ) ;
353
+ if ( credentialField ? . GetValue ( exporterOptionsDelegate . Target ) is ManagedIdentityCredential managedIdentityCredential )
354
+ {
355
+ var clientProperty = managedIdentityCredential . GetType ( ) . GetProperty ( "Client" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
356
+ return clientProperty ? . GetValue ( managedIdentityCredential ) ;
357
+ }
358
+ }
359
+ }
360
+ }
361
+ return null ;
362
+ }
247
363
}
248
364
}
0 commit comments