13 February 2019 Releases

Elastic APM .NET Agent Preview Released

By Greg Kalapos

We are proud to announce the preview release of the Elastic APM .NET agent! To make sure everyone is on the same page I’d like to start by defining what we exactly mean by “Preview Release”: this release is the first properly packaged version of the .NET APM agent. The main goal is to collect feedback and to share our progress with the community. If you are interested in this work, please try the agent in your test environment and let us know how it works for you and what features you miss! The best way to give feedback and ask questions is in our discussion forum or if you find an issue or you would like to submit a pull request, jump to our GitHub repository. The .NET agent is Apache licensed and we are more than happy to receive contributions from the community!

Elastic APM is an Application Performance Monitoring solution from Elastic and alongside the .NET agent, there are official agents available for Java, Node.js, Python, Ruby, JavaScript/RUM, and Go. Elastic APM helps you to gain insight into the performance of your application, track errors, and gauge the end-user experience in the browser.

I will dive into this new APM .NET agent below, but if you'd prefer to watch a preview rather than read about it, check out this video:

If you would like to know more details, please take a look at the Elastic APM .NET agent documentation.

Supported frameworks

We started the the design of the agent by asking our potential user base about what frameworks and .NET flavors they use for their .NET applications.

Based on that feedback the preview release has auto instrumentation features for the following libraries:

  • ASP.NET Core 2.x
  • Entity Framework Core 2.x
  • Outgoing web requests with the HttpClient class on .NET Core

Additionally, the APM .NET agent offers a Public Agent API that enables you to manually instrument your application for other frameworks, or for custom tagging.

This API is shipped as a .NET Standard 2.0 library, which means you can use it on every .NET flavour that supports .NET Standard 2.0. In the case of .NET Full Framework this is version 4.6.1 or newer, and in case of .NET Core this is version 2.0 or newer. The is no restriction in terms of operating systems, the agent packages should work on any operating system that is supported by the .NET Framework you use.

Downloading the agent

The agent ships as a set of NuGet packages via nuget.org.

The following packages are available:

  • Elastic.Apm.All: This is a meta package that references every other Elastic APM .NET agent package. If you plan to monitor a typical ASP.NET Core application that depends on the Microsoft.AspNetCore.All package and uses Entity Framework Core then you should reference this package.

    In order to avoid adding unnecessary dependencies in applications that aren’t depending on the Microsoft.AspNetCore.All package we also shipped some other packages - those are all referenced by the Elastic.Apm.All package.
  • Elastic.Apm: This is the core of the agent, which we didn’t name “Core”, because someone already took that name :) This package also contains the Public Agent API and it is a .NET Standard 2.0 package. We also ship every tracing component that traces classes that are part of .NET Standard 2.0 in this package, which includes the monitoring part for HttpClient. Every other Elastic APM package references this package.
  • Elastic.Apm.AspNetCore: This package contains ASP.NET Core monitoring related code. The main difference between this package and the Elastic.Apm.All package is that this package does not reference the Elastic.Apm.EntityFrameworkCore package, so if you have an ASP.NET Core application that does not use EF Core and you want to avoid adding additional unused references, you should use this package.
  • Elastic.Apm.EntityFrameworkCore: This package contains EF Core monitoring related code.

ASP.NET Core + Elastic APM

The focus of this release is clearly ASP.NET Core monitoring. The first step to enable the agent is to add the Elastic.Apm.All package to your application:

Elastic APM Demo in ASP.NET

The next step is to call the UseElasticApm method in the Configure method within the Startup.cs file:

public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    app.UseElasticApm(Configuration);
    //…rest of the method
  }
  //…rest of the class
}

And that’s it! With this the agent will automatically capture incoming requests, outgoing database and HTTP calls and it will also monitor unhandled exceptions. You can also configure the agent with environment variables or by using the IConfiguration interface. For configuration options please see the Elastic APM .NET agent documentation.

What about non-web .NET applications? Public Agent API

As already hinted, even if you don’t use ASP.NET Core, you can still monitor your application with the public Agent API. For this, all you need to do is to reference the Elastic.Apm package. Once you did that you will have access to the public Agent API. The entry point of the API is the Agent.Tracer property.

For more details about the public Agent API please refer to the documentation.

This small sample shows how to monitor an application that listens to incoming HTTP requests with the HttpListener class:

using System;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Elastic.Apm;
using Elastic.Apm.Api;
using Elastic.Apm.DiagnosticSource;
using Newtonsoft.Json.Linq;
namespace HttpListenerSample
{
  class Program
  {
    static async Task Main(string[] args)
    {
      //We enable outgoing HTTP request capturing:
      Agent.Subscribe(new HttpDiagnosticsSubscriber());
      // Create a listener.
      var listener = new HttpListener();
      // Add the prefix
      listener.Prefixes.Add("http://localhost:8080/");
      listener.Start();
      Console.WriteLine("Listening...");
      while (true)
      {
        // Note: The GetContext method blocks while waiting for a request.
        var context = listener.GetContext();
        // Capture the incoming request as a 
        //transaction with the Elastic APM .NET Agent
        await Agent.Tracer.CaptureTransaction("Request", 
                 ApiConstants.TypeRequest, async () =>
        {
          var request = context.Request;
          // Obtain a response object.
          var response = context.Response;
          // Construct a response.
          var responseString = $"<HTML><BODY> <p> Hello world! Random number:"
              + $" < {await GenerateRandomNumber()} </p>"
              + $" <p> Number of stargazers on <a" 
              + $" href=\"https://github.com/elastic/apm-agent-dotnet\">"
              + $" GitHub for the APM .NET Agent </a>:"
              + $" {await GetNumberOfStars()} </p>"
              + $" </BODY></HTML>";
          var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
          // Get a response stream and write the response to it.
          response.ContentLength64 = buffer.Length;
          var output = response.OutputStream;
          output.Write(buffer, 0, buffer.Length);
          // You must close the output stream.
          output.Close();
        });
      }
      listener.Stop();
    }
    static readonly Random Random = new Random();
    private static async Task<int> GenerateRandomNumber()
    {
        // Get the current transaction and then capture 
        // this method as a span on the current transaction
        return await Agent.Tracer.CurrentTransaction
          .CaptureSpan("RandomGenerator", "Random",
            async () =>
            {
              // Simulate some work
              await Task.Delay(5);
              return Random.Next();
           });
    }
    private static async Task<int> GetNumberOfStars()
    {
      var httpClient = new HttpClient();
      httpClient.DefaultRequestHeaders.Add("User-Agent", "APM-Sample-App");
      //This HTTP request is automatically captured by the Elastic APM .NET Agent:
      var responseMsg = await httpClient
          .GetAsync("https://api.github.com/repos/elastic/apm-agent-dotnet");
      var responseStr = await responseMsg.Content.ReadAsStringAsync();
      return int.TryParse(JObject.Parse(responseStr)["stargazers_count"]
                                  .ToString(), out var retVal) ? retVal : 0;
    }
  }
}

One thing I’d like to point out is that in case of an ASP.NET Core application by executing the UseElasticApm() method the agent automatically starts capturing database and outgoing HTTP calls. This is not the case in non-ASP.NET Core applications therefore you have to subscribe to those events, which can be done with the Agent.Subscribe method.

This is how you can subscribe to capture outgoing HTTP calls:

Agent.Subscribe(new HttpDiagnosticsSubscriber());

And this is how you can subscribe to capture Entity Framework Core calls (for this the Elastic.Apm.EntityFrameworkCore package must be referenced):

Agent.Subscribe(new EfCoreDiagnosticsSubscriber());

More complex configuration can be done through Agent.Setup() if so desired.

Summary and future

We would be thrilled to get feedback in our discussion forum or in our GitHub repository. Please keep in mind that the current release is a preview and we may introduce breaking changes based on feedback or in case we find some issue with the current release.Of course we are just in the beginning of this journey with .NET within the Elastic APM solution. We have lots of exciting things we are currently working on. Some examples are distributed tracing support, support for ASP.NET Classic, and many other things.

Additionally, we are also always open for contributions, so feel free to check out the source code over at GitHub and to open a pull request.