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; } } } }