elastic / apm-agent-dotnet

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

HostService Timer no tracing #1748

Closed KINGGUOKUN closed 2 years ago

KINGGUOKUN commented 2 years ago

Bellow is my code in the Timer callback of my custom HostService,but Elastic.Apm.Agent.Tracer.CaptureTransaction does't trace any thing.

        if (!Elastic.Apm.Agent.IsConfigured)
        {
            return;
        }

        using (var scope = _serviceProvider.CreateScope())
        {

            var myService = scope.ServiceProvider.GetService<IMyService>();
            await Elastic.Apm.Agent.Tracer.CaptureTransaction("ZtePhonePostSaleSurveyCall", "backgroundjob", async () =>
            {
                // DoSomeThing  with db query, http request ,etc
                await myService .DoSomeThing();
            });
KINGGUOKUN commented 2 years ago

My code is like this:

public class XXHostService : IHostedService, IDisposable
{
    #region Private Fields 

    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<XXHostService > _logger;
    private Timer _timer;

    #endregion

    #region Constructors

    public XXHostService (IServiceProvider serviceProvider,
        ILogger<XXHostService > logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }

    #endregion

    public void Dispose()
    {
        _timer?.Dispose();
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("start...");

        _timer= new Timer(async (state) =>
        {
            await DoXXX(state);
        },
        null, TimeSpan.Zero, TimeSpan.FromMinutes(1);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("stop...");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    private async Task DoXXX(object state)
    {
        if (!Elastic.Apm.Agent.IsConfigured)
        {
            return;
        }

        using (var scope = _serviceProvider.CreateScope())
        {
            var myService = scope.ServiceProvider.GetService<IMyService>();
            await Elastic.Apm.Agent.Tracer.CaptureTransaction("XXXX", "backgroundjob", async () =>
            {
                // DoSomeThing  with  db query, http request, etc
                await myService .DoSomeThing();
            });
        }
    }
}
gregkalapos commented 2 years ago

Hi @KINGGUOKUN

tldr: I think it's because of the if (!Elastic.Apm.Agent.IsConfigured) return check - that returns false and you always return from the DoXXX method.

How do you enable the agent?

With a hosted service, the best is to not use the static API. Instead you can enable the agent in your program.cs this way:

private static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((context, services) => { services.AddHostedService<HostedService>(); })
        .UseAllElasticApm();

this will register the agent with DI and it'll also configure the agent, so with the UseAllElasticApm() line you are ready to go.

Then you could dependency inject an ITracer into your XXHostService:

    public XXHostService (IServiceProvider serviceProvider,
        ILogger<XXHostService > logger, ITracer tracer)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
        _tracer = tracer; // use this tracer instead of the static Elastic.Apm.Agent.Tracer.
    }

We have a similar sample here.

Does this help?

KINGGUOKUN commented 2 years ago

I enable it in Starup.Configure, like this: public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto });

        app.UseElasticApm(Configuration,
            new HttpDiagnosticsSubscriber(),
            new EfCoreDiagnosticsSubscriber(),
            new SqlClientDiagnosticSubscriber());

        if (env.IsDevelopment())
        {
            System.Net.Http.HttpClient.DefaultProxy = new System.Net.WebProxy("http://proxy.zte.com.cn:80");
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

}

And before I post the issue, I do use it as you suggest, the result is: HostService traced success fully, but other traces lose, eg, my normal http request!

KINGGUOKUN commented 2 years ago

The check if (!Elastic.Apm.Agent.IsConfigured) return; is to avoid double initialize the agent. Without it, Elastic.Apm.Agent will be initialized with something like http://localhost:8200 and never load the apmserver configuration.

gregkalapos commented 2 years ago

Ok, I see.

In this setup, I'd still avoid the static API and enable the agent with the UseElasticApm method.

HostService traced success fully, but other traces lose, eg, my normal http request!

We should look into this. Could you maybe turn on trace logs and show agent logs when this happened? Also.. where did those HTTP request happened? An HTTP request is captured as a span and it needs to be in a transaction - so make sure there is an active transaction. Btw. we also have a UseAllElasticApm method which turns on all the listeners for you in this package - of course you can also enable listeners one by one, so this is just an info that may help you.

The check if (!Elastic.Apm.Agent.IsConfigured) return; is to avoid double initialize the agent.

Yes, that's the typical use case for it. So in this sense you use it correctly, but somewhere the agent must be initialized

Without it, Elastic.Apm.Agent will be initialized with something like http://localhost:8200/ and never load the apmserver configuration.

Correct, and that will read configs from environment variables in this case. You can still configure the agent, but in this case you can't use e.g. appsettings.json (because you don't pass the IConfiguration instance to the agent), instead you need to set configs via environment variables. Environment variable names can be found in the docs. An example is here. (spot the ELASTIC_APM_SERVER_URL).

Nevertheless I'd still look into the UseElasticApm way of adding the agent and figure out why that didn't work.

KINGGUOKUN commented 2 years ago

OK, I will turn on trace logs and look details into it later.

gregkalapos commented 2 years ago

Since this issue is fairly old and I think most questions are answered I'm closing this now. Feel free to comment if anything is left.