| name | correlation-id-tracking |
| description | Manages correlation-id in .NET applications using AsyncLocal for async context isolation. Use when implementing correlation-id tracking, HTTP request/response correlation, logging integration, or when working with distributed tracing in .NET applications. |
Using Traceability Package for Correlation-ID Tracking
This skill helps you use the WhiteBeard.Traceability NuGet package to implement correlation-id tracking in .NET applications. The package provides automatic correlation-id management with zero-configuration setup.
Installation
dotnet add package WhiteBeard.Traceability
Or via Package Manager:
Install-Package WhiteBeard.Traceability
Quick Setup
ASP.NET Core (.NET 8) - Zero Configuration
Minimal setup (one line!):
using Traceability.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTraceability();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
With explicit service name:
builder.Services.AddTraceability("MyService");
Done! Correlation-id is now automatically:
- ✅ Generated on each request (if not provided via
X-Correlation-Id header)
- ✅ Available via
CorrelationContext.Current
- ✅ Added to response headers as
X-Correlation-Id
- ✅ Propagated in HttpClient calls automatically
- ✅ Included in logs (when logging is configured)
ASP.NET Framework 4.8 - Zero Code
Just install the package - no code needed!
The library automatically:
- ✅ Registers
CorrelationIdHttpModule via PreApplicationStartMethod
- ✅ Manages correlation-id automatically
Optional: Configure Serilog
using Traceability.Extensions;
using Serilog;
protected void Application_Start()
{
Log.Logger = new LoggerConfiguration()
.WithTraceability("MyService")
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Source} {CorrelationId} {Message:lj}{NewLine}{Exception}")
.CreateLogger();
}
Using Correlation-ID in Code
In Controllers
using Traceability;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ApiController : ControllerBase
{
[HttpGet("test")]
public IActionResult Test()
{
var correlationId = CorrelationContext.Current;
return Ok(new { CorrelationId = correlationId });
}
}
In Console Applications
using Traceability;
var correlationId = CorrelationContext.GetOrCreate();
Console.WriteLine($"Correlation ID: {correlationId}");
await SomeAsyncMethod();
var sameId = CorrelationContext.Current;
CorrelationContext API
var id = CorrelationContext.Current;
if (CorrelationContext.TryGetValue(out var correlationId))
{
}
CorrelationContext.Current = "existing-correlation-id";
CorrelationContext.Clear();
HttpClient Integration
Automatic propagation - no extra code needed!
builder.Services.AddTraceability();
builder.Services.AddHttpClient("ExternalApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
});
var client = _httpClientFactory.CreateClient("ExternalApi");
var response = await client.GetAsync("posts/1");
The CorrelationIdHandler is automatically registered - all HttpClient instances created via IHttpClientFactory will include the correlation-ID header.
Logging Integration
Serilog
using Traceability.Logging;
using Serilog;
Log.Logger = new LoggerConfiguration()
.Enrich.With<CorrelationIdEnricher>()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {CorrelationId} {Message:lj}{NewLine}{Exception}")
.CreateLogger();
logger.LogInformation("Processing request");
Microsoft.Extensions.Logging
using Traceability.Logging;
builder.Services.AddTraceability();
builder.Logging.AddConsole(options => options.IncludeScopes = true);
logger.LogInformation("Processing request");
Logging Best Practices
Log Levels: When to Use Each
DEBUG - Development and troubleshooting only:
- Detailed execution flow
- Variable values and intermediate states
- Step-by-step process information
- Not visible in production (typically filtered out)
INFO - Production-ready, important events:
- Request start/completion
- Business operations (create, update, delete)
- External API calls (start/end)
- Important state changes
- Visible in production
WARNING - Potential issues that don't break functionality:
- Retry attempts
- Fallback to default values
- Deprecated API usage
- Performance degradation
ERROR - Failures that need attention:
- Exceptions and errors
- Failed operations
- External service failures
What to Log (and What NOT to Log)
✅ DO Log:
- Request identifiers (correlation-ID, user ID, request ID)
- Business operations (what happened)
- External service calls (start, end, duration)
- Important state changes
- Errors with context (correlation-ID, user, operation)
❌ DON'T Log:
- Sensitive data (passwords, tokens, PII, credit cards)
- Large payloads (use summaries instead)
- Every iteration in loops (log once per operation)
- Redundant information (correlation-ID is already in context)
- Excessive detail in production (use DEBUG for that)
Structured Logging with Correlation-ID
Always include correlation-ID in logs for traceability:
_logger.LogInformation(
"Processing order {OrderId} for user {UserId}. CorrelationId: {CorrelationId}",
orderId, userId, CorrelationContext.Current);
_logger.LogInformation(
"External API call completed. Endpoint: {Endpoint}, Duration: {Duration}ms, StatusCode: {StatusCode}",
endpoint, duration, statusCode);
_logger.LogInformation($"Processing order {orderId}");
_logger.LogInformation($"User password: {password}");
Production vs Development Logging
Production Configuration:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning"
}
}
}
Development Configuration:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information",
"System": "Information"
}
}
}
Logging Patterns for Debugging
Pattern 1: Request Lifecycle
[HttpGet("orders/{id}")]
public async Task<IActionResult> GetOrder(int id)
{
var correlationId = CorrelationContext.Current;
_logger.LogInformation(
"Getting order {OrderId}. CorrelationId: {CorrelationId}",
id, correlationId);
try
{
_logger.LogDebug(
"Querying database for order {OrderId}. CorrelationId: {CorrelationId}",
id, correlationId);
var order = await _repository.GetByIdAsync(id);
if (order == null)
{
_logger.LogWarning(
"Order {OrderId} not found. CorrelationId: {CorrelationId}",
id, correlationId);
return NotFound();
}
_logger.LogInformation(
"Order {OrderId} retrieved successfully. CorrelationId: {CorrelationId}",
id, correlationId);
return Ok(order);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error retrieving order {OrderId}. CorrelationId: {CorrelationId}",
id, correlationId);
return StatusCode(500);
}
}
Pattern 2: External Service Calls
public async Task<PaymentResult> ProcessPayment(PaymentRequest request)
{
var correlationId = CorrelationContext.Current;
_logger.LogInformation(
"Calling payment service. Amount: {Amount}, Currency: {Currency}. CorrelationId: {CorrelationId}",
request.Amount, request.Currency, correlationId);
var stopwatch = Stopwatch.StartNew();
try
{
var response = await _paymentClient.ProcessAsync(request);
stopwatch.Stop();
_logger.LogInformation(
"Payment service call completed. Status: {Status}, Duration: {Duration}ms. CorrelationId: {CorrelationId}",
response.Status, stopwatch.ElapsedMilliseconds, correlationId);
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex,
"Payment service call failed. Duration: {Duration}ms. CorrelationId: {CorrelationId}",
stopwatch.ElapsedMilliseconds, correlationId);
throw;
}
}
Pattern 3: Conditional Debug Logging
public async Task ProcessItems(List<Item> items)
{
var correlationId = CorrelationContext.Current;
_logger.LogInformation(
"Processing {Count} items. CorrelationId: {CorrelationId}",
items.Count, correlationId);
for (int i = 0; i < items.Count; i++)
{
_logger.LogDebug(
"Processing item {Index}/{Total}: {ItemId}. CorrelationId: {CorrelationId}",
i + 1, items.Count, items[i].Id, correlationId);
await ProcessItem(items[i]);
}
_logger.LogInformation(
"Processed {Count} items successfully. CorrelationId: {CorrelationId}",
items.Count, correlationId);
}
Key Principles
- Correlation-ID Always: Every log should include correlation-ID (automatically via enricher/scope)
- Structured Properties: Use structured logging with named properties, not string interpolation
- Context Matters: Include relevant context (user ID, operation, IDs) but not sensitive data
- Level Appropriately: Use DEBUG for detailed troubleshooting, INFO for production visibility
- Performance Aware: Don't log in tight loops; summarize instead
- Error Context: Always include correlation-ID and relevant context in error logs
Environment Variables
Set service name via environment variable to reduce code:
Linux/Mac:
export TRACEABILITY_SERVICENAME="UserService"
Windows PowerShell:
$env:TRACEABILITY_SERVICENAME="UserService"
Then use:
builder.Services.AddTraceability();
Common Patterns
Pattern 1: Reading Correlation-ID from Request
The middleware automatically:
- Reads
X-Correlation-Id from incoming request headers
- If present, uses that value
- If missing, generates new GUID (32 chars, no hyphens)
- Sets
CorrelationContext.Current
- Adds to response headers
No code needed - this happens automatically!
Pattern 2: Preserving Across Async Operations
var correlationId = CorrelationContext.Current;
await SomeAsyncMethod();
var sameId = CorrelationContext.Current;
Pattern 3: Isolated Contexts
var mainId = CorrelationContext.Current;
await Task.Run(async () =>
{
if (!CorrelationContext.HasValue)
{
CorrelationContext.Current = "id2";
}
});
Pattern 4: Manual Propagation
CorrelationContext.Current = externalCorrelationId;
await ProcessRequest();
Key Features
- Zero Configuration: Works out of the box with minimal setup
- Async-Safe: Uses
AsyncLocal<string> to preserve correlation-ID across async/await
- Automatic Propagation: HttpClient automatically includes correlation-ID in headers
- Logging Integration: Works with Serilog and Microsoft.Extensions.Logging
- Framework Support: Works with .NET 8 and .NET Framework 4.8
- Header-Based: Uses
X-Correlation-Id header for HTTP propagation
Important Notes
- GUID Format: Correlation-IDs are 32-character GUIDs without hyphens
- Never Overwrites: If correlation-ID exists (from headers), it's preserved
- Independent from OpenTelemetry: Correlation-ID is separate from
Activity.TraceId - both can coexist
- Thread-Safe: All operations are thread-safe and async-safe
Examples
See complete examples in:
- ASP.NET Core:
samples/Sample.WebApi.Net8/
- Console App:
samples/Sample.Console.Net8/
- .NET Framework:
samples/Sample.Console.NetFramework/
Troubleshooting
Correlation-ID not appearing in logs?
- Ensure
CorrelationIdEnricher is registered (Serilog) or CorrelationIdScopeProvider is registered (ILogger)
HttpClient not including correlation-ID?
- Ensure you're using
IHttpClientFactory.CreateClient() (not new HttpClient())
- Verify
AddTraceability() was called in service configuration
Correlation-ID not preserved across async?
- This shouldn't happen - the package uses
AsyncLocal<string> which handles this automatically
- Check if you're using
Task.Run() which creates isolated contexts