File: ConsoleLogs\LogEntries.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using Aspire.Dashboard.Model;
using Aspire.Dashboard.Otlp.Storage;
 
namespace Aspire.Dashboard.ConsoleLogs;
 
public sealed class LogEntries(int maximumEntryCount)
{
    private readonly CircularBuffer<LogEntry> _logEntries = new(maximumEntryCount);
 
    private int? _baseLineNumber;
 
    public void Clear()
    {
        _logEntries.Clear();
 
        _baseLineNumber = null;
    }
 
    public IList<LogEntry> GetEntries() => _logEntries;
 
    public void InsertSorted(LogEntry logEntry, int lineNumber)
    {
        // Keep track of the base line number to ensure that we can calculate the line number of each log entry.
        // This becomes important when the total number of log entries exceeds the limit and is truncated.
        _baseLineNumber ??= lineNumber;
 
        if (logEntry.ParentId != null)
        {
            // If we have a parent id, then we know we're on a non-timestamped line that is part
            // of a multi-line log entry. We need to find the prior line from that entry
            for (var rowIndex = _logEntries.Count - 1; rowIndex >= 0; rowIndex--)
            {
                var current = _logEntries[rowIndex];
 
                if (current.Id == logEntry.ParentId && logEntry.LineIndex - 1 == current.LineIndex)
                {
                    InsertAt(rowIndex + 1);
                    return;
                }
            }
        }
        else if (logEntry.Timestamp != null)
        {
            // Otherwise, if we have a timestamped line, we just need to find the prior line.
            // Since the rows are always in order, as soon as we see a timestamp
            // that is less than the one we're adding, we can insert it immediately after that
            for (var rowIndex = _logEntries.Count - 1; rowIndex >= 0; rowIndex--)
            {
                var current = _logEntries[rowIndex];
                var currentTimestamp = current.Timestamp ?? current.ParentTimestamp;
 
                if (currentTimestamp != null && currentTimestamp <= logEntry.Timestamp)
                {
                    InsertAt(rowIndex + 1);
                    return;
                }
            }
        }
 
        // If we didn't find a place to insert then append it to the end. This happens with the first entry, but
        // could also happen if the logs don't have recognized timestamps.
        InsertAt(_logEntries.Count);
 
        void InsertAt(int index)
        {
            // Set the line number of the log entry.
            if (index == 0)
            {
                Debug.Assert(_baseLineNumber != null, "Should be set before this method is run.");
                logEntry.LineNumber = _baseLineNumber.Value;
            }
            else
            {
                logEntry.LineNumber = _logEntries[index - 1].LineNumber + 1;
            }
 
            // Insert the entry.
            _logEntries.Insert(index, logEntry);
 
            // If a log entry isn't inserted at the end then update the line numbers of all subsequent entries.
            for (var i = index + 1; i < _logEntries.Count; i++)
            {
                _logEntries[i].LineNumber++;
            }
        }
    }
}