Course contentsShow
← C#
Lesson 1524 of 1,90814. Background Work and MessagingFree lesson

Logging and Telemetry for Background Workers

Integrate structured logging and diagnostic traces into background service workflows.

In this lesson, you will learn how to inject observability into your BackgroundService to track long-running operations and correlate distributed background tasks.

When running processes in the background, you cannot rely on user-triggered requests to flow through your logs. Instead, your background workers must act as the "source" of diagnostic data. To do this effectively, we leverage ILogger for structured data and ActivitySource for distributed tracing.

Structured Logging with Context

Avoid simple string interpolation in your logs. Instead, use structured logging so that your log aggregation system can index specific properties. By capturing the state of your background worker within the log template, you make it trivial to query for specific processing cycles.

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        // Use a scope to attach context to every log inside this loop
        using (_logger.BeginScope(new Dictionary<string, object> { ["WorkerId"] = _id }))
        {
            _logger.LogInformation("Processing batch at {Timestamp}", DateTime.UtcNow);
            await DoWorkAsync();
        }
        await Task.Delay(1000, stoppingToken);
    }
}

Implementing Distributed Traces

Since background workers often trigger downstream processes (like database updates or message bus publishing), they should initialize an Activity. An Activity acts as a span in a distributed trace, allowing you to visualize exactly how long a background task took and where potential bottlenecks exist.

By creating an ActivitySource in your class, you can wrap your work logic:

private static readonly ActivitySource MySource = new("MyWorkerService");

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    using var activity = MySource.StartActivity("BackgroundWorkLoop");
    // Any operations performed here are now linked to this Activity trace
    await ProcessDataAsync();
}

By combining these, you ensure that even when your worker runs silently in the background, you have a breadcrumb trail of logs for errors and traces for performance bottlenecks. This visibility is essential for maintaining production health when human interaction is absent.

Key Takeaway: Use BeginScope to attach identifying metadata to background tasks and ActivitySource to track the lifecycle of worker operations across distributed systems.