Skip to content

Commit 1651da8

Browse files
authored
Add IServiceCollection extension methods to register ApmAgent (#2326)
* Add IServiceCollection extension methods to register ApmAgent * Reverting updates to sample apps * Update doc * Remove unused NET5_0 compiler directives
1 parent 049c290 commit 1651da8

File tree

51 files changed

+1084
-395
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1084
-395
lines changed

ElasticApmAgent.sln

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "applications", "application
229229
EndProject
230230
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.Apm.AzureFunctionApp.Core", "test\azure\applications\Elastic.Apm.AzureFunctionApp.Core\Elastic.Apm.AzureFunctionApp.Core.csproj", "{50F14EA5-DF72-425B-81A6-C7D532D2DD07}"
231231
EndProject
232-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Apm.Profiling", "benchmarks\Elastic.Apm.Profiling\Elastic.Apm.Profiling.csproj", "{CB6B3BA6-9D16-4CDC-95C2-7680CF50747D}"
232+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.Apm.Profiling", "benchmarks\Elastic.Apm.Profiling\Elastic.Apm.Profiling.csproj", "{CB6B3BA6-9D16-4CDC-95C2-7680CF50747D}"
233233
EndProject
234-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiExample", "sample\WebApiExample\WebApiExample.csproj", "{00A025F1-0A31-4676-AA06-1773FC9744ED}"
234+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiExample", "sample\WebApiExample\WebApiExample.csproj", "{00A025F1-0A31-4676-AA06-1773FC9744ED}"
235+
EndProject
236+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerServiceSample", "sample\WorkerServiceSample\WorkerServiceSample.csproj", "{C73BF86B-5359-4811-B698-8BE9A66C5EFA}"
237+
EndProject
238+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostingTestApp", "test\integrations\applications\HostingTestApp\HostingTestApp.csproj", "{61F5A733-DC79-44FC-B9CB-4EF34FC9D96B}"
239+
EndProject
240+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Apm.Extensions.Tests.Shared", "test\integrations\Elastic.Apm.Extensions.Tests.Shared\Elastic.Apm.Extensions.Tests.Shared.csproj", "{7482D2D9-BBF7-4C8B-B26C-BEA9BCF345B0}"
235241
EndProject
236242
Global
237243
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -581,6 +587,18 @@ Global
581587
{00A025F1-0A31-4676-AA06-1773FC9744ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
582588
{00A025F1-0A31-4676-AA06-1773FC9744ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
583589
{00A025F1-0A31-4676-AA06-1773FC9744ED}.Release|Any CPU.Build.0 = Release|Any CPU
590+
{C73BF86B-5359-4811-B698-8BE9A66C5EFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
591+
{C73BF86B-5359-4811-B698-8BE9A66C5EFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
592+
{C73BF86B-5359-4811-B698-8BE9A66C5EFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
593+
{C73BF86B-5359-4811-B698-8BE9A66C5EFA}.Release|Any CPU.Build.0 = Release|Any CPU
594+
{61F5A733-DC79-44FC-B9CB-4EF34FC9D96B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
595+
{61F5A733-DC79-44FC-B9CB-4EF34FC9D96B}.Debug|Any CPU.Build.0 = Debug|Any CPU
596+
{61F5A733-DC79-44FC-B9CB-4EF34FC9D96B}.Release|Any CPU.ActiveCfg = Release|Any CPU
597+
{61F5A733-DC79-44FC-B9CB-4EF34FC9D96B}.Release|Any CPU.Build.0 = Release|Any CPU
598+
{7482D2D9-BBF7-4C8B-B26C-BEA9BCF345B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
599+
{7482D2D9-BBF7-4C8B-B26C-BEA9BCF345B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
600+
{7482D2D9-BBF7-4C8B-B26C-BEA9BCF345B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
601+
{7482D2D9-BBF7-4C8B-B26C-BEA9BCF345B0}.Release|Any CPU.Build.0 = Release|Any CPU
584602
EndGlobalSection
585603
GlobalSection(SolutionProperties) = preSolution
586604
HideSolutionNode = FALSE
@@ -684,6 +702,9 @@ Global
684702
{50F14EA5-DF72-425B-81A6-C7D532D2DD07} = {09CE5AC1-01F6-48C8-B266-2F891C408051}
685703
{CB6B3BA6-9D16-4CDC-95C2-7680CF50747D} = {2825A761-5372-4620-99AB-253AD953E8CD}
686704
{00A025F1-0A31-4676-AA06-1773FC9744ED} = {3C791D9C-6F19-4F46-B367-2EC0F818762D}
705+
{C73BF86B-5359-4811-B698-8BE9A66C5EFA} = {3C791D9C-6F19-4F46-B367-2EC0F818762D}
706+
{61F5A733-DC79-44FC-B9CB-4EF34FC9D96B} = {59F3FB6E-4B48-4E87-AF3B-78DFED427EF1}
707+
{7482D2D9-BBF7-4C8B-B26C-BEA9BCF345B0} = {67A4BFE3-F96E-4BDE-9383-DD0ACE6D3BA1}
687708
EndGlobalSection
688709
GlobalSection(ExtensibilityGlobals) = postSolution
689710
SolutionGuid = {69E02FD9-C9DE-412C-AB6B-5B8BECC6BFA5}

benchmarks/Elastic.Apm.Benchmarks/AspNetCorePerf/AspNetCoreSampleRunner.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System;
66
using System.Reflection;
77
using System.Threading;
8-
using Elastic.Apm.AspNetCore;
98
using Elastic.Apm.DiagnosticSource;
109
using Elastic.Apm.EntityFrameworkCore;
1110
using Microsoft.AspNetCore.Hosting;
@@ -25,6 +24,7 @@ public void StartSampleAppWithAgent(bool withAgent, string url) =>
2524
Startup.ConfigureServicesExceptMvc(services);
2625

2726
services
27+
.AddElasticApm(new HttpDiagnosticsSubscriber(), new EfCoreDiagnosticsSubscriber())
2828
.AddMvc()
2929
.AddApplicationPart(Assembly.Load(new AssemblyName(nameof(SampleAspNetCoreApp))));
3030
}
@@ -34,10 +34,6 @@ public void StartSampleAppWithAgent(bool withAgent, string url) =>
3434
if (withAgent)
3535
{
3636
Environment.SetEnvironmentVariable("ELASTIC_APM_FLUSH_INTERVAL", "0");
37-
app.UseElasticApm(subscribers: new IDiagnosticsSubscriber[]
38-
{
39-
new HttpDiagnosticsSubscriber(), new EfCoreDiagnosticsSubscriber()
40-
});
4137
}
4238

4339
Startup.ConfigureAllExceptAgent(app);

docs/setup-asp-net-core.asciidoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
[float]
88
==== Quick start
99

10-
[NOTE]
10+
[IMPORTANT]
1111
--
12-
We suggest using the approach described in the <<setup-dotnet-net-core, .NET Core setup instructions>>,
13-
to register the agent on `IHostBuilder`, as opposed to using `IApplicationBuilder` as described below.
12+
We strongly suggest using the approach described in the <<setup-dotnet-net-core, .NET applications using Microsoft.Extensions.Hosting instructions>>,
13+
to register the agent on the `IServiceCollection`, as opposed to using `IApplicationBuilder` as described below.
1414

1515
We keep the `IApplicationBuilder` introduced here only for backwards compatibility.
1616
--

docs/setup-dotnet-net-core.asciidoc

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,156 @@
22
:dot: .
33

44
[[setup-dotnet-net-core]]
5-
=== .NET Core
5+
=== .NET Core and .NET 5+
66

77
[float]
88
==== Quick start
99

10-
On .NET Core, the agent can be registered on the `IHostBuilder`. This applies to both ASP.NET Core and to other .NET Core applications that depend on `IHostBuilder`, like https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services[background tasks]. In this case, you need to reference the {nuget}/Elastic.Apm.NetCoreAll[`Elastic.Apm.NetCoreAll`] package.
10+
In .NET (Core) applications using `Microsoft.Extensions.Hosting`, the agent can be registered on the `IServiceCollection`. This applies to ASP.NET Core and to other .NET applications that depend on the hosting APIs, such as those created using the https://learn.microsoft.com/en-us/dotnet/core/extensions/workers[worker services] template.
1111

12+
The simplest way to enable the agent and its instrumentations requires a reference to the {nuget}/Elastic.Apm.NetCoreAll[`Elastic.Apm.NetCoreAll`] package.
1213

14+
[source,xml]
15+
----
16+
<PackageReference Include="Elastic.Apm.NetCoreAll" Version="<LATEST>" /> <1>
17+
----
18+
<1> Replace the `<LATEST>` placeholder with the latest version of the agent available on NuGet.
19+
20+
[NOTE]
21+
--
22+
The following code sample assumes the instrumentation of a .NET 8 worker service, using https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/top-level-statements[top-level statements].
23+
--
24+
25+
*Program.cs*
26+
[source,csharp]
27+
----
28+
using WorkerServiceSample;
29+
30+
var builder = Host.CreateApplicationBuilder(args);
31+
32+
builder.Services.AddHttpClient();
33+
builder.Services.AddAllElasticApm(); <1>
34+
builder.Services.AddHostedService<Worker>();
35+
36+
var host = builder.Build();
37+
host.Run();
38+
----
39+
<1> Register Elastic APM before registering other IHostedServices to ensure its dependencies are initialized first.
40+
41+
When registering services with `AddAllElasticApm()`, an APM agent with all instrumentations is enabled. On ASP.NET Core, it'll automatically capture incoming requests, database calls through supported technologies, outgoing HTTP requests, etc.
42+
43+
For other application templates, such as worker services, you must manually instrument your `BackgroundService` to identify one or more units of work that should be captured.
44+
45+
[float]
46+
==== Manual instrumentation using `ITracer`
47+
48+
`AddAllElasticApm` adds an `ITracer` to the Dependency Injection system, which can be used in your code to manually instrument your application, using the <<public-api>>
49+
50+
*Worker.cs*
1351
[source,csharp]
1452
----
15-
using Elastic.Apm.NetCoreAll;
53+
using Elastic.Apm.Api;
1654
17-
namespace MyApplication
55+
namespace WorkerServiceSample
1856
{
19-
public class Program
57+
public class Worker : BackgroundService
2058
{
21-
public static IHostBuilder CreateHostBuilder(string[] args) =>
22-
Host.CreateDefaultBuilder(args)
23-
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
24-
.UseAllElasticApm();
59+
private readonly IHttpClientFactory _httpClientFactory;
60+
private readonly ITracer _tracer;
2561
26-
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
62+
public Worker(IHttpClientFactory httpClientFactory, ITracer tracer)
63+
{
64+
_httpClientFactory = httpClientFactory;
65+
_tracer = tracer;
66+
}
67+
68+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
69+
{
70+
while (!stoppingToken.IsCancellationRequested)
71+
{
72+
await _tracer.CaptureTransaction("UnitOfWork", ApiConstants.TypeApp, async () => <1>
73+
{
74+
var client = _httpClientFactory.CreateClient();
75+
await client.GetAsync("https://www.elastic.co", stoppingToken);
76+
await Task.Delay(5000, stoppingToken);
77+
});
78+
}
79+
}
2780
}
2881
}
2982
----
83+
<1> The `CaptureTransaction` method creates a transaction named 'UnitOfWork' and type 'App'. The lambda passed to it represents the unit of work that should be captured within the context of the transaction.
3084

31-
With the `UseAllElasticApm()`, the agent with all its components is turned on. On ASP.NET Core, it'll automatically capture incoming requests, database calls through supported technologies, outgoing HTTP requests, and so on.
85+
When this application runs, a new transaction will be captured and sent for each while loop iteration. A span named 'HTTP GET' within the transaction will be created for the HTTP request to `https://www.elastic.co`. The HTTP span is captured because the NetCoreAll package enables this instrumentation automatically.
3286

3387
[float]
34-
==== Manual instrumentation
88+
==== Manual instrumentation using OpenTelemetry
89+
90+
As an alternative to using the Elastic APM API by injecting an `ITracer`, you can use the OpenTelemetry API to manually instrument your application. The Elastic APM agent automatically bridges instrumentations created using the OpenTelemetry API, so you can use it to create spans and transactions. In .NET, the https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs[`Activity` API] can be used to instrument applications.
3591

36-
The `UseAllElasticApm` will add an `ITracer` to the Dependency Injection system, which can be used in your code to manually instrument your application, using the <<public-api>>
92+
In the case of this sample worker service, we can update the code to prefer the OpenTelemetry API.
3793

94+
*Worker.cs*
3895
[source,csharp]
3996
----
40-
using Elastic.Apm.Api;
97+
using System.Diagnostics;
4198
42-
namespace WebApplication.Controllers
99+
namespace WorkerServiceSample
43100
{
44-
public class HomeController : Controller
45-
{
46-
private readonly ITracer _tracer;
101+
public class Worker : BackgroundService
102+
{
103+
private readonly IHttpClientFactory _httpClientFactory;
104+
private static readonly ActivitySource ActivitySource = new("MyActivitySource"); <1>
47105
48-
//ITracer injected through Dependency Injection
49-
public HomeController(ITracer tracer) => _tracer = tracer;
106+
public Worker(IHttpClientFactory httpClientFactory)
107+
{
108+
_httpClientFactory = httpClientFactory;
109+
}
50110
51-
public IActionResult Index()
52-
{
53-
//use ITracer
54-
var span = _tracer.CurrentTransaction?.StartSpan("MySampleSpan", "Sample");
55-
try
56-
{
57-
//your code here
58-
}
59-
catch (Exception e)
60-
{
61-
span?.CaptureException(e);
62-
throw;
63-
}
64-
finally
65-
{
66-
span?.End();
67-
}
68-
return View();
69-
}
111+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
112+
{
113+
while (!stoppingToken.IsCancellationRequested)
114+
{
115+
using var activity = ActivitySource.StartActivity("UnitOfWork"); <2>
116+
var client = _httpClientFactory.CreateClient();
117+
await client.GetAsync("https://www.elastic.co", stoppingToken);
118+
await Task.Delay(5000, stoppingToken);
119+
}
70120
}
121+
}
71122
}
72123
----
73-
74-
Similarly to this ASP.NET Core controller, you can use the same approach with `IHostedService` implementations.
124+
<1> Defines an `ActivitySource` for this application from which activities can be created.
125+
<2> Starts an `Activity` with the name `UnitOfWork`. As this is `IDisposable`, it will automatically end when each iteration of the `while` block ends.
75126

76127
[float]
77128
==== Instrumentation modules
78129

79-
The `Elastic.Apm.NetCoreAll` package references every agent component that can be automatically configured. This is usually not a problem, but if you want to keep dependencies minimal, you can instead reference the `Elastic.Apm.Extensions.Hosting` package and use the `UseElasticApm` method, instead of `UseAllElasticApm`. With this setup you can control what the agent will listen for.
130+
The `Elastic.Apm.NetCoreAll` package references every agent component that can be automatically configured. This is usually not a problem, but if you want to keep dependencies minimal, you can instead reference the `Elastic.Apm.Extensions.Hosting` package and register services with `AddElasticApm` method, instead of `AddAllElasticApm`. With this setup you can explicitly control what the agent will listen for.
80131

81-
The following example only turns on outgoing HTTP monitoring (so, for instance, database or Elasticsearch calls won't be automatically captured):
132+
The following example only turns on outgoing HTTP monitoring (so, for instance, database and Elasticsearch calls won't be automatically captured):
82133

83134
[source,csharp]
84135
----
85-
public static IHostBuilder CreateHostBuilder(string[] args) =>
86-
Host.CreateDefaultBuilder(args)
87-
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
88-
.UseElasticApm(new HttpDiagnosticsSubscriber());
89-
----
136+
using Elastic.Apm.DiagnosticSource;
137+
using WorkerServiceSample;
138+
139+
var builder = Host.CreateApplicationBuilder(args);
90140
141+
builder.Services.AddHttpClient();
142+
builder.Services.AddElasticApm(new HttpDiagnosticsSubscriber()); <1>
143+
builder.Services.AddHostedService<Worker>();
144+
145+
var host = builder.Build();
146+
host.Run();
147+
----
148+
<1> The `HttpDiagnosticsSubscriber` is a diagnostic listener that captures spans for outgoing HTTP requests.
91149

92150
[float]
93151
[[zero-code-change-setup]]
94152
==== Zero code change setup on .NET Core and .NET 5+ (added[1.7])
95153

96-
If you can't or don't want to reference NuGet packages in your application, you can use the startup hook feature to inject the agent during startup, if your application runs on .NET Core 3.0 or .NET 5 or newer.
154+
If you can't or don't want to reference NuGet packages in your application, you can use the startup hook feature to inject the agent during startup, if your application runs on .NET Core 3.0, .NET Core 3.1 or .NET 5 or newer.
97155

98156
To configure startup hooks
99157

@@ -109,9 +167,9 @@ set DOTNET_STARTUP_HOOKS=<path-to-agent>\ElasticApmAgentStartupHook.dll <1>
109167

110168
. Start your .NET Core application in a context where the `DOTNET_STARTUP_HOOKS` environment variable is visible.
111169

112-
With this setup the agent will be injected into the application during startup and it will start every auto instrumentation feature. On ASP.NET Core (including gRPC), incoming requests will be automatically captured.
170+
With this setup, the agent will be injected into the application during startup, enabling every instrumentation feature. Incoming requests will be automatically captured on ASP.NET Core (including gRPC).
113171

114172
[NOTE]
115173
--
116-
Agent configuration can be controlled through environment variables with the startup hook feature.
174+
Agent configuration can be controlled through environment variables when using the startup hook feature.
117175
--

docs/setup.asciidoc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ On **.NET Core 3.0+ or .NET 5+**, the agent supports auto instrumentation withou
2020
any recompilation of your projects. See <<zero-code-change-setup, Zero code change setup on .NET Core>>
2121
for more details.
2222

23-
2423
[float]
2524
== Get started
2625

sample/WebApiExample/Controllers/WeatherForecastController.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,10 @@ namespace WebApiExample.Controllers;
66
[Route("[controller]")]
77
public class WeatherForecastController : ControllerBase
88
{
9-
private static readonly string[] Summaries = new[]
10-
{
9+
private static readonly string[] Summaries =
10+
[
1111
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
12-
};
13-
14-
private readonly ILogger<WeatherForecastController> _logger;
15-
16-
public WeatherForecastController(ILogger<WeatherForecastController> logger) => _logger = logger;
12+
];
1713

1814
[HttpGet(Name = "GetWeatherForecast")]
1915
public IEnumerable<WeatherForecast> Get() =>

sample/WebApiExample/Program.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
1-
using Elastic.Apm.AspNetCore;
2-
31
var builder = WebApplication.CreateBuilder(args);
42

5-
// Add services to the container.
6-
3+
builder.Services.AddAllElasticApm();
74
builder.Services.AddControllers();
8-
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
95
builder.Services.AddEndpointsApiExplorer();
106
builder.Services.AddSwaggerGen();
117

128
var app = builder.Build();
13-
app.UseElasticApm(app.Configuration);
149

15-
// Configure the HTTP request pipeline.
1610
if (app.Environment.IsDevelopment())
1711
{
1812
app.UseSwagger();
1913
app.UseSwaggerUI();
2014
}
2115

2216
app.UseHttpsRedirection();
23-
2417
app.UseAuthorization();
25-
2618
app.MapControllers();
2719

2820
app.Run();

sample/WebApiExample/WebApiExample.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.2"/>
11-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
10+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.2" />
11+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
1212
</ItemGroup>
1313

1414
<ItemGroup>
15-
<ProjectReference Include="..\..\src\integrations\Elastic.Apm.AspNetCore\Elastic.Apm.AspNetCore.csproj" />
15+
<ProjectReference Include="..\..\src\integrations\Elastic.Apm.NetCoreAll\Elastic.Apm.NetCoreAll.csproj" />
1616
</ItemGroup>
1717

1818
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.Hosting.Lifetime": "Information"
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)