Skip to content

Commit 9c33704

Browse files
authored
Post/migrate-aspnetcore-to-otel (#12)
* scaffolded initial post structure * WIP current app insights setup * WIP working on migration section * added section on including scopes * wip enriching logs * WIP finished initial content * renamed post * added sample project and wrap up * grammar improvements
1 parent aa59184 commit 9c33704

File tree

6 files changed

+178
-0
lines changed

6 files changed

+178
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
---
2+
title: Migrating ASP.NET Core to OpenTelemetry
3+
category: .NET
4+
tags:
5+
- .NET
6+
- Dotnet
7+
- ASPNET
8+
- ASP.NET
9+
- ASP.NET Core
10+
- Migration
11+
- Logging
12+
- Monitoring
13+
- App Insights
14+
- OpenTelemetry
15+
---
16+
17+
App Insights integration with ASP.NET Core has been a common feature of many applications since its release in 2016. However, with the rise of the open-standard **[OpenTelemetry](https://opentelemetry.io/)** and its wide adoption by multiple platforms and monitoring tools, Microsoft has been working on adopting OpenTelemetry as the telemetry middleware for ASP.NET Core.
18+
19+
As part of Microsoft recently **[starting to caution against using the legacy App Insights integration](https://learn.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core)**, I've started migrating my projects to use the OpenTelemetry integration. For this post, I want to share how I've gone about the migration as well as a couple of obstacles I encountered.
20+
21+
## The Current App Insights Integration
22+
23+
To set the scene, let's have a quick look at how the legacy App Insights integration is set up and how it looks. Firstly, the App Insights Nuget package needs to be installed:
24+
25+
``` bash
26+
dotnet add package Microsoft.ApplicationInsights.AspNetCore
27+
```
28+
29+
Once installed, the `.AddApplicationInsightsTelemetry()` extension is then added to register the logging provider:
30+
31+
``` cs
32+
var builder = WebApplication.CreateBuilder(args);
33+
builder.Services.AddApplicationInsightsTelemetry();
34+
35+
// Add other services
36+
37+
var app = builder.Build();
38+
await app.RunAsync();
39+
```
40+
41+
Lastly, the App Insights connection string needs to be configured by configuring `APPLICATIONINSIGHTS_CONNECTION_STRING`.
42+
43+
![image1](/images/migrating-aspnet-core-to-opentelemetry/image1.png)
44+
45+
Logging events using `ILogger` will then create logs in App Insights similar to above. The key thing to note is the inclusion of various values under **customDimensions** such as **RequestPath and CategoryName** which can help filter and group logs without needing to deconstruct a log message.
46+
47+
## What is OpenTelemetry?
48+
49+
OpenTelemetry is an **open-source observability framework** which has been widely adopted by the industry. The framework is intended to function as a middleware in generating, collecting and exporting telemetry from solution components (including software and platform components) to observability tools.
50+
51+
![image2](/images/migrating-aspnet-core-to-opentelemetry/image2.png)
52+
53+
So why use OpenTelemetry? Just as cloud computing has resulted in more decoupled and microservice-style solutions, OpenTelemetry addresses these needs through its open standard. This open standard therefore allows decoupling the generation and collection of telemetry from exporting that telemetry. The **[OpenTelemetry docs](https://opentelemetry.io/docs/what-is-opentelemetry/)** are incredibly detailed if you want to dig deeper.
54+
55+
### Migrating to OpenTelemetry with App Insights
56+
57+
So how can we migrate to using OpenTelemetry to integrate with App Insights? Microsoft do offer a **[guide for migration](https://learn.microsoft.com/en-us/azure/azure-monitor/app/opentelemetry-dotnet-migrate)** but lets start by summarising the removal steps:
58+
59+
1. Remove the `Microsoft.ApplicationInsights.AspNetCore` package from projects.
60+
2. Remove the `builder.Services.AddApplicationInsightsTelemetry()` integration
61+
3. Remove references to App Insights components and clean the solution
62+
63+
With the legacy App Insights integration removed, we can now start to **add the OpenTelemetry integration**. Firstly, the ASP.NET Core OpenTelemetry packages need to be installed:
64+
65+
``` bash
66+
dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore
67+
```
68+
69+
The basic OpenTelemetry and App Insights can then be enabled by adding `builder.Services.AddOpenTelemetry().UseAzureMonitor()`
70+
71+
``` cs
72+
var builder = WebApplication.CreateBuilder(args);
73+
74+
var otelBuilder = builder.Services.AddOpenTelemetry();
75+
if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
76+
otelBuilder.UseAzureMonitor();
77+
78+
// Add other services
79+
80+
var app = builder.Build();
81+
await app.RunAsync();
82+
```
83+
84+
Above is a basic example of enabling the OpenTelemetry integration. However, the first difference with the legacy App Insights integration is that the connection string **is now mandatory** either by reusing the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable or configuring in an options delegate in `.UseAzureMonitor()`. To maintain the previous optional configuration (for scenarios such as running locally where App Insights isn't needed) we can check the environment variable is set before configuring the builder returned by `.AddOpenTelemetry()`.
85+
86+
Let's have a look at the logs this integration produces:
87+
88+
![image3](/images/migrating-aspnet-core-to-opentelemetry/image3.png)
89+
90+
So, we can see the basic logged message and the values of the formatted message, however, the **custom dimensions** contain a lot less detail. So how can we get these details back?
91+
92+
### Including Scopes
93+
94+
One of the great features of the .NET Core `ILogger` is its **[scoping capability]({% post_url 2024-12-02-my-approach-to-logging%})** allowing contextual values to be attached to all logged events in a scope. However, after **[digging around in the Azure.Monitor.OpenTelemetry.AspNetCore packge](https://github.com/Azure/azure-sdk-for-net/blob/b04b7e05f7a7002f8dec9d897d0400777874c3b4/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs#L155)** I found that including the scoped values weren't enabled by default:
95+
96+
``` cs
97+
var otelBuilder = services.AddOpenTelemetry()
98+
.WithLogging(configureBuilder => { }, configureOptions =>
99+
{
100+
configureOptions.IncludeScopes = true;
101+
configureOptions.IncludeFormattedMessage = true;
102+
configureOptions.ParseStateValues = true;
103+
})
104+
```
105+
106+
There are a few ways to configure enabling including scopes, I've opted for the approach above of using the `.WithLogging()` extension on the `OpenTelemetryBuilder` to keep the configuration all under the `IServiceCollection` extension. Below, we can now see **more customDimensions data included**.
107+
108+
![image4](/images/migrating-aspnet-core-to-opentelemetry/image4.png)
109+
110+
### Enriching the Logs
111+
112+
Two values that are still missing are **CategoryName and OriginalFormat** which I find can be useful for filtering logs specific to the namespace of your application and looking for logs using the message straight out of your code.
113+
114+
These values are available as properties on the OpenTelemetry `LogRecord` model, however, the **Azure Monitor exporter** populates the customDimensions using the `LogRecord.Attributes` property.
115+
116+
``` cs
117+
public class LogEnrichmentProcessor : BaseProcessor<LogRecord>
118+
{
119+
private const string CategoryNameKey = nameof(LogRecord.CategoryName);
120+
private const string LogLevelKey = nameof(LogRecord.LogLevel);
121+
private const string OriginalFormatKey = "OriginalFormat";
122+
123+
public override void OnEnd(LogRecord data)
124+
{
125+
var attributes = data.Attributes is not null
126+
? new List<KeyValuePair<string, object?>>(data.Attributes)
127+
: [];
128+
129+
if (data.Attributes is not null && data.Attributes.Any())
130+
attributes.AddRange(data.Attributes);
131+
132+
if (!attributes.Any(a => a.Key == LogLevelKey))
133+
attributes.Add(new(LogLevelKey, data.LogLevel.ToString()));
134+
135+
if (!attributes.Any(a => a.Key == CategoryNameKey) && !string.IsNullOrWhiteSpace(data.CategoryName))
136+
attributes.Add(new(CategoryNameKey, data.CategoryName));
137+
138+
if (!attributes.Any(a => a.Key == OriginalFormatKey) && !string.IsNullOrWhiteSpace(data.Body))
139+
attributes.Add(new(OriginalFormatKey, data.Body));
140+
141+
data.Attributes = attributes;
142+
base.OnEnd(data);
143+
}
144+
}
145+
```
146+
147+
OpenTelemetry offers the ability to **[enrich telemetry through processors](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/docs/trace/extending-the-sdk/README.md#enriching-processor)**. The documentation and samples for creating processors for `LogRecord` can be **[found here](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/docs/logs/extending-the-sdk/README.md#processor)**. Above is the processor I put together to enrich logs with **LogLevel, CategoryName and OriginalFormat**.
148+
149+
``` cs
150+
var otelBuilder = services.AddOpenTelemetry()
151+
.WithLogging(configureBuilder =>
152+
{
153+
configureBuilder.AddProcessor<LogEnrichmentProcessor>();
154+
}, configureOptions =>
155+
{
156+
configureOptions.IncludeScopes = true;
157+
configureOptions.IncludeFormattedMessage = true;
158+
configureOptions.ParseStateValues = true;
159+
});
160+
```
161+
162+
The processor can then be registered with our existing `.WithLogging()` setup.
163+
164+
![image5](/images/migrating-aspnet-core-to-opentelemetry/image5.png)
165+
166+
The logs created in Azure Monitor should now look similar to above with the **LogLevel, CategoryName and OriginalFormat** included.
167+
168+
## Sample Project
169+
170+
As always, the samples in this post are taken from the sample project I've prepared.
171+
172+
[![milkyware/blog-migrate-aspnetcore-appinsights-to-otel - GitHub](https://gh-card.dev/repos/milkyware/blog-migrate-aspnetcore-appinsights-to-otel.svg)](https://github.com/milkyware/blog-migrate-aspnetcore-appinsights-to-otel)
173+
174+
## Wrapping Up
175+
176+
With the direction of travel from Microsoft being to adopt OpenTelemetry, I've wanted to share how I've migrated my projects to use OpenTelemetry. As a framework, OpenTelemetry is a fantastic tool that offers decoupling and flexibility such as swapping out exporters and, although not covered in this post, supports distributed tracing for developing observability in solutions using microservice components.
177+
178+
I've also highlighted some of the differences in functionality between the legacy App Insights integration and the default OpenTelemetry setup, but this can be configured to a similar level and retain support for any existing Azure Monitor KQL queries we may be using. I hope you find this useful and please feel free to try it out.
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)