using System; using System.Text; using UnityEngine; namespace BugReporter { /// /// Fixed-size ring of the most recent log lines. Subscribes to Unity's log callback, which fires on /// whatever thread logged — so writes are locked. Allocation-light: the ring is a preallocated string[] /// and only the (rare) Dump() call builds a string. /// internal sealed class LogBuffer : IDisposable { private readonly string[] _lines; private readonly object _lock = new object(); private readonly bool _includeWarnings; private int _next; // next slot to write private bool _wrapped; // has the ring wrapped at least once public LogBuffer(int capacity, bool includeWarnings = false) { _lines = new string[Mathf.Max(16, capacity)]; _includeWarnings = includeWarnings; Application.logMessageReceivedThreaded += OnLog; } private void OnLog(string message, string stackTrace, LogType type) { // Warnings are the loudest, least useful thing in a bug report — a single frame can emit dozens and // shove the real error out of the ring. Drop them unless explicitly asked for. (Errors/asserts/ // exceptions always stay.) if (type == LogType.Warning && !_includeWarnings) return; // Stack traces only for the log types where they carry information — otherwise a log // buffer of 200 lines becomes 200 pages of Unity internals. bool wantStack = type == LogType.Exception || type == LogType.Error || type == LogType.Assert; string line = wantStack && !string.IsNullOrEmpty(stackTrace) ? $"[{type}] {message}\n{stackTrace.TrimEnd()}" : $"[{type}] {message}"; lock (_lock) { _lines[_next] = line; _next = (_next + 1) % _lines.Length; if (_next == 0) _wrapped = true; } } /// Oldest-to-newest, newline separated. Safe to call from the main thread while logging continues. public string Dump() { lock (_lock) { int count = _wrapped ? _lines.Length : _next; if (count == 0) return string.Empty; var sb = new StringBuilder(count * 80); int start = _wrapped ? _next : 0; for (int i = 0; i < count; i++) { string line = _lines[(start + i) % _lines.Length]; if (line != null) sb.AppendLine(line); } return sb.ToString(); } } public void Dispose() { Application.logMessageReceivedThreaded -= OnLog; } } }