Skip to content

Commit 0200a8e

Browse files
[AzureMonitorExporter] Prevent multiple Azure Monitor Exporter registrations on the same IServiceCollection (Azure#48926)
* Prevent multiple Azure Monitor Exporter registrations on the same IServiceCollection * Add EnsureSingleUseAzureMonitorExporterRegistration at metrics
1 parent 6b64aec commit 0200a8e

File tree

5 files changed

+82
-5
lines changed

5 files changed

+82
-5
lines changed

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/AzureMonitorExporterExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public static TracerProviderBuilder AddAzureMonitorTraceExporter(
8282
exporterOptions.Credential ??= credential;
8383
}
8484

85+
sp.EnsureNoUseAzureMonitorExporterRegistrations();
86+
8587
builder.AddProcessor(new CompositeProcessor<Activity>(new BaseProcessor<Activity>[]
8688
{
8789
new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(exporterOptions)),
@@ -143,6 +145,8 @@ public static MeterProviderBuilder AddAzureMonitorMetricExporter(
143145
exporterOptions.Credential ??= credential;
144146
}
145147

148+
sp.EnsureNoUseAzureMonitorExporterRegistrations();
149+
146150
return new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(exporterOptions))
147151
{ TemporalityPreference = MetricReaderTemporalityPreference.Delta };
148152
});
@@ -239,6 +243,8 @@ public static LoggerProviderBuilder AddAzureMonitorLogExporter(
239243
exporterOptions.Credential ??= credential;
240244
}
241245

246+
sp.EnsureNoUseAzureMonitorExporterRegistrations();
247+
242248
// TODO: Do we need provide an option to alter BatchExportLogRecordProcessorOptions?
243249
return new BatchLogRecordExportProcessor(new AzureMonitorLogExporter(exporterOptions));
244250
});

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/ExporterRegistrationHostedService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ private static void Initialize(IServiceProvider serviceProvider)
5353
var tracerProvider = serviceProvider!.GetService<TracerProvider>();
5454
if (tracerProvider != null)
5555
{
56+
// Ensure that the AzureMonitorTraceExporter is registered only once
57+
serviceProvider!.EnsureSingleUseAzureMonitorExporterRegistration();
58+
5659
// Add a processor manually to the TracerProvider created by the SDK
5760
var exporterOptions = serviceProvider!.GetRequiredService<IOptionsMonitor<AzureMonitorExporterOptions>>().Get(Options.DefaultName);
5861

@@ -73,6 +76,9 @@ private static void Initialize(IServiceProvider serviceProvider)
7376
var loggerProvider = serviceProvider!.GetService<LoggerProvider>();
7477
if (loggerProvider != null)
7578
{
79+
// Ensure that the AzureMonitorLogExporter is registered only once
80+
serviceProvider!.EnsureSingleUseAzureMonitorExporterRegistration();
81+
7682
// Add a processor manually to the LoggerProvider created by the SDK
7783
var exporterOptions = serviceProvider!.GetRequiredService<IOptionsMonitor<AzureMonitorExporterOptions>>().Get(Options.DefaultName);
7884
var exporter = new AzureMonitorLogExporter(exporterOptions);

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/OpenTelemetryBuilderExtensions.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using OpenTelemetry;
1313
using OpenTelemetry.Logs;
1414
using System;
15+
using OpenTelemetry.Metrics;
1516

1617
namespace Azure.Monitor.OpenTelemetry.Exporter
1718
{
@@ -64,21 +65,33 @@ public static IOpenTelemetryBuilder UseAzureMonitorExporter(this IOpenTelemetryB
6465
builder.Services.Configure(configureAzureMonitor);
6566
}
6667

68+
builder.Services.AddSingleton(UseAzureMonitorExporterRegistration.Instance);
69+
6770
// Note: We automatically turn on signals for "UseAzureMonitorExporter"
6871
builder
6972
.WithLogging()
70-
.WithMetrics(metrics => metrics.AddAzureMonitorMetricExporter())
73+
.WithMetrics()
7174
.WithTracing();
7275

7376
builder.Services.Configure<OpenTelemetryLoggerOptions>((loggingOptions) =>
7477
{
7578
loggingOptions.IncludeFormattedMessage = true;
7679
});
7780

81+
builder.Services.ConfigureOpenTelemetryMeterProvider((serviceProvider, meterProviderBuilder) =>
82+
{
83+
// Ensure that the AzureMonitorMetricExporter is registered only once
84+
serviceProvider!.EnsureSingleUseAzureMonitorExporterRegistration();
85+
86+
var exporterOptions = serviceProvider!.GetRequiredService<IOptionsMonitor<AzureMonitorExporterOptions>>().Get(Options.DefaultName);
87+
meterProviderBuilder.AddReader(new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(exporterOptions))
88+
{ TemporalityPreference = MetricReaderTemporalityPreference.Delta });
89+
});
90+
7891
// Register Manager as a singleton
79-
builder.Services.TryAddSingleton<LiveMetricsClientManager>(sp =>
92+
builder.Services.TryAddSingleton<LiveMetricsClientManager>(serviceProvider =>
8093
{
81-
AzureMonitorExporterOptions exporterOptions = sp.GetRequiredService<IOptionsMonitor<AzureMonitorExporterOptions>>().Get(Options.DefaultName);
94+
AzureMonitorExporterOptions exporterOptions = serviceProvider.GetRequiredService<IOptionsMonitor<AzureMonitorExporterOptions>>().Get(Options.DefaultName);
8295
var azureMonitorLiveMetricsOptions = new AzureMonitorLiveMetricsOptions();
8396
exporterOptions.SetValueToLiveMetricsOptions(azureMonitorLiveMetricsOptions);
8497

@@ -95,9 +108,9 @@ public static IOpenTelemetryBuilder UseAzureMonitorExporter(this IOpenTelemetryB
95108
}
96109
});
97110

98-
builder.Services.AddHostedService(sp =>
111+
builder.Services.AddHostedService(serviceProvider =>
99112
{
100-
return new ExporterRegistrationHostedService(sp);
113+
return new ExporterRegistrationHostedService(serviceProvider);
101114
});
102115

103116
return builder;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Linq;
6+
using Microsoft.Extensions.DependencyInjection;
7+
8+
namespace Azure.Monitor.OpenTelemetry.Exporter
9+
{
10+
internal static class OpenTelemetryBuilderServiceProviderExtensions
11+
{
12+
public static void EnsureSingleUseAzureMonitorExporterRegistration(this IServiceProvider serviceProvider)
13+
{
14+
var registrations = serviceProvider.GetServices<UseAzureMonitorExporterRegistration>();
15+
if (registrations.Count() > 1)
16+
{
17+
throw new NotSupportedException("Multiple calls to UseAzureMonitorExporter on the same IServiceCollection are not supported.");
18+
}
19+
}
20+
21+
public static void EnsureNoUseAzureMonitorExporterRegistrations(this IServiceProvider serviceProvider)
22+
{
23+
var registrations = serviceProvider.GetServices<UseAzureMonitorExporterRegistration>();
24+
if (registrations.Any())
25+
{
26+
throw new NotSupportedException("Signal-specific AddAzureMonitorExporter / UseAzureMonitor methods and the cross-cutting UseAzureMonitorExporter method being invoked on the same IServiceCollection is not supported.");
27+
}
28+
}
29+
}
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Monitor.OpenTelemetry.Exporter;
5+
6+
// Note: This class is added to the IServiceCollection when UseAzureMonitorExporter is
7+
// called. Its purpose is to detect registrations so that subsequent calls and
8+
// calls to signal-specific UseAzureMonitorExporter can throw.
9+
internal sealed class UseAzureMonitorExporterRegistration
10+
{
11+
public static readonly UseAzureMonitorExporterRegistration Instance = new();
12+
13+
private UseAzureMonitorExporterRegistration()
14+
{
15+
// Note: Some dependency injection containers (ex: Unity, Grace) will
16+
// automatically create services if they have a public constructor even
17+
// if the service was never registered into the IServiceCollection. The
18+
// behavior of UseAzureMonitorExporterRegistration requires that it should only
19+
// exist if registered. This private constructor is intended to prevent
20+
// automatic instantiation.
21+
}
22+
}

0 commit comments

Comments
 (0)