Skip to content

Commit 46e895c

Browse files
committed
feat: Add support for environment variable configuration.
1 parent 0341b0f commit 46e895c

File tree

5 files changed

+237
-5
lines changed

5 files changed

+237
-5
lines changed

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class BaseBuilder<TBuilder> where TBuilder : BaseBuilder<TBuilder>
1717
{
1818
private const string DefaultOtlpEndpoint = "https://otel.observability.app.launchdarkly.com:4318";
1919
private const string DefaultBackendUrl = "https://pub.observability.app.launchdarkly.com";
20-
private string _otlpEndpoint = DefaultOtlpEndpoint;
20+
private string _otlpEndpoint = string.Empty;
2121
private string _backendUrl = DefaultBackendUrl;
2222
private string _serviceName = string.Empty;
2323
private string _environment = string.Empty;
@@ -36,14 +36,18 @@ protected BaseBuilder()
3636
/// For most configurations, the OTLP endpoint will not need to be set.
3737
/// </para>
3838
/// <para>
39+
/// If not explicitly set, the OTLP endpoint will be read from the OTEL_EXPORTER_OTLP_ENDPOINT
40+
/// environment variable. Values set with this method take precedence over the environment variable.
41+
/// </para>
42+
/// <para>
3943
/// Setting the endpoint to null will reset the builder value to the default.
4044
/// </para>
4145
/// </summary>
4246
/// <param name="otlpEndpoint">The OTLP exporter endpoint URL.</param>
4347
/// <returns>A reference to this builder.</returns>
4448
public TBuilder WithOtlpEndpoint(string otlpEndpoint)
4549
{
46-
_otlpEndpoint = otlpEndpoint ?? DefaultOtlpEndpoint;
50+
_otlpEndpoint = otlpEndpoint;
4751
return (TBuilder)this;
4852
}
4953

@@ -66,6 +70,10 @@ public TBuilder WithBackendUrl(string backendUrl)
6670

6771
/// <summary>
6872
/// Set the service name.
73+
/// <para>
74+
/// If not explicitly set, the service name will be read from the OTEL_SERVICE_NAME environment variable.
75+
/// Values set with this method take precedence over the environment variable.
76+
/// </para>
6977
/// </summary>
7078
/// <param name="serviceName">The logical service name used in telemetry resource attributes.</param>
7179
/// <returns>A reference to this builder.</returns>
@@ -228,10 +236,20 @@ internal ObservabilityConfig BuildConfig(string sdkKey)
228236
"SDK key cannot be null when creating an ObservabilityConfig builder.");
229237
}
230238

231-
return new ObservabilityConfig(
239+
var effectiveServiceName = EnvironmentHelper.GetValueOrEnvironment(
240+
_serviceName,
241+
EnvironmentVariables.OtelServiceName,
242+
string.Empty);
243+
244+
var effectiveOtlpEndpoint = EnvironmentHelper.GetValueOrEnvironment(
232245
_otlpEndpoint,
246+
EnvironmentVariables.OtelExporterOtlpEndpoint,
247+
DefaultOtlpEndpoint);
248+
249+
return new ObservabilityConfig(
250+
effectiveOtlpEndpoint,
233251
_backendUrl,
234-
_serviceName,
252+
effectiveServiceName,
235253
_environment,
236254
_serviceVersion,
237255
sdkKey,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
3+
namespace LaunchDarkly.Observability
4+
{
5+
/// <summary>
6+
/// Helper methods for working with environment variables.
7+
/// </summary>
8+
internal static class EnvironmentHelper
9+
{
10+
/// <summary>
11+
/// Returns the provided value if it's not null or empty, otherwise attempts to get the value from the specified environment variable.
12+
/// If the environment variable is also not set, returns the default value.
13+
/// </summary>
14+
/// <param name="value">The primary value to use.</param>
15+
/// <param name="environmentVariable">The name of the environment variable to check if the primary value is null or empty.</param>
16+
/// <param name="defaultValue">The default value to use if both the primary value and environment variable are not set.</param>
17+
/// <returns>The resolved value based on the precedence: primary value > environment variable > default value.</returns>
18+
public static string GetValueOrEnvironment(string value, string environmentVariable, string defaultValue = "")
19+
{
20+
if (!string.IsNullOrEmpty(value))
21+
{
22+
return value;
23+
}
24+
25+
var envValue = Environment.GetEnvironmentVariable(environmentVariable);
26+
return !string.IsNullOrEmpty(envValue) ? envValue : defaultValue;
27+
}
28+
}
29+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace LaunchDarkly.Observability
2+
{
3+
/// <summary>
4+
/// Contains constants for environment variable names used by the Observability SDK.
5+
/// </summary>
6+
internal static class EnvironmentVariables
7+
{
8+
/// <summary>
9+
/// The OpenTelemetry standard environment variable for the service name.
10+
/// When not explicitly set via WithServiceName(), this environment variable will be used.
11+
/// </summary>
12+
public const string OtelServiceName = "OTEL_SERVICE_NAME";
13+
14+
/// <summary>
15+
/// The OpenTelemetry standard environment variable for the OTLP exporter endpoint.
16+
/// When not explicitly set via WithOtlpEndpoint(), this environment variable will be used.
17+
/// </summary>
18+
public const string OtelExporterOtlpEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT";
19+
}
20+
}

sdk/@launchdarkly/observability-dotnet/test/LaunchDarkly.Observability.Tests/ObservabilityConfigBuilderTests.cs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using LaunchDarkly.Observability;
32
using NUnit.Framework;
43
using OpenTelemetry.Logs;
54
using OpenTelemetry.Metrics;
@@ -10,6 +9,13 @@ namespace LaunchDarkly.Observability.Test
109
[TestFixture]
1110
public class ObservabilityConfigBuilderTests
1211
{
12+
[SetUp]
13+
public void SetUp()
14+
{
15+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelExporterOtlpEndpoint, null);
16+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, null);
17+
}
18+
1319
[Test]
1420
public void Build_WithAllFields_SetsValues()
1521
{
@@ -256,5 +262,114 @@ public void WithExtendedConfigurations_NullActionsAreAccepted()
256262
Assert.That(config.ExtendedMeterConfiguration, Is.Null);
257263
});
258264
}
265+
266+
[Test]
267+
public void Build_UsesOtelServiceNameEnvironmentVariable_WhenServiceNameNotSet()
268+
{
269+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, "service-from-env");
270+
271+
// Build config without setting service name explicitly
272+
var config = ObservabilityConfig.Builder().Build("sdk-key");
273+
274+
// Should use the environment variable value
275+
Assert.That(config.ServiceName, Is.EqualTo("service-from-env"));
276+
}
277+
278+
[Test]
279+
public void Build_PrefersExplicitServiceName_OverEnvironmentVariable()
280+
{
281+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, "service-from-env");
282+
283+
// Build config with explicit service name
284+
var config = ObservabilityConfig.Builder()
285+
.WithServiceName("explicit-service")
286+
.Build("sdk-key");
287+
288+
// Should use the explicitly set value, not the environment variable
289+
Assert.That(config.ServiceName, Is.EqualTo("explicit-service"));
290+
}
291+
292+
[Test]
293+
public void Build_HandlesAbsentOtelServiceNameEnvironmentVariable()
294+
{
295+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, null);
296+
297+
// Build config without setting service name
298+
var config = ObservabilityConfig.Builder().Build("sdk-key");
299+
300+
// Should use empty string as default
301+
Assert.That(config.ServiceName, Is.EqualTo(string.Empty));
302+
}
303+
304+
[Test]
305+
public void Build_WithServiceNameSetToNull_UsesEnvironmentVariable()
306+
{
307+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, "service-from-env");
308+
309+
// Build config with service name explicitly set to null (which becomes empty string in WithServiceName)
310+
var config = ObservabilityConfig.Builder()
311+
.WithServiceName(null)
312+
.Build("sdk-key");
313+
314+
// Should use the environment variable since WithServiceName(null) sets it to empty string
315+
Assert.That(config.ServiceName, Is.EqualTo("service-from-env"));
316+
}
317+
318+
[Test]
319+
public void Build_UsesOtelExporterOtlpEndpointEnvironmentVariable_WhenOtlpEndpointNotSet()
320+
{
321+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelExporterOtlpEndpoint,
322+
"https://custom-otlp.example.com:4318");
323+
324+
// Build config without setting OTLP endpoint explicitly
325+
var config = ObservabilityConfig.Builder().Build("sdk-key");
326+
327+
// Should use the environment variable value
328+
Assert.That(config.OtlpEndpoint, Is.EqualTo("https://custom-otlp.example.com:4318"));
329+
}
330+
331+
[Test]
332+
public void Build_PrefersExplicitOtlpEndpoint_OverEnvironmentVariable()
333+
{
334+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelExporterOtlpEndpoint,
335+
"https://env-otlp.example.com:4318");
336+
337+
// Build config with explicit OTLP endpoint
338+
var config = ObservabilityConfig.Builder()
339+
.WithOtlpEndpoint("https://explicit-otlp.example.com:4318")
340+
.Build("sdk-key");
341+
342+
// Should use the explicitly set value, not the environment variable
343+
Assert.That(config.OtlpEndpoint, Is.EqualTo("https://explicit-otlp.example.com:4318"));
344+
}
345+
346+
[Test]
347+
public void Build_HandlesAbsentOtelExporterOtlpEndpointEnvironmentVariable()
348+
{
349+
// Clear the environment variable
350+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelExporterOtlpEndpoint, null);
351+
352+
// Build config without setting OTLP endpoint
353+
var config = ObservabilityConfig.Builder().Build("sdk-key");
354+
355+
// Should use default OTLP endpoint
356+
Assert.That(config.OtlpEndpoint, Is.EqualTo("https://otel.observability.app.launchdarkly.com:4318"));
357+
}
358+
359+
[Test]
360+
public void Build_WithOtlpEndpointSetToNull_UsesDefaultNotEnvironmentVariable()
361+
{
362+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelExporterOtlpEndpoint,
363+
"https://env-otlp.example.com:4318");
364+
365+
// Build config with OTLP endpoint explicitly set to null (which resets to default)
366+
var config = ObservabilityConfig.Builder()
367+
.WithOtlpEndpoint(null)
368+
.Build("sdk-key");
369+
370+
// Should use the default value when explicitly set to null, and then check env var
371+
// Since null resets to default, and default means "check env var", it should use env var
372+
Assert.That(config.OtlpEndpoint, Is.EqualTo("https://env-otlp.example.com:4318"));
373+
}
259374
}
260375
}

sdk/@launchdarkly/observability-dotnet/test/LaunchDarkly.Observability.Tests/ObservabilityPluginBuilderTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,55 @@ public void Build_WithNullValues_HandlesNullsCorrectly()
6565

6666
Assert.That(plugin, Is.InstanceOf<ObservabilityPlugin>());
6767
}
68+
69+
[Test]
70+
public void Build_UsesOtelServiceNameEnvironmentVariable_WhenServiceNameNotSet()
71+
{
72+
// Save the original environment variable value
73+
var originalValue = Environment.GetEnvironmentVariable(EnvironmentVariables.OtelServiceName);
74+
75+
try
76+
{
77+
// Set the environment variable
78+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, "plugin-service-from-env");
79+
80+
// Build plugin without setting service name explicitly
81+
var plugin = ObservabilityPlugin.Builder(_services).Build();
82+
83+
// Plugin should be created successfully and will use the env var internally
84+
Assert.That(plugin, Is.InstanceOf<ObservabilityPlugin>());
85+
}
86+
finally
87+
{
88+
// Restore the original environment variable value
89+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, originalValue);
90+
}
91+
}
92+
93+
[Test]
94+
public void Build_PrefersExplicitServiceName_OverEnvironmentVariable()
95+
{
96+
// Save the original environment variable value
97+
var originalValue = Environment.GetEnvironmentVariable(EnvironmentVariables.OtelServiceName);
98+
99+
try
100+
{
101+
// Set the environment variable
102+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, "plugin-service-from-env");
103+
104+
// Build plugin with explicit service name
105+
var plugin = ObservabilityPlugin.Builder(_services)
106+
.WithServiceName("explicit-plugin-service")
107+
.Build();
108+
109+
// Plugin should be created successfully and will use the explicit value internally
110+
Assert.That(plugin, Is.InstanceOf<ObservabilityPlugin>());
111+
}
112+
finally
113+
{
114+
// Restore the original environment variable value
115+
Environment.SetEnvironmentVariable(EnvironmentVariables.OtelServiceName, originalValue);
116+
}
117+
}
68118
}
69119
}

0 commit comments

Comments
 (0)