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