.NET Core is widely used for building modern, cross-platform backend services and web applications. With performance, scalability, and reliability at stake, teams need real visibility into how their applications are running. This means going beyond simple logs—true observability requires metrics and traces.
OpenTelemetry (OTel) makes this possible. It's an open standard for collecting telemetry data—logs, metrics, and traces—in a consistent, vendor-neutral way. With OTel, you can track performance issues, monitor dependencies, and analyze runtime behavior across your entire stack.
In this guide, we’ll walk through how to instrument a .NET Core application with OTel. You’ll see how to capture metrics and trace data, and export that telemetry to SolarWinds® Observability SaaS for visualization and analysis.
Download and Run the Sample Application
To get started, we’ll use the Razor Pages movie listing app from Microsoft’s ASP.NET Core tutorials. This gives us a clean, functional project to work with.
First, download the sample application. You can also check out the official Microsoft Learn instructions. Make sure you have downloaded and installed the .NET 9.0 SDK.
For this basic demo, you will build and run the app locally. This means modifying the application to use SQLite.
Modifications for Local Demo (Use SQLite)
First, add this line <span style="font-weight:400;"><</span><span style="font-weight:400;">ItemGroup</span><span style="font-weight:400;">></span>
<span style="font-weight:400;"> </span>
in <span style="font-weight:400;"> RazorPagesMovie.csproj:</span>
<span style="font-weight:400;"><</span><span style="font-weight:400;">PackageReference</span> <span style="font-weight:400;">Include</span><span style="font-weight:400;">=</span><span style="font-weight:400;">"Microsoft.EntityFrameworkCore.Sqlite"</span> <span style="font-weight:400;">Version</span><span style="font-weight:400;">=</span><span style="font-weight:400;">"9.0.0-preview.3.24172.4"</span><span style="font-weight:400;"> /></span><span style="font-weight:400;"></span>
Next, modify <span style="font-weight:400;">Program.cs</span>
<span style="font-weight:400;"> </span>
to accommodate the SQLite case. The resulting file looks like this:
<span style="font-weight:400;">using Microsoft.EntityFrameworkCore;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using RazorPagesMovie.Data;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using RazorPagesMovie.Models;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">var</span><span style="font-weight:400;"> builder = WebApplication.CreateBuilder(</span><span style="font-weight:400;">args</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Set environment to Development</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">builder.Environment.EnvironmentName = </span><span style="font-weight:400;">"Development"</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">builder.Services.AddRazorPages();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">builder.Services.AddDbContext<RazorPagesMovieContext>(options =></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> options.UseSqlite(builder.Configuration.GetConnectionString(</span><span style="font-weight:400;">"RazorPagesMovieContext"</span><span style="font-weight:400;">) ?? throw new InvalidOperationException(</span><span style="font-weight:400;">"Connection string 'RazorPagesMovieContext' not found."</span><span style="font-weight:400;">)));</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">var</span> <span style="font-weight:400;">app</span><span style="font-weight:400;"> = builder.Build();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using (</span><span style="font-weight:400;">var</span><span style="font-weight:400;"> scope = </span><span style="font-weight:400;">app</span><span style="font-weight:400;">.Services.CreateScope())</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> services = scope.ServiceProvider;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> SeedData.Initialize(services);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Always use developer exception page in development</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (</span><span style="font-weight:400;">app</span><span style="font-weight:400;">.Environment.IsDevelopment())</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseDeveloperExceptionPage();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">else</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseExceptionHandler(</span><span style="font-weight:400;">"/Error"</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseHsts();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseHttpsRedirection();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseStaticFiles();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseRouting();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">app</span><span style="font-weight:400;">.UseAuthorization();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">app</span><span style="font-weight:400;">.MapRazorPages();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">app</span><span style="font-weight:400;">.</span><span style="font-weight:400;">Run</span><span style="font-weight:400;">();</span>
Finally, modify appsettings.json
to point to the correct data source file:
<span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"Logging"</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"LogLevel"</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"Default"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Information"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"Microsoft.AspNetCore"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Warning"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"AllowedHosts"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"*"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"ConnectionStrings"</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"RazorPagesMovieContext"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Data Source=RazorPagesMovie.db"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span>
With these file changes in place, go ahead and run the following commands:
<span style="font-weight:400;">rm -rf Migrations/</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">dotnet ef migrations add InitialCreate</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">dotnet ef database update</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">dotnet run</span>
Now, when you open your browser to <span style="background-color:#fff0cf;">localhost:5000</span>
, you’ll see the application running:

Configure the Application for OpenTelemetry
Before instrumenting the app, you need to add OTel-related dependencies to your project.
Add OpenTelemetry Packages
Use <span style="background-color:#fff0cf;">dotnet add package</span>
to install the necessary OTel packages:
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Extensions.Hosting</span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Instrumentation.AspNetCore</span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Instrumentation.Runtime</span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Exporter.OpenTelemetryProtocol</span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Extensions.Logging</span><span style="font-weight:400;"> </span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Instrumentation.Http</span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;">dotnet add package OpenTelemetry</span><span style="font-weight:400;">.Instrumentation.SqlClient</span></span>
Next, modify Program.cs to set up the OTel services and configure the OTLP exporter to send data to SolarWinds Observability SaaS:
<span style="font-weight:400;">// Program.cs</span>
<span style="font-weight:400;">using Microsoft.EntityFrameworkCore;</span><span style="font-weight:400;"><br /></span><b>using OpenTelemetry;</b><b><br /></b><b>using OpenTelemetry.Logs;</b><b><br /></b><b>using OpenTelemetry.Metrics;</b><b><br /></b><b>using OpenTelemetry.Resources;</b><b><br /></b><b>using OpenTelemetry.Trace;</b>
<b>using OpenTelemetry.Instrumentation.SqlClient;</b>
<span style="font-weight:400;">using RazorPagesMovie.Data;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using RazorPagesMovie.Models;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">var</span><span style="font-weight:400;"> builder = WebApplication.CreateBuilder(args);</span>
<span style="font-weight:400;">// Add OpenTelemetry configuration</span>
<b>builder.Configuration.AddJsonFile("appsettings.OpenTelemetry.json", optional: false, reloadOnChange: true);</b>
<span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Set environment to Development</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">builder.Environment.EnvironmentName = </span><span style="font-weight:400;">"Development"</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Configure OpenTelemetry</span><b><br /></b><b>var otelConfig = builder.Configuration.GetSection("OpenTelemetry");</b><b><br /></b><b>var serviceName = otelConfig["ServiceName"] ?? "RazorPagesMovie";</b><b><br /></b><b>var serviceVersion = otelConfig["ServiceVersion"] ?? "1.0.0";</b><b><br /></b><b>var solarWindsEndpoint = otelConfig.GetSection("SolarWinds")["Endpoint"];</b><b><br /></b><b>var solarWindsToken = otelConfig.GetSection("SolarWinds")["ApiToken"];</b><b><br /></b><b><br /></b><b>if (string.IsNullOrEmpty(solarWindsEndpoint) || string.IsNullOrEmpty(solarWindsToken))</b><b><br /></b><b>{</b><b><br /></b><b> throw new InvalidOperationException("SolarWinds endpoint and API token must be configured in appsettings.OpenTelemetry.json");</b><b><br /></b><b>}</b><b><br /></b><b><br /></b><b>builder.Services.AddOpenTelemetry()</b>
<b> .ConfigureResource(resource => resource</b>
<b> .AddService(serviceName: serviceName, serviceVersion: serviceVersion))</b>
<b> .WithTracing(tracing => tracing</b>
<b> .AddAspNetCoreInstrumentation()</b>
<b> .AddHttpClientInstrumentation()</b>
<b> .AddSqlClientInstrumentation()</b>
<b> .AddOtlpExporter(opts => {</b>
<b> opts.Endpoint = new Uri(solarWindsEndpoint);</b>
<b> opts.Headers = $"authorization=Bearer {solarWindsToken}";</b>
<b> }))</b>
<b> .WithMetrics(metrics => metrics</b>
<b> .AddAspNetCoreInstrumentation()</b>
<b> .AddHttpClientInstrumentation()</b>
<b> .AddRuntimeInstrumentation()</b>
<b> .AddOtlpExporter(opts => {</b>
<b> opts.Endpoint = new Uri(solarWindsEndpoint);</b>
<b> opts.Headers = $"authorization=Bearer {solarWindsToken}";</b>
<b> }));</b>
<span style="font-weight:400;">// Configure OpenTelemetry Logging</span>
<b>builder.Logging.AddOpenTelemetry(logging =></b>
<b>{</b>
<b> logging.IncludeFormattedMessage = true;</b>
<b> logging.IncludeScopes = true;</b>
<b> logging.ParseStateValues = true;</b>
<b> logging.AddOtlpExporter(opts => {</b>
<b> opts.Endpoint = new Uri(solarWindsEndpoint);</b>
<b> opts.Headers = $"authorization=Bearer {solarWindsToken}";</b>
<b> });</b>
<b>});</b>
builder.Services.AddRazorPages();
<span style="font-weight:400;">...</span>
Next, configure your application with the credentials to send OTel data to SolarWinds Observability SaaS.
Get SolarWinds Observability SaaS OTel Endpoint and API Token
To find your OTel endpoint and create an API token for data ingestion, follow these steps.
Log in to SolarWinds Observability SaaS. Take note of the URL. It may look similar to this:
<span style="background-color:#fff0cf;"><a id="" style="background-color:#fff0cf;" href="https://my.na-01.cloud.solarwinds.com/">https://my.na-01.cloud.solarwinds.com/</a></span>
The <span style="background-color:#fff0cf;">xx-yy</span>
part of the URL (in the above example, that's <span style="background-color:#fff0cf;">na-01</span>
) indicates the data center for your organization. You will need this when determining your OTel endpoint.
Navigate to Settings > API Tokens.

On the API Tokens page, click Create API Token.

Specify a name for your new API token. Select Ingestion as the token type. You can also add tags if you'd like. Click Create API Token.

Copy the resulting token value.

Configure OTel Export Settings
Next, create a new configuration file (appsettings.OpenTelemetry.json) to store the SolarWinds Observability SaaS endpoint and API token.
<span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"OpenTelemetry"</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"ServiceName"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"RazorPagesMovie"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"ServiceVersion"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.0.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"SolarWinds"</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"Endpoint"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"https://otel.collector.</span><b>xx-yy</b><span style="font-weight:400;">.cloud.solarwinds.com:443"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"ApiToken"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"</span><b>YOUR_API_TOKEN_HERE</b><span style="font-weight:400;">"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span>
Replace the xx-yy
in the endpoint with your data center code (such as na-01). Then, paste in the API token value that was just copied.
Lastly, update the project file (RazorPagesMovie.csproj) to include this configuration file:
<span style="font-weight:400;"><ItemGroup></span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /></span><b> <None Update="appsettings.OpenTelemetry.json"></b><b><br /></b><b> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></b><b><br /></b><b> </None></b><span style="font-weight:400;"><br /></span><span style="font-weight:400;"></ItemGroup></span>
Instrument the Application
With the application configured with OTel and ready to collect and send telemetry data to SolarWinds Observability SaaS, you're ready to instrument the application.
Logging Middleware and Telemetry Helpers
For convenience, create a class (<span style="background-color:#fff0cf;">Middleware/RequestLoggingMiddleware.cs</span>
) so that you can log every request:
<span style="font-weight:400;">// Middleware/RequestLoggingMiddleware.cs</span>
<span style="font-weight:400;">using</span><span style="font-weight:400;"> System.Diagnostics;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using</span><span style="font-weight:400;"> Microsoft.AspNetCore.Http;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using</span><span style="font-weight:400;"> Microsoft.Extensions.Logging;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">namespace</span> <span style="font-weight:400;">RazorPagesMovie.Middleware</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">public</span> <span style="font-weight:400;">class</span> <span style="font-weight:400;">RequestLoggingMiddleware</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">private</span> <span style="font-weight:400;">readonly</span><span style="font-weight:400;"> RequestDelegate _next;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">private</span> <span style="font-weight:400;">readonly</span><span style="font-weight:400;"> ILogger<RequestLoggingMiddleware> _logger;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">public</span> <span style="font-weight:400;">RequestLoggingMiddleware</span><span style="font-weight:400;">(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> _next = next;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> _logger = logger;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">public</span> <span style="font-weight:400;">async</span><span style="font-weight:400;"> Task </span><span style="font-weight:400;">InvokeAsync</span><span style="font-weight:400;">(HttpContext context)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> sw = Stopwatch.StartNew();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">try</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> _next(context);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">finally</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> sw.Stop();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> elapsed = sw.ElapsedMilliseconds;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> statusCode = context.Response?.StatusCode ?? </span><span style="font-weight:400;">500</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> method = context.Request.Method;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> path = context.Request.Path;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> _logger.LogInformation(</span>
<span style="font-weight:400;"> </span><span style="font-weight:400;">"Request {Method} {Path} completed with status code {StatusCode} in {ElapsedMilliseconds}ms"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> method, path, statusCode, elapsed</span>
<span style="font-weight:400;"> );</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span>
Next, create a class (<span style="background-color:#fff0cf;">Telemetry/TelemetryHelper.cs</span>
) to implement custom traces and metrics.
<span style="font-weight:400;">using</span><span style="font-weight:400;"> System.Diagnostics;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using</span><span style="font-weight:400;"> System.Diagnostics.Metrics;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using</span><span style="font-weight:400;"> OpenTelemetry.Trace;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">using</span><span style="font-weight:400;"> Microsoft.Extensions.Configuration;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">namespace</span> <span style="font-weight:400;">RazorPagesMovie.Telemetry</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">public</span> <span style="font-weight:400;">static</span> <span style="font-weight:400;">class</span> <span style="font-weight:400;">TelemetryHelper</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">private</span> <span style="font-weight:400;">static</span> <span style="font-weight:400;">readonly</span><span style="font-weight:400;"> ActivitySource ActivitySource;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">private</span> <span style="font-weight:400;">static</span> <span style="font-weight:400;">readonly</span><span style="font-weight:400;"> Meter Meter;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Metrics</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">private</span> <span style="font-weight:400;">static</span> <span style="font-weight:400;">readonly</span><span style="font-weight:400;"> Counter<</span><span style="font-weight:400;">long</span><span style="font-weight:400;">> MovieOperationCounter;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">private</span> <span style="font-weight:400;">static</span> <span style="font-weight:400;">readonly</span><span style="font-weight:400;"> Histogram<</span><span style="font-weight:400;">double</span><span style="font-weight:400;">> MovieOperationDuration;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">static</span> <span style="font-weight:400;">TelemetryHelper</span><span style="font-weight:400;">()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> configuration = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> ConfigurationBuilder()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> .AddJsonFile(</span><span style="font-weight:400;">"appsettings.OpenTelemetry.json"</span><span style="font-weight:400;">, optional: </span><span style="font-weight:400;">false</span><span style="font-weight:400;">)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> .Build();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> serviceName = configuration.GetSection(</span><span style="font-weight:400;">"OpenTelemetry"</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">"ServiceName"</span><span style="font-weight:400;">] ?? </span><span style="font-weight:400;">"RazorPagesMovie"</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ctivitySource = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> ActivitySource(serviceName);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> Meter = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> Meter(serviceName);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> MovieOperationCounter = Meter.CreateCounter<</span><span style="font-weight:400;">long</span><span style="font-weight:400;">>(</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"movie_operations_total"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"operations"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"Total number of movie operations performed"</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> MovieOperationDuration = Meter.CreateHistogram<</span><span style="font-weight:400;">double</span><span style="font-weight:400;">>(</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"movie_operation_duration_seconds"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"seconds"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"Duration of movie operations"</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">public</span> <span style="font-weight:400;">static</span><span style="font-weight:400;"> Activity? StartMovieOperation(</span><span style="font-weight:400;">string</span><span style="font-weight:400;"> operation, </span><span style="font-weight:400;">int</span><span style="font-weight:400;">? movieId = </span><span style="font-weight:400;">null</span><span style="font-weight:400;">)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> activity = ActivitySource.StartActivity(</span><span style="font-weight:400;">$"Movie.</span><span style="font-weight:400;">{operation}</span><span style="font-weight:400;">"</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (activity != </span><span style="font-weight:400;">null</span><span style="font-weight:400;">)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> activity.SetTag(</span><span style="font-weight:400;">"movie.operation"</span><span style="font-weight:400;">, operation);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (movieId.HasValue)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> activity.SetTag(</span><span style="font-weight:400;">"movie.id"</span><span style="font-weight:400;">, movieId.Value);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span><span style="font-weight:400;"> activity;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">public</span> <span style="font-weight:400;">static</span> <span style="font-weight:400;">void</span> <span style="font-weight:400;">RecordMovieOperation</span><span style="font-weight:400;">(</span><span style="font-weight:400;">string</span><span style="font-weight:400;"> operation, </span><span style="font-weight:400;">double</span><span style="font-weight:400;"> durationSeconds, </span><span style="font-weight:400;">int</span><span style="font-weight:400;">? movieId = </span><span style="font-weight:400;">null</span><span style="font-weight:400;">)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">var</span><span style="font-weight:400;"> tags = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> KeyValuePair<</span><span style="font-weight:400;">string</span><span style="font-weight:400;">, </span><span style="font-weight:400;">object</span><span style="font-weight:400;">?>[]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">new</span><span style="font-weight:400;">(</span><span style="font-weight:400;">"operation"</span><span style="font-weight:400;">, operation)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> };</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (movieId.HasValue)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> tags = tags.Append(</span><span style="font-weight:400;">new</span><span style="font-weight:400;"> KeyValuePair<</span><span style="font-weight:400;">string</span><span style="font-weight:400;">, </span><span style="font-weight:400;">object</span><span style="font-weight:400;">?>(</span><span style="font-weight:400;">"movie.id"</span><span style="font-weight:400;">, movieId.Value)).ToArray();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> MovieOperationCounter.Add(</span><span style="font-weight:400;">1</span><span style="font-weight:400;">, tags);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> MovieOperationDuration.Record(durationSeconds, tags);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span>
Back in Program.cs
, register your middleware and telemetry components.
<span style="font-weight:400;">// Program.cs</span>
<span style="font-weight:400;">…</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">builder.Services.AddOpenTelemetry()</span>
<span style="font-weight:400;"> .ConfigureResource(resource => resource</span>
<span style="font-weight:400;"> .AddService(serviceName: serviceName, serviceVersion: serviceVersion))</span>
<span style="font-weight:400;"> .WithTracing(tracing => tracing</span>
<span style="font-weight:400;"> .AddAspNetCoreInstrumentation()</span>
<span style="font-weight:400;"> .AddHttpClientInstrumentation()</span>
<span style="font-weight:400;"> .AddSqlClientInstrumentation()</span>
<span style="font-weight:400;"> </span><b>.AddSource(serviceName)</b>
<span style="font-weight:400;"> .AddOtlpExporter(opts => {</span>
<span style="font-weight:400;"> opts.Endpoint = new Uri(solarWindsEndpoint);</span>
<span style="font-weight:400;"> opts.Headers = $"authorization=Bearer {solarWindsToken}";</span>
<span style="font-weight:400;"> }))</span>
<span style="font-weight:400;"> .WithMetrics(metrics => metrics</span>
<span style="font-weight:400;"> .AddAspNetCoreInstrumentation()</span>
<span style="font-weight:400;"> .AddHttpClientInstrumentation()</span>
<span style="font-weight:400;"> .AddRuntimeInstrumentation()</span>
<span style="font-weight:400;"> </span><b>.AddMeter(serviceName)</b>
<span style="font-weight:400;"> .AddOtlpExporter(opts => {</span>
<span style="font-weight:400;"> opts.Endpoint = new Uri(solarWindsEndpoint);</span>
<span style="font-weight:400;"> opts.Headers = $"authorization=Bearer {solarWindsToken}";</span>
<span style="font-weight:400;"> }));</span>
<span style="font-weight:400;">…</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><b>app.UseMiddleware<RequestLoggingMiddleware>();</b>
Set up Tracing and Metrics
Finally, throughout the application, add instrumentation code. For example, in the Create movie operation, you can capture request duration metrics.
<span style="font-weight:400;">// Pages/Movies/Create.cshtml.cs</span>
<span style="font-weight:400;">public async Task<IActionResult> OnPostAsync()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">{</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> var </span><span style="font-weight:400;">sw </span><span style="font-weight:400;">= Stopwatch.StartNew()</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> using var activity = TelemetryHelper.StartMovieOperation(</span><span style="font-weight:400;">"Create"</span><span style="font-weight:400;">)</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> try</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> if (!ModelState.IsValid)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> return Page()</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> _</span><span style="font-weight:400;">context</span><span style="font-weight:400;">.Movie.</span><span style="font-weight:400;">Add(Movie);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> await _</span><span style="font-weight:400;">context</span><span style="font-weight:400;">.SaveChangesAsync()</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> _logger.LogInformation(</span><span style="font-weight:400;">"Created movie: {Title}"</span><span style="font-weight:400;">, Movie.Title)</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> TelemetryHelper.RecordMovieOperation(</span><span style="font-weight:400;">"Create"</span><span style="font-weight:400;">, </span><span style="font-weight:400;">sw.Elapsed.TotalSeconds);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> return RedirectToPage(</span><span style="font-weight:400;">"./Index"</span><span style="font-weight:400;">)</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> catch (Exception ex)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> _logger.LogError(ex, </span><span style="font-weight:400;">"Error creating movie: {Title}"</span><span style="font-weight:400;">, Movie.Title)</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> activity?.SetStatus(ActivityStatusCode.Error, ex.Message)</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> throw</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span>
<span style="font-weight:400;">}</span>
Your application is now instrumented with OTel across all three pillars:
- Logging: HTTP requests (via
RequestLoggingMiddleware
) and movie operations (in page models) - Traces: HTTP/SQL operations (automatic) and movie operations (via
TelemetryHelper
) - Metrics: Runtime/HTTP metrics (automatic) and movie operation counts/durations (via
<span style="background-color:#fff0cf;">TelemetryHelper</span>
)
All telemetry is exported to SolarWinds with consistent service naming and proper authentication. You're ready to start it up again.
<b>dotnet run</b>
This time, with the application running, try performing different operations and transactions to simulate activity and generate telemetry data.
Confirm Data Ingestion With SolarWinds Observability SaaS
To confirm that your application is successfully sending telemetry data to SolarWinds Observability SaaS, log in to your account and navigate to APM.

There, you should see your application listed.

Clicking the application brings you to an APM overview page for your application. You’ll see the presence of traces (50+) and metrics (4) just by looking at the top bar nav.

The Traces subpage shows the custom spans that you have instrumented for tracing in your application's Movie model.

When you click on the details for any individual trace, you’ll see detailed information about the times spent in each of the components throughout the action.

Similarly, you can visit the Metrics subpage for your application. From there, you can see metrics, such as the average response time.

Finally, to see log events captured with OTel and exported to SolarWinds Observability SaaS, navigate to the Logs section of the site.

On that page, there’s a comprehensive set of log events, from your request middleware, explicit logging statements in your Movie actions, and within your database operations.

You have successfully instrumented your .NET Core application with OTel, and your telemetry data is shipping to SolarWinds Observability SaaS for easier analysis and querying.
Wrap-up and Next Steps
We’ve shown how to instrument a .NET Core app with OpenTelemetry to capture logs, metrics, and traces. By using open standards, you can make your observability setup portable and extensible—there is no vendor lock-in.
If you’re looking for a powerful and user-friendly way to analyze and visualize your telemetry data, check out SolarWinds Observability SaaS. It’s built to work seamlessly with OTel and supports a wide range of application stacks, including .NET Core.
<b></b>
<span style="font-weight:400;"></span>
<b></b>
<span style="font-weight:400;"> </span>
</code></span><span style="background-color:#fff0cf;"><code><span style="font-weight:400;"></span>
<span style="font-weight:400;"> </span>
<span style="background-color:#fff0cf;"></span>
<span style="font-weight:400;"></span>
<span style="font-weight:400;"></span>
<span style="font-weight:400;"></span><span style="font-weight:400;"></span>