Course
This lesson is a part of our OpenTelemetry masterclass. If you haven't already, checkout the chapter introduction.
Each lesson in this lab builds on the last one, so make sure you read the workshop introduction before proceeding with this one.
In this workshop, you instrument a .NET web application with OpenTelemetry using the fundamentals you learned in the previous chapters of this masterclass! You also send your telemetry data to your New Relic account and see how useful the data is for monitoring your system and observing its behaviors.
If you haven’t already, sign up for a free New Relic account. You need one to complete this workshop.
Set up your environment
Clone our demo repository:
$git clone https://github.com/newrelic-experimental/mcv3-apps/
Change to the Uninstrumented/dotnet directory:
$cd mcv3-apps/Uninstrumented/dotnet
Next, you familiarize yourself with the app logic.
Familiarize yourself with the application
This demo service uses ASP.NET Core, a framework for building web applications.
Controllers/FibonacciController.cs has a single endpoint, called /fibonacci
, that takes an argument, n
, calculates the nth fibonacci number, and returns the results in a JSON-serialized format:
using System.Diagnostics;using Microsoft.AspNetCore.Mvc;
namespace dotnet.Controllers;
[ApiController][Route("[controller]")]public class FibonacciController : ControllerBase{
[HttpGet] public IActionResult Get(long n) { try { return Ok(new { n = n, result = Fibonacci(n) }); } catch (ArgumentOutOfRangeException ex) { return BadRequest(new { message = ex.Message }); } }
private long Fibonacci(long n) {
var result = 0L; if (n <= 2) { result = 1; } else { var a = 0L; var b = 1L;
for (var i = 1; i < n; i++) { result = checked(a + b); a = b; b = result; } }
return result; }
private void ThrowIfOutOfRange(long n) { if (n < 1 || n > 90) { throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90"); } }}
The logic for calculating the nth fibonacci number is contained within a function called Fibonacci()
:
using System.Diagnostics;using Microsoft.AspNetCore.Mvc;
namespace dotnet.Controllers;
[ApiController][Route("[controller]")]public class FibonacciController : ControllerBase{
[HttpGet] public IActionResult Get(long n) { try { return Ok(new { n = n, result = Fibonacci(n) }); } catch (ArgumentOutOfRangeException ex) { return BadRequest(new { message = ex.Message }); } }
private long Fibonacci(long n) {
var result = 0L; if (n <= 2) { result = 1; } else { var a = 0L; var b = 1L;
for (var i = 1; i < n; i++) { result = checked(a + b); a = b; b = result; } }
return result; }
private void ThrowIfOutOfRange(long n) { if (n < 1 || n > 90) { throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90"); } }}
This function takes the argument, n
, and checks if it’s between 1 and 90. If it’s not, the function throws an ArgumentOutOfRangeException
and rejects the request. Otherwise, it computes and returns the nth fibonacci number.
In Program.cs, you configure and build your ASP.NET Core app:
using dotnet.Controllers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
You use this configuration to instrument your application in the next section.
Those are the most important functions you need to know about this .NET application before you instrument it. Next, you install your OpenTelemetry dependencies.
Install dependencies
Before you can instrument your application, you need to add some OpenTelemetry dependencies to your project. In the dotnet directory, you’ll find a dotnet.csproj file. This holds the dependency requirements for the demo application.
From your shell, add the OpenTelemetry SDK and supporting packages (please note that these versions are necessary to run this tutorial):
$dotnet add package OpenTelemetry --version 1.2.0-rc1$dotnet add package OpenTelemetry.Api --version 1.2.0-rc1$dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol --version 1.2.0-rc1$dotnet add package OpenTelemetry.Extensions.Hosting --version 1.0.0-rc8$dotnet add package OpenTelemetry.Instrumentation.AspNetCore --version 1.0.0-rc8
Here, you added several OpenTelemetry packages to your application:
OpenTelemetry
: Provides the .NET OpenTelemetry SDK that implements the APIOpenTelemetry.Api
: Provides the API for managing your telemetry dataOpenTelemetry.Exporter.OpenTelemetryProtocol
: Provides the OTLP exporterOpenTelemetry.Extensions.Hosting
: Provides extensions to register OpenTelemetry with your applicationOpenTelemetry.Instrumentation.AspNetCore
: Provides automatic instrumentation for ASP.NET Core
Tip
These dependencies are sufficient for this workshop. In your applications, you might need other packages.
See your dependencies in dotnet.csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup>
<ItemGroup> <PackageReference Include="OpenTelemetry" Version="1.2.0-rc2" /> <PackageReference Include="OpenTelemetry.Api" Version="1.2.0-rc2" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.2.0-rc2" /> <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc9" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9" /> </ItemGroup>
</Project>
Now, you’re ready to instrument your app.
Instrument your application
You’ve set up your environment, read the code, and spun up your app and a load generator. Now, it’s time to instrument your app with OpenTelemetry!
In Program.cs, create a ResourceBuilder
:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3
4var builder = WebApplication.CreateBuilder(args);5
6builder.Services.AddControllers();7
8var resourceBuilder = ResourceBuilder9 .CreateDefault()10 .AddService("fibonacci")11 .AddTelemetrySdk();12
13var app = builder.Build();14
15app.MapControllers();16
17app.Run();
1using Microsoft.AspNetCore.Mvc;2
3namespace dotnet.Controllers;4
5[ApiController]6[Route("[controller]")]7public class FibonacciController : ControllerBase8{9
10 [HttpGet]11 public IActionResult Get(long n)12 {13 try14 {15 return Ok(new16 {17 n = n,18 result = Fibonacci(n)19 });20 }21 catch (ArgumentOutOfRangeException ex)22 {23 return BadRequest(new { message = ex.Message });24 }25 }26
27 private long Fibonacci(long n)28 {29 ThrowIfOutOfRange(n);30 var result = 0L;31 if (n <= 2)32 {33 result = 1;34 }35 else36 {37 var a = 0L;38 var b = 1L;39
40 for (var i = 1; i < n; i++)41 {42 result = checked(a + b);43 a = b;44 b = result;45 }46 }47
48 return result;49 }50
51 private void ThrowIfOutOfRange(long n)52 {53 if (n < 1 || n > 90)54 {55 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");56 }57 }58}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 load-generator:8 build: ./load-generator
Here, you use the ResourceBuilder
from the OpenTelemetry.Resources
namespace to create a resource. With it, you can compose resource attributes from different semantic convention categories.
Specifically, you use:
.CreateDefault()
, which adds some attributes about the current process..AddService()
with the service name "fibonacci". This sets the conventional attributeservice.name
..AddTelemetrySdk()
to set some conventional telemetry SDK attributes. These are:telemetry.sdk.name
telemetry.sdk.language
telemetry.sdk.version
Configure OpenTelemetry tracing:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3using OpenTelemetry.Trace;4
5var builder = WebApplication.CreateBuilder(args);6
7builder.Services.AddControllers();8
9var resourceBuilder = ResourceBuilder10 .CreateDefault()11 .AddService("fibonacci")12 .AddTelemetrySdk();13
14builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>15{16 tracerProviderBuilder17 .SetResourceBuilder(resourceBuilder)18 .AddSource(FibonacciController.ActivitySourceName)19 .AddAspNetCoreInstrumentation()20 .AddOtlpExporter();21});22
23var app = builder.Build();24
25app.MapControllers();26
27app.Run();
1using Microsoft.AspNetCore.Mvc;2
3namespace dotnet.Controllers;4
5[ApiController]6[Route("[controller]")]7public class FibonacciController : ControllerBase8{9
10 [HttpGet]11 public IActionResult Get(long n)12 {13 try14 {15 return Ok(new16 {17 n = n,18 result = Fibonacci(n)19 });20 }21 catch (ArgumentOutOfRangeException ex)22 {23 return BadRequest(new { message = ex.Message });24 }25 }26
27 private long Fibonacci(long n)28 {29 ThrowIfOutOfRange(n);30 var result = 0L;31 if (n <= 2)32 {33 result = 1;34 }35 else36 {37 var a = 0L;38 var b = 1L;39
40 for (var i = 1; i < n; i++)41 {42 result = checked(a + b);43 a = b;44 b = result;45 }46 }47
48 return result;49 }50
51 private void ThrowIfOutOfRange(long n)52 {53 if (n < 1 || n > 90)54 {55 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");56 }57 }58}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 load-generator:8 build: ./load-generator
Here, you use .AddOpenTelemetryTracing()
to inject a new TracerProviderBuilder
into your WebApplicationBuilder
. You configure your TracerProviderBuilder
with:
- The resource builder you created in the last step
- A tracer for your application
- Automatic instrumentation for ASP.NET Core
- An OTLP exporter
You named the tracer that you created with .AddSource()
using a constant, called ActivitySourceName
. You’ll create this in a later step. Before you do, you need to configure your OTLP exporter.
The exporter you configured in the last step needs two important values:
- A location where you want to send the data (New Relic, in this case)
- An API key so that New Relic can accept your data and associate it to your account
In Uninstrumented/dotnet/docker-compose.yaml, configure your OTLP exporter:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3using OpenTelemetry.Trace;4
5var builder = WebApplication.CreateBuilder(args);6
7builder.Services.AddControllers();8
9var resourceBuilder = ResourceBuilder10 .CreateDefault()11 .AddService("fibonacci")12 .AddTelemetrySdk();13
14builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>15{16 tracerProviderBuilder17 .SetResourceBuilder(resourceBuilder)18 .AddSource(FibonacciController.ActivitySourceName)19 .AddAspNetCoreInstrumentation()20 .AddOtlpExporter();21});22
23var app = builder.Build();24
25app.MapControllers();26
27app.Run();
1using Microsoft.AspNetCore.Mvc;2
3namespace dotnet.Controllers;4
5[ApiController]6[Route("[controller]")]7public class FibonacciController : ControllerBase8{9
10 [HttpGet]11 public IActionResult Get(long n)12 {13 try14 {15 return Ok(new16 {17 n = n,18 result = Fibonacci(n)19 });20 }21 catch (ArgumentOutOfRangeException ex)22 {23 return BadRequest(new { message = ex.Message });24 }25 }26
27 private long Fibonacci(long n)28 {29 ThrowIfOutOfRange(n);30 var result = 0L;31 if (n <= 2)32 {33 result = 1;34 }35 else36 {37 var a = 0L;38 var b = 1L;39
40 for (var i = 1; i < n; i++)41 {42 result = checked(a + b);43 a = b;44 b = result;45 }46 }47
48 return result;49 }50
51 private void ThrowIfOutOfRange(long n)52 {53 if (n < 1 || n > 90)54 {55 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");56 }57 }58}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 environment:8 OTEL_EXPORTER_OTLP_ENDPOINT: https://otlp.nr-data.net:43179 OTEL_EXPORTER_OTLP_HEADERS: "api-key=${NEW_RELIC_API_KEY}"10 load-generator:11 build: ./load-generator
The first variable you set configures the exporter to send telemetry data to https://otlp.nr-data.net:4317. This is our US-based OTLP endpoint. If you’re in the EU, use https://otlp.eu01.nr-data.net:4317 instead.
The second variable passes the NEW_RELIC_API_KEY
from your local machine in the api-key
header of your OTLP requests. You set this environment variable in the next step.
The OTLP exporter looks for these variables in the Docker container’s environment and applies them at runtime.
Get or create a New Relic license key for your account, and set it in an environment variable called NEW_RELIC_API_KEY
:
$export NEW_RELIC_API_KEY=<YOUR_LICENSE_KEY>
Important
Don’t forget to replace <YOUR_LICENSE_KEY>
with your real license key!
In Controller/FibonacciController.cs, create a tracer:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3using OpenTelemetry.Trace;4
5var builder = WebApplication.CreateBuilder(args);6
7builder.Services.AddControllers();8
9var resourceBuilder = ResourceBuilder10 .CreateDefault()11 .AddService("fibonacci")12 .AddTelemetrySdk();13
14builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>15{16 tracerProviderBuilder17 .SetResourceBuilder(resourceBuilder)18 .AddSource(FibonacciController.ActivitySourceName)19 .AddAspNetCoreInstrumentation()20 .AddOtlpExporter();21});22
23var app = builder.Build();24
25app.MapControllers();26
27app.Run();
1using System.Diagnostics;2using Microsoft.AspNetCore.Mvc;3
4namespace dotnet.Controllers;5
6[ApiController]7[Route("[controller]")]8public class FibonacciController : ControllerBase9{10 public const string ActivitySourceName = "FibonacciService";11 private ActivitySource activitySource = new ActivitySource(ActivitySourceName);12
13 [HttpGet]14 public IActionResult Get(long n)15 {16 try17 {18 return Ok(new19 {20 n = n,21 result = Fibonacci(n)22 });23 }24 catch (ArgumentOutOfRangeException ex)25 {26 return BadRequest(new { message = ex.Message });27 }28 }29
30 private long Fibonacci(long n)31 {32 ThrowIfOutOfRange(n);33 var result = 0L;34 if (n <= 2)35 {36 result = 1;37 }38 else39 {40 var a = 0L;41 var b = 1L;42
43 for (var i = 1; i < n; i++)44 {45 result = checked(a + b);46 a = b;47 b = result;48 }49 }50
51 return result;52 }53
54 private void ThrowIfOutOfRange(long n)55 {56 if (n < 1 || n > 90)57 {58 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");59 }60 }61}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 environment:8 OTEL_EXPORTER_OTLP_ENDPOINT: https://otlp.nr-data.net:43179 OTEL_EXPORTER_OTLP_HEADERS: "api-key=${NEW_RELIC_API_KEY}"10 load-generator:11 build: ./load-generator
In .NET, the OpenTelemetry tracer and span APIs make use of existing interfaces from the System.Diagnostics
namespace, called ActivitySource
and Activity
, respectively. They both adhere to the specification.
At the top of Fibonacci()
, use your tracer to start a new span:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3using OpenTelemetry.Trace;4
5var builder = WebApplication.CreateBuilder(args);6
7builder.Services.AddControllers();8
9var resourceBuilder = ResourceBuilder10 .CreateDefault()11 .AddService("fibonacci")12 .AddTelemetrySdk();13
14builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>15{16 tracerProviderBuilder17 .SetResourceBuilder(resourceBuilder)18 .AddSource(FibonacciController.ActivitySourceName)19 .AddAspNetCoreInstrumentation()20 .AddOtlpExporter();21});22
23var app = builder.Build();24
25app.MapControllers();26
27app.Run();
1using System.Diagnostics;2using Microsoft.AspNetCore.Mvc;3
4namespace dotnet.Controllers;5
6[ApiController]7[Route("[controller]")]8public class FibonacciController : ControllerBase9{10 public const string ActivitySourceName = "FibonacciService";11 private ActivitySource activitySource = new ActivitySource(ActivitySourceName);12
13 [HttpGet]14 public IActionResult Get(long n)15 {16 try17 {18 return Ok(new19 {20 n = n,21 result = Fibonacci(n)22 });23 }24 catch (ArgumentOutOfRangeException ex)25 {26 return BadRequest(new { message = ex.Message });27 }28 }29
30 private long Fibonacci(long n)31 {32 using var activity = activitySource.StartActivity(nameof(Fibonacci));33
34 ThrowIfOutOfRange(n);35 var result = 0L;36 if (n <= 2)37 {38 result = 1;39 }40 else41 {42 var a = 0L;43 var b = 1L;44
45 for (var i = 1; i < n; i++)46 {47 result = checked(a + b);48 a = b;49 b = result;50 }51 }52
53 return result;54 }55
56 private void ThrowIfOutOfRange(long n)57 {58 if (n < 1 || n > 90)59 {60 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");61 }62 }63}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 environment:8 OTEL_EXPORTER_OTLP_ENDPOINT: https://otlp.nr-data.net:43179 OTEL_EXPORTER_OTLP_HEADERS: "api-key=${NEW_RELIC_API_KEY}"10 load-generator:11 build: ./load-generator
activitySource.StartActivity()
starts a new span (called an Activity
in .NET). With this call, you pass the name of the Fibonacci()
function.
Add some attributes to capture important values in the execution:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3using OpenTelemetry.Trace;4
5var builder = WebApplication.CreateBuilder(args);6
7builder.Services.AddControllers();8
9var resourceBuilder = ResourceBuilder10 .CreateDefault()11 .AddService("fibonacci")12 .AddTelemetrySdk();13
14builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>15{16 tracerProviderBuilder17 .SetResourceBuilder(resourceBuilder)18 .AddSource(FibonacciController.ActivitySourceName)19 .AddAspNetCoreInstrumentation()20 .AddOtlpExporter();21});22
23var app = builder.Build();24
25app.MapControllers();26
27app.Run();
1using System.Diagnostics;2using Microsoft.AspNetCore.Mvc;3
4namespace dotnet.Controllers;5
6[ApiController]7[Route("[controller]")]8public class FibonacciController : ControllerBase9{10 public const string ActivitySourceName = "FibonacciService";11 private ActivitySource activitySource = new ActivitySource(ActivitySourceName);12
13 [HttpGet]14 public IActionResult Get(long n)15 {16 try17 {18 return Ok(new19 {20 n = n,21 result = Fibonacci(n)22 });23 }24 catch (ArgumentOutOfRangeException ex)25 {26 return BadRequest(new { message = ex.Message });27 }28 }29
30 private long Fibonacci(long n)31 {32 using var activity = activitySource.StartActivity(nameof(Fibonacci));33 activity?.SetTag("fibonacci.n", n);34
35 ThrowIfOutOfRange(n);36 var result = 0L;37 if (n <= 2)38 {39 result = 1;40 }41 else42 {43 var a = 0L;44 var b = 1L;45
46 for (var i = 1; i < n; i++)47 {48 result = checked(a + b);49 a = b;50 b = result;51 }52 }53
54 activity?.SetTag("fibonacci.result", result);55 return result;56 }57
58 private void ThrowIfOutOfRange(long n)59 {60 if (n < 1 || n > 90)61 {62 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");63 }64 }65}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 environment:8 OTEL_EXPORTER_OTLP_ENDPOINT: https://otlp.nr-data.net:43179 OTEL_EXPORTER_OTLP_HEADERS: "api-key=${NEW_RELIC_API_KEY}"10 load-generator:11 build: ./load-generator
Because .NET implements the span specification with its Activity
class, some of the API looks slightly different. Here you see that attributes are called "tags" in the .NET implementation.
The first attribute you set is called “fibonacci.n”, which stores the value of n
from the user’s request. If the computation was successful, you set “fibonacci.result”, which captures the result.
Record an exception span event if n
is invalid:
1using dotnet.Controllers;2using OpenTelemetry.Resources;3using OpenTelemetry.Trace;4
5var builder = WebApplication.CreateBuilder(args);6
7builder.Services.AddControllers();8
9var resourceBuilder = ResourceBuilder10 .CreateDefault()11 .AddService("fibonacci")12 .AddTelemetrySdk();13
14builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>15{16 tracerProviderBuilder17 .SetResourceBuilder(resourceBuilder)18 .AddSource(FibonacciController.ActivitySourceName)19 .AddAspNetCoreInstrumentation()20 .AddOtlpExporter();21});22
23var app = builder.Build();24
25app.MapControllers();26
27app.Run();
1using System.Diagnostics;2using Microsoft.AspNetCore.Mvc;3using OpenTelemetry.Trace;4
5namespace dotnet.Controllers;6
7[ApiController]8[Route("[controller]")]9public class FibonacciController : ControllerBase10{11 public const string ActivitySourceName = "FibonacciService";12 private ActivitySource activitySource = new ActivitySource(ActivitySourceName);13
14 [HttpGet]15 public IActionResult Get(long n)16 {17 try18 {19 return Ok(new20 {21 n = n,22 result = Fibonacci(n)23 });24 }25 catch (ArgumentOutOfRangeException ex)26 {27 return BadRequest(new { message = ex.Message });28 }29 }30
31 private long Fibonacci(long n)32 {33 using var activity = activitySource.StartActivity(nameof(Fibonacci));34 activity?.SetTag("fibonacci.n", n);35
36 try37 {38 ThrowIfOutOfRange(n);39 }40 catch (ArgumentOutOfRangeException ex)41 {42 activity?.SetStatus(Status.Error.WithDescription(ex.Message));43 activity?.RecordException(ex);44 throw;45 }46
47 var result = 0L;48 if (n <= 2)49 {50 result = 1;51 }52 else53 {54 var a = 0L;55 var b = 1L;56
57 for (var i = 1; i < n; i++)58 {59 result = checked(a + b);60 a = b;61 b = result;62 }63 }64
65 activity?.SetTag("fibonacci.result", result);66 return result;67 }68
69 private void ThrowIfOutOfRange(long n)70 {71 if (n < 1 || n > 90)72 {73 throw new ArgumentOutOfRangeException(nameof(n), n, "n must be between 1 and 90");74 }75 }76}
1version: '3'2services:3 fibonacci:4 build: ./5 ports:6 - "8080:80"7 environment:8 OTEL_EXPORTER_OTLP_ENDPOINT: https://otlp.nr-data.net:43179 OTEL_EXPORTER_OTLP_HEADERS: "api-key=${NEW_RELIC_API_KEY}"10 load-generator:11 build: ./load-generator
If n
is invalid, you throw an ArgumentOutofRangeException
, record an exception span event on the span, and set the span’s status to Status.Error
. You also use the exception message for the status description.
In the same shell you used to export your environment variable, navigate to Uninstrumented/dotnet, then spin up the project's containers with Docker Compose:
$docker-compose up --build
This runs two docker services:
- fibonacci: Your app service
- load-generator: A service that simulates traffic to your app
The load generator makes periodic requests to your application. It sends a mixture of requests that you expect to succeed and ones that you expect to fail. Looking at the Docker Compose log stream, you should see that both your application and load generator are running.
You’re now ready to view your data in New Relic.
Course
This lesson is a part of our OpenTelemetry masterclass. Continue on to the next lesson: View a summary of your data.