-
Notifications
You must be signed in to change notification settings - Fork 763
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
Dependency injection support #1889
Dependency injection support #1889
Conversation
src/OpenTelemetry.Exporter.Jaeger/JaegerExporterHelperExtensions.cs
Outdated
Show resolved
Hide resolved
Looks interesting. Not seeing how I would go about registering services ( |
Ya, you're right, it only has the IServiceProvider. I'll keep messing with it and see if I can make that work too. Feel free to ping me on slack if you have any ideas. Or competing PR is also welcome. I feel like this is possible but it might take a couple of tries 😄 |
@ejsmith I just pushed a new extension method called
Example... services.AddOpenTelemetryTracing((builder) => builder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(this.Configuration.GetValue<string>("Jaeger:ServiceName")));
.AddJaegerExporter());
services.Configure<JaegerExporterOptions>(this.Configuration.GetSection("Jaeger"));
services.AddOpenTelemetryEventLogging(); // Pretend this is the new thing from your PR.
public static IServiceCollection AddOpenTelemetryEventLogging(this IServiceCollection services) {
services.AddHostedService<MyHostedService>();
services.AddSingleton<MyService>();
services.ConfigureOpenTelemetryTracing((sp, builder) => builder.AddProcessor<MyProcessor>()); // This will be called with the other stuff from above when the TracerProvider is being built up.
} I know it isn't exactly what you were looking for, but it would work? |
I created a PR to show what I am thinking for being able to separate the configuration and construction phases which I think would help with this PR. #1901 |
if (builder is IDeferredTracerBuilder deferredTracerBuilder) | ||
{ | ||
return deferredTracerBuilder.Configure((sp, builder) => | ||
{ | ||
AddZipkinExporter(builder, sp.GetOptions<ZipkinExporterOptions>(), configure); | ||
}); | ||
} | ||
|
||
return AddZipkinExporter(builder, new ZipkinExporterOptions(), configure); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't like how this approach is causing all exporters and instrumentations to have to type check the builder and then have different code paths. IMO, it shouldn't be optional for these exporters and instrumentations to support separating configuration and construction phases. It should be mandatory that they register things with a deferred approach so that we can keep configuration and construction phases cleanly separated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would personally prefer if we could force all of them to be registered with lambdas and not even make it an option to immediately new them up when configuring. Using a factory method approach for configuration is compatible with all targeted platforms without taking a dependency on Microsoft.Extensions.DependencyInjection
for the core library while still allowing a DI approach to be implemented in a platform that supports it like the hosting package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of everything on this PR, I'm the least happy with this bit. But I don't think it is possible to do it any other way 😭 It's not really a problem of separating configuration and construction, this is about the inversion of dependencies we've created for ourselves. To do it "properly" we would need this ctor: public ZipkinExporter(IOptions<ZipkinExporterOptions> options)
. But that of course requires a direct dependency.
{ | ||
private readonly List<InstrumentationFactory> instrumentationFactories = new List<InstrumentationFactory>(); | ||
private readonly List<Type> processorTypes = new List<Type>(); | ||
private readonly List<Action<IServiceProvider, TracerProviderBuilder>> configurationActions = new List<Action<IServiceProvider, TracerProviderBuilder>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like having generic configuration actions like this.
src/OpenTelemetry.Extensions.Hosting/Trace/TracerProviderBuilderExtensions.cs
Outdated
Show resolved
Hide resolved
Pushed some updates:
|
@alanwest @reyang @cijothomas @ejsmith I incorporated all the feedback I got from people (on PR and on Slack) please take a look and let me know if this is something worth completing (unit tests, documentation, etc.). I tried to make it as clean as I could with the smallest public API footprint possible while still providing the use cases and without forcing any users into IServiceProvider. |
This is looking pretty good @CodeBlanch. I am going to try and play with the code soon, but I think it's a good approach. |
The approach looks good to me 👍. |
Sounds good to me. There are a couple of broken tests I'll need to fix up. I'll do that but I'm going to leave this as a draft until @ejsmith has a chance to test out his options approach. We can go with whichever people like more. |
if (tracerProviderBuilder is TracerProviderBuilderSdk tracerProviderBuilderSdk) | ||
if (tracerProviderBuilder is IDeferredTracerProviderBuilder) | ||
{ | ||
throw new NotSupportedException("DeferredTracerBuilder requires a ServiceProvider to build."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can't be here because the Build
method from the TracerProviderBuilderHosting
class calls this method and then it blows up the build process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops! Fixed it up by making the build on TracerProviderBuilderBase protected. I feel like we should keep this throw here to prevent anyone from accidentally using the hosting builder with the non-IServerProvider build extension?
This approach is good with me @CodeBlanch. I experimented with a pretty similar approach after you said you didn't really like the options being public. I also realized that we need need to use the builder immediately and we can have a hosting builder that lives by itself which is what you are doing. I think there are some flaws in the current public API that limit things, but I know that we can't change those now after it was marked stable with 1.0. So I think your approach is the best one. |
Codecov Report
@@ Coverage Diff @@
## main #1889 +/- ##
==========================================
- Coverage 84.39% 83.73% -0.67%
==========================================
Files 188 192 +4
Lines 6108 6159 +51
==========================================
+ Hits 5155 5157 +2
- Misses 953 1002 +49
|
This has been a hot topic lately so I took a stab at making it work. This isn't done, but enough of it is there to demonstrate the concept. Looking for feedback, please fire away!
Design Considerations
The main goal is for only OpenTelemetry.Extensions.Hosting to have a dependency on anything in Microsoft.Extensions.* suite.Turns out that dependency is already there so the main goal now is to keep .NET Framework users away from having to use IServiceProvider to build TracerProvider. You should only need that if you are using Hosting.Public API Changes
SDK
Hosting
Usage Examples
Binding exporter options to IConfiguration (#101, #343):
Registering a Processor using DI:
Extension method that also registers services (#894):
Implementation Examples
This is how JaegerExporter can use dependency injection to get
IOptions<JaegerExporterOptions>
from the IServiceProvider (if the application is using it).