diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props index 787499b26756..f29622214146 100644 --- a/eng/Packages.Data.props +++ b/eng/Packages.Data.props @@ -117,6 +117,8 @@ + + diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/Azure.Monitor.OpenTelemetry.sln b/sdk/monitor/Azure.Monitor.OpenTelemetry/Azure.Monitor.OpenTelemetry.sln index d51c6475f74b..8fb447278d8e 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry/Azure.Monitor.OpenTelemetry.sln +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/Azure.Monitor.OpenTelemetry.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependent Projects", "Depen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Monitor.OpenTelemetry.Exporter", "..\Azure.Monitor.OpenTelemetry.Exporter\src\Azure.Monitor.OpenTelemetry.Exporter.csproj", "{4158B8A7-971D-4A57-B4C8-5077BFD10AE6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Monitor.OpenTelemetry.Demo", "tests\Azure.Monitor.OpenTelemetry.Demo\Azure.Monitor.OpenTelemetry.Demo.csproj", "{6F094CAA-F6BB-4A9C-9D1F-1165F0758C99}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,6 +32,10 @@ Global {4158B8A7-971D-4A57-B4C8-5077BFD10AE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {4158B8A7-971D-4A57-B4C8-5077BFD10AE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4158B8A7-971D-4A57-B4C8-5077BFD10AE6}.Release|Any CPU.Build.0 = Release|Any CPU + {6F094CAA-F6BB-4A9C-9D1F-1165F0758C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F094CAA-F6BB-4A9C-9D1F-1165F0758C99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F094CAA-F6BB-4A9C-9D1F-1165F0758C99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F094CAA-F6BB-4A9C-9D1F-1165F0758C99}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/api/Azure.Monitor.OpenTelemetry.netstandard2.0.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry/api/Azure.Monitor.OpenTelemetry.netstandard2.0.cs new file mode 100644 index 000000000000..fa4004097bad --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/api/Azure.Monitor.OpenTelemetry.netstandard2.0.cs @@ -0,0 +1,18 @@ +namespace Azure.Monitor.OpenTelemetry +{ + public static partial class AzureMonitorOpenTelemetryExtensions + { + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureMonitorOpenTelemetry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureMonitorOpenTelemetry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Azure.Monitor.OpenTelemetry.AzureMonitorOpenTelemetryOptions options) { throw null; } + public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAzureMonitorOpenTelemetry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configureAzureMonitorOpenTelemetry) { throw null; } + } + public partial class AzureMonitorOpenTelemetryOptions + { + public AzureMonitorOpenTelemetryOptions() { } + public Azure.Monitor.OpenTelemetry.Exporter.AzureMonitorExporterOptions AzureMonitorExporterOptions { get { throw null; } set { } } + public string ConnectionString { get { throw null; } set { } } + public bool EnableLogs { get { throw null; } set { } } + public bool EnableMetrics { get { throw null; } set { } } + public bool EnableTraces { get { throw null; } set { } } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/src/Azure.Monitor.OpenTelemetry.csproj b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/Azure.Monitor.OpenTelemetry.csproj index 7ade99189371..99bcb095fca1 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry/src/Azure.Monitor.OpenTelemetry.csproj +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/Azure.Monitor.OpenTelemetry.csproj @@ -8,6 +8,11 @@ true + + + + + diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryExtensions.cs new file mode 100644 index 000000000000..7e305b3fe352 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Azure.Monitor.OpenTelemetry +{ + /// + /// Extension methods for setting up Azure Monitor OpenTelemetry in an . + /// + public static class AzureMonitorOpenTelemetryExtensions + { + /// + /// Adds Azure Monitor OpenTelemetry into service collection. + /// + /// The to add services to. + /// The so that additional calls can be chained. + public static IServiceCollection AddAzureMonitorOpenTelemetry(this IServiceCollection services) + { + return services.AddAzureMonitorOpenTelemetry(o => o = new AzureMonitorOpenTelemetryOptions()); + } + + /// + /// Adds Azure Monitor OpenTelemetry into service collection. + /// + /// The to add services to. + /// The instance for configuration. + /// The so that additional calls can be chained. + public static IServiceCollection AddAzureMonitorOpenTelemetry(this IServiceCollection services, AzureMonitorOpenTelemetryOptions options) + { + options ??= new AzureMonitorOpenTelemetryOptions(); + return services.AddAzureMonitorOpenTelemetry(o => o.Clone(options)); + } + + /// + /// Adds Azure Monitor OpenTelemetry into service collection. + /// + /// . + /// Callback action for configuring . + /// . + public static IServiceCollection AddAzureMonitorOpenTelemetry(this IServiceCollection services, Action configureAzureMonitorOpenTelemetry) + { + return AzureMonitorOpenTelemetryImplementations.AddAzureMonitorOpenTelemetryWithAction(services, configureAzureMonitorOpenTelemetry); + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryImplementations.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryImplementations.cs new file mode 100644 index 000000000000..4b7e894c6ab8 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryImplementations.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Monitor.OpenTelemetry.Exporter; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Azure.Monitor.OpenTelemetry +{ + internal class AzureMonitorOpenTelemetryImplementations + { + internal static IServiceCollection AddAzureMonitorOpenTelemetryWithAction(IServiceCollection services, Action configureAzureMonitorOpenTelemetry) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (configureAzureMonitorOpenTelemetry != null) + { + services.Configure(configureAzureMonitorOpenTelemetry); + } + + var builder = services.AddOpenTelemetry(); + + builder.WithTracing(b => b + .AddAspNetCoreInstrumentation() + .AddAzureMonitorTraceExporter()); + + builder.WithMetrics(b => b + .AddAspNetCoreInstrumentation() + .AddAzureMonitorMetricExporter()); + + ServiceDescriptor? sdkTracerProviderServiceRegistration = null; + ServiceDescriptor? sdkMeterProviderServiceRegistration = null; + + foreach (var service in services) + { + if (service.ServiceType == typeof(TracerProvider)) + { + sdkTracerProviderServiceRegistration = service; + } + else if (service.ServiceType == typeof(MeterProvider)) + { + sdkMeterProviderServiceRegistration = service; + } + } + + if (sdkTracerProviderServiceRegistration?.ImplementationFactory == null || + sdkMeterProviderServiceRegistration?.ImplementationFactory == null) + { + throw new InvalidOperationException("OpenTelemetry SDK has changed its registration mechanism."); + } + + // We looped through the registered services so that we can take over + // the SDK registrations. + + services.Remove(sdkTracerProviderServiceRegistration); + services.Remove(sdkMeterProviderServiceRegistration); + + // Now we register our own services for TracerProvider & MeterProvider + // so that we can return no-op versions when it isn't enabled. + + services.AddSingleton(sp => + { + var options = sp.GetRequiredService>().Get(""); + if (!options.EnableTraces) + { + return new NoopTracerProvider(); + } + else + { + SetValueToExporterOptions(sp, options); + var sdkProviderWrapper = sp.GetRequiredService(); + sdkProviderWrapper.SdkTracerProvider = (TracerProvider)sdkTracerProviderServiceRegistration.ImplementationFactory(sp); + return sdkProviderWrapper.SdkTracerProvider; + } + }); + + services.AddSingleton(sp => + { + var options = sp.GetRequiredService>().Get(""); + if (!options.EnableMetrics) + { + return new NoopMeterProvider(); + } + else + { + SetValueToExporterOptions(sp, options); + var sdkProviderWrapper = sp.GetRequiredService(); + sdkProviderWrapper.SdkMeterProvider = (MeterProvider)sdkMeterProviderServiceRegistration.ImplementationFactory(sp); + return sdkProviderWrapper.SdkMeterProvider; + } + }); + + // SdkProviderWrapper is here to make sure the SDK services get properly + // shutdown when the service provider is disposed. + services.AddSingleton(); + + return services; + } + + private static void SetValueToExporterOptions(IServiceProvider sp, AzureMonitorOpenTelemetryOptions options) + { + var exporterOptions = sp.GetRequiredService>().Get(""); + var defaultOptions = new AzureMonitorExporterOptions(); + + if (ReferenceEquals(exporterOptions, defaultOptions)) + { + exporterOptions.ConnectionString = options.AzureMonitorExporterOptions.ConnectionString; + exporterOptions.DisableOfflineStorage = options.AzureMonitorExporterOptions.DisableOfflineStorage; + exporterOptions.StorageDirectory = options.AzureMonitorExporterOptions.StorageDirectory; + } + else if (exporterOptions.ConnectionString == null) + { + exporterOptions.ConnectionString = options.AzureMonitorExporterOptions.ConnectionString; + } + } + + private sealed class NoopTracerProvider : TracerProvider + { + } + + private sealed class NoopMeterProvider : MeterProvider + { + } + + private sealed class SdkProviderWrapper : IDisposable + { + public TracerProvider? SdkTracerProvider; + public MeterProvider? SdkMeterProvider; + + public void Dispose() + { + this.SdkTracerProvider?.Dispose(); + this.SdkMeterProvider?.Dispose(); + } + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryOptions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryOptions.cs new file mode 100644 index 000000000000..25eac898722c --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/src/AzureMonitorOpenTelemetryOptions.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable disable + +using System.Runtime.Serialization; +using Azure.Monitor.OpenTelemetry.Exporter; + +namespace Azure.Monitor.OpenTelemetry +{ + /// + /// Options that allow users to configure the Azure Monitor OpenTelemetry. + /// + public class AzureMonitorOpenTelemetryOptions + { + /// + /// Gets or sets a value of Azure Monitor Exporter Options. + /// + public AzureMonitorExporterOptions AzureMonitorExporterOptions { get; set; } = new AzureMonitorExporterOptions(); + + /// + /// The Connection String provides users with a single configuration setting to identify the Azure Monitor resource and endpoint. + /// + /// + /// (https://docs.microsoft.com/azure/azure-monitor/app/sdk-connection-string). + /// + public string ConnectionString + { + get + { + return AzureMonitorExporterOptions.ConnectionString; + } + set + { + AzureMonitorExporterOptions.ConnectionString = value; + } + } + + /// + /// Gets or sets a value indicating whether Logs should be enabled. + /// + public bool EnableLogs { get; set; } = true; + + /// + /// Gets or sets a value indicating whether Metrics should be enabled. + /// + public bool EnableMetrics { get; set; } = true; + + /// + /// Gets or sets a value indicating whether Traces should be enabled. + /// + public bool EnableTraces { get; set; } = true; + + internal AzureMonitorOpenTelemetryOptions Clone(AzureMonitorOpenTelemetryOptions options) + { + if (options != null) + { + AzureMonitorExporterOptions = options.AzureMonitorExporterOptions; + ConnectionString = options.ConnectionString; + EnableLogs = options.EnableLogs; + EnableMetrics = options.EnableMetrics; + EnableTraces = options.EnableTraces; + } + + return this; + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/Azure.Monitor.OpenTelemetry.Demo.csproj b/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/Azure.Monitor.OpenTelemetry.Demo.csproj new file mode 100644 index 000000000000..4422db4c9ccb --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/Azure.Monitor.OpenTelemetry.Demo.csproj @@ -0,0 +1,11 @@ + + + + $(RequiredTargetFrameworks) + + + + + + + diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/Program.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/Program.cs new file mode 100644 index 000000000000..6fc8f8a8f2d6 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/Program.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#if NET6_0_OR_GREATER +using System.Diagnostics; +using Azure.Monitor.OpenTelemetry; +using Microsoft.AspNetCore.Builder; + +var builder = WebApplication.CreateBuilder(args); + +// builder.Services.AddAzureMonitorOpenTelemetry(); +builder.Services.AddAzureMonitorOpenTelemetry(o => +{ + o.ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000"; +}); + +var app = builder.Build(); +app.MapGet("/", () => $"Hello World! OpenTelemetry Trace: {Activity.Current?.Id}"); + +app.Run(); +#endif diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/ProgramNet461Compat.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/ProgramNet461Compat.cs new file mode 100644 index 000000000000..846e70386afe --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry/tests/Azure.Monitor.OpenTelemetry.Demo/ProgramNet461Compat.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#if NET461 + +namespace Azure.Monitor.OpenTelemetry.Demo +{ + public partial class Program + { + public static void Main(string[] args) + { + } + } +} + +#endif