22// Licensed under the MIT License.
33
44using System ;
5+ using System . Collections . Generic ;
56using System . Reflection ;
67using System . Threading ;
78using System . Threading . Tasks ;
89using Azure . Monitor . OpenTelemetry . Exporter ;
910using Microsoft . Extensions . DependencyInjection ;
1011using Microsoft . Extensions . Hosting ;
1112using OpenTelemetry . Trace ;
13+ using OpenTelemetry . Metrics ;
14+ using OpenTelemetry . Logs ;
1215using Xunit ;
16+ using System . Linq ;
1317
1418namespace Azure . Monitor . OpenTelemetry . AspNetCore . Tests
1519{
@@ -30,7 +34,7 @@ public async Task VerifyCannotCallUseAzureMonitorTwice()
3034 . UseAzureMonitor ( )
3135 . UseAzureMonitor ( ) ;
3236
33- var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
37+ var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
3438
3539 await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider ) ) ;
3640 }
@@ -77,29 +81,97 @@ public async Task VerifyCanCallAddAzureMonitorTraceExporterTwice()
7781 }
7882
7983 [ Fact ]
80- public async Task VerifyCannotAddMonitorTraceExporterAndUseAzureMonitor ( )
84+ public async Task VerifyCanCallAddAzureMonitorMetricExporterTwice ( )
8185 {
8286 var serviceCollection = new ServiceCollection ( ) ;
8387 serviceCollection . AddOpenTelemetry ( )
84- . WithTracing ( builder => builder
85- . AddAzureMonitorTraceExporter ( x => x . ConnectionString = TestConnectionString ) )
86- . UseAzureMonitor ( x => x . ConnectionString = TestConnectionString2 ) ;
88+ . WithMetrics ( builder => builder
89+ . AddAzureMonitorMetricExporter ( x => x . ConnectionString = TestConnectionString )
90+ . AddAzureMonitorMetricExporter ( x => x . ConnectionString = TestConnectionString2 ) ) ;
8791
8892 using var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
89- await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider ) ) ;
93+ await StartHostedServicesAsync ( serviceProvider ) ;
9094 }
9195
9296 [ Fact ]
93- public async Task VerifyCannotAddMonitorTraceExporterAndUseAzureMonitorExporter ( )
97+ public async Task VerifyCanCallAddAzureMonitorLogExporterTwice ( )
9498 {
9599 var serviceCollection = new ServiceCollection ( ) ;
96100 serviceCollection . AddOpenTelemetry ( )
101+ . WithLogging ( builder => builder
102+ . AddAzureMonitorLogExporter ( x => x . ConnectionString = TestConnectionString )
103+ . AddAzureMonitorLogExporter ( x => x . ConnectionString = TestConnectionString2 ) ) ;
104+
105+ using var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
106+ await StartHostedServicesAsync ( serviceProvider ) ;
107+ }
108+
109+ [ Fact ]
110+ public async Task VerifyCannotAddExportersAndUseAzureMonitor ( )
111+ {
112+ // Traces
113+ var serviceCollection1 = new ServiceCollection ( ) ;
114+ serviceCollection1 . AddOpenTelemetry ( )
115+ . WithTracing ( builder => builder
116+ . AddAzureMonitorTraceExporter ( x => x . ConnectionString = TestConnectionString ) )
117+ . UseAzureMonitor ( x => x . ConnectionString = TestConnectionString2 ) ;
118+
119+ using var serviceProvider1 = serviceCollection1 . BuildServiceProvider ( ) ;
120+ await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider1 ) ) ;
121+
122+ // Metrics
123+ var serviceCollection2 = new ServiceCollection ( ) ;
124+ serviceCollection2 . AddOpenTelemetry ( )
125+ . WithMetrics ( builder => builder
126+ . AddAzureMonitorMetricExporter ( x => x . ConnectionString = TestConnectionString ) )
127+ . UseAzureMonitor ( x => x . ConnectionString = TestConnectionString2 ) ;
128+
129+ using var serviceProvider2 = serviceCollection2 . BuildServiceProvider ( ) ;
130+ await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider2 ) ) ;
131+
132+ // Logs
133+ var serviceCollection3 = new ServiceCollection ( ) ;
134+ serviceCollection3 . AddOpenTelemetry ( )
135+ . WithLogging ( builder => builder
136+ . AddAzureMonitorLogExporter ( x => x . ConnectionString = TestConnectionString ) )
137+ . UseAzureMonitor ( x => x . ConnectionString = TestConnectionString2 ) ;
138+
139+ using var serviceProvider3 = serviceCollection3 . BuildServiceProvider ( ) ;
140+ await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider3 ) ) ;
141+ }
142+
143+ [ Fact ]
144+ public async Task VerifyCannotAddExporterAndUseAzureMonitorExporter ( )
145+ {
146+ // Traces
147+ var serviceCollection1 = new ServiceCollection ( ) ;
148+ serviceCollection1 . AddOpenTelemetry ( )
97149 . WithTracing ( builder => builder
98150 . AddAzureMonitorTraceExporter ( x => x . ConnectionString = TestConnectionString ) )
99151 . UseAzureMonitorExporter ( x => x . ConnectionString = TestConnectionString2 ) ;
100152
101- using var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
102- await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider ) ) ;
153+ using var serviceProvider1 = serviceCollection1 . BuildServiceProvider ( ) ;
154+ await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider1 ) ) ;
155+
156+ // Metrics
157+ var serviceCollection2 = new ServiceCollection ( ) ;
158+ serviceCollection2 . AddOpenTelemetry ( )
159+ . WithMetrics ( builder => builder
160+ . AddAzureMonitorMetricExporter ( x => x . ConnectionString = TestConnectionString ) )
161+ . UseAzureMonitorExporter ( x => x . ConnectionString = TestConnectionString2 ) ;
162+
163+ using var serviceProvider2 = serviceCollection2 . BuildServiceProvider ( ) ;
164+ await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider2 ) ) ;
165+
166+ // Logs
167+ var serviceCollection3 = new ServiceCollection ( ) ;
168+ serviceCollection3 . AddOpenTelemetry ( )
169+ . WithLogging ( builder => builder
170+ . AddAzureMonitorLogExporter ( x => x . ConnectionString = TestConnectionString ) )
171+ . UseAzureMonitorExporter ( x => x . ConnectionString = TestConnectionString2 ) ;
172+
173+ using var serviceProvider3 = serviceCollection1 . BuildServiceProvider ( ) ;
174+ await Assert . ThrowsAsync < NotSupportedException > ( async ( ) => await StartHostedServicesAsync ( serviceProvider3 ) ) ;
103175 }
104176
105177 [ Theory ]
@@ -112,18 +184,20 @@ public async Task VerifyUseAzureMonitor(bool enableLiveMetrics)
112184 . UseAzureMonitor ( options => {
113185 options . ConnectionString = TestConnectionString ;
114186 options . EnableLiveMetrics = enableLiveMetrics ;
115- } ) ;
187+ } ) ;
116188
117189 using var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
118190 await StartHostedServicesAsync ( serviceProvider ) ;
119191
120- var tracerProvider = serviceProvider . GetRequiredService < TracerProvider > ( ) ;
121- EvaluateTraceProvider . Evaluate (
122- tracerProvider : tracerProvider ,
123- expectedAzureMonitorTraceExporter : true ,
192+ EvaluationHelper . EvaluateTracerProvider (
193+ serviceProvider : serviceProvider ,
124194 expectedLiveMetricsProcessor : enableLiveMetrics ,
125195 expectedProfilingSessionTraceProcessor : true ,
126- expectedStandardMetricsExtractionProcessor : true ) ;
196+ hasInstrumentations : true ) ;
197+
198+ EvaluationHelper . EvaluateMeterProvider ( serviceProvider ) ;
199+
200+ EvaluationHelper . EvaluateLoggerProvider ( serviceProvider , enableLiveMetrics ) ;
127201 }
128202
129203 [ Theory ]
@@ -141,13 +215,15 @@ public async Task VerifyUseAzureMonitorExporter(bool enableLiveMetrics)
141215 using var serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
142216 await StartHostedServicesAsync ( serviceProvider ) ;
143217
144- var tracerProvider = serviceProvider . GetRequiredService < TracerProvider > ( ) ;
145- EvaluateTraceProvider . Evaluate (
146- tracerProvider : tracerProvider ,
147- expectedAzureMonitorTraceExporter : true ,
218+ EvaluationHelper . EvaluateTracerProvider (
219+ serviceProvider : serviceProvider ,
148220 expectedLiveMetricsProcessor : enableLiveMetrics ,
149221 expectedProfilingSessionTraceProcessor : false ,
150- expectedStandardMetricsExtractionProcessor : true ) ;
222+ hasInstrumentations : false ) ;
223+
224+ EvaluationHelper . EvaluateMeterProvider ( serviceProvider ) ;
225+
226+ EvaluationHelper . EvaluateLoggerProvider ( serviceProvider , enableLiveMetrics ) ;
151227 }
152228
153229 private static async Task StartHostedServicesAsync ( ServiceProvider serviceProvider )
@@ -159,18 +235,19 @@ private static async Task StartHostedServicesAsync(ServiceProvider serviceProvid
159235 }
160236 }
161237
162- private class EvaluateTraceProvider ( )
238+ private class EvaluationHelper ( )
163239 {
164- private class Variables
240+ private class TracerProviderVariables
165241 {
166242 public bool foundProfilingSessionTraceProcessor ;
167243 public bool foundAzureMonitorTraceExporter ;
168244 public bool foundLiveMetricsProcessor ;
169245 public bool foundStandardMetricsExtractionProcessor ;
170246 }
171247
172- public static void Evaluate ( TracerProvider tracerProvider , bool expectedAzureMonitorTraceExporter , bool expectedLiveMetricsProcessor , bool expectedProfilingSessionTraceProcessor , bool expectedStandardMetricsExtractionProcessor )
248+ public static void EvaluateTracerProvider ( IServiceProvider serviceProvider , bool expectedLiveMetricsProcessor , bool expectedProfilingSessionTraceProcessor , bool hasInstrumentations )
173249 {
250+ var tracerProvider = serviceProvider . GetRequiredService < TracerProvider > ( ) ;
174251 Assert . NotNull ( tracerProvider ) ;
175252
176253 // Get TracerProviderSdk
@@ -181,19 +258,128 @@ public static void Evaluate(TracerProvider tracerProvider, bool expectedAzureMon
181258 var processor = processorProperty . GetValue ( tracerProvider ) ;
182259 Assert . NotNull ( processor ) ;
183260
184- var variables = new Variables ( ) ;
185-
186261 // Start walking the CompositeProcessor
187- WalkCompositeProcessor ( processor , variables ) ;
188-
189- // Final assertions
262+ var variables = new TracerProviderVariables ( ) ;
263+ WalkTracerCompositeProcessor ( processor , variables ) ;
190264 Assert . Equal ( expectedLiveMetricsProcessor , variables . foundLiveMetricsProcessor ) ;
191- Assert . Equal ( expectedStandardMetricsExtractionProcessor , variables . foundStandardMetricsExtractionProcessor ) ;
192- Assert . Equal ( expectedAzureMonitorTraceExporter , variables . foundAzureMonitorTraceExporter ) ;
265+ Assert . True ( variables . foundStandardMetricsExtractionProcessor ) ;
266+ Assert . True ( variables . foundAzureMonitorTraceExporter ) ;
193267 Assert . Equal ( expectedProfilingSessionTraceProcessor , variables . foundProfilingSessionTraceProcessor ) ;
268+
269+ // Validate Sampler
270+ var samplerProperty = tracerProviderSdkType . GetProperty ( "Sampler" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
271+ Assert . NotNull ( samplerProperty ) ;
272+ var sampler = samplerProperty . GetValue ( tracerProvider ) ;
273+ Assert . NotNull ( sampler ) ;
274+ Assert . Equal ( nameof ( Exporter . Internals . ApplicationInsightsSampler ) , sampler . GetType ( ) . Name ) ;
275+
276+ // Validate Instrumentations
277+ var instrumentationsProperty = tracerProvider . GetType ( ) . GetProperty ( "Instrumentations" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
278+ Assert . NotNull ( instrumentationsProperty ) ;
279+
280+ var instrumentations = instrumentationsProperty . GetValue ( tracerProvider ) as IEnumerable < object > ;
281+ Assert . NotNull ( instrumentations ) ;
282+
283+ if ( hasInstrumentations )
284+ {
285+ var instrumentationTypes = instrumentations . Select ( i => i . GetType ( ) . Name ) . ToList ( ) ;
286+ Assert . Contains ( "SqlClientInstrumentation" , instrumentationTypes ) ;
287+ Assert . Contains ( "AspNetCoreInstrumentation" , instrumentationTypes ) ;
288+ #if NET
289+ Assert . Contains ( "HttpClientInstrumentation" , instrumentationTypes ) ;
290+
291+ Assert . Equal ( 3 , instrumentations . Count ( ) ) ;
292+ #else
293+ Assert . Equal ( 2 , instrumentations . Count ( ) ) ;
294+ #endif
295+ }
296+ else
297+ {
298+ Assert . Empty ( instrumentations ) ;
299+ }
300+ }
301+
302+ public static void EvaluateMeterProvider ( IServiceProvider serviceProvider )
303+ {
304+ var meterProvider = serviceProvider . GetRequiredService < MeterProvider > ( ) ;
305+ Assert . NotNull ( meterProvider ) ;
306+
307+ // Get the Reader property (private) from MeterProviderSdk
308+ var readerProperty = meterProvider . GetType ( ) . GetProperty ( "Reader" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
309+ Assert . NotNull ( readerProperty ) ;
310+
311+ var reader = readerProperty . GetValue ( meterProvider ) ;
312+ Assert . NotNull ( reader ) ;
313+
314+ // Get the Exporter property (private) from PeriodicExportingMetricReader
315+ var exporterProperty = reader . GetType ( ) . GetProperty ( "Exporter" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
316+ Assert . NotNull ( exporterProperty ) ;
317+
318+ var exporter = exporterProperty . GetValue ( reader ) ;
319+ Assert . NotNull ( exporter ) ;
320+
321+ // Assert Exporter is of type AzureMonitorMetricExporter
322+ Assert . Equal ( nameof ( AzureMonitorMetricExporter ) , exporter . GetType ( ) . Name ) ;
323+ }
324+
325+ public static void EvaluateLoggerProvider ( IServiceProvider serviceProvider , bool liveMetricsEnabled = false )
326+ {
327+ var loggerProvider = serviceProvider . GetService < LoggerProvider > ( ) ;
328+ Assert . NotNull ( loggerProvider ) ;
329+
330+ var processorProperty = loggerProvider . GetType ( ) . GetProperty ( "Processor" , BindingFlags . Public | BindingFlags . Instance ) ;
331+ Assert . NotNull ( processorProperty ) ;
332+
333+ var processor = processorProperty . GetValue ( loggerProvider ) ;
334+ Assert . NotNull ( processor ) ;
335+
336+ if ( liveMetricsEnabled )
337+ {
338+ // When LiveMetrics is enabled, processor should be a CompositeProcessor
339+ Assert . Contains ( "CompositeProcessor" , processor . GetType ( ) . Name ) ;
340+
341+ // Get the first processor (LiveMetricsLogProcessor)
342+ var headField = processor . GetType ( ) . GetField ( "Head" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
343+ Assert . NotNull ( headField ) ;
344+ var firstNode = headField . GetValue ( processor ) ;
345+ Assert . NotNull ( firstNode ) ;
346+
347+ var valueField = firstNode . GetType ( ) . GetField ( "Value" , BindingFlags . Public | BindingFlags . Instance ) ;
348+ var firstProcessor = valueField ! . GetValue ( firstNode ) ;
349+ Assert . NotNull ( firstProcessor ) ;
350+ Assert . Contains ( nameof ( LiveMetrics . LiveMetricsLogProcessor ) , firstProcessor . GetType ( ) . Name ) ;
351+
352+ // Get the second processor (BatchLogRecordExportProcessor & AzureMonitorLogExporter)
353+ var nextProperty = firstNode . GetType ( ) . GetProperty ( "Next" , BindingFlags . Public | BindingFlags . Instance ) ;
354+ var secondNode = nextProperty ! . GetValue ( firstNode ) ;
355+ Assert . NotNull ( secondNode ) ;
356+
357+ var secondProcessor = valueField . GetValue ( secondNode ) ;
358+ Assert . NotNull ( secondProcessor ) ;
359+ Assert . Contains ( "BatchLogRecordExportProcessor" , secondProcessor . GetType ( ) . Name ) ;
360+
361+ var exporterProperty = secondProcessor . GetType ( ) . GetProperty ( "Exporter" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
362+ Assert . NotNull ( exporterProperty ) ;
363+
364+ var exporter = exporterProperty . GetValue ( secondProcessor ) ;
365+ Assert . NotNull ( exporter ) ;
366+ Assert . Contains ( nameof ( AzureMonitorLogExporter ) , exporter . GetType ( ) . Name ) ;
367+ }
368+ else
369+ {
370+ // When LiveMetrics is disabled, processor should be a BatchLogRecordExportProcessor
371+ Assert . Contains ( "BatchLogRecordExportProcessor" , processor . GetType ( ) . Name ) ;
372+
373+ var exporterProperty = processor . GetType ( ) . GetProperty ( "Exporter" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
374+ Assert . NotNull ( exporterProperty ) ;
375+
376+ var exporter = exporterProperty . GetValue ( processor ) ;
377+ Assert . NotNull ( exporter ) ;
378+ Assert . Contains ( nameof ( AzureMonitorLogExporter ) , exporter . GetType ( ) . Name ) ;
379+ }
194380 }
195381
196- private static void WalkCompositeProcessor ( object compositeProcessor , Variables variables )
382+ private static void WalkTracerCompositeProcessor ( object compositeProcessor , TracerProviderVariables variables )
197383 {
198384 var compositeProcessorType = compositeProcessor . GetType ( ) ;
199385
@@ -242,7 +428,7 @@ private static void WalkCompositeProcessor(object compositeProcessor, Variables
242428 else if ( processorType . Name . Contains ( "CompositeProcessor" ) )
243429 {
244430 // Recursive step: walk inner CompositeProcessor
245- WalkCompositeProcessor ( processorValue , variables ) ;
431+ WalkTracerCompositeProcessor ( processorValue , variables ) ;
246432 }
247433 }
248434
0 commit comments