| File: Cli\NativeCliLogBuffer.cs | Web Access |
| Project: src\src\Aspire.Hosting.Native.Server\Aspire.Hosting.Native.Server.csproj (Aspire.Hosting.Native.Server) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; using Aspire.Hosting.Native.Diagnostics; using Microsoft.Extensions.Logging; namespace Aspire.Hosting.Native.Cli; /// <summary>Retains bounded, classified AppHost diagnostic logs independently of CLI connections.</summary> internal sealed class NativeCliLogBuffer(int capacity) { private readonly Lock _gate = new(); private readonly Queue<NativeCliLogEntry> _entries = []; private TaskCompletionSource _changed = new(TaskCreationOptions.RunContinuationsAsynchronously); private long _sequence; private readonly Guid _generation = Guid.NewGuid(); public void Append(OperationEvent value) { lock (_gate) { var sequence = checked(++_sequence); _entries.Enqueue(new NativeCliLogEntry(sequence, _generation, new EventId(0, value.Operation), value.Outcome == OperationOutcome.Error || value.ErrorType is not null ? LogLevel.Error : LogLevel.Debug, $"{value.Operation}: {value.Outcome.ToWire()}" + (value.ErrorType is null ? "" : $" ({value.ErrorType})"), value.Timestamp, NativeDiagnostics.SourceName)); if (_entries.Count > capacity) { _entries.Dequeue(); } var changed = _changed; _changed = new(TaskCreationOptions.RunContinuationsAsynchronously); changed.TrySetResult(); } } public async IAsyncEnumerable<NativeCliLogEntry> ReadAsync([EnumeratorCancellation] CancellationToken cancellationToken) { long sequence = 0; while (true) { cancellationToken.ThrowIfCancellationRequested(); NativeCliLogEntry[] entries; Task changed; lock (_gate) { entries = _entries.Where(entry => entry.SequenceNumber > sequence).ToArray(); changed = _changed.Task; } foreach (var entry in entries) { sequence = entry.SequenceNumber; yield return entry; } await changed.WaitAsync(cancellationToken).ConfigureAwait(false); } } }