Distributed Tracing with Jaeger UI in .NET Microservices
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
- Jaeger backend setup (All-in-one Docker container for simplicity).
- .NET Core SDK.
- 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