Distributed Tracing with Jaeger UI in .NET Microservices

DotNet Full Stack Dev
3 min readJul 26, 2024

--

Distributed tracing helps track requests flowing through various microservices, essential for diagnosing latency issues, errors, and performance bottlenecks. Jaeger is a popular open-source distributed tracing system that traces requests in microservices architectures.

In this guide, we’ll integrate Jaeger into a .NET microservices setup involving Product and Order services.

Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.

Prerequisites

  1. Jaeger backend setup (All-in-one Docker container for simplicity).
  2. .NET Core SDK.
  3. OpenTelemetry NuGet packages.

Step-by-Step Guide

Step 1: Setting Up Jaeger

Run Jaeger using Docker:

docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:1.26
  • We use the Jaeger all-in-one Docker image for simplicity, which includes the Jaeger agent, collector, query service, and UI.
  • The -p flags expose necessary ports for the Jaeger components. The UI is accessible at http://localhost:16686.

Step 2: Adding OpenTelemetry and Jaeger to .NET Services

Install the necessary NuGet packages in both Product and Order services:

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.Jaeger
  • OpenTelemetry: The core library for OpenTelemetry.
  • OpenTelemetry.Extensions.Hosting: Integration with .NET's hosting and dependency injection.
  • OpenTelemetry.Exporter.Jaeger: The exporter that sends tracing data to Jaeger.

Step 3: Configure OpenTelemetry in .NET Services

ProductService/Program.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Add OpenTelemetry tracing
builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("ProductService"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddJaegerExporter(options =>
{
options.AgentHost = "localhost";
options.AgentPort = 6831;
});
});

builder.Services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

app.Run();

OrderService/Program.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Add OpenTelemetry tracing
builder.Services.AddOpenTelemetryTracing(tracerProviderBuilder =>
{
tracerProviderBuilder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OrderService"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddJaegerExporter(options =>
{
options.AgentHost = "localhost";
options.AgentPort = 6831;
});
});

builder.Services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

app.Run();
  • ResourceBuilder: Adds service name to the telemetry data.
  • AddAspNetCoreInstrumentation: Captures incoming HTTP requests.
  • AddHttpClientInstrumentation: Captures outgoing HTTP requests.
  • AddJaegerExporter: Configures the exporter to send data to Jaeger.

Step 4: Create Controllers for Product and Order Services

ProductService/Controllers/ProductController.cs

using Microsoft.AspNetCore.Mvc;

namespace ProductService.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return Ok(new { Id = id, Name = "Sample Product", Price = 99.99 });
}
}
}

OrderService/Controllers/OrderController.cs

using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;

namespace OrderService.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly IHttpClientFactory _httpClientFactory;

public OrderController(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}

[HttpPost]
public async Task<IActionResult> CreateOrder(int productId)
{
var client = _httpClientFactory.CreateClient();
var productResponse = await client.GetStringAsync($"http://localhost:5000/api/product/{productId}");

return Ok(new { OrderId = 123, Product = productResponse, Status = "Created" });
}
}
}
  • ProductController: Provides a simple endpoint to get product details.
  • OrderController: Demonstrates a service calling another service, creating an order by fetching product details from the Product service.

Step 5: Running the Services

Run both services:

dotnet run --project ProductService
dotnet run --project OrderService

Each service is started independently. Ensure they are running on different ports to avoid conflicts.

Step 6: Viewing Traces in Jaeger

Navigate to http://localhost:16686 to view the traces. You should be able to see traces from both Product and Order services, with detailed information on the flow of requests between them.

  • Jaeger UI provides a detailed view of the traces, showing the end-to-end flow of requests across microservices.

Conclusion

Integrating Jaeger with .NET microservices using OpenTelemetry allows you to trace requests across services, providing visibility into the system’s behavior and performance. This setup helps in diagnosing issues, monitoring performance, and improving the overall reliability of the system. With distributed tracing, you can pinpoint latency issues, track errors, and understand the flow of requests in your microservices architecture.

You may also like : https://medium.com/@siva.veeravarapu/centralized-logging-with-elk-stack-elasticsearch-logstash-kibana-in-net-microservices-08c07ad6cab3

--

--

DotNet Full Stack Dev
DotNet Full Stack Dev

Written by DotNet Full Stack Dev

Join me to master .NET Full Stack Development & boost your skills by 1% daily with insights, examples, and techniques! https://dotnet-fullstack-dev.blogspot.com

No responses yet