|
@@ -0,0 +1,161 @@
|
|
|
+using System;
|
|
|
+using System.IO;
|
|
|
+using System.Text;
|
|
|
+using UnityEditor;
|
|
|
+using UnityEngine;
|
|
|
+using System.Reflection;
|
|
|
+
|
|
|
+namespace LLM.Editor.PoC
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// A Unity editor utility to export all current console log entries to a text file.
|
|
|
+ /// </summary>
|
|
|
+ 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/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($"<color=lime>Successfully exported console logs to:</color> <a href=\"{path}\">{path}</a>");
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Debug.LogError($"Failed to export console logs. Error: {e.Message}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes the reflection members needed to access Unity's internal console log API.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>True if initialization was successful, otherwise false.</returns>
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Reads all console entries and formats them into a single, readable string.
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>A formatted string containing all log data.</returns>
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Converts the internal 'mode' integer from a LogEntry into a human-readable log type.
|
|
|
+ /// </summary>
|
|
|
+ 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";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|