using System; using System.Collections.Generic; using Newtonsoft.Json; using UnityEditor; using UnityEngine; namespace Unity.Pipeline.Editor.Commands.Observability { /// /// A single captured Editor console entry. Returned (most-recent-first) by the /// get_console_logs command so an agent can read structured log output without scraping /// the Editor UI. Mirrors the fields Unity surfaces on . /// [Serializable] public class ConsoleLogEntryDto { /// Unity name, e.g. "Log", "Warning", "Error", "Exception", "Assert". [JsonProperty("type")] public string Type { get; set; } /// The logged message text. [JsonProperty("message")] public string Message { get; set; } /// Captured stack trace, if Unity provided one for the entry. [JsonProperty("stackTrace")] public string StackTrace { get; set; } /// Capture time in UTC, ISO-8601 round-trip ("o") format. [JsonProperty("timestampUtc")] public string TimestampUtc { get; set; } } /// /// Captures Editor console output into a bounded, thread-safe ring buffer so it can be read back /// structurally over the pipeline (CLI-198). Subscribes to /// on load — the threaded variant is used /// because Unity may raise log callbacks off the main thread, and the handler is fully lock-guarded. /// /// The buffer holds at most entries; the oldest is dropped when full. /// public static class ConsoleLogBuffer { /// Maximum number of entries retained; oldest entries are dropped past this. public const int MaxEntries = 1000; private static readonly object m_Lock = new object(); private static readonly Queue m_Entries = new Queue(MaxEntries); /// /// Subscribe to Unity's threaded log callback as soon as the Editor domain loads, so capture /// is live before any command runs. Idempotent across domain reloads (handler is removed /// first to avoid double-subscription if Init somehow runs twice). /// [InitializeOnLoadMethod] private static void Init() { Application.logMessageReceivedThreaded -= OnLogMessageThreaded; Application.logMessageReceivedThreaded += OnLogMessageThreaded; } private static void OnLogMessageThreaded(string condition, string stackTrace, LogType type) { var entry = new ConsoleLogEntryDto { Type = type.ToString(), Message = condition, StackTrace = stackTrace, TimestampUtc = DateTime.UtcNow.ToString("o") }; lock (m_Lock) { if (m_Entries.Count >= MaxEntries) m_Entries.Dequeue(); m_Entries.Enqueue(entry); } } /// /// Take a point-in-time copy of the buffer in chronological (oldest-first) order. The copy is /// detached from the live buffer, so callers can filter/reverse it without holding the lock. /// public static IReadOnlyList Snapshot() { lock (m_Lock) { return new List(m_Entries); } } /// Number of entries currently retained in the buffer. public static int Count { get { lock (m_Lock) { return m_Entries.Count; } } } /// Drop all captured entries. public static void Clear() { lock (m_Lock) { m_Entries.Clear(); } } } }