-
Notifications
You must be signed in to change notification settings - Fork 773
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
PrometheusExporterOptions
does not follow Options<T>
pattern.
#2971
Comments
I've been working on bridging this in my local solution and one way this could be addressed is with First, the actual configuration (from public class OpenTelemetryExporterConfigurator : IConfigureOptions<OtlpExporterOptions>
{
private readonly IConfiguration _configuration;
public OpenTelemetryExporterConfigurator()
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
{
}
public OpenTelemetryExporterConfigurator(IConfiguration configuration)
{
this._configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public void Configure(OtlpExporterOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (this._configuration.TryGetValue<Uri>(OpenTelemetryEnvironmentVariable.OpenTelemetryExporterEndpoint, out var endpoint))
{
options.Endpoint = endpoint;
}
if (this._configuration.TryGetValue<string>(OpenTelemetryEnvironmentVariable.OpenTelemetryExporterHeaders, out var headers))
{
options.Headers = headers;
}
if (this._configuration.TryGetValue<int>(OpenTelemetryEnvironmentVariable.OpenTelemetryExporterTimeout, out var timeout))
{
options.TimeoutMilliseconds = timeout;
}
if (this._configuration.TryGetValue<string>(OpenTelemetryEnvironmentVariable.OpenTelemetryExporterProtocol, out var protocol))
{
options.Protocol = protocol?.Trim() switch
{
"grpc" => OtlpExportProtocol.Grpc,
"http/protobuf" => OtlpExportProtocol.HttpProtobuf,
_ => throw new FormatException($"{OpenTelemetryEnvironmentVariable.OpenTelemetryExporterProtocol} environment variable has an invalid value: '{protocol}'"),
};
}
}
} Then you can have the extension method handle the non-deferred/direct read from config... public static MeterProviderBuilder AddOtlpExporter(
this MeterProviderBuilder builder,
Action<OtlpExporterOptions, MetricReaderOptions> configureExporterAndMetricReader)
{
Guard.ThrowIfNull(builder, nameof(builder));
if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
{
return deferredMeterProviderBuilder.Configure((sp, builder) =>
{
AddOtlpExporter(builder, sp.GetOptions<OtlpExporterOptions>(), sp.GetOptions<MetricReaderOptions>(), null, configureExporterAndMetricReader, sp);
});
}
var configurator = new OpenTelemetryExporterConfigurator();
var options = new OtlpExporterOptions();
configurator.Configur(options);
return AddOtlpExporter(builder, options, new MetricReaderOptions(), null, configureExporterAndMetricReader, serviceProvider: null);
} This would, of course, require that somewhere along the way someone has to do something like: services.AddOptions<OtlpExporterOptions>();
services.AddTransient<IConfigureOptions<OtlpExporterOptions>, OpenTelemetryExporterConfigurator>(); Then if someone registers their own The public static MeterProviderBuilder AddOtlpExporter(
this MeterProviderBuilder builder,
Action<OtlpExporterOptions, MetricReaderOptions> configureExporterAndMetricReader)
{
Guard.ThrowIfNull(builder, nameof(builder));
if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder)
{
// Add the options setup automatically when the exporter is added.
builder.Services.AddOptions<OtlpExporterOptions>();
builder.Services.AddTransient<IConfigureOptions<OtlpExporterOptions>, OpenTelemetryExporterConfigurator>();
return deferredMeterProviderBuilder.Configure((sp, builder) =>
{
AddOtlpExporter(builder, sp.GetOptions<OtlpExporterOptions>(), sp.GetOptions<MetricReaderOptions>(), null, configureExporterAndMetricReader, sp);
});
}
var configurator = new OpenTelemetryExporterConfigurator();
var options = new OtlpExporterOptions();
configurator.Configur(options);
return AddOtlpExporter(builder, options, new MetricReaderOptions(), null, configureExporterAndMetricReader, serviceProvider: null);
} |
It appears that Unclear if there's any appetite for that. It would make it more like other builders and add some flexibility at the cost of exposing that |
@tillig I just ran the tests above on my branch with #3780 and they all pass now! I'm going to close this issue. Feel free to do your own testing and re-open if needed. Here are a couple explanations for why it is working now:
|
Sounds great! Thanks! 🎉 |
Got this integrated into our local solution, verified it works perfectly. Thanks again! |
Bug Report
List of all OpenTelemetry NuGet packages and version that you are using (e.g.
OpenTelemetry 1.0.2
):Runtime version (e.g.
net461
,net48
,netcoreapp3.1
,net5.0
etc. You can find this information from the*.csproj
file):Symptom
The configuration of the Prometheus exporter with the
MeterProviderBuilder
appears to follow theOptions<T>
pattern where you can provide configuration for thePrometheusExporterOptions
and later have those retrieved from theIServiceProvider
, but the configuration action is not actually registered with the container or able to be reused. Further, nothing ever actually callsAddOptions<PrometheusExporterOptions>
as part of the base dependency registration, so every timesp.GetOptions<PrometheusExporterOptions>()
is executed here - in examples and in tests - it's always going to be a new instance ofPrometheusExporterOptions
. This is really hard to figure out and isn't documented anywhere if that's the expected behavior.What is the expected behavior?
I expected to see:
IServiceCollection.AddOptions<PrometheusExporterOptions>()
to register the default options. Ostensibly inAddPrometheusExporter
, but the structure of theMeterProviderBuilder
doesn't currently support registering things with the callingIServiceCollection
.AddPrometheusExporter
translated into some sort ofservices.Configure<PrometheusExporterOptions>
call so it can be reused/resolved from the container.UseOpenTelemetryScrapingEndpoint
method able to use the configuration fromAddPrometheusExporter
.What is the actual behavior?
AddOptions<PrometheusExporterOptions>()
so every instance will always be a new instance internal to OpenTelemetry.AddPrometheusExporter
only configures the exporter and is not used byUseOpenTelemetryScrapingEndpoint
.IOptions<PrometheusExporterOptions>
to try to get the options that have been configured, it's null.AddOptions<PrometheusExporterOptions>()
or encouraging use of that plusConfigure<PrometheusExporterOptions>()
instead of passing that lambda toAddPrometheusExporter
.Reproduce
Here are some unit tests showing the problem.
Additional Context
Most "builder" implementations in the core frameworks have a
Services
property so things attached to the builder can register things with the owningIServiceCollection
- likeIMvcBuilder.Services
orIHealthChecksBuilder.Services
. You'd have to add that to theMeterProviderBuilder
to make this work.The text was updated successfully, but these errors were encountered: