[Home] Edit Article
Author Information
File Uploads
Edit Article XML Content
<document> <header> <issuecode /> <articlecode /> <zone /> <title>An Introduction to Distributed Tracing with OpenTelemetry in .NET 7</title> <authors /> <copyright>CODE Magazine</copyright> <owner>CODE Magazine</owner> </header> <body> <p id="0">Although enterprises are gathering more data than ever, they may not be using it to their advantage. In most cases, companies don’t even know what kind of information they possess or how to use it. This is where OpenTelemetry can help.</p> <p id="1">OpenTelemetry facilitates the collection and analysis of data from multiple sources simultaneously, enabling businesses to make more informed decisions about their operations. With OpenTelemetry, enterprises have transformed their approach to observability. OpenTelemetry is adept at troubleshooting, alerting, and debugging applications and is well poised to be the future of instrumentation.</p> <p id="2">OpenTelemetry includes a set of tools and libraries for distributed tracing and monitoring. It was created by the Cloud Native Computing Foundation (CNCF) to provide a standard way of instrumenting code for tracing. OpenTelemetry allows you to collect data about the flow of requests through your system and it can be used with any programming language. It has particularly good support for .NET. </p> <p id="3">The data collected can be used to generate a trace of how the requests were handled. This is useful for diagnosing performance issues or errors. In addition to .NET, there are libraries available for ASP.NET Core and other frameworks. These libraries make it easy to instrument your code and collect trace data. Overall, OpenTelemetry is a great tool for distributed tracing and monitoring. It’s easy to use with .NET and it has good support for various frameworks.</p> <p id="4">This article talks about the concepts related to OpenTelemetry, why it’s useful, and how you can work with it in ASP.NET 7 Core applications.</p> <p id="5">If you’re to work with the code examples discussed in this article, you need the following installed in your system:</p> <list type="bulleted"> <bulletedlist>Visual Studio 2022 Preview</bulletedlist> <bulletedlist>.NET 7.0</bulletedlist> <bulletedlist>ASP.NET 7.0 Runtime</bulletedlist> </list> <p id="6">If you don’t already have Visual Studio 2022 Preview installed in your computer, you can download it from here: https://visualstudio.microsoft.com/downloads/.</p> <p id="7">In this article, you’ll:</p> <list type="bulleted"> <bulletedlist>Learn OpenTelemetry and its benefits</bulletedlist> <bulletedlist>Build a simple application in ASP.NET 7 Core</bulletedlist> <bulletedlist>Configure the application to provide support for OpenTelemetry</bulletedlist> <bulletedlist>Extend OpenTelemetry</bulletedlist> <bulletedlist>Build a custom processor</bulletedlist> <bulletedlist>Build a custom exporter</bulletedlist> <bulletedlist>Export telemetry data</bulletedlist> <bulletedlist>Understand the future of OpenTelemetry</bulletedlist> </list> <h2>What Is Distributed Tracing?</h2> <p id="8">Distributed tracing is a method used to capture events across multiple services in order to troubleshoot issues and optimize performance. It’s used to understand the execution of a distributed system and involves tracking the execution of each component in the system and recording information about each step. Using this information, the path taken by the system during execution can be recreated. </p> <p id="9">Distributed tracing can be used to identify bottlenecks in a system, or to understand the behavior of a complex distributed system. It can also be used to diagnose errors in a distributed system. This can help you understand how your application is performing and find root causes for any performance issues.</p> <h2>What Is Observability? Why Is It Important?</h2> <p id="10">Observability refers to a system's ability to measure its current state from the data it generates, such as log files, metrics, and traces. Observability can improve operational visibility by capturing data and proactively observing distributed systems. It can identify relevant data based on how an application performs in a production environment over time.</p> <p id="11">Metrics, logging, and tracing are the three foundation elements of observability. I’ll discuss each of these shortly.</p> <h2>What Is Open Telemetry? Why Should I Use It?</h2> <p id="12">OpenTelemetry is an open-source distributed tracing framework for .NET 7. It uses the open-source framework for collecting, storing, and analyzing telemetry data (metrics, logs, and traces) and provides a high-level API for distributed tracing and metrics that can be used by applications in different environments, including monoliths, microservices, and serverless applications.</p> <p id="13">OpenTelemetry is a .NET library for distributed tracing and metrics that was originally introduced in 2016 by Microsoft as part of the OpenTracing standard adopted by the OpenTracing Working Group at CNCF.</p> <p id="14">Since then, it’s been widely adopted by various projects and companies across multiple industries, including Microsoft’s own Azure services and Azure Functions. OpenTelemetry is a community-driven project, released under the Apache 2.0 license. It’s available on GitHub and NuGet, and supports .NET Core 1.1 or higher, ASP.NET 5+, or any other .NET framework that implements the OpenTracing API.</p> <p id="15">OpenTelemetry comprises a collection of APIs, SDKs, tooling, and integrations for collecting and storing telemetry data that includes traces, metrics, and logs. In OpenTelemetry, a collector is a component that receives, processes, and exports telemetry data, as shown in <b>Figure 1</b>.</p> <figure id="1" src="image1.png"> <b>Figure 1</b> <b>:</b> An OpenTelemetry Collector at work</figure> <p id="16">OpenTelemetry can be used to correlate events that occur when your application is in execution. You can correlate events based on:</p> <list type="bulleted"> <bulletedlist> <b>Execution context:</b> You can correlate logs and traces based on the traceId of an event.</bulletedlist> <bulletedlist> <b>Execution time:</b> You can correlate logs and traces based on the time of the event, i.e., when the event occurred.</bulletedlist> <bulletedlist> <b>Telemetry Origin:</b> You can correlate events based on the service name, the service instance, or the version of the service.</bulletedlist> </list> <h2>Why Open Telemetry?</h2> <p id="17">OpenTelemetry is a new distributed tracing system designed to be used in a wide variety of programming languages and platforms. There are many benefits to using OpenTelemetry for distributed tracing, including:</p> <list type="bulleted"> <bulletedlist>It is open source and well-supported by the community.</bulletedlist> <bulletedlist>It has good integration with other popular open-source tracing systems like Jaeger.</bulletedlist> <bulletedlist>It offers many features that make it simple to instrument your code and collect trace data.</bulletedlist> <bulletedlist>It has strong support for .NET and other Microsoft technologies.</bulletedlist> </list> <p id="18">If you’re looking for a distributed tracing system to use in your .NET applications, OpenTelemetry is a great option to consider.</p> <h2>Components of OpenTelemetry</h2> <p id="19">The OpenTelemetry collector has three components: receivers, processers, and exporters.</p> <h3>Receivers</h3> <p id="20">An OpenTelemetry receiver collects data and sends it to a collector. A receiver can support one or more data sources and may be push- or pull-based. In the OpenTelemetry pipeline, receivers receive data in a specified format, convert it to an internal format, and pass it on to processors and exporters.</p> <h3>Processors</h3> <p id="21">A processor is an optional component in the collector pipeline that processes data before sending it to an exporter. Using processors, you can batch-process, sample, transform, and enrich the telemetry data you receive from the collector before exporting it.</p> <h3>Exporters</h3> <p id="22">OpenTelemetry can export telemetry data, such as metrics and traces, to several back-ends. An exporter can be push- or pull-based and is responsible for exporting data to one or more back-ends or destinations, such as Azure Monitor, Jaeger, Splunk, Prometheus, etc.</p> <h2>What Are Traces, Metrics, and Logs?</h2> <p id="23">There are three main types of data that are important for understanding the performance of a system: tracing data, metrics, and logs. </p> <h3>Traces</h3> <p id="24">Tracing is a process that records the details of all events. It captures data about the flow of a particular request or transaction across multiple components in your system, including information such as the event, event type, method calls, and exceptions raised. Tracing data is very useful for understanding how a system works and for finding bottlenecks.</p> <h3>Metrics</h3> <p id="25">Metrics are numerical data that provide insights on the performance of an application, such as the number of requests per second, the average response time, and so on. For example, you could collect the average number of milliseconds it took to render a web page over time.</p> <p id="26">Metrics can be used to evaluate performance, capacity planning, or other aspects of an application's health. Metrics are very useful for understanding the overall performance of a system. The data captured includes specific events within certain boundaries of time, such as average response times or throughput rates.</p> <h3>Logs</h3> <p id="27">Logs record events that occur when an application is in execution or information about how long a snippet of code or a method took to complete. This includes information about errors, warnings, and more. Logs are typically used for debugging and troubleshooting problems in an application. </p> <p id="28">These historical records describe events in an application during some period (i.e., user log-in and authentication). You can use logs to debug problems because they allow you to see what went wrong when an issue occurs, as shown in <b>Figure 2</b>.</p> <figure id="2" src="image2.png"> <b>Figure 2:</b> The OpenTelemetry ollcector at work</figure> <h2>Distributed Tracing Using Open Telemetry in ASP.NET 7 </h2> <p id="29">In this section, I’ll examine how you can work with OpenTelemetry in an ASP.NET 7 application.</p> <h3>Create a New ASP.NET 7 Project in Visual Studio 2022</h3> <p id="30">You can create a project in Visual Studio 2022 in several ways. When you launch Visual Studio 2022, you'll see the Start window. You can choose "Continue without code" to launch the main screen of the Visual Studio 2022 IDE.</p> <p id="31">To create a new ASP.NET 7 Project in Visual Studio 2022 Preview:</p> <list type="numbered"> <numberedlist>Start the Visual Studio 2022 Preview IDE.</numberedlist> <numberedlist>In the "Create a new project" window, select "ASP.NET Core Web API" and click Next to move on.</numberedlist> <numberedlist>Specify the project name as OpenTelemetryDemo and the path where it should be created in the "Configure your new project" window.</numberedlist> <numberedlist>If you want the solution file and project to be created in the same directory, you can optionally check the "Place solution and project in the same directory" checkbox. Click Next to move on.</numberedlist> <numberedlist>In the next screen, specify the target framework as .NET 7 (Preview) and the authentication type as well. Ensure that the "Configure for HTTPS," "Enable Docker Support," and the "Enable OpenAPI support" checkboxes are unchecked because you won’t use any of these in this example, as shown in <b>Figure 3</b>.</numberedlist> </list> <figure id="3" src="image3.png"> <b>Figure 3:</b> Specify the framework version and other metadata for your project.</figure> <list type="numbered"> <numberedlist>Because you'll be using minimal APIs in this example, remember to uncheck the Use controllers (uncheck to use minimal APIs) checkbox.</numberedlist> <numberedlist>Click Create to complete the process.</numberedlist> </list> <p id="32">You’ll use this application in the subsequent sections in this article.</p> <p id="33">As the name suggests, a prerelease package is one that is still in development, is not yet fully tested, and has yet to be officially available as a stable version. For supporting the release lifecycle of a software product, NuGet provides a feature that allows it to work with prerelease packages. These packages are typically available between major releases.</p> <p id="34">You use prerelease packages when working with OpenTelemetry because these packages are constantly updated, and you can get the latest updates.</p> <h3>Install NuGet Package(s)</h3> <p id="35">So far so good. The next step is to install the necessary NuGet Package(s) onto your project. You can install the required packages from the Developer Command Prompt for VS 2022 Preview by executing the following commands:</p> <codesnippet>dotnet»add»package»</codesnippet> <codesnippet>--prerelease»</codesnippet> <codesnippet>OpenTelemetry.</codesnippet> <codesnippet>Exporter.Console</codesnippet> <codesnippet>dotnet»add»package»</codesnippet> <codesnippet>--prerelease»</codesnippet> <codesnippet>OpenTelemetry.</codesnippet> <codesnippet>Extensions.Hosting</codesnippet> <codesnippet>dotnet»add»package»</codesnippet> <codesnippet>--prerelease»</codesnippet> <codesnippet>OpenTelemetry.</codesnippet> <codesnippet>Instrumentation.AspNetCore</codesnippet> <p id="36">The OpenTelemetry.Exporter.Console package is used for printing telemetry data at the developer console in your local computer. The OpenTelemetry.Extensions.Hosting package contains the extension methods to register OpenTelemetry into the applications using Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Hosting. The OpenTelemetry.Instrumentation.AspNetCore package is an instrumentation library that tracks the inbound web requests and collects telemetry data. </p> <p id="37">You can also install the packages inside the Visual Studio 2022 IDE by running the following commands at the NuGet Package Manager Console:</p> <codesnippet>Install-Package»</codesnippet> <codesnippet>--prerelease»</codesnippet> <codesnippet>OpenTelemetry.</codesnippet> <codesnippet>Exporter.Console</codesnippet> <codesnippet>Install-Package»</codesnippet> <codesnippet>--prerelease»</codesnippet> <codesnippet>OpenTelemetry.</codesnippet> <codesnippet>Extensions.Hosting</codesnippet> <codesnippet>Install-Package»</codesnippet> <codesnippet>--prerelease»</codesnippet> <codesnippet>OpenTelemetry.</codesnippet> <codesnippet>Instrumentation.AspNetCore</codesnippet> <p id="38">You’ll also be able to install these packages using the NuGet Package Manager window inside the Visual Studio 2022 Preview IDE. To install the required packages into your project, right-click on the solution and then select <b>Manage NuGet Packages for Solution...</b>. Now search for these packages one at a time in the search box and install them.</p> <h2>Implementing OpenTelemetry in ASP.NET 7 Core</h2> <p id="39">In this section, I’ll examine how you can take advantage of OpenTelemetry in ASP.NET 7 Core. You’ll use the application you created earlier to add support for telemetry data.</p> <h3>Add Support for Tracing</h3> <p id="40">To add support for tracing in your application, you can write the following code in the Program.cs file:</p> <codesnippet>builder.Services</codesnippet> <codesnippet>.AddOpenTelemetryTracing</codesnippet> <codesnippet>(<font>(builder)</font><font>»=></font>»builder»»»»</codesnippet> <codesnippet>.SetResourceBuilder</codesnippet> <codesnippet>(ResourceBuilder.</codesnippet> <codesnippet>CreateDefault().</codesnippet> <codesnippet>AddService(<font color="#A31515">"MyDemoService"</font>))</codesnippet> <codesnippet>.AddAspNetCoreInstrumentation()</codesnippet> <codesnippet>.AddHttpClientInstrumentation()</codesnippet> <codesnippet>.AddConsoleExporter());</codesnippet> <h3>Add Support for Metrics</h3> <p id="41">The following code snippet illustrates how you can add metrics to your application:</p> <codesnippet>builder.Services.</codesnippet> <codesnippet>AddOpenTelemetryMetrics(<font>builder</font><font>»=></font>»</codesnippet> <codesnippet>builder.SetResourceBuilder</codesnippet> <codesnippet>(ResourceBuilder.</codesnippet> <codesnippet>CreateDefault().AddService</codesnippet> <codesnippet>(<font color="#A31515">"MyDemoService"</font>))</codesnippet> <codesnippet>.AddAspNetCoreInstrumentation()</codesnippet> <codesnippet>.AddConsoleExporter());</codesnippet> <h3>Add Support for Logging</h3> <p id="42">To add support for logging in your application, you can use the code snippet given below:</p> <codesnippet>builder.Host</codesnippet> <codesnippet>.ConfigureLogging(<font>builder</font><font>»=></font>»builder</codesnippet> <codesnippet>.ClearProviders()</codesnippet> <codesnippet>.AddOpenTelemetry(<font>options</font><font>»=></font></codesnippet> <codesnippet>{</codesnippet> <codesnippet>»»options.</codesnippet> <codesnippet>»»IncludeFormattedMessage»=»<font color="#A31515">true</font>;</codesnippet> <codesnippet>»»options.SetResourceBuilder</codesnippet> <codesnippet>»»(ResourceBuilder.CreateDefault().</codesnippet> <codesnippet>»»AddService(<font color="#A31515">"MyDemoService"</font>));</codesnippet> <codesnippet>»»options.AddConsoleExporter();</codesnippet> <codesnippet>}));</codesnippet> <p id="43">The complete source code of the Program.cs file is given in <b>Listing 1</b> for your reference.</p> <h2>A Real-World Use Case</h2> <p id="44">In this section, you’ll implement a simple OrderProcessing application. To keep things simple, this application only displays one or more order records. The source code of this application contains the following classes and interfaces:</p> <list type="bulleted"> <bulletedlist>Order class</bulletedlist> <bulletedlist>IOrderRepository interface</bulletedlist> <bulletedlist>OrderRepository class</bulletedlist> <bulletedlist>OrdersController class</bulletedlist> </list> <h3>Create the Model Class</h3> <p id="45">Create a new class named Order in a file having the same name with a .cs extension, and write the following code in there:</p> <codesnippet> <font color="Blue">public</font>»<font color="Blue">class</font>»<font color="#A31515">Order</font></codesnippet> <codesnippet>{</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»<font color="Blue">int</font>»Id»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»<font color="Blue">int</font>»CustomerId»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»<font color="Blue">int</font>»ProductId»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»<font color="Blue">decimal</font>»UnitPrice»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»<font color="Blue">int</font>»OrderQuantity»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»<font color="Blue">decimal</font>»Amount»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»DateTime»OrderDate»{»<font color="Blue">get</font>;»<font color="Blue">set</font>;»}</codesnippet> <codesnippet>}</codesnippet> <p id="46">The Product and Customer classes aren’t being shown here for brevity and also because this is a minimal implementation to illustrate how you can work with OpenTelemetry in ASP.NET 7 Core. </p> <h3>Create the OrderRepository Class</h3> <p id="47">Now, create a new class named OrderRepository in a file having the same name with a .cs extension. Now write the following code in there:</p> <codesnippet> <font color="Blue">public</font>»<font color="Blue">class</font><font>»</font><font color="#A31515">OrderRepository</font><font>»:</font>»</codesnippet> <codesnippet>IOrderRepository</codesnippet> <codesnippet>{»»»»»»»</codesnippet> <codesnippet>»»»»»»»»</codesnippet> <codesnippet>}</codesnippet> <p id="48">The OrderRepository class illustrated in the code snippet below implements the methods of the IorderRepository interface. Here is how the IorderRepository interface should look:</p> <codesnippet> <font color="Blue">public</font>»<font color="Blue">interface</font><font>»</font><font color="#A31515">I</font><font color="#A31515">o</font><font color="#A31515">rderRepository</font></codesnippet> <codesnippet>{</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»Task<<font color="Blue">List</font><Order>>»GetAllOrders();</codesnippet> <codesnippet>»»»»<font color="Blue">public</font>»Task<Order>»GetOrder(int»Id);</codesnippet> <codesnippet>}</codesnippet> <p id="49">The OrderRepository class implements the two methods of the IorderRepository interface:</p> <codesnippet> <font color="Blue">public</font>»<font color="Blue">async</font>»Task<List<Order>>»GetOrders()</codesnippet> <codesnippet>{</codesnippet> <codesnippet>»»»»<font color="Blue">return</font>»<font color="Blue">await</font>»Task.FromResult(orders);</codesnippet> <codesnippet>}</codesnippet> <codesnippet> <font color="Blue">public</font> <font>»</font> <font color="Blue">async</font> <font>»Task<Order>»</font> <font color="#A31515">GetOrder</font> <font>(</font> <font color="Blue">int</font> <font>»Id</font> <font>)</font> </codesnippet> <codesnippet>{</codesnippet> <codesnippet>»»<font color="Blue">return</font>»<font color="Blue">await</font>»Task.FromResult(orders.</codesnippet> <codesnippet>»»FirstOrDefault(x»=>»x.Id»==»Id));</codesnippet> <codesnippet>}</codesnippet> <p id="50">The complete source code of the OrderRepository class is given is <b>Listing 2</b>.</p> <h3>Register the OrderRepository Instance with IserviceCollection</h3> <p id="51">The following code snippet illustrates how an instance of type IorderRepository is added as a scoped service to the IserviceCollection.</p> <codesnippet> <font color="Blue">B</font> <font color="Blue">uilder</font> <font>.Services.</font> </codesnippet> <codesnippet> <font>AddScoped</font><<font color="Blue">I</font><font color="Blue">o</font><font color="Blue">rderRepository</font>,»</codesnippet> <codesnippet> <font color="Blue">OrderRepository</font>>();</codesnippet> <p id="52">The complete source code of the Program.cs file is given in <b>Listing 3</b>.</p> <h3>The OrderController Class</h3> <p id="53">Lastly, create a new API controller class named OrderController in your project with the code given in <b>Listing 4</b> in there. The OrdersController class contains two action methods. Although the GetOrders action method returns a list of Order instances, the GetOrder action method returns one Order based on the Order ID passed to the action method as a parameter. The GetOrders and GetOrder action methods call the GetOrders and GetOrder methods of the OrderRepository class respectively. An instance of type IorderRepository is injected into the OrdersController using constructor injection. </p> <h2>Executing the Application</h2> <p id="54">When you run the application, you’ll be able to see telemetry data displayed at the console window, as shown in <b>Figure 4</b>:</p> <figure id="4" src="image4.png"> <b>Figure </b> <b>4</b> <b>:</b> Telemetry data displayed at the Developer Console Window</figure> <h2>Extending the OpenTelemetry .NET SDK</h2> <p id="55">In this section, I’ll examine how to extend the OpenTelemetry .NET SDK. You’ll see how you can build a custom exporter and a custom processor.</p> <h3>Building Your Custom Exporter</h3> <p id="56">You might often want to build custom exporters to send telemetry data to destinations not supported by built-in exporters.</p> <p id="57">Your custom exporter is a C# class that extends the BaseExporter class of the OpenTelemetry library, as shown in the code snippet given below:</p> <codesnippet> <font color="Blue">class</font> <font>»</font> <font color="#A31515">MyConsoleExporter</font> <font>»:</font>»</codesnippet> <codesnippet>BaseExporter<Activity>{</codesnippet> <codesnippet>»»»»<font color="Blue">public</font><font>»override»ExportResult»</font></codesnippet> <codesnippet> <font color="#A31515">Export</font> <font>(in»Batch<Activity>»batch)</font> </codesnippet> <codesnippet> <font>»»»»</font>{</codesnippet> <codesnippet>»»»»»»»»<font color="Blue">using</font>»var»scope»=»SuppressInstrumentationScope.Begin();</codesnippet> <codesnippet>»»»»»»»»Console.WriteLine</codesnippet> <codesnippet>(<font color="#A31515">"Displaying»telemetry»data:-"</font>);</codesnippet> <codesnippet>»»»»»»»»foreach»(var»activity»in»batch)</codesnippet> <codesnippet>»»»»»»»»{</codesnippet> <codesnippet>»»»»»»»»»»»»Console.WriteLine</codesnippet> <codesnippet>($<font color="#A31515">"Activity»Id:»{activity.Id}"</font>);</codesnippet> <codesnippet>»»»»»»»»»»»»Console.WriteLine</codesnippet> <codesnippet>($<font color="#A31515">"Trace»Id:»</font></codesnippet> <codesnippet> <font color="#A31515">{activity.TraceId.ToString()}"</font>);</codesnippet> <codesnippet>»»»»»»»»»»»»Console.WriteLine</codesnippet> <codesnippet>($<font color="#A31515">"Display»Name:»</font></codesnippet> <codesnippet> <font color="#A31515">{activity.DisplayName}"</font>);</codesnippet> <codesnippet>»»»»»»»»}</codesnippet> <codesnippet>»»»»»»»»<font color="Blue">return</font>»ExportResult.Success;</codesnippet> <codesnippet>»»»»}</codesnippet> <codesnippet>}</codesnippet> <p id="58">Here's how you can add your custom exporter in the Program.cs file:</p> <codesnippet>builder.Services.</codesnippet> <codesnippet>AddOpenTelemetryTracing(</codesnippet> <codesnippet> <font>(builder)</font> <font>»=></font>»builder</codesnippet> <codesnippet>.SetResourceBuilder(ResourceBuilder.</codesnippet> <codesnippet>CreateDefault().AddService</codesnippet> <codesnippet>(<font color="#A31515">"MyServiceName"</font>))</codesnippet> <codesnippet>.AddAspNetCoreInstrumentation()</codesnippet> <codesnippet>.AddHttpClientInstrumentation()</codesnippet> <codesnippet>.AddMyConsoleExporter()</codesnippet> <codesnippet>);</codesnippet> <p id="59">When you execute the application and hit the GetOrders endpoint, the activity details will be displayed at the developer console window, as shown in <b>Figure 5</b>.</p> <p id="60"> </p> <figure id="5" src=""> <b>Figure </b> <b>5</b> <b>:</b> Displaying activity details using a custom exporter</figure> <h2>Build a Custom Processor</h2> <p id="61">To create a custom processor, create a C# class that extends the BaseProcessor class of the OpenTelemetry library, as shown in the code snippet given below:</p> <codesnippet> <font color="Blue">class</font> <font>»</font> <font color="#A31515">MyProcessor</font> <font>»:»</font> </codesnippet> <codesnippet> <font color="#A31515">BaseProcessor</font> <font><Activity></font> </codesnippet> <codesnippet>{</codesnippet> <codesnippet>»»»public»override»void»OnStart</codesnippet> <codesnippet>(Activity»activity)</codesnippet> <codesnippet>»»»{</codesnippet> <codesnippet>»»»»»»Console.WriteLine</codesnippet> <codesnippet>»»»»»»($"OnStart»<font color="#00B0E8">called:</font></codesnippet> <codesnippet>»»»»»»{activity.DisplayName}<font color="#A31515">");</font></codesnippet> <codesnippet> <font color="#A31515">»»»}</font> </codesnippet> <codesnippet> <font color="#A31515">»»»public»override»void»OnEnd</font> </codesnippet> <codesnippet> <font color="#A31515">(Activity»activity)</font> </codesnippet> <codesnippet> <font color="#A31515">»»»{</font> </codesnippet> <codesnippet> <font color="#A31515">»»»»»»Console.WriteLine</font> </codesnippet> <codesnippet> <font color="#A31515">»»»»»»($"</font>OnEnd»<font color="#00B0E8">called:</font>»</codesnippet> <codesnippet>»»»»»»{activity.DisplayName}<font color="#A31515">");</font></codesnippet> <codesnippet> <font color="#A31515">»»»}</font> </codesnippet> <codesnippet> <font color="#A31515">}</font> </codesnippet> <h2>Export Telemetry Data</h2> <p id="62">OpenTelemetry data can be exported to back-ends using trace exporters. Some of the popular trace exporters available are:</p> <list type="bulleted"> <bulletedlist>Jaeger</bulletedlist> <bulletedlist>Zipkin</bulletedlist> <bulletedlist>OLTP gRPC</bulletedlist> <bulletedlist>OLTP HTTP</bulletedlist> </list> <p id="63">In this example, you'll examine how you can export telemetry data using Jaeger. To export your telemetry data using Jaeger, here’s what you’ll need to do:</p> <list type="numbered"> <numberedlist>Install Docker Desktop from here: https://www.docker.com/products/docker-desktop/.</numberedlist> <numberedlist>Install the OpenTelemetry.Exporter.Jaeger NuGet package.</numberedlist> <numberedlist>Configure Jaeger Exporter in the Program.cs file.</numberedlist> <numberedlist>Use Docker-Compose to Start Jaeger.</numberedlist> </list> <h3>Install the OpenTelemetry.Exporter.Jaeger NuGet Package</h3> <p id="64">You can install the OpenTelemetry.Exporter.Jaeger package from the Developer Command Prompt for VS 2022 Preview by executing the following commands:</p> <codesnippet> <font color="Blue">dotnet</font>»<font color="Blue">add</font>»<font color="Blue">package</font>»</codesnippet> <codesnippet> <font color="Blue">--prerelease</font>»</codesnippet> <codesnippet> <font color="Blue">OpenTelemetry</font> <font>.Exporter.Jaeger</font> </codesnippet> <p id="65">You can also install the package from the NuGet Package Manager windows inside the Visual Studio 2022 Preview IDE.</p> <h3>Configure Jaeger Exporter in the Program.cs file</h3> <p id="66">Now, replace the code you wrote earlier to enable OpenTelemetry tracing using the following piece of code:</p> <codesnippet>builder.Services.</codesnippet> <codesnippet>AddOpenTelemetryTracing</codesnippet> <codesnippet>(<font>(builder)</font><font>»=></font>»builder</codesnippet> <codesnippet>»»»».SetResourceBuilder</codesnippet> <codesnippet>(ResourceBuilder.</codesnippet> <codesnippet>CreateDefault().AddService</codesnippet> <codesnippet>(<font color="#A31515">"MyDemoService"</font>))</codesnippet> <codesnippet>»»»».AddAspNetCoreInstrumentation()</codesnippet> <codesnippet>»»»».AddJaegerExporter()</codesnippet> <codesnippet>);</codesnippet> <p id="67">This allows you to export your telemetry trace data using Jaeger. The call to the AddAspNetCoreInstrumentation method enables ASP.NET Core instrumentation for your application at application startup.</p> <h3>Use Docker-Compose to Start Jaeger</h3> <p id="68">Assuming Docker Desktop is already running in your system, you can use the following command to start Jaeger:</p> <codesnippet>docker-compose»up»-d</codesnippet> <p id="69">You can write the above command in a PowerShell window or a Command window. In either case, ensure that you open the windows in administrator mode, as shown in <b>Figure 6</b>.</p> <figure id="6" src="image6.png"> <b>Figure 6:</b> The docker-compose command starts a container</figure> <h3>The Jaeger UI</h3> <p id="70">Assuming that Jaeger started successfully, you can browse the default URL of the Jaeger UI, as shown in <b>Figure 7</b>. By default, the Jaeger UI can be viewed at http://localhost:16686.</p> <figure id="7" src="image7.png"> <b>Figure </b> <b>7</b> <b>:</b> The Jaeger UI</figure> <p id="71">Finally, execute your application in a web browser. You can now see the traces in the Jaeger UI for your service, as shown in <b>Figure 8</b>:</p> <figure id="8" src="image8.png"> <b>Figure </b> <b>8</b> <b>:</b> Viewing MyDemoService traces in the Jaeger UI</figure> <h2>Conclusion</h2> <p id="72">OpenTelemetry is an open-source project that provides tools for distributed tracing. It includes libraries for different programming languages, including .NET. OpenTelemetry can be used to prepare applications for distributed tracing. It also provides a set of APIs that can be used to collect trace data from a variety of sources.</p> <codesnippet> <font color="Red">public</font>»<font color="#A31515">class»OrderRepository»:»IOrderRepository</font></codesnippet> <codesnippet> <font color="Red">{</font> </codesnippet> <codesnippet>»»»»<font color="Red">private</font>»<font color="#A31515">readonly»List<Order>»orders»=»new»List<Order></font></codesnippet> <codesnippet>»»»»<font color="Red">{</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">new</font>»<font color="#A31515">Order</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">{</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">Id</font>»=»<font color="#A31515">1,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">ProductId</font>»=»<font color="#A31515">1,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">CustomerId</font>»=»<font color="#A31515">2,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">OrderQuantity</font>»=»<font color="#A31515">10,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">UnitPrice</font>»=»<font color="#A31515">12500.00m,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">Amount</font>»=»<font color="#A31515">125000.00m,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">OrderDate</font>»=»<font color="#A31515">DateTime.Now</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">},</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">new</font>»<font color="#A31515">Order</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">{</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">Id</font>»=»<font color="#A31515">2,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">ProductId</font>»=»<font color="#A31515">2,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">CustomerId</font>»=»<font color="#A31515">1,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">OrderQuantity</font>»=»<font color="#A31515">20,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">UnitPrice</font>»=»<font color="#A31515">10000.00m,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">Amount</font>»=»<font color="#A31515">200000.00m,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">OrderDate</font>»=»<font color="#A31515">DateTime.Now</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">},</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">new</font>»<font color="#A31515">Order</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">{</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">Id</font>»=»<font color="#A31515">3,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">ProductId</font>»=»<font color="#A31515">3,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">CustomerId</font>»=»<font color="#A31515">3,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">OrderQuantity</font>»=»<font color="#A31515">50,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">UnitPrice</font>»=»<font color="#A31515">15000.00m,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">Amount</font>»=»<font color="#A31515">750000.00m,</font></codesnippet> <codesnippet>»»»»»»»»»»»»<font color="Red">OrderDate</font>»=»<font color="#A31515">DateTime.Now</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">}</font></codesnippet> <codesnippet>»»»»<font color="Red">};</font></codesnippet> <codesnippet>»»»»<font color="Red">public</font>»<font color="#A31515">async»Task<List<Order>>»GetOrders()</font></codesnippet> <codesnippet>»»»»<font color="Red">{</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">return</font>»<font color="#A31515">await»Task.FromResult(orders);</font></codesnippet> <codesnippet>»»»»<font color="Red">}</font></codesnippet> <codesnippet>»»»»<font color="Red">public</font>»<font color="#A31515">async»Task<Order>»GetOrder(int»Id)</font>»»»»<font color="Red">{</font></codesnippet> <codesnippet>»»»»»»»»<font color="Red">return</font>»<font color="#A31515">await»Task.FromResult</font></codesnippet> <codesnippet>»»»»»»»»<font color="#2B91AF">(orders.FirstOrDefault(x</font>»=<font color="#A31515">>»x.Id»==»Id));</font></codesnippet> <codesnippet>»»»»<font color="Red">}</font></codesnippet> <codesnippet> <font color="Red">}</font> </codesnippet> </body> <sidebars> <sidebar title="Sponsored Sidebar" /> <sidebar title="Ready to Modernize a Legacy App?"> <p id="73">Need FREE advice on migrating yesterday’s legacy applications to today’s modern platforms? Get answers by taking advantage of CODE Consulting’s years of experience by contacting us today to schedule your free hour of CODE consulting call. No strings. No commitment. Nothing to buy. For more information, visit www.codemag.com/consulting or email us at info@codemag.com.</p> </sidebar> </sidebars> <tables /> <codelistings> <codelisting id="1" header="Listing 1: The Complete Source Code of the Program.cs file"> <code>using»OpenTelemetry;</code> <code>using»OpenTelemetry.Logs;</code> <code>using»OpenTelemetry.Metrics;</code> <code>using»OpenTelemetry.Resources;</code> <code>using»OpenTelemetry.Trace;</code> <code>using»OpenTelemetryDemo;</code> <code /> <code> <font color="Blue">var</font>»builder»=»WebApplication.</code> <code>CreateBuilder(args);</code> <code /> <code>builder.Services.AddControllers();</code> <code>builder.Services.AddEndpointsApiExplorer();</code> <code>builder.Services.AddSwaggerGen();</code> <code /> <code>builder.Services.AddOpenTelemetryTracing</code> <code>(<font>(</font><font>builder</font><font>)»=></font>»builder</code> <code>»»»»»»»»»»»».SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyServiceName"</font>))</code> <code>»»»»»»»»»»»».AddAspNetCoreInstrumentation()</code> <code>»»»»»»»»»»»».AddHttpClientInstrumentation()</code> <code>»»»»»»»»»»»».AddMyConsoleExporter()</code> <code>);</code> <code> <font color="Green">//»Add»support»for»tracing</font> </code> <code>builder.Services»»»».AddOpenTelemetryTracing(<font>(</font><font>builder</font><font>)»=></font>»</code> <code>builder</code> <code>»»»»»»»».SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyDemoService"</font>))</code> <code>»»»»»»»».AddAspNetCoreInstrumentation()</code> <code>»»»»»»»»».AddHttpClientInstrumentation()</code> <code>»»»»»»»».AddConsoleExporter()</code> <code>»»»»);</code> <code /> <code> <font color="Green">//»Add»support»for»metrics</font> </code> <code>builder.Services</code> <code>»»»».AddOpenTelemetryMetrics(<font>builder</font><font>»=></font>»</code> <code>builder</code> <code>»»»»»»»».SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyDemoService"</font>))</code> <code>»»»»»»»».AddAspNetCoreInstrumentation()</code> <code>»»»»»»»».AddConsoleExporter()</code> <code>»»»»);</code> <code> <font color="Green">//»Add»support»for»logging</font> </code> <code>builder.Host</code> <code>»»»».ConfigureLogging(<font>builder</font><font>»=></font>»builder</code> <code>»»»»»»»».ClearProviders()</code> <code>»»»»»»»».AddOpenTelemetry(<font>options</font><font>»=></font></code> <code>»»»»»»»»{</code> <code>»»»»»»»»»»»»options.IncludeFormattedMessage»=»<font color="#A31515">true</font>;</code> <code>»»»»»»»»»»»»options.SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyDemoService"</font>));</code> <code>»»»»»»»»»»»»options.AddConsoleExporter();</code> <code>»»»»»»»»}));</code> <code /> <code>builder.Services.AddControllers();</code> <code>builder.Services.AddEndpointsApiExplorer();</code> <code>builder.Services.AddSwaggerGen();</code> <code /> <code> <font color="Blue">var</font>»app»=»builder.Build();</code> <code /> <code> <font color="Green">//»Configure»the»HTTP»request»pipeline.</font> </code> <code> <font color="Blue">if</font>»(app.Environment.IsDevelopment())</code> <code>{</code> <code>»»»»app.UseSwagger();</code> <code>»»»»app.UseSwaggerUI();</code> <code>}</code> <code /> <code>app.UseHttpsRedirection();</code> <code>app.UseAuthorization();</code> <code>app.MapControllers();</code> <code /> <code>app.Run();</code> </codelisting> <codelisting id="2" header="Listing 2: The OrderRepository Class" /> <codelisting id="3" header="Listing 3: The Complete Source of Program.cs file"> <code>using»OpenTelemetry;</code> <code>using»OpenTelemetry.Logs;</code> <code>using»OpenTelemetry.Metrics;</code> <code>using»OpenTelemetry.Resources;</code> <code>using»OpenTelemetry.Trace;</code> <code>using»OpenTelemetryDemo;</code> <code /> <code> <font color="Blue">var</font>»builder»=»WebApplication.CreateBuilder(args);</code> <code /> <code>builder.Services.AddControllers();</code> <code>builder.Services.AddEndpointsApiExplorer();</code> <code>builder.Services.AddScoped</code> <code><IOrderRepository,»OrderRepository>();</code> <code /> <code>builder.Services.AddOpenTelemetryTracing(</code> <code>»»»»<font>(</font><font>builder</font><font>)»=></font></code> <code>»»»»»»»»Builder</code> <code>»»»»»»»»»»»».SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyServiceName"</font>))</code> <code>»»»»»»»»»»»».AddAspNetCoreInstrumentation()</code> <code>»»»»»»»»»»»».AddHttpClientInstrumentation()</code> <code>»»»»»»»»»»»».AddMyConsoleExporter()</code> <code>);</code> <code> <font color="Green">//»Configure»tracing</font> </code> <code>builder.Services.AddOpenTelemetryTracing(</code> <code>»»»»<font>(</font><font>builder</font><font>)»=></font></code> <code>»»»»»»»»Builder</code> <code>»»»»»»»»»»»».SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyDemoService"</font>))</code> <code>»»»»»»»»»»»».AddAspNetCoreInstrumentation()</code> <code>»»»»»»»»»»»».AddHttpClientInstrumentation()</code> <code>»»»»»»»»»»»».AddConsoleExporter()</code> <code>);</code> <code /> <code> <font color="Green">//»Configure»metrics</font> </code> <code>builder.Services.AddOpenTelemetryMetrics(</code> <code>»»»»<font>builder</font><font>»=></font></code> <code>»»»»»»»»builder</code> <code>»»»»»»»»»»»».SetResourceBuilder(ResourceBuilder.</code> <code>CreateDefault().AddService(<font color="#A31515">"MyDemoService"</font>))</code> <code>»»»»»»»»»»»».AddAspNetCoreInstrumentation()</code> <code>»»»»»»»»»»»».AddConsoleExporter()</code> <code>);</code> <code /> <code> <font color="Green">//»Configure»logging</font> </code> <code>builder.Host.ConfigureLogging(</code> <code>»»»»<font>builder</font><font>»=></font></code> <code>»»»»»»»»builder</code> <code>»»»»»»»»»»»».ClearProviders()</code> <code>»»»»»»»»»»»».AddOpenTelemetry(</code> <code>»»»»»»»»»»»»»»»»<font>options</font><font>»=></font></code> <code>»»»»»»»»»»»»»»»»{</code> <code>»»»»»»»»»»»»»»»»»»»»options.IncludeFormattedMessage»=»<font color="#A31515">true</font>;</code> <code>»»»»»»»»»»»»»»»»»»»»options.SetResourceBuilder(»»ResourceBuilder.CreateDefault().</code> <code>AddService(<font color="#A31515">"MyDemoService"</font>)</code> <code>»»»»»»»»»»»»»»»»»»»»);</code> <code>»»»»»»»»»»»»»»»»»»»»options.AddConsoleExporter();</code> <code>»»»»»»»»»»»»»»»»}</code> <code>»»»»»»»»»»»»)</code> <code>);</code> <code /> <code>builder.Services.AddControllers();</code> <code>builder.Services.AddEndpointsApiExplorer();</code> <code> <font color="Blue">var</font>»app»=»builder.Build();</code> <code /> <code> <font color="Green">//»Configure»the»HTTP»request»pipeline.</font> </code> <code>app.UseHttpsRedirection();</code> <code>app.UseAuthorization();</code> <code>app.MapControllers();</code> <code /> <code>app.Run();</code> </codelisting> <codelisting id="4" header="Listing 4: The OrderController Class"> <code>[<font color="#2B91AF">Route(</font><font color="#2B91AF">"api/[controller]"</font><font color="#2B91AF">)</font>]</code> <code>[<font color="#2B91AF">ApiController</font>]</code> <code> <font color="Blue">public</font>»<font color="Blue">class</font>»<font color="#A31515">OrdersController</font>»:»</code> <code> <font color="#A31515">ControllerBase</font> </code> <code>{</code> <code>»»»»<font color="Blue">private</font>»IOrderRepository»_orderRepository;</code> <code>»»»»<font color="Blue">public</font><font>»</font><font color="#A31515">OrdersController</font><font>(</font><font>IOrderRepository»</font></code> <code> <font color="Blue">»»»»</font> <font>orderRepository</font> <font>)</font> </code> <code>»»»»{</code> <code>»»»»»»»»_orderRepository»=»orderRepository;</code> <code>»»»»}</code> <code /> <code>»»»»[<font color="#2B91AF">HttpGet(</font><font color="#2B91AF">"GetOrders"</font><font color="#2B91AF">)</font>]</code> <code>»»»»<font color="Blue">public</font>»<font color="Blue">async</font>»Task<List<Order>>»GetOrders()</code> <code>»»»»{</code> <code>»»»»»»»»<font color="Blue">return</font>»<font color="Blue">await</font>»_orderRepository.GetOrders();</code> <code>»»»»}</code> <code /> <code>»»»»[<font color="#2B91AF">HttpGet(</font><font color="#2B91AF">"{id}"</font><font color="#2B91AF">)</font>]</code> <code>»»»»<font color="Blue">public</font><font>»</font><font color="Blue">async</font><font>»Task<Order>»</font><font color="#A31515">GetOrder</font><font>(</font><font color="Blue">int</font><font>»id</font><font>)</font></code> <code>»»»»{</code> <code>»»»»»»»»<font color="Blue">return</font>»<font color="Blue">await</font>»_orderRepository.GetOrder(id);</code> <code>»»»»}</code> <code>}</code> </codelisting> </codelistings> <Errors> <Error>Actual figure not found for this caption: Figure 5: Displaying activity details using a custom exporter</Error> </Errors> </document>