---
title: "OpenTelemetry in .NET: A Beginner-Friendly Guide"  
description: "Learn OpenTelemetry in .NET from scratch. Understand logs, metrics, traces, spans, instrumentation, exporters, OTLP, and see practical C# examples with comments"  
author: "ICSM Computer"  
published: 2026-08-14  
updated: 2026-08-14  
canonical: https://answers.mindstick.com/blog/544/opentelemetry-in-dot-net-a-beginner-friendly-guide  
category: "programming"  
tags: [".net programming", "asp.net"]  
reading_time: 21 minutes  

---

# OpenTelemetry in .NET: A Beginner-Friendly Guide

If you are a .NET developer, you have probably used `ILogger` to write logs. But when your application becomes bigger, logs alone are not always enough.

You may start asking questions like:

- Why is my API slow?
- Which database query is taking too long?
- Which external API is causing the delay?
- How many requests are failing?
- What happened to a particular request when it passed through multiple services?
- This is where **observability** and **OpenTelemetry** become useful.

According to the official [OpenTelemetry documentation](https://opentelemetry.io/docs/), OpenTelemetry .NET supports the three major telemetry signals: **traces, metrics, and logs**.

## What Is OpenTelemetry?

In simple words:

> **OpenTelemetry is an open-source standard and set of tools used to collect telemetry data from your application.**

Telemetry means information about what your application is doing.

For example:

```plaintext
Your .NET Application
        |
        +---- Logs
        |
        +---- Metrics
        |
        +---- Traces
        |
        v
   OpenTelemetry
        |
        v
 Observability System
```

- OpenTelemetry can collect this information and send it to an observability backend for analysis.
- It is important to understand that OpenTelemetry itself is not simply a dashboard.
- Think of it as a **standard way of generating, collecting, and exporting telemetry**.

![OpenTelemetry in .NET: A Beginner-Friendly Guide](https://answers.mindstick.com/blogs/909ff4ff-97e8-4867-be6a-ec16c46e556b/images/ac4861e6-36aa-4f22-95bb-719b71699aca.png)

## What Is Observability?

Before OpenTelemetry, let's understand observability.

Suppose you have a Web API:

```plaintext
User
 |
 v
.NET API
 |
 +---- Database
 |
 +---- Payment API
 |
 +---- Email Service
```

A customer tells you:

> "The checkout page takes 5 seconds to load."

You need to find out where those 5 seconds are being spent.

Maybe:

```plaintext
API processing       200 ms
Database              500 ms
Payment API          4000 ms
Email Service         300 ms
----------------------------
Total                5000 ms
```

- Now you immediately know that the payment API is the main problem.
- This ability to understand what is happening inside your system is called **observability**.
- In .NET, observability commonly combines logs, metrics, and distributed tracing.

## The Three Pillars of Observability

There are three important types of telemetry.

```plaintext
             Observability
                   |
       +-----------+-----------+
       |           |           |
      Logs       Metrics     Traces
```

Let's understand them one by one.

## 1. Logs

Logs tell us about events that happened in the application.

For example:

```plaintext
// Write an informational log when an order is created.
_logger.LogInformation("Order {OrderId} was created",orderId);
```

The output could look like:

```plaintext
Order 1001 was created
```

Logs can answer:

> ## What happened?

## 2. Metrics

Metrics are numbers that help us understand the behavior of an application over time.

For example:

```plaintext
Requests per second = 250
Error rate = 1.2%
Average response time = 180 ms
CPU usage = 65%
Memory usage = 3 GB
```

For example, if you have 100,000 requests and 2,000 failed requests, your error rate is a useful metric.

## 3. Traces

Traces show the journey of a request through your application.

Suppose a user creates an order:

```plaintext
POST /orders
     |
     +----> Order Service
     |
     +----> Database
     |
     +----> Payment Service
     |
     +----> Email Service
```

A trace can show something like:

```plaintext
POST /orders              2500 ms
 |
 +-- Database              200 ms
 |
 +-- Payment Service      1800 ms
 |
 +-- Email Service         300 ms
```

Now we know that the payment service is taking most of the time.

## Logs vs Metrics vs Traces

A simple way to remember them is:

| Telemetry | Question | Example |
| --- | --- | --- |
| Logs | What happened? | Payment failed |
| Metrics | How much/how often? | 500 errors per minute |
| Traces | Where did the request go? | Payment API took 2 seconds |

## What Is a Trace and What Is a Span?

- When learning OpenTelemetry, you will hear the word **span** a lot.
- A **trace** represents the complete journey of a request.
- A **span** represents one operation within that journey.

For example:

```plaintext
Trace: Place Order
    Span: HTTP POST /orders
       |
       +-- Span: Database Query
       |
       +-- Span: Payment API
       |
       +-- Span: Send Email
```

So:

```plaintext
Trace = Complete request
Span = One operation inside the request
```

In .NET, OpenTelemetry tracing is built around the `System.Diagnostics` APIs, including `ActivitySource` and `Activity`. The .NET `Activity` concept maps to an OpenTelemetry span.

## Why Do We Need OpenTelemetry?

Imagine you have a simple application today:

```plaintext
Browser
   |
   v
.NET Web API
   |
   v
SQL Database
```

You can probably manage this with logs and normal debugging.

But your application may eventually become:

```plaintext
                 API Gateway
                      |
        +-------------+-------------+
        |             |             |
        v             v             v
   User Service  Order Service  Product Service
                      |
              +-------+-------+
              |               |
              v               v
        Payment Service   Notification
              |
              v
          Database
```

- Now one request can travel through many services.
- If the request fails, finding the problem becomes much harder.
- OpenTelemetry helps standardize the way telemetry is generated and collected across these components.

## OpenTelemetry in .NET

OpenTelemetry has good support for .NET.

The official .NET implementation supports:

- **Traces**
- **Metrics**
- **Logs**

and provides instrumentation libraries for common .NET technologies.

For example, we can instrument an ASP.NET Core application.

## Let's Create a Simple .NET API

First, create a new Web API project.

```plaintext
# Create a new ASP.NET Core Web API project.
dotnet new webapi -n OpenTelemetryDemo

# Move into the project directory.
cd OpenTelemetryDemo

# Run the application.
dotnet run
```

- Now we have a normal .NET application.
- Next, let's add OpenTelemetry.

## Installing OpenTelemetry Packages

For basic ASP.NET Core tracing, install these packages:

```plaintext
# Core OpenTelemetry hosting integration.
dotnet add package OpenTelemetry.Extensions.Hosting

# Adds automatic instrumentation for ASP.NET Core requests.
dotnet add package OpenTelemetry.Instrumentation.AspNetCore

# Sends telemetry to the console.
dotnet add package OpenTelemetry.Exporter.Console
```

These packages are also used in the official OpenTelemetry ASP.NET Core getting-started example.

If you want to instrument outgoing HTTP requests:

```plaintext
# Automatically creates telemetry for HttpClient calls.
dotnet add package OpenTelemetry.Instrumentation.Http
```

## Basic OpenTelemetry Configuration

Now open `Program.cs`.

A simple tracing setup looks like this:

```cs
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Register OpenTelemetry with the ASP.NET Core application.
builder.Services.AddOpenTelemetry()

    // Add information about our service.
    .ConfigureResource(resource =>
        resource.AddService(
            serviceName: builder.Environment.ApplicationName))

    // Configure distributed tracing.
    .WithTracing(tracing =>
    {
        // Automatically create traces for incoming
        // ASP.NET Core HTTP requests.
        tracing.AddAspNetCoreInstrumentation();

        // Print traces to the console.
        // This is useful while learning and testing locally.
        tracing.AddConsoleExporter();
    });

var app = builder.Build();

// A simple endpoint.
app.MapGet("/", () =>
{
    return "Hello from OpenTelemetry!";
});

app.Run();
```

The important part is:

```cs
.AddAspNetCoreInstrumentation();
```

This automatically creates telemetry for incoming ASP.NET Core HTTP requests. The official documentation shows that this instrumentation can capture information such as request duration, HTTP method, route, and response status.

## What Does Automatic Instrumentation Mean?

The word **instrumentation** sounds complicated, but the idea is simple.

Instrumentation means:

> Adding the ability to collect telemetry from your application.

For example, without instrumentation, you might have to manually write code for every request.

With:

```plaintext
.AddAspNetCoreInstrumentation()
```

OpenTelemetry can automatically create telemetry for ASP.NET Core requests.

So when someone calls:

```plaintext
GET /api/products
```

OpenTelemetry can create a span for that request.

## Adding HttpClient Instrumentation

Most real applications call other APIs.

For example:

```plaintext
Order API
    |
    v
Payment API
```

In .NET, you might use `HttpClient`.

Add:

```cs
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource =>
        resource.AddService(
            serviceName: builder.Environment.ApplicationName))

    .WithTracing(tracing =>
    {
        // Trace incoming ASP.NET Core requests.
        tracing.AddAspNetCoreInstrumentation();

        // Trace outgoing HttpClient requests.
        tracing.AddHttpClientInstrumentation();

        // Display traces in the console.
        tracing.AddConsoleExporter();
    });

var app = builder.Build();

app.MapGet("/", () => "Hello!");

app.Run();
```

Now OpenTelemetry can capture both:

```plaintext
Incoming request
       |
       v
ASP.NET Core
       |
       v
Outgoing HttpClient request
```

This is very useful in microservice applications.

## Example HttpClient Code

Suppose our API calls another service:

```cs
app.MapGet("/products", async (IHttpClientFactory factory) =>
{
    // Get a configured HttpClient from dependency injection.
    var client = factory.CreateClient();

    // Call another API.
    var response = await client.GetAsync(
        "https://example.com/products");

    // Return the response status.
    return response.StatusCode.ToString();
});
```

- With HTTP instrumentation enabled, OpenTelemetry can create telemetry for that outgoing request.
- You don't need to manually create a span just for this HTTP call.

## Adding Runtime Metrics

OpenTelemetry can also collect information about the .NET runtime.

Install the runtime instrumentation package:

```plaintext
# Adds .NET runtime metrics.
dotnet add package OpenTelemetry.Instrumentation.Runtime
```

Then configure metrics:

```cs
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()

    .ConfigureResource(resource =>
        resource.AddService(
            serviceName: builder.Environment.ApplicationName))

    // -------------------------
    // Configure tracing
    // -------------------------
    .WithTracing(tracing =>
    {
        // Trace incoming HTTP requests.
        tracing.AddAspNetCoreInstrumentation();

        // Trace outgoing HttpClient requests.
        tracing.AddHttpClientInstrumentation();

        // Print traces to the console.
        tracing.AddConsoleExporter();
    })

    // -------------------------
    // Configure metrics
    // -------------------------
    .WithMetrics(metrics =>
    {
        // Collect ASP.NET Core metrics.
        metrics.AddAspNetCoreInstrumentation();

        // Collect .NET runtime metrics.
        metrics.AddRuntimeInstrumentation();
    });

var app = builder.Build();

app.MapGet("/", () => "Hello!");

app.Run();
```

Now we are collecting both traces and metrics.

## What Is an Exporter?

- So far, we have collected telemetry.
- But where should the telemetry go?
- This is where an **exporter** comes in.

Think about the architecture:

```plaintext
.NET Application
       |
       v
OpenTelemetry
       |
       v
Exporter
       |
       v
Telemetry Backend
```

An exporter sends telemetry to another system.

For learning, we can use the console exporter:

```plaintext
.AddConsoleExporter();
```

This simply prints telemetry in the terminal.

In a real production system, you will normally send telemetry to an observability backend.

## What Is OTLP?

You will often see the term [**OTLP**](https://opentelemetry.io/docs/specs/otlp/).

OTLP stands for:

## OpenTelemetry Protocol

It is a standard protocol for transporting OpenTelemetry telemetry.

A common architecture looks like:

```plaintext
.NET Application
       |
       v
OpenTelemetry SDK
       |
       v
      OTLP
       |
       v
OpenTelemetry Collector
       |
       +----> Traces
       +----> Metrics
       +----> Logs
```

You don't have to understand every OTLP detail when starting.

For now, remember:

> **OTLP is one of the standard ways to send OpenTelemetry telemetry between systems.**

## What Is the OpenTelemetry Collector?

The OpenTelemetry Collector is a separate component that can receive, process, and export telemetry.

Imagine you have several applications:

```plaintext
Order API -------\
Payment API ------\
User API ----------> OpenTelemetry Collector
Product API -------/
```

The Collector can then send the telemetry to your chosen backend.

Conceptually:

```plaintext
Applications
     |
     v
OpenTelemetry Collector
     |
     +----> Tracing backend
     |
     +----> Metrics backend
     |
     +----> Logging backend
```

This can be very useful when you have many services.

## Manual Instrumentation

Automatic instrumentation is great, but sometimes you want to trace your own business logic.

For example:

```plaintext
ProcessOrder
CalculatePrice
ProcessPayment
GenerateInvoice
```

These are business operations that the framework doesn't necessarily know about.

For these cases, we can create our own spans.

In .NET, we normally use `ActivitySource`.

## Creating an ActivitySource

Create a class like this:

```cs
using System.Diagnostics;

public static class Telemetry
{
    // ActivitySource is used to create custom tracing activities.
    // Give it a unique name for your application or service.
    public static readonly ActivitySource ActivitySource =
        new ActivitySource("MyCompany.OrderService");
}
```

Now we can create a custom span.

## Creating a Custom Span

Suppose we have an order service:

```cs
public async Task ProcessOrderAsync(int orderId)
{
    // Start a custom tracing span.
    using var activity =
        Telemetry.ActivitySource.StartActivity("ProcessOrder");

    // Add useful information to the span.
    activity?.SetTag("order.id", orderId);

    // Simulate some business logic.
    await Task.Delay(100);

    // When the using block finishes,
    // the activity is automatically stopped.
}
```

Let's understand this line:

```plaintext
StartActivity("ProcessOrder");
```

It creates a span named:

```plaintext
ProcessOrder
```

Then:

```plaintext
activity?.SetTag("order.id", orderId);
```

adds extra information.

The result might look conceptually like:

```plaintext
Span: ProcessOrder

order.id = 12345
duration = 100 ms
```

## Why Do We Use `activity?`

You may notice this:

```plaintext
activity?.SetTag(...)
```

The `?` is important.

`StartActivity()` can return `null` when no listener is interested in that activity.

So this:

```plaintext
activity?.SetTag(...)
```

means:

> "If an Activity exists, add this tag."

It prevents a null-reference exception.

## Registering Your ActivitySource

There is one important step that beginners often miss.

We created:

```plaintext
new ActivitySource("MyCompany.OrderService");
```

But OpenTelemetry also needs to listen to this source.

Add:

```cs
.WithTracing(tracing =>
{
    // Listen to our custom ActivitySource.
    tracing.AddSource("MyCompany.OrderService");

    // Automatically trace ASP.NET Core requests.
    tracing.AddAspNetCoreInstrumentation();

    // Export traces to the console.
    tracing.AddConsoleExporter();
});
```

The name must match.

```plaintext
ActivitySource name:
MyCompany.OrderService

AddSource:
MyCompany.OrderService
```

The OpenTelemetry .NET SDK uses an explicit opt-in model for `ActivitySource`; sources need to be registered with the tracer provider to be collected.

## A Complete Simple Example

Let's put the important pieces together.

```cs
using System.Diagnostics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

// Create the application builder.
var builder = WebApplication.CreateBuilder(args);

// Configure OpenTelemetry.
builder.Services.AddOpenTelemetry()

    // Tell OpenTelemetry which service is producing telemetry.
    .ConfigureResource(resource =>
        resource.AddService("MyOrderService"))

    // Configure tracing.
    .WithTracing(tracing =>
    {
        // Automatically trace incoming ASP.NET Core requests.
        tracing.AddAspNetCoreInstrumentation();

        // Automatically trace outgoing HttpClient requests.
        tracing.AddHttpClientInstrumentation();

        // Listen to our custom ActivitySource.
        tracing.AddSource("MyOrderService");

        // Print traces to the terminal.
        tracing.AddConsoleExporter();
    });

var app = builder.Build();

// Create an ActivitySource for custom application tracing.
var activitySource = new ActivitySource("MyOrderService");

app.MapGet("/order/{id}", async (int id) =>
{
    // Create a custom span for our business operation.
    using var activity =
        activitySource.StartActivity("ProcessOrder");

    // Add the order ID as span information.
    activity?.SetTag("order.id", id);

    // Pretend that we are doing some work.
    await Task.Delay(200);

    return Results.Ok(new
    {
        OrderId = id,
        Message = "Order processed successfully"
    });
});

app.Run();
```

Now call:

```plaintext
GET /order/1001
```

You can think about the resulting trace like this:

```plaintext
GET /order/{id}
      |
      +---- ProcessOrder
```

The first span is automatically created for the HTTP request.

The second span is our custom business operation.

## Adding a Custom Metric

Tracing tells us about individual requests.

But what if we want to know:

> How many orders have been created?

A metric is better for this.

.NET provides the `Meter` API for creating metrics, and OpenTelemetry can collect those metrics.

Create a metric:

```cs
using System.Diagnostics.Metrics;

public static class OrderMetrics
{
    // Create a Meter for our application.
    public static readonly Meter Meter =
        new Meter("MyCompany.OrderService");

    // Create a counter.
    // This counter will track how many orders are created.
    public static readonly Counter<long> OrdersCreated =
        Meter.CreateCounter<long>(
            "orders.created",
            description: "Number of orders created");
}
```

Now increment the counter:

```plaintext
// Add 1 whenever an order is created.
OrderMetrics.OrdersCreated.Add(1);
```

After 100 orders:

```plaintext
orders.created = 100
```

This is much more useful than searching through 100 log messages just to calculate the number.

## Registering the Custom Meter

Just like `ActivitySource`, we need to tell OpenTelemetry to listen to our `Meter`.

```cs
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        // Collect ASP.NET Core metrics.
        metrics.AddAspNetCoreInstrumentation();

        // Collect .NET runtime metrics.
        metrics.AddRuntimeInstrumentation();

        // Listen to our custom Meter.
        metrics.AddMeter("MyCompany.OrderService");

        // Export metrics to the console.
        metrics.AddConsoleExporter();
    });
```

Now OpenTelemetry can collect our custom `orders.created` metric.

## Custom Metric with an Endpoint

Here is a small example:

```cs
app.MapPost("/orders", () =>
{
    // In a real application, we would save the order
    // to a database here.

    // Increase the custom metric by 1.
    OrderMetrics.OrdersCreated.Add(1);

    // Return a successful response.
    return Results.Ok("Order created");
});
```

Every successful request increases:

```plaintext
orders.created
```

by one.

## Automatic vs Manual Instrumentation

You might now be wondering:

> Should I manually create an Activity for everything?

No.

Start with automatic instrumentation.

For example:

```plaintext
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
```

These handle common framework operations.

Then add manual instrumentation for important business operations:

```plaintext
ProcessOrder
ProcessPayment
GenerateInvoice
CalculateShipping
CreateSubscription
```

A good approach is:

```plaintext
Automatic instrumentation
          +
Useful custom instrumentation
          =
Better observability
```

OpenTelemetry's documentation also recommends combining automatic and manual instrumentation when you need additional application-specific visibility.

## OpenTelemetry and Databases

Suppose your application uses a database.

Your request may look like:

```plaintext
GET /orders
    |
    v
Order Service
    |
    v
SQL Database
```

You may want to know:

```plaintext
API request       800 ms
Database query    650 ms
Other work        150 ms
```

- Database instrumentation can help you understand this.
- The exact package and configuration depend on the database technology and library you are using.

The important idea is:

```plaintext
Request
  |
  +---- Database operation
```

The database operation can become part of the trace.

## Distributed Tracing

Now let's talk about one of the biggest benefits of OpenTelemetry.

Suppose you have:

```plaintext
Client
  |
  v
Order API
  |
  v
Payment API
  |
  v
Payment Database
```

- A request starts at the Order API.
- It then calls the Payment API.
- The Payment API talks to the database.
- Distributed tracing can connect these operations into one trace.

Conceptually:

```plaintext
Trace: Create Order

Order API                 2000 ms
 |
 +-- Payment API           1700 ms
       |
       +-- Database        1400 ms
```

Now you can see exactly where the time is being spent.

## Why Trace Context Matters

How does the Payment API know that the request came from the Order API?

Trace context is propagated between services.

Conceptually:

```plaintext
Order API
    |
    | Trace Context
    v
Payment API
    |
    | Trace Context
    v
Database
```

This allows different operations to be connected to the same overall request.

For HTTP-based communication, supported .NET/OpenTelemetry instrumentation can handle much of this propagation automatically.

## OpenTelemetry and Logs

You may already use:

```plaintext
ILogger
```

For example:

```plaintext
_logger.LogInformation("Processing order {OrderId}",orderId);
```

Logs are still useful even when you have traces and metrics.

A useful observability setup might look like:

```plaintext
                  Application
                      |
          +-----------+-----------+
          |           |           |
         Logs      Metrics      Traces
          |           |           |
          +-----------+-----------+
                      |
                      v
             Observability System
```

- The goal is not to replace logs with traces.
- The goal is to use each type of telemetry for the job it is best at.

## A Real-World Debugging Example

Imagine your customer reports:

> "Checkout is very slow."

You look at metrics and see:

```plaintext
Average checkout time = 4 seconds
```

Then you open a trace:

```plaintext
POST /checkout            4 seconds
 |
 +-- Validate cart         100 ms
 |
 +-- Database              300 ms
 |
 +-- Payment API          3300 ms
 |
 +-- Send email            300 ms
```

- Now you know the payment API is the main problem.
- Then you check logs related to that operation and find:

```plaintext
Payment provider timeout
```

- Now you have a much better understanding of the problem.

This is the real power of combining:

```plaintext
Metrics + Traces + Logs
```

## Sampling

Imagine your API receives:

```plaintext
10 million requests per day
```

Do you really need to keep every trace?

Maybe not.

**Sampling** lets you decide which traces should be recorded or exported.

For example:

```plaintext
10,000,000 requests
        |
        v
     Sampling
        |
        v
   1,000,000 traces
```

- Sampling can help reduce storage and processing costs.
- But don't blindly sample everything at a very low rate. You may accidentally lose important information.
- A good production sampling strategy depends on your application and observability requirements.

## What Is Grafana?

[Grafana](https://grafana.com/) is commonly used to create dashboards for telemetry.

For example:

```plaintext
----------------------------------
        API Dashboard
----------------------------------
Requests/sec          350
Error rate             1.2%
Average latency       210 ms
CPU                    62%
Memory                  3 GB
----------------------------------
```

OpenTelemetry and Grafana are not the same thing.

Think:

```plaintext
OpenTelemetry
     |
     | Collect / export telemetry
     v
Observability backend
     |
     v
Grafana
     |
     v
Dashboard
```

The exact architecture depends on the backend you choose.

## What Is Jaeger?

[Jaeger](https://www.jaegertracing.io/) is commonly used for distributed tracing.

You can use it to visually inspect traces.

For example:

```plaintext
GET /orders
--------------------------------
API                  500 ms
 |
 +-- Database        100 ms
 |
 +-- Payment API     300 ms
 |
 +-- Other           100 ms
--------------------------------
```

This is especially useful when learning tracing because you can visually see the request journey.

## OpenTelemetry in a Microservices Architecture

A larger application might look like:

```plaintext
                    Client
                       |
                       v
                  API Gateway
                       |
          +------------+------------+
          |            |            |
          v            v            v
       User API     Order API    Product API
                       |
                       v
                  Payment API
                       |
                       v
                    Database

Each service
      |
      v
OpenTelemetry
      |
      v
OpenTelemetry Collector
      |
      +------> Metrics
      +------> Logs
      +------> Traces
```

- This is where OpenTelemetry becomes particularly useful.
- Different services can produce telemetry using a common approach.

## Common Beginner Mistakes

## 1. Thinking OpenTelemetry Is a Dashboard

It isn't.

- OpenTelemetry helps generate, collect, process, and export telemetry.
- A separate system is normally used to store and visualize that data.

## 2. Creating Spans Everywhere

Don't do this:

```cs
// Usually unnecessary.
StartActivity("Method1");
StartActivity("Method2");
StartActivity("Method3");
StartActivity("Method4");
```

Instead, focus on meaningful operations.

## 3. Forgetting `AddSource()`

If you create:

```cs
new ActivitySource("MyCompany.OrderService");
```

remember to register it:

```cs
tracing.AddSource("MyCompany.OrderService");
```

Otherwise, your custom spans may not be collected.

## 4. Putting Too Much Data in Tags

Don't attach huge objects to every span.

Prefer small, useful attributes:

```plaintext
order.id
order.type
payment.status
```

and make sure the information is safe to collect.

## 5. Only Using Logs

Logs are useful, but they don't give you the complete picture.

Use:

```plaintext
Logs    -> Events
Metrics -> Measurements
Traces  -> Request flow
```

## 6. Ignoring Sampling

Large applications can produce huge amounts of telemetry.

Think about sampling and telemetry volume before going to production.

## A Good Beginner Project

If you want to practice OpenTelemetry, build a small application like this:

```plaintext
Online Store API
       |
       +---- Products
       |
       +---- Orders
       |
       +---- Payments
```

Add:

```plaintext
ASP.NET Core instrumentation
HttpClient instrumentation
Runtime metrics
Custom order metric
Custom ProcessOrder span
Console exporter
```

Then make a request:

```plaintext
POST /orders
```

Try to answer:

```plaintext
What trace was created?
How long did the request take?
How many spans were created?
What custom attributes are available?
How many orders were created?
What happens when the payment API fails?
```

This small project will teach you much more than simply reading documentation.

## The Big Picture

At this point, you can think about OpenTelemetry like this:

```plaintext
                  .NET Application
                         |
          +--------------+--------------+
          |              |              |
        Logs           Metrics        Traces
          |              |              |
          +--------------+--------------+
                         |
                         v
                  OpenTelemetry
                         |
                         v
                     Exporter
                         |
                         v
              Collector / Backend
                         |
              +----------+----------+
              |          |          |
              v          v          v
            Logs      Metrics     Traces
```

That is the main idea.

## Final Example: Simple .NET Setup

Here is a compact example you can keep as a reference.

```cs
using System.Diagnostics;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Name of the application/service.
// This name will appear in your telemetry.
const string ServiceName = "MyOrderService";

// Register OpenTelemetry.
builder.Services.AddOpenTelemetry()

    // Add service information to telemetry.
    .ConfigureResource(resource =>
        resource.AddService(ServiceName))

    // -------------------------
    // Configure tracing
    // -------------------------
    .WithTracing(tracing =>
    {
        // Automatically trace incoming HTTP requests.
        tracing.AddAspNetCoreInstrumentation();

        // Automatically trace outgoing HttpClient calls.
        tracing.AddHttpClientInstrumentation();

        // Collect our custom ActivitySource.
        tracing.AddSource(ServiceName);

        // Print traces to the console.
        tracing.AddConsoleExporter();
    })

    // -------------------------
    // Configure metrics
    // -------------------------
    .WithMetrics(metrics =>
    {
        // Collect ASP.NET Core metrics.
        metrics.AddAspNetCoreInstrumentation();

        // Collect .NET runtime metrics.
        metrics.AddRuntimeInstrumentation();
    });

var app = builder.Build();

// Create a source for custom application traces.
var activitySource = new ActivitySource(ServiceName);

app.MapGet("/orders/{id}", async (int id) =>
{
    // Create a custom span.
    using var activity =
        activitySource.StartActivity("ProcessOrder");

    // Add useful information to the span.
    activity?.SetTag("order.id", id);

    // Simulate business processing.
    await Task.Delay(100);

    // Return the result.
    return Results.Ok(new
    {
        OrderId = id,
        Status = "Processed"
    });
});

app.Run();
```

This small example demonstrates the most important concepts:

```plaintext
ASP.NET Core instrumentation
        +
HttpClient instrumentation
        +
Runtime metrics
        +
Custom ActivitySource
        +
Custom span
        +
Telemetry exporter
```

## Conclusion

OpenTelemetry can look complicated when you first see words like:

```plaintext
Trace
Span
Activity
ActivitySource
Meter
Instrumentation
Exporter
OTLP
Collector
Sampling
```

- But the basic idea is actually quite simple.
- Your application produces information.
- OpenTelemetry helps you collect and export that information.

Remember:

```plaintext
Logs
"What happened?"

Metrics
"How much/how often?"

Traces
"Where did the request go?"
```

- Don't try to instrument every method in your application.
- Start small, look at the telemetry, understand what you are seeing, and then gradually add more.
- Once you understand the basics, OpenTelemetry becomes much less scary.

The simplest definition to remember is:

> **OpenTelemetry gives your .NET application a standard way to produce, collect, and export observability data so you can understand what your application is doing.**

The official OpenTelemetry .NET documentation currently lists traces, metrics, and logs as stable signals and provides dedicated guides for each area.

---

Original Source: https://answers.mindstick.com/blog/544/opentelemetry-in-dot-net-a-beginner-friendly-guide

Copyright © MindStick Software Pvt. Ltd. This Markdown version is provided for developers, AI systems, and offline reading.
