I have a Razor web app (.NET 8) client I want to get metrics for execution and calls to other APIs
I have the following nuget pkgs installed:
- OpenTelemetry
- OpenTelemetry.Exporter.Console
- OpenTelemetry.Instrumentation.AspNetCore
- OpenTelemetry.Instrumentation.Http
In Program.Main I configure it
TracerProvider tracer = null;
try
{
...
app.MapRazorPages();
// using ResourceBuilder.CreateDefault has no effect, nor does moving init to top
tracer = Sdk.CreateTracerProviderBuilder()
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.SetResourceBuilder(ResourceBuilder.CreateEmpty())
.AddSource("test")
.AddConsoleExporter()
.Build();
app.Run();
}
finally
{
tracer?.Dispose();
}
I have a service where I need to call an API I’d like get trace for. Using static ActivitySource
for now, but the goal is to create one (or few) for my application and re-use (not one per class)
class MyService : IDisposable
{
private static readonly ActivitySource _source1= new ActivitySource("test");
async Task<HttpResponseMessage> MyMethod()
{
using var activity1 = _source1.StartActivity("action");
var source2 = new ActivitySource("test");
using var activity2 = source2.StartActivity("action");
...
}
void Dispose()
{
_source1?.Dispose();
}
}
I am seeing
_source1
doesn’t have any listener andactivity1
is nullsource2
has a listener, andactivity2
is a valid object
I am trying to construct only one ActivitySource per best practice, but it’s not working. What am I doing wrong or missing in my setup?
UPDATE:
The problem turned out doing builder.Services.AddScoped<MyService>
causes the service to be constructed when the page is first rendered, and dispose is invoked after render. When I click on button to trigger action, it gets constructed again, but ActivitySource doesn’t get reconstructed.
Will need to better understand AddScoped
, but switching to AddSingleton
works for my purpose now
2
Answers
To instrument OpenTelemetry HttpClient in a web application, follow these steps:
Install the Necessary Packages: First, you need to add the OpenTelemetry.Instrumentation.Http package to your project. This package provides the instrumentation for HttpClient and HttpWebRequest. You can add it using the .NET CLI with the command:
Additionally, ensure you have other necessary OpenTelemetry packages installed, such as exporters and any other instrumentations you might need [2].
Enable HTTP Instrumentation at Application Startup: In your application’s startup configuration, enable the HttpClient instrumentation. This can be done by adding the instrumentation to the TracerProviderBuilder or MeterProviderBuilder, depending on whether you’re focusing on traces or metrics. For traces, use the
.AddHttpClientInstrumentation()
method on the TracerProviderBuilder. For metrics, use the.AddHttpClientInstrumentation()
method on the MeterProviderBuilder. Here’s an example for traces:And for metrics:
These examples also include the OpenTelemetry Console Exporter for demonstration purposes, which you might replace with your preferred exporter [2].
Filtering HttpClient Requests (Optional): If you need to filter which HttpClient requests are instrumented, you can use the
FilterHttpRequestMessage
option. This allows you to specify a condition for which requests should be instrumented. For example, to only instrument GET requests:This option is useful for reducing the volume of telemetry data collected from HttpClient requests [2].
Advanced Configuration: For .NET 8.0 and newer, the HttpClient library has built-in support for metrics following OpenTelemetry semantic conventions. You can enable all built-in metrics with a single line of code using
.AddHttpClientInstrumentation()
. For more granular control over metrics, you can use the.AddMeter()
extension onMeterProviderBuilder
Few comments around your solution.
Based on your example, it looks like we are dealing with an AspNetCore project (due to the
app.MapRazorPages()
stuff). Given that, I’d strongly recommend leveraging theOpenTelemetry.Extensions.Hosting
package instead of manually handling the SDK creation.Secondly, instead of using
ResourceBuilder.CreateEmpty()
it is best to doResourceBuilder.CreateDefault()
, but again, that is also taken care of by Hosting extensions and you don’t need to add it. I would however recommend adding at least some service information withAddService()
on theResourceBuilder
.Thirdly, it appears that you need to have the
ActivitySource
in place before you callAddSource
, otherwise OTEL will not be able to find your activity source. This explains why you switching to a singleton works (I assume you were passing in a pre-created instance, which is what triggers the static field to be newed up and thus become available for OTEL to pick it up).Try something like this:
For the
ActivitySource
, Microsoft defines a few best practices in their main article here:You are already using a
static
field to store your source, but it is inside a class that is only instantiated later in the flow. Instead, consider moving theActivitySource
initialization to the beginning of the app itself and then pass it along (you can even use DI if you want).