elastic / apm-agent-dotnet

https://www.elastic.co/guide/en/apm/agent/dotnet/current/index.html
Apache License 2.0
587 stars 206 forks source link

ITracer is not added to DI system when using UseAllElasticApm #1375

Closed slacksach closed 3 years ago

slacksach commented 3 years ago

APM Agent version

1.11.0

Environment

Operating system and version: Windows 10 (Latest) Ubuntu 18.04 (Latest LTS)

NET 5.0.8

Describe the bug

Documentation says that using UseAllElasticAPM will add an ITracer into the DI System, however, when attempting to resolve this using Constructor Injection an "Unable to resolve service" error is thrown.

To Reproduce

Expected behavior

ITracer should be resolved

Actual behavior

Exception is thrown "Unable to resolve service for type 'Elastic.Apm.Api.ITracer' while attempting to activate "

slacksach commented 3 years ago

Further clarification, this appears only when using UseAllElasticAPM from the IApplicationBuilder, if you use the UseAllElasticAPM from the IHostBuilder it works but no longer provides the means to pass Configuration - https://github.com/elastic/apm-agent-dotnet/blob/master/src/Elastic.Apm.NetCoreAll/ApmMiddlewareExtension.cs

russcam commented 3 years ago

@slacksach, this is correct, only the UseAllElasticApm() extension method on IHostBuilder can automatically register agent components like ITracer with DI. The documentation does indicate that this is only for IHostBuilder and not IApplicationBuilder.

The point at which UseAllElasticApm() extension method would be called on IApplicationBuilder happens after services are already configured, and an IServiceProvider instantiated, so it's too late to add services in the IApplicationBuilder UseAllElasticApm() extension method. I don't believe there's any more that we can automatically do here, unfortunately. It is possible however for a user to register agent components in their ConfigureServices() method; here's an example

public class Startup
{
    public Startup(IConfiguration configuration) => Configuration = configuration;

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        // register a delegate that will resolve ITracer
        services.AddSingleton(_ => Agent.Tracer); 
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // configure the application to use Elastic APM
        app.UseAllElasticApm(Configuration);

        if (env.IsDevelopment())
            app.UseDeveloperExceptionPage();
        else
            app.UseExceptionHandler("/Home/Error");

        app.UseStaticFiles();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

By registering a delegate to create the singleton ITracer instance from Agent.Tracer, the first time the dependency is needed, allows the agent to be configured in UseAllElasticApm() on IApplicationBuilder.