Skip to content

Commit b215109

Browse files
authored
feat: Add support for non-core ASP.Net. (#199)
## Summary Adds support for ASP.Net on .Net Framework. netstandard2.0 and dotnet 8.0 support ASP.Net core. Code that is common between the two is shared, code and dependencies that are not common are conditionally included. ## How did you test this change? Manual testing. ## Are there any deployment considerations? <!-- Backend - Do we need to consider migrations or backfilling data? -->
1 parent c4bb5ab commit b215109

File tree

12 files changed

+737
-269
lines changed

12 files changed

+737
-269
lines changed

sdk/@launchdarkly/observability-dotnet/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
**/obj
33
LaunchDarkly.Observability.sln.DotSettings.user
44
**/launchSettings.json
5+
**/.vs/**
6+
packages/
57
docs/
68
api/
79
nupkgs/

sdk/@launchdarkly/observability-dotnet/README.md

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ LaunchDarkly Observability Plugin for .Net
99

1010
**NB: APIs are subject to change until a 1.x version is released.**
1111

12-
## Install
12+
## Install for ASP.Net Core
1313

1414
```shell
1515
dotnet add package LaunchDarkly.Observability
@@ -18,7 +18,54 @@ dotnet add package LaunchDarkly.Observability
1818
Install the plugin when configuring your LaunchDarkly SDK.
1919

2020
```csharp
21-
// TODO: Add example.
21+
var builder = WebApplication.CreateBuilder(args);
22+
23+
24+
var config = Configuration.Builder("your-sdk-key")
25+
.Plugins(new PluginConfigurationBuilder()
26+
.Add(ObservabilityPlugin.Builder(builder.Services)
27+
.WithServiceName("your-service-name")
28+
.WithServiceVersion("example-sha")
29+
.Build()
30+
)
31+
).Build();
32+
33+
// Building the LdClient with the Observability plugin. This line will add services to the web application.
34+
var client = new LdClient(config);
35+
36+
// Client must be built before this line.
37+
var app = builder.Build();
38+
```
39+
40+
## Install for an ASP.Net application with .Net Framework
41+
42+
```csharp
43+
// In Global.asax.cs
44+
protected void Application_Start()
45+
{
46+
// Other application specific code.
47+
var client = new LdClient(Configuration.Builder("your-sdk-key")
48+
.Plugins(new PluginConfigurationBuilder().Add(ObservabilityPlugin.Builder()
49+
.WithServiceName("your-service-name")
50+
.WithServiceVersion("example-sha")
51+
.Build()))
52+
.Build());
53+
}
54+
55+
protected void Application_End() {
56+
Observe.Shutdown();
57+
}
58+
```
59+
60+
```xml
61+
<!-- In Web.config -->
62+
<system.webServer>
63+
// Any existing content should remain and the following should be added.
64+
<modules>
65+
<add name="TelemetryHttpModule" type="OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule,
66+
OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule" preCondition="integratedMode,managedHandler" />
67+
</modules>
68+
</system.webServer>
2269
```
2370

2471
LaunchDarkly overview
@@ -49,4 +96,4 @@ We encourage pull requests and other contributions from the community. Check out
4996
[dotnetplugin-sdk-ci]: https://github.com/launchdarkly/observability-sdk/actions/workflows/dotnet-plugin.yml
5097
[o11y-docs-link]: https://launchdarkly.github.io/observability-sdk/sdk/@launchdarkly/observability-dotnet/
5198
[dotnetplugin-nuget-badge]: https://img.shields.io/nuget/v/LaunchDarkly.Observability.svg?style=flat-square
52-
[dotnetplugin-nuget-link]: https://www.nuget.org/packages/LaunchDarkly.Observability/
99+
[dotnetplugin-nuget-link]: https://www.nuget.org/packages/LaunchDarkly.Observability/
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: 6 additions & 3 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
@@ -20,7 +22,7 @@ public class ObservabilityPlugin : Plugin
2022
/// In a typical configuration, this method will not need to be used.
2123
/// </para>
2224
/// <para>
23-
/// This method only needs to be used when observability related functionality must be intialized before it
25+
/// This method only needs to be used when observability related functionality must be initialized before it
2426
/// is possible to initialize the LaunchDarkly SDK.
2527
/// </para>
2628
/// </summary>
@@ -32,15 +34,16 @@ 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
///
3841
///
39-
/// var config = Configuration.Builder(Environment.GetEnvironmentVariable("your-sdk-key")
42+
/// var config = Configuration.Builder("your-sdk-key")
4043
/// .Plugins(new PluginConfigurationBuilder()
4144
/// .Add(ObservabilityPlugin.Builder(builder.Services)
4245
/// .WithServiceName("ryan-test-service")
43-
/// .WithServiceVersion("0.0.0")
46+
/// .WithServiceVersion("example-sha")
4447
/// .Build())).Build();
4548
/// // Building the LdClient with the Observability plugin. This line will add services to the web application.
4649
/// var client = new LdClient(config);
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System.Collections.Generic;
2+
using LaunchDarkly.Sdk.Integrations.Plugins;
3+
using LaunchDarkly.Sdk.Server.Hooks;
4+
using LaunchDarkly.Sdk.Server.Interfaces;
5+
using LaunchDarkly.Sdk.Server.Plugins;
6+
using LaunchDarkly.Sdk.Server.Telemetry;
7+
8+
// ReSharper disable once CheckNamespace
9+
namespace LaunchDarkly.Observability
10+
{
11+
public class ObservabilityPlugin : Plugin
12+
{
13+
private readonly ObservabilityPluginBuilder _config;
14+
15+
/// <summary>
16+
/// Create a new builder for <see cref="ObservabilityPlugin"/>.
17+
/// <para>
18+
/// When using this builder, LaunchDarkly client must be constructed during Application_Start of your
19+
/// web application in Global.asax.cs.
20+
/// For example:
21+
///
22+
/// <code>
23+
/// protected void Application_Start()
24+
/// {
25+
/// // Other application specific code.
26+
/// var client = new LdClient(Configuration.Builder("your-sdk-key")
27+
/// .Plugins(new PluginConfigurationBuilder().Add(ObservabilityPlugin.Builder()
28+
/// .WithServiceName("classic-asp-application")
29+
/// .WithServiceVersion("example-sha")
30+
/// .Build()))
31+
/// .Build());
32+
/// }
33+
///
34+
/// protected void Application_End() {
35+
/// Observe.Shutdown();
36+
/// }
37+
/// </code>
38+
///
39+
/// Additionally the Web.config must include the following.
40+
///
41+
/// <code>
42+
/// <system.webServer>
43+
/// // Any existing content should remain and the following should be added.
44+
/// <modules>
45+
/// <add name="TelemetryHttpModule" type="OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule,
46+
/// OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule" preCondition="integratedMode,managedHandler" />
47+
/// </modules>
48+
/// </system.webServer>
49+
/// </code>
50+
///
51+
/// </para>
52+
/// </summary>
53+
/// <returns>A new <see cref="ObservabilityPluginBuilder"/> instance for configuring the observability plugin.</returns>
54+
public static ObservabilityPluginBuilder Builder() =>
55+
new ObservabilityPluginBuilder();
56+
57+
/// <summary>
58+
/// Construct a plugin which is intended to be used with already configured observability services.
59+
/// <para>
60+
/// In a typical configuration, this method will not need to be used.
61+
/// </para>
62+
/// <para>
63+
/// This method only needs to be used when observability related functionality must be initialized before it
64+
/// is possible to initialize the LaunchDarkly SDK.
65+
/// </para>
66+
/// </summary>
67+
/// <returns>an observability plugin instance</returns>
68+
public static ObservabilityPlugin ForExistingServices() => new ObservabilityPlugin();
69+
70+
internal ObservabilityPlugin(ObservabilityPluginBuilder config) : base(
71+
"LaunchDarkly.Observability")
72+
{
73+
_config = config;
74+
}
75+
76+
internal ObservabilityPlugin() : base("LaunchDarkly.Observability")
77+
{
78+
_config = null;
79+
}
80+
81+
/// <inheritdoc />
82+
public override void Register(ILdClient client, EnvironmentMetadata metadata)
83+
{
84+
if (_config == null) return;
85+
var config = _config.BuildConfig(metadata.Credential);
86+
OpenTelemetry.Register(config);
87+
}
88+
89+
/// <inheritdoc />
90+
public override IList<Hook> GetHooks(EnvironmentMetadata metadata)
91+
{
92+
return new List<Hook>
93+
{
94+
TracingHook.Builder().IncludeValue().Build()
95+
};
96+
}
97+
98+
/// <summary>
99+
/// Used to build an instance of the Observability Plugin.
100+
/// </summary>
101+
public sealed class ObservabilityPluginBuilder : BaseBuilder<ObservabilityPluginBuilder>
102+
{
103+
/// <summary>
104+
/// Build an <see cref="ObservabilityPlugin"/> instance with the configured settings.
105+
/// </summary>
106+
/// <returns>The constructed <see cref="ObservabilityPlugin"/>.</returns>
107+
public ObservabilityPlugin Build()
108+
{
109+
return new ObservabilityPlugin(this);
110+
}
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)