using System; using System.IO; using System.Text; using UnityEditor; using UnityEngine; using System.Reflection; namespace LLM.Editor.PoC { /// /// A Unity editor utility to export all current console log entries to a text file. /// public static class ConsoleLogExporter { // --- Internal Unity Types and Methods accessed via Reflection --- private static Type _logEntriesType; private static MethodInfo _startGettingEntriesMethod; private static MethodInfo _endGettingEntriesMethod; private static MethodInfo _getEntryInternalMethod; private static int _logEntryCount; private static object _logEntry; // --- Menu Item to Trigger the Export --- [MenuItem("Tools/Exporter/Export Console Logs")] public static void ExportLogs() { if (!InitializeReflection()) { Debug.LogError("Failed to initialize reflection for accessing console logs. The Unity Editor API may have changed."); return; } // Prompt user for a save location var path = EditorUtility.SaveFilePanel("Save Console Logs", "", "console_logs.txt", "txt"); if (string.IsNullOrEmpty(path)) { // User cancelled the save dialog return; } try { // Build the formatted log string var formattedLogs = BuildFormattedLogString(); // Write the string to the chosen file File.WriteAllText(path, formattedLogs); Debug.Log($"Successfully exported console logs to: {path}"); } catch (Exception e) { Debug.LogError($"Failed to export console logs. Error: {e.Message}"); } } /// /// Initializes the reflection members needed to access Unity's internal console log API. /// /// True if initialization was successful, otherwise false. private static bool InitializeReflection() { // Get the internal LogEntries class _logEntriesType = Type.GetType("UnityEditor.LogEntries, UnityEditor.dll"); if (_logEntriesType == null) return false; // Get the necessary static methods from LogEntries _startGettingEntriesMethod = _logEntriesType.GetMethod("StartGettingEntries", BindingFlags.Static | BindingFlags.Public); _endGettingEntriesMethod = _logEntriesType.GetMethod("EndGettingEntries", BindingFlags.Static | BindingFlags.Public); _getEntryInternalMethod = _logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public); // Get the total number of log entries var getCountMethod = _logEntriesType.GetMethod("GetCount", BindingFlags.Static | BindingFlags.Public); if (getCountMethod != null) { _logEntryCount = (int)getCountMethod.Invoke(null, null); } // Create an instance of the internal LogEntry class to reuse var logEntryType = Type.GetType("UnityEditor.LogEntry, UnityEditor.dll"); if (logEntryType == null) return false; _logEntry = Activator.CreateInstance(logEntryType); return _startGettingEntriesMethod != null && _endGettingEntriesMethod != null && _getEntryInternalMethod != null; } /// /// Reads all console entries and formats them into a single, readable string. /// /// A formatted string containing all log data. private static string BuildFormattedLogString() { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("=================================================="); stringBuilder.AppendLine($" CONSOLE LOGS EXPORTED ON: {DateTime.Now}"); stringBuilder.AppendLine("=================================================="); stringBuilder.AppendLine(); // Safely get all entries _startGettingEntriesMethod.Invoke(null, null); for (var i = 0; i < _logEntryCount; i++) { // Retrieve a single log entry by its index _getEntryInternalMethod.Invoke(null, new[] { i, _logEntry }); // Extract message and stack trace using reflection from the LogEntry instance var messageField = _logEntry.GetType().GetField("message", BindingFlags.Instance | BindingFlags.Public); var stackTraceField = _logEntry.GetType().GetField("stackTrace", BindingFlags.Instance | BindingFlags.Public); var modeField = _logEntry.GetType().GetField("mode", BindingFlags.Instance | BindingFlags.Public); var message = messageField?.GetValue(_logEntry).ToString(); var stackTrace = stackTraceField?.GetValue(_logEntry).ToString(); var mode = (int)(modeField?.GetValue(_logEntry) ?? 0); // Append formatted entry to the string builder stringBuilder.AppendLine("--------------------------------------------------"); stringBuilder.AppendLine($"TYPE: {GetLogTypeFromMode(mode)}"); stringBuilder.AppendLine("--------------------------------------------------"); stringBuilder.AppendLine(message?.Trim()); stringBuilder.AppendLine(); if (string.IsNullOrWhiteSpace(stackTrace)) continue; stringBuilder.AppendLine("STACK TRACE:"); stringBuilder.AppendLine(stackTrace.Trim()); stringBuilder.AppendLine(); } // Clean up _endGettingEntriesMethod.Invoke(null, null); return stringBuilder.ToString(); } /// /// Converts the internal 'mode' integer from a LogEntry into a human-readable log type. /// private static string GetLogTypeFromMode(int mode) { // These are flags, so we need to check for them with bitwise operations. // The key is knowing which bits correspond to which log type. // This is based on an analysis of Unity's internal LogEntry class. const int errorFlag = 1; const int assertFlag = 2; const int warningFlag = 4; const int logFlag = 8; // There are other flags for things like compiler errors, etc. if ((mode & errorFlag) != 0) return "Error"; if ((mode & assertFlag) != 0) return "Assert"; if ((mode & warningFlag) != 0) return "Warning"; return (mode & logFlag) != 0 ? "Log" : // It's possible for a log entry to have other modes (e.g., from packages) "Unknown"; } } }