Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AzureMonitorOpenTelemetryDistro] AddAzureMonitorOpenTelemetry extension methods and AzureMonitorOpenTelemetryOptions #34198

Merged
merged 13 commits into from
Feb 24, 2023
Merged
2 changes: 2 additions & 0 deletions eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@

<!-- OpenTelemetry dependency approved for Azure.Monitor.OpenTelemetry.Exporter package only -->
<PackageReference Update="OpenTelemetry" Version="[1.4.0-rc.4]" Condition="'$(MSBuildProjectName)' == 'Azure.Monitor.OpenTelemetry.Exporter'" />
<PackageReference Update="OpenTelemetry.Extensions.Hosting" Version="1.4.0-rc.4" />
<PackageReference Update="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9.13" />
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Update="OpenTelemetry.Extensions.PersistentStorage" Version="1.0.0-beta.1" Condition="'$(MSBuildProjectName)' == 'Azure.Monitor.OpenTelemetry.Exporter'" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Azure.Monitor.OpenTelemetry.AzureMonitorOpenTelemetryOptions> 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 { } }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
<IncludeOperationsSharedSource>true</IncludeOperationsSharedSource>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Azure.Monitor.OpenTelemetry.Exporter\src\Azure.Monitor.OpenTelemetry.Exporter.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Extension methods for setting up Azure Monitor OpenTelemetry in an <see cref="IServiceCollection" />.
/// </summary>
public static class AzureMonitorOpenTelemetryExtensions
{
/// <summary>
/// Adds Azure Monitor OpenTelemetry into service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddAzureMonitorOpenTelemetry(this IServiceCollection services)
{
return services.AddAzureMonitorOpenTelemetry(o => o = new AzureMonitorOpenTelemetryOptions());
}

/// <summary>
/// Adds Azure Monitor OpenTelemetry into service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="options">The <see cref="AzureMonitorOpenTelemetryOptions" /> instance for configuration.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddAzureMonitorOpenTelemetry(this IServiceCollection services, AzureMonitorOpenTelemetryOptions options)
{
options ??= new AzureMonitorOpenTelemetryOptions();
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
return services.AddAzureMonitorOpenTelemetry(o => o.Clone(options));
}

/// <summary>
/// Adds Azure Monitor OpenTelemetry into service collection.
/// </summary>
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <param name="configureAzureMonitorOpenTelemetry">Callback action for configuring <see cref="AzureMonitorOpenTelemetryOptions"/>.</param>
/// <returns><see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddAzureMonitorOpenTelemetry(this IServiceCollection services, Action<AzureMonitorOpenTelemetryOptions> configureAzureMonitorOpenTelemetry)
{
return AzureMonitorOpenTelemetryImplementations.AddAzureMonitorOpenTelemetryWithAction(services, configureAzureMonitorOpenTelemetry);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<AzureMonitorOpenTelemetryOptions> 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
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
// the SDK registrations.

services.Remove(sdkTracerProviderServiceRegistration);
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
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<IOptionsMonitor<AzureMonitorOpenTelemetryOptions>>().Get("");
if (!options.EnableTraces)
{
return new NoopTracerProvider();
}
else
{
SetValueToExporterOptions(sp, options);
var sdkProviderWrapper = sp.GetRequiredService<SdkProviderWrapper>();
sdkProviderWrapper.SdkTracerProvider = (TracerProvider)sdkTracerProviderServiceRegistration.ImplementationFactory(sp);
return sdkProviderWrapper.SdkTracerProvider;
}
});

services.AddSingleton(sp =>
{
var options = sp.GetRequiredService<IOptionsMonitor<AzureMonitorOpenTelemetryOptions>>().Get("");
if (!options.EnableMetrics)
{
return new NoopMeterProvider();
}
else
{
SetValueToExporterOptions(sp, options);
var sdkProviderWrapper = sp.GetRequiredService<SdkProviderWrapper>();
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<SdkProviderWrapper>();

return services;
}

private static void SetValueToExporterOptions(IServiceProvider sp, AzureMonitorOpenTelemetryOptions options)
{
var exporterOptions = sp.GetRequiredService<IOptionsMonitor<AzureMonitorExporterOptions>>().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();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable disable
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved

using System.Runtime.Serialization;
using Azure.Monitor.OpenTelemetry.Exporter;

namespace Azure.Monitor.OpenTelemetry
{
/// <summary>
/// Options that allow users to configure the Azure Monitor OpenTelemetry.
/// </summary>
public class AzureMonitorOpenTelemetryOptions
{
/// <summary>
/// Gets or sets a value of Azure Monitor Exporter Options.
/// </summary>
public AzureMonitorExporterOptions AzureMonitorExporterOptions { get; set; } = new AzureMonitorExporterOptions();

/// <summary>
/// The Connection String provides users with a single configuration setting to identify the Azure Monitor resource and endpoint.
/// </summary>
/// <remarks>
/// (https://docs.microsoft.com/azure/azure-monitor/app/sdk-connection-string).
/// </remarks>
public string ConnectionString
{
get
{
return AzureMonitorExporterOptions.ConnectionString;
}
set
{
AzureMonitorExporterOptions.ConnectionString = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether Logs should be enabled.
/// </summary>
public bool EnableLogs { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether Metrics should be enabled.
/// </summary>
public bool EnableMetrics { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether Traces should be enabled.
/// </summary>
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Azure.Monitor.OpenTelemetry.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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