Skip to content

Commit ed9b3cc

Browse files
committed
Add support for non-core ASP.Net.
1 parent e72871e commit ed9b3cc

File tree

10 files changed

+542
-250
lines changed

10 files changed

+542
-250
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using LaunchDarkly.Observability.Otel;
6+
using LaunchDarkly.Logging;
7+
using LaunchDarkly.Observability.Logging;
8+
using LaunchDarkly.Observability.Sampling;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Hosting;
11+
using Microsoft.Extensions.Logging;
12+
using OpenTelemetry;
13+
using OpenTelemetry.Resources;
14+
using OpenTelemetry.Trace;
15+
using OpenTelemetry.Exporter;
16+
using OpenTelemetry.Logs;
17+
using OpenTelemetry.Metrics;
18+
19+
// ReSharper disable once CheckNamespace
20+
namespace LaunchDarkly.Observability
21+
{
22+
/// <summary>
23+
/// Static class containing extension methods for configuring observability
24+
/// </summary>
25+
public static class ObservabilityExtensions
26+
{
27+
private class LdObservabilityHostedService : IHostedService
28+
{
29+
private readonly ObservabilityConfig _config;
30+
private readonly ILoggerProvider _loggerProvider;
31+
32+
public LdObservabilityHostedService(ObservabilityConfig config, IServiceProvider provider)
33+
{
34+
_loggerProvider = provider.GetService<ILoggerProvider>();
35+
_config = config;
36+
}
37+
38+
public Task StartAsync(CancellationToken cancellationToken)
39+
{
40+
Observe.Initialize(_config, _loggerProvider);
41+
return Task.CompletedTask;
42+
}
43+
44+
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
45+
}
46+
47+
internal static void AddLaunchDarklyObservabilityWithConfig(this IServiceCollection services,
48+
ObservabilityConfig config, Logger logger = null)
49+
{
50+
DebugLogger.SetLogger(logger);
51+
52+
var sampler = CommonOtelOptions.GetSampler(config);
53+
54+
var resourceBuilder = CommonOtelOptions.GetResourceBuilder(config);
55+
56+
services.AddOpenTelemetry().WithTracing(tracing =>
57+
{
58+
tracing
59+
.WithCommonLaunchDarklyConfig(config, resourceBuilder, sampler)
60+
.AddAspNetCoreInstrumentation(options => { options.RecordException = true; });
61+
}).WithLogging(logging =>
62+
{
63+
logging.SetResourceBuilder(resourceBuilder)
64+
.AddProcessor(new SamplingLogProcessor(sampler))
65+
.AddOtlpExporter(options =>
66+
{
67+
options.WithCommonLaunchDarklyLoggingExport(config);
68+
});
69+
config.ExtendedLoggerConfiguration?.Invoke(logging);
70+
}).WithMetrics(metrics =>
71+
{
72+
metrics
73+
.WithCommonLaunchDarklyConfig(config, resourceBuilder)
74+
.AddAspNetCoreInstrumentation();
75+
});
76+
77+
// Attach a hosted service which will allow us to get a logger provider instance from the built
78+
// service collection.
79+
services.AddHostedService((serviceProvider) =>
80+
new LdObservabilityHostedService(config, serviceProvider));
81+
}
82+
83+
/// <summary>
84+
/// Add the LaunchDarkly Observability services. This function would typically be called by the LaunchDarkly
85+
/// Observability plugin. This should only be called by the end user if the Observability plugin needs to be
86+
/// initialized earlier than the LaunchDarkly client.
87+
/// </summary>
88+
/// <param name="services">The service collection</param>
89+
/// <param name="sdkKey">The LaunchDarkly SDK</param>
90+
/// <param name="configure">A method to configure the services</param>
91+
/// <returns>The service collection</returns>
92+
public static IServiceCollection AddLaunchDarklyObservability(
93+
this IServiceCollection services,
94+
string sdkKey,
95+
Action<ObservabilityConfig.ObservabilityConfigBuilder> configure)
96+
{
97+
var builder = ObservabilityConfig.Builder();
98+
configure(builder);
99+
100+
var config = builder.Build(sdkKey);
101+
AddLaunchDarklyObservabilityWithConfig(services, config);
102+
return services;
103+
}
104+
}
105+
}

sdk/@launchdarkly/observability-dotnet/src/LaunchDarkly.Observability/ObservabilityPlugin.cs renamed to sdk/@launchdarkly/observability-dotnet/src/LaunchDarkly.Observability/Asp/Core/ObservabilityPlugin.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using LaunchDarkly.Sdk.Server.Plugins;
77
using LaunchDarkly.Sdk.Server.Telemetry;
88
using Microsoft.Extensions.DependencyInjection;
9+
using OpenTelemetry.Logs;
910

11+
// ReSharper disable once CheckNamespace
1012
namespace LaunchDarkly.Observability
1113
{
1214
public class ObservabilityPlugin : Plugin
@@ -32,6 +34,7 @@ public class ObservabilityPlugin : Plugin
3234
/// <para>
3335
/// When using this builder, LaunchDarkly client must be constructed before your application is built.
3436
/// For example:
37+
///
3538
/// <code>
3639
/// var builder = WebApplication.CreateBuilder(args);
3740
///
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using LaunchDarkly.Sdk.Integrations.Plugins;
4+
using LaunchDarkly.Sdk.Server.Hooks;
5+
using LaunchDarkly.Sdk.Server.Interfaces;
6+
using LaunchDarkly.Sdk.Server.Plugins;
7+
using LaunchDarkly.Sdk.Server.Telemetry;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using OpenTelemetry.Logs;
10+
11+
// ReSharper disable once CheckNamespace
12+
namespace LaunchDarkly.Observability
13+
{
14+
public class ObservabilityPlugin : Plugin
15+
{
16+
private readonly ObservabilityPluginBuilder _config;
17+
private readonly IServiceCollection _services;
18+
19+
/// <summary>
20+
/// Create a new builder for <see cref="ObservabilityPlugin"/>.
21+
/// <para>
22+
/// When using this builder, LaunchDarkly client must be constructed before your application is built.
23+
/// For example:
24+
///
25+
/// <code>
26+
/// TODO: Add example.
27+
/// </code>
28+
/// </para>
29+
/// </summary>
30+
/// <returns>A new <see cref="ObservabilityPluginBuilder"/> instance for configuring the observability plugin.</returns>
31+
public static ObservabilityPluginBuilder Builder() =>
32+
new ObservabilityPluginBuilder();
33+
34+
internal ObservabilityPlugin(ObservabilityPluginBuilder config) : base(
35+
"LaunchDarkly.Observability")
36+
{
37+
_config = config;
38+
}
39+
40+
internal ObservabilityPlugin() : base("LaunchDarkly.Observability")
41+
{
42+
_services = null;
43+
_config = null;
44+
}
45+
46+
/// <inheritdoc />
47+
public override void Register(ILdClient client, EnvironmentMetadata metadata)
48+
{
49+
if (_services == null || _config == null) return;
50+
var config = _config.BuildConfig(metadata.Credential);
51+
OpenTelemetry.Register(config);
52+
}
53+
54+
/// <inheritdoc />
55+
public override IList<Hook> GetHooks(EnvironmentMetadata metadata)
56+
{
57+
return new List<Hook>
58+
{
59+
TracingHook.Builder().IncludeValue().Build()
60+
};
61+
}
62+
63+
/// <summary>
64+
/// Used to build an instance of the Observability Plugin.
65+
/// </summary>
66+
public sealed class ObservabilityPluginBuilder : BaseBuilder<ObservabilityPluginBuilder>
67+
{
68+
/// <summary>
69+
/// Build an <see cref="ObservabilityPlugin"/> instance with the configured settings.
70+
/// </summary>
71+
/// <returns>The constructed <see cref="ObservabilityPlugin"/>.</returns>
72+
public ObservabilityPlugin Build()
73+
{
74+
return new ObservabilityPlugin(this);
75+
}
76+
}
77+
}
78+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using LaunchDarkly.Logging;
2+
using LaunchDarkly.Observability.Otel;
3+
using Microsoft.Extensions.Logging;
4+
using OpenTelemetry;
5+
using OpenTelemetry.Logs;
6+
using OpenTelemetry.Metrics;
7+
using OpenTelemetry.Trace;
8+
9+
// ReSharper disable once CheckNamespace
10+
namespace LaunchDarkly.Observability
11+
{
12+
public static class OpenTelemetry
13+
{
14+
private static TracerProvider _tracerProvider;
15+
private static MeterProvider _meterProvider;
16+
private static readonly object ProviderLock = new object();
17+
18+
/// <summary>
19+
/// Extension method which adds LaunchDarkly logging to a logging factory.
20+
///
21+
/// <code>
22+
/// using (var factory = LoggerFactory.Create(builder => { builder.AddLaunchDarklyLogging(config); })) {
23+
/// // Use factory to get logger which uses LaunchDarkly loging.
24+
/// }
25+
/// </code>
26+
/// </summary>
27+
/// <param name="loggingBuilder">the logging builder for the factory</param>
28+
/// <param name="config">the LaunchDarkly observability configuration</param>
29+
public static void AddLaunchDarklyLogging(this ILoggingBuilder loggingBuilder, ObservabilityConfig config)
30+
{
31+
loggingBuilder.AddOpenTelemetry(options =>
32+
{
33+
options.SetResourceBuilder(CommonOtelOptions.GetResourceBuilder(config))
34+
.AddProcessor(new SamplingLogProcessor(CommonOtelOptions.GetSampler(config)))
35+
.AddOtlpExporter(exportOptions => { exportOptions.WithCommonLaunchDarklyLoggingExport(config); });
36+
});
37+
}
38+
39+
public static void Register(ObservabilityConfig config, Logger debugLogger = null)
40+
{
41+
lock (ProviderLock)
42+
{
43+
// If the providers are set, then the implementation has already been configured.
44+
if (_tracerProvider != null)
45+
{
46+
return;
47+
}
48+
49+
var resourceBuilder = CommonOtelOptions.GetResourceBuilder(config);
50+
var sampler = CommonOtelOptions.GetSampler(config);
51+
52+
_tracerProvider = global::OpenTelemetry.Sdk.CreateTracerProviderBuilder()
53+
.WithCommonLaunchDarklyConfig(config, resourceBuilder, sampler)
54+
.AddAspNetInstrumentation()
55+
.Build();
56+
57+
_meterProvider = global::OpenTelemetry.Sdk.CreateMeterProviderBuilder()
58+
.WithCommonLaunchDarklyConfig(config, resourceBuilder)
59+
.AddAspNetInstrumentation()
60+
.Build();
61+
}
62+
63+
using (var factory = LoggerFactory.Create(builder => { builder.AddLaunchDarklyLogging(config); }))
64+
{
65+
var logger = factory.CreateLogger<ObservabilityPlugin>();
66+
Observe.Initialize(config, logger);
67+
}
68+
}
69+
70+
public static void Shutdown()
71+
{
72+
lock (ProviderLock)
73+
{
74+
_tracerProvider?.Dispose();
75+
_meterProvider?.Dispose();
76+
_tracerProvider = null;
77+
_meterProvider = null;
78+
}
79+
}
80+
}
81+
}

sdk/@launchdarkly/observability-dotnet/src/LaunchDarkly.Observability/BaseBuilder.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
namespace LaunchDarkly.Observability
88
{
9+
#if NETFRAMEWORK
10+
using LoggerBuilderType = OpenTelemetryLoggerOptions;
11+
#else
12+
using LoggerBuilderType = LoggerProviderBuilder;
13+
#endif
14+
915
/// <summary>
1016
/// Base builder which allows for methods to be shared between building a config directly and building a plugin.
1117
/// <remarks>
@@ -23,7 +29,7 @@ public class BaseBuilder<TBuilder> where TBuilder : BaseBuilder<TBuilder>
2329
private string _environment = string.Empty;
2430
private string _serviceVersion = string.Empty;
2531
private Action<TracerProviderBuilder> _extendedTracerConfiguration;
26-
private Action<LoggerProviderBuilder> _extendedLoggerConfiguration;
32+
private Action<LoggerBuilderType> _extendedLoggerConfiguration;
2733
private Action<MeterProviderBuilder> _extendedMeterConfiguration;
2834

2935
protected BaseBuilder()
@@ -171,7 +177,7 @@ public TBuilder WithExtendedTracingConfig(Action<TracerProviderBuilder> extended
171177
/// </example>
172178
/// <param name="extendedLoggerConfiguration">A function used to extend the logging configuration.</param>
173179
/// <returns>A reference to this builder.</returns>
174-
public TBuilder WithExtendedLoggerConfiguration(Action<LoggerProviderBuilder> extendedLoggerConfiguration)
180+
public TBuilder WithExtendedLoggerConfiguration(Action<LoggerBuilderType> extendedLoggerConfiguration)
175181
{
176182
_extendedLoggerConfiguration = extendedLoggerConfiguration;
177183
return (TBuilder)this;

0 commit comments

Comments
 (0)