84 lines
2.6 KiB
C#
84 lines
2.6 KiB
C#
using System;
|
|
using System.Threading;
|
|
|
|
namespace Svrnty.CQRS.Events.Logging;
|
|
|
|
/// <summary>
|
|
/// Manages correlation ID propagation across async operations for distributed tracing.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <strong>Phase 6 Feature:</strong>
|
|
/// Uses AsyncLocal to maintain correlation ID context across async boundaries.
|
|
/// Enables full request tracing across event streams, subscriptions, and consumers.
|
|
/// </para>
|
|
/// <para>
|
|
/// <strong>Usage Pattern:</strong>
|
|
/// <code>
|
|
/// using (CorrelationContext.Begin(correlationId))
|
|
/// {
|
|
/// // All operations within this scope will have access to the correlation ID
|
|
/// await PublishEventAsync(myEvent);
|
|
/// _logger.LogEventPublished(eventId, eventType, streamName, CorrelationContext.Current);
|
|
/// }
|
|
/// </code>
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class CorrelationContext
|
|
{
|
|
private static readonly AsyncLocal<string?> _correlationId = new();
|
|
|
|
/// <summary>
|
|
/// Gets the current correlation ID for this async context.
|
|
/// </summary>
|
|
/// <returns>The current correlation ID, or null if not set.</returns>
|
|
public static string? Current => _correlationId.Value;
|
|
|
|
/// <summary>
|
|
/// Begins a new correlation context with the specified ID.
|
|
/// </summary>
|
|
/// <param name="correlationId">The correlation ID to use for this context.</param>
|
|
/// <returns>A disposable scope that restores the previous correlation ID when disposed.</returns>
|
|
/// <remarks>
|
|
/// If correlationId is null, a new GUID will be generated.
|
|
/// Always use with a using statement to ensure proper cleanup.
|
|
/// </remarks>
|
|
public static IDisposable Begin(string? correlationId = null)
|
|
{
|
|
return new CorrelationScope(correlationId ?? Guid.NewGuid().ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the correlation ID for the current async context.
|
|
/// </summary>
|
|
/// <param name="correlationId">The correlation ID to set.</param>
|
|
/// <remarks>
|
|
/// Prefer using Begin() with a using statement for automatic cleanup.
|
|
/// </remarks>
|
|
internal static void Set(string? correlationId)
|
|
{
|
|
_correlationId.Value = correlationId;
|
|
}
|
|
|
|
private sealed class CorrelationScope : IDisposable
|
|
{
|
|
private readonly string? _previousCorrelationId;
|
|
private bool _disposed;
|
|
|
|
public CorrelationScope(string correlationId)
|
|
{
|
|
_previousCorrelationId = Current;
|
|
Set(correlationId);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
Set(_previousCorrelationId);
|
|
_disposed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|