using System;
using System.Threading;
namespace Svrnty.CQRS.Events.Logging;
///
/// Manages correlation ID propagation across async operations for distributed tracing.
///
///
///
/// Phase 6 Feature:
/// Uses AsyncLocal to maintain correlation ID context across async boundaries.
/// Enables full request tracing across event streams, subscriptions, and consumers.
///
///
/// Usage Pattern:
///
/// 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);
/// }
///
///
///
public static class CorrelationContext
{
private static readonly AsyncLocal _correlationId = new();
///
/// Gets the current correlation ID for this async context.
///
/// The current correlation ID, or null if not set.
public static string? Current => _correlationId.Value;
///
/// Begins a new correlation context with the specified ID.
///
/// The correlation ID to use for this context.
/// A disposable scope that restores the previous correlation ID when disposed.
///
/// If correlationId is null, a new GUID will be generated.
/// Always use with a using statement to ensure proper cleanup.
///
public static IDisposable Begin(string? correlationId = null)
{
return new CorrelationScope(correlationId ?? Guid.NewGuid().ToString());
}
///
/// Sets the correlation ID for the current async context.
///
/// The correlation ID to set.
///
/// Prefer using Begin() with a using statement for automatic cleanup.
///
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;
}
}
}
}