DEV Community

Cover image for Mastering Distributed Tracing with Serilog and Seq in .NET
Odumosu Matthew
Odumosu Matthew

Posted on

Mastering Distributed Tracing with Serilog and Seq in .NET

Distributed tracing and structured logging are critical when building and maintaining modern microservices or distributed systems. They help track requests as they propagate through different services and components. In .NET applications, tools like Serilog and Seq simplify this process by enabling structured, real-time log management and tracing.

In this article, we’ll cover how to implement distributed tracing and structured logging using Serilog (for logging) and Seq (for log aggregation and visualization) in a .NET project.

Why Distributed Tracing and Structured Logging?
In a distributed system, understanding how requests flow through multiple services is vital for debugging and performance monitoring. Distributed tracing provides insights into how each service contributes to the overall request lifecycle. Meanwhile, structured logging captures log data in a structured format (JSON), making it easier to search, analyze, and visualize logs.

Getting Started with Serilog
1. Setting Up Serilog in Your .NET Project
Serilog is a structured logging library for .NET that supports various sinks, including Seq. Let’s first install the necessary packages.

Step 1: Install Serilog and Seq using NuGet:

dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Seq

Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Serilog in Program.cs (or Startup.cs for older versions of .NET):

using Serilog;

public class Program
{
    public static void Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .Enrich.FromLogContext()
            .WriteTo.Console() // Log to console
            .WriteTo.Seq("http://localhost:5341") // Log to Seq
            .CreateLogger();

        try
        {
            Log.Information("Starting the application...");
            CreateHostBuilder(args).Build().Run();
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Application start-up failed");
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog() // Use Serilog for logging
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Enter fullscreen mode Exit fullscreen mode

In this configuration, Serilog writes logs both to the console and to Seq, a web-based log server (running locally on http://localhost:5341 by default). You can change this to a remote Seq instance if necessary.

What is Seq?
Seq is a log aggregation and visualization tool that provides powerful querying, alerts, and dashboards for structured logs. You can download Seq from the official website:https://datalust.co/seq.

  • Run Seq locally using Docker with:
docker run --name seq -d --restart unless-stopped -e ACCEPT_EULA=Y -p 5341:80 datalust/seq

Enter fullscreen mode Exit fullscreen mode

Once running, you can access Seq at http://localhost:5341.

Adding Context to Logs
To make logs more meaningful, enrich them with additional context such as request details, user information, and service-specific metadata.

app.Use(async (context, next) =>
{
    LogContext.PushProperty("UserId", context.User?.Identity?.Name ?? "anonymous");
    LogContext.PushProperty("CorrelationId", Guid.NewGuid());

    await next.Invoke();
});

Enter fullscreen mode Exit fullscreen mode

In this example, every log will have UserId and CorrelationId fields, providing more structured and useful logs.

Implementing Distributed Tracing
Distributed tracing requires tracking the flow of requests across multiple services. This can be done using unique identifiers for each request (often referred to as Correlation ID).

Step 1: Install OpenTelemetry to add distributed tracing support.

dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http

Enter fullscreen mode Exit fullscreen mode

Step 2: Configure OpenTelemetry for tracing in Startup.cs:

using OpenTelemetry.Trace;

public void ConfigureServices(IServiceCollection services)
{
    services.AddOpenTelemetryTracing(tracerProviderBuilder =>
    {
        tracerProviderBuilder
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddConsoleExporter(); // Export traces to the console
    });
}

Enter fullscreen mode Exit fullscreen mode

Step 3: Capture Correlation IDs in logs for each request:

app.Use(async (context, next) =>
{
    var correlationId = Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString();
    LogContext.PushProperty("CorrelationId", correlationId);

    context.Response.Headers.Add("Correlation-Id", correlationId);
    await next.Invoke();
});

Enter fullscreen mode Exit fullscreen mode

Now, all logs will be tagged with a Correlation ID, and traces from OpenTelemetry will allow you to see how requests propagate through different services.

Putting It All Together: Sample Controller
Let’s implement a sample controller with distributed tracing and structured logging.

[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
    private readonly ILogger<OrdersController> _logger;

    public OrdersController(ILogger<OrdersController> logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}")]
    public IActionResult GetOrder(int id)
    {
        _logger.LogInformation("Fetching order with ID: {OrderId}", id);

        if (id <= 0)
        {
            _logger.LogWarning("Invalid order ID: {OrderId}", id);
            return BadRequest("Invalid Order ID");
        }

        var order = new { Id = id, Product = "Laptop", Quantity = 1 };

        _logger.LogInformation("Order found: {@Order}", order);

        return Ok(order);
    }
}

Enter fullscreen mode Exit fullscreen mode

Visualizing Logs and Traces in Seq
Once you run the application, you can navigate to Seq (http://localhost:5341) to view real-time logs. You’ll notice the structured fields like UserId, CorrelationId, and more in each log, making it easier to filter and search for logs related to specific requests or users.

In Seq, you can also create dashboards, set up alerts, and monitor the overall health of your services.

Conclusion
By combining Serilog for structured logging and Seq for log aggregation, you gain the ability to track your application’s performance in real time. Adding OpenTelemetry for distributed tracing further enriches your ability to monitor and debug requests across services.

With this setup, you’ve equipped your .NET application with essential tools to handle logging and tracing in a distributed microservices environment.

Key Takeaways:
Serilog provides structured logging for your .NET applications.
Seq allows you to aggregate, visualize, and analyze logs.
OpenTelemetry adds distributed tracing, enabling you to track request flows across services.
Combining these tools improves observability, debugging, and performance monitoring in distributed systems.

LinkedIn Account : LinkedIn
Twitter Account: Twitter
Credit: Graphics sourced from LoginRadius

Top comments (2)

Collapse
 
martinbaun profile image
Martin Baun

Great read! There is logging and then there is logging :P

Collapse
 
iamcymentho profile image
Odumosu Matthew

Thanks! I’m glad you enjoyed it! 😂