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

[SDK + Jaeger] Support loading environment variables from IConfiguration in Traces & Metrics #3720

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
using System.Net.Http;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenTelemetry.Exporter;
using OpenTelemetry.Internal;
Expand Down Expand Up @@ -70,7 +69,7 @@ public static TracerProviderBuilder AddJaegerExporter(
services.Configure(name, configure);
}

services.TryAddSingleton<IOptionsFactory<JaegerExporterOptions>, JaegerExporterOptions.JaegerExporterOptionsFactory>();
services.RegisterOptionsFactory(configuration => new JaegerExporterOptions(configuration));
});

return builder.ConfigureBuilder((sp, builder) =>
Expand Down
60 changes: 10 additions & 50 deletions src/OpenTelemetry.Exporter.Jaeger/JaegerExporterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@
// </copyright>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;

Expand Down Expand Up @@ -56,45 +54,27 @@ public JaegerExporterOptions()

internal JaegerExporterOptions(IConfiguration configuration)
{
var protocol = configuration.GetValue<string>(OTelProtocolEnvVarKey, null);
if (!string.IsNullOrEmpty(protocol))
if (configuration.TryGetValue<JaegerExportProtocol>(
OTelProtocolEnvVarKey,
JaegerExporterProtocolParser.TryParse,
out var protocol))
{
if (JaegerExporterProtocolParser.TryParse(protocol, out var parsedProtocol))
{
this.Protocol = parsedProtocol;
}
else
{
throw new FormatException($"{OTelProtocolEnvVarKey} environment variable has an invalid value: '{protocol}'");
}
this.Protocol = protocol;
}

var agentHost = configuration.GetValue<string>(OTelAgentHostEnvVarKey, null);
if (!string.IsNullOrEmpty(agentHost))
if (configuration.TryGetStringValue(OTelAgentHostEnvVarKey, out var agentHost))
{
this.AgentHost = agentHost;
}

var agentPort = configuration.GetValue<string>(OTelAgentPortEnvVarKey, null);
if (!string.IsNullOrEmpty(agentPort))
if (configuration.TryGetIntValue(OTelAgentPortEnvVarKey, out var agentPort))
{
if (EnvironmentVariableHelper.LoadNumeric(OTelAgentPortEnvVarKey, agentPort, out int parsedAgentPort))
{
this.AgentPort = parsedAgentPort;
}
this.AgentPort = agentPort;
}

var endpoint = configuration.GetValue<string>(OTelEndpointEnvVarKey, null);
if (!string.IsNullOrEmpty(endpoint))
if (configuration.TryGetUriValue(OTelEndpointEnvVarKey, out var endpoint))
{
if (Uri.TryCreate(endpoint, UriKind.Absolute, out Uri parsedEndpoint))
{
this.Endpoint = parsedEndpoint;
}
else
{
throw new FormatException($"{OTelEndpointEnvVarKey} environment variable has an invalid value: '{endpoint}'");
}
this.Endpoint = endpoint;
}
}

Expand Down Expand Up @@ -158,25 +138,5 @@ internal JaegerExporterOptions(IConfiguration configuration)
/// </list>
/// </remarks>
public Func<HttpClient> HttpClientFactory { get; set; } = DefaultHttpClientFactory;

internal sealed class JaegerExporterOptionsFactory : OptionsFactory<JaegerExporterOptions>
{
private readonly IConfiguration configuration;

public JaegerExporterOptionsFactory(
IConfiguration configuration,
IEnumerable<IConfigureOptions<JaegerExporterOptions>> setups,
IEnumerable<IPostConfigureOptions<JaegerExporterOptions>> postConfigures,
IEnumerable<IValidateOptions<JaegerExporterOptions>> validations)
: base(setups, postConfigures, validations)
{
Debug.Assert(configuration != null, "configuration was null");

this.configuration = configuration;
}

protected override JaegerExporterOptions CreateInstance(string name)
=> new(this.configuration);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal static class JaegerExporterProtocolParser
{
public static bool TryParse(string value, out JaegerExportProtocol result)
{
switch (value?.Trim())
switch (value.Trim().ToLower())
{
case "udp/thrift.compact":
result = JaegerExportProtocol.UdpCompactThrift;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\ActivityHelperExtensions.cs" Link="Includes\ActivityHelperExtensions.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\StatusHelper.cs" Link="Includes\StatusHelper.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\EnvironmentVariableHelper.cs" Link="Includes\EnvironmentVariableHelper.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\ConfigurationExtensions.cs" Link="Includes\ConfigurationExtensions.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\OpenTelemetrySdkEventSource.cs" Link="Includes\OpenTelemetrySdkEventSource.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\PooledList.cs" Link="Includes\PooledList.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\PeerServiceResolver.cs" Link="Includes\PeerServiceResolver.cs" />
Expand Down
5 changes: 5 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

* Fixed an issue where `LogRecord.ForEachScope` may return scopes from a
previous log if accessed in a custom processor before
`BatchLogRecordExportProcessor.OnEnd` is fired.
([#3731](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3731))

* Added support for loading environment variables from `IConfiguration` when
using `TracerProviderBuilder` or `MeterProviderBuilder`
([#3720](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3720))
Expand Down
164 changes: 164 additions & 0 deletions src/OpenTelemetry/Internal/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// <copyright file="ConfigurationExtensions.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics;
#if !NETFRAMEWORK && !NETSTANDARD2_0
using System.Diagnostics.CodeAnalysis;
#endif
using System.Globalization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace OpenTelemetry.Internal;

internal static class ConfigurationExtensions
{
public delegate bool TryParseFunc<T>(
string value,
#if !NETFRAMEWORK && !NETSTANDARD2_0
[NotNullWhen(true)]
#endif
out T? parsedValue);

public static bool TryGetStringValue(
this IConfiguration configuration,
string key,
#if !NETFRAMEWORK && !NETSTANDARD2_0
[NotNullWhen(true)]
#endif
out string? value)
{
value = configuration.GetValue<string?>(key, null);

return !string.IsNullOrWhiteSpace(value);
}

public static bool TryGetUriValue(
this IConfiguration configuration,
string key,
#if !NETFRAMEWORK && !NETSTANDARD2_0
[NotNullWhen(true)]
#endif
out Uri? value)
{
if (!configuration.TryGetStringValue(key, out var stringValue))
{
value = null;
return false;
}

if (!Uri.TryCreate(stringValue, UriKind.Absolute, out value))
{
throw new FormatException($"{key} environment variable has an invalid value: '{stringValue}'");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a bug reminder #3690 😉

Of course, it can (and should) be addressed in a separate issue. Feel free to resolve this comment.

}

return true;
}

public static bool TryGetIntValue(
this IConfiguration configuration,
string key,
out int value)
{
if (!configuration.TryGetStringValue(key, out var stringValue))
{
value = default;
return false;
}

if (!int.TryParse(stringValue, NumberStyles.None, CultureInfo.InvariantCulture, out value))
{
throw new FormatException($"{key} environment variable has an invalid value: '{stringValue}'");
}

return true;
}

public static bool TryGetValue<T>(
this IConfiguration configuration,
string key,
TryParseFunc<T> tryParseFunc,
#if !NETFRAMEWORK && !NETSTANDARD2_0
[NotNullWhen(true)]
#endif
out T? value)
{
if (!configuration.TryGetStringValue(key, out var stringValue))
{
value = default;
return false;
}

if (!tryParseFunc(stringValue!, out value))
{
throw new FormatException($"{key} environment variable has an invalid value: '{stringValue}'");
}

return true;
}

public static IServiceCollection RegisterOptionsFactory<T>(
this IServiceCollection services,
Func<IConfiguration, T> optionsFactoryFunc)
where T : class
{
Debug.Assert(services != null, "services was null");
Debug.Assert(optionsFactoryFunc != null, "optionsFactoryFunc was null");

services!.TryAddSingleton<IOptionsFactory<T>>(sp =>
{
return new DelegatingOptionsFactory<T>(
optionsFactoryFunc!,
sp.GetRequiredService<IConfiguration>(),
sp.GetServices<IConfigureOptions<T>>(),
sp.GetServices<IPostConfigureOptions<T>>(),
sp.GetServices<IValidateOptions<T>>());
});

return services!;
}

private sealed class DelegatingOptionsFactory<T> : OptionsFactory<T>
where T : class
{
private readonly Func<IConfiguration, T> optionsFactoryFunc;
private readonly IConfiguration configuration;

public DelegatingOptionsFactory(
Func<IConfiguration, T> optionsFactoryFunc,
IConfiguration configuration,
IEnumerable<IConfigureOptions<T>> setups,
IEnumerable<IPostConfigureOptions<T>> postConfigures,
IEnumerable<IValidateOptions<T>> validations)
: base(setups, postConfigures, validations)
{
Debug.Assert(optionsFactoryFunc != null, "optionsFactoryFunc was null");
Debug.Assert(configuration != null, "configuration was null");

this.optionsFactoryFunc = optionsFactoryFunc!;
this.configuration = configuration!;
}

protected override T CreateInstance(string name)
=> this.optionsFactoryFunc(this.configuration);
}
}