skip to Main Content

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

  1. _source1 doesn’t have any listener and activity1 is null
  2. source2 has a listener, and activity2 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


  1. To instrument OpenTelemetry HttpClient in a web application, follow these steps:

    1. 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:

      dotnet add package OpenTelemetry.Instrumentation.Http
      

      Additionally, ensure you have other necessary OpenTelemetry packages installed, such as exporters and any other instrumentations you might need [2].

    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:

      using OpenTelemetry;
      using OpenTelemetry.Trace;
      
      public class Program
      {
          public static void Main(string[] args)
          {
              using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                  .AddHttpClientInstrumentation()
                  .AddConsoleExporter()
                  .Build();
          }
      }
      

      And for metrics:

      using OpenTelemetry;
      using OpenTelemetry.Metrics;
      
      public class Program
      {
          public static void Main(string[] args)
          {
              using var meterProvider = Sdk.CreateMeterProviderBuilder()
                  .AddHttpClientInstrumentation()
                  .AddConsoleExporter()
                  .Build();
          }
      }
      

      These examples also include the OpenTelemetry Console Exporter for demonstration purposes, which you might replace with your preferred exporter [2].

    3. 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:

      using var tracerProvider = Sdk.CreateTracerProviderBuilder()
          .AddHttpClientInstrumentation(options => options.FilterHttpRequestMessage = httpRequestMessage =>
          {
              return httpRequestMessage.Method.Equals(HttpMethod.Get);
          })
          .AddConsoleExporter()
          .Build();
      

      This option is useful for reducing the volume of telemetry data collected from HttpClient requests [2].

    4. 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 on MeterProviderBuilder

    Login or Signup to reply.
  2. 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 the OpenTelemetry.Extensions.Hosting package instead of manually handling the SDK creation.

    Secondly, instead of using ResourceBuilder.CreateEmpty() it is best to do ResourceBuilder.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 with AddService() on the ResourceBuilder.

    Thirdly, it appears that you need to have the ActivitySource in place before you call AddSource, 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:

    
        builder.Services.AddOpenTelemetry()
            .ConfigureResource(c => c.AddService("YourServiceNameHere"))
            .WithTracing(c => c
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddSource("test")
                .AddConsoleExporter();
    
        ...
        app.MapRazorPages();           
    
        app.Run();
    

    For the ActivitySource, Microsoft defines a few best practices in their main article here:

    Create the ActivitySource once, store it in a static variable and use that instance as long as needed. Each library or library subcomponent can (and often should) create its own source.

    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 the ActivitySource initialization to the beginning of the app itself and then pass it along (you can even use DI if you want).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search