ConsoleLogExporter.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using System.Reflection;
  7. namespace LLM.Editor.PoC
  8. {
  9. /// <summary>
  10. /// A Unity editor utility to export all current console log entries to a text file.
  11. /// </summary>
  12. public static class ConsoleLogExporter
  13. {
  14. // --- Internal Unity Types and Methods accessed via Reflection ---
  15. private static Type _logEntriesType;
  16. private static MethodInfo _startGettingEntriesMethod;
  17. private static MethodInfo _endGettingEntriesMethod;
  18. private static MethodInfo _getEntryInternalMethod;
  19. private static int _logEntryCount;
  20. private static object _logEntry;
  21. // --- Menu Item to Trigger the Export ---
  22. [MenuItem("Tools/Export Console Logs")]
  23. public static void ExportLogs()
  24. {
  25. if (!InitializeReflection())
  26. {
  27. Debug.LogError("Failed to initialize reflection for accessing console logs. The Unity Editor API may have changed.");
  28. return;
  29. }
  30. // Prompt user for a save location
  31. var path = EditorUtility.SaveFilePanel("Save Console Logs", "", "console_logs.txt", "txt");
  32. if (string.IsNullOrEmpty(path))
  33. {
  34. // User cancelled the save dialog
  35. return;
  36. }
  37. try
  38. {
  39. // Build the formatted log string
  40. var formattedLogs = BuildFormattedLogString();
  41. // Write the string to the chosen file
  42. File.WriteAllText(path, formattedLogs);
  43. Debug.Log($"<color=lime>Successfully exported console logs to:</color> <a href=\"{path}\">{path}</a>");
  44. }
  45. catch (Exception e)
  46. {
  47. Debug.LogError($"Failed to export console logs. Error: {e.Message}");
  48. }
  49. }
  50. /// <summary>
  51. /// Initializes the reflection members needed to access Unity's internal console log API.
  52. /// </summary>
  53. /// <returns>True if initialization was successful, otherwise false.</returns>
  54. private static bool InitializeReflection()
  55. {
  56. // Get the internal LogEntries class
  57. _logEntriesType = Type.GetType("UnityEditor.LogEntries, UnityEditor.dll");
  58. if (_logEntriesType == null) return false;
  59. // Get the necessary static methods from LogEntries
  60. _startGettingEntriesMethod = _logEntriesType.GetMethod("StartGettingEntries", BindingFlags.Static | BindingFlags.Public);
  61. _endGettingEntriesMethod = _logEntriesType.GetMethod("EndGettingEntries", BindingFlags.Static | BindingFlags.Public);
  62. _getEntryInternalMethod = _logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);
  63. // Get the total number of log entries
  64. var getCountMethod = _logEntriesType.GetMethod("GetCount", BindingFlags.Static | BindingFlags.Public);
  65. if (getCountMethod != null)
  66. {
  67. _logEntryCount = (int)getCountMethod.Invoke(null, null);
  68. }
  69. // Create an instance of the internal LogEntry class to reuse
  70. var logEntryType = Type.GetType("UnityEditor.LogEntry, UnityEditor.dll");
  71. if (logEntryType == null) return false;
  72. _logEntry = Activator.CreateInstance(logEntryType);
  73. return _startGettingEntriesMethod != null && _endGettingEntriesMethod != null && _getEntryInternalMethod != null;
  74. }
  75. /// <summary>
  76. /// Reads all console entries and formats them into a single, readable string.
  77. /// </summary>
  78. /// <returns>A formatted string containing all log data.</returns>
  79. private static string BuildFormattedLogString()
  80. {
  81. var stringBuilder = new StringBuilder();
  82. stringBuilder.AppendLine("==================================================");
  83. stringBuilder.AppendLine($" CONSOLE LOGS EXPORTED ON: {DateTime.Now}");
  84. stringBuilder.AppendLine("==================================================");
  85. stringBuilder.AppendLine();
  86. // Safely get all entries
  87. _startGettingEntriesMethod.Invoke(null, null);
  88. for (var i = 0; i < _logEntryCount; i++)
  89. {
  90. // Retrieve a single log entry by its index
  91. _getEntryInternalMethod.Invoke(null, new[] { i, _logEntry });
  92. // Extract message and stack trace using reflection from the LogEntry instance
  93. var messageField = _logEntry.GetType().GetField("message", BindingFlags.Instance | BindingFlags.Public);
  94. var stackTraceField = _logEntry.GetType().GetField("stackTrace", BindingFlags.Instance | BindingFlags.Public);
  95. var modeField = _logEntry.GetType().GetField("mode", BindingFlags.Instance | BindingFlags.Public);
  96. var message = messageField?.GetValue(_logEntry).ToString();
  97. var stackTrace = stackTraceField?.GetValue(_logEntry).ToString();
  98. var mode = (int)(modeField?.GetValue(_logEntry) ?? 0);
  99. // Append formatted entry to the string builder
  100. stringBuilder.AppendLine("--------------------------------------------------");
  101. stringBuilder.AppendLine($"TYPE: {GetLogTypeFromMode(mode)}");
  102. stringBuilder.AppendLine("--------------------------------------------------");
  103. stringBuilder.AppendLine(message?.Trim());
  104. stringBuilder.AppendLine();
  105. if (string.IsNullOrWhiteSpace(stackTrace)) continue;
  106. stringBuilder.AppendLine("STACK TRACE:");
  107. stringBuilder.AppendLine(stackTrace.Trim());
  108. stringBuilder.AppendLine();
  109. }
  110. // Clean up
  111. _endGettingEntriesMethod.Invoke(null, null);
  112. return stringBuilder.ToString();
  113. }
  114. /// <summary>
  115. /// Converts the internal 'mode' integer from a LogEntry into a human-readable log type.
  116. /// </summary>
  117. private static string GetLogTypeFromMode(int mode)
  118. {
  119. // These are flags, so we need to check for them with bitwise operations.
  120. // The key is knowing which bits correspond to which log type.
  121. // This is based on an analysis of Unity's internal LogEntry class.
  122. const int errorFlag = 1;
  123. const int assertFlag = 2;
  124. const int warningFlag = 4;
  125. const int logFlag = 8;
  126. // There are other flags for things like compiler errors, etc.
  127. if ((mode & errorFlag) != 0) return "Error";
  128. if ((mode & assertFlag) != 0) return "Assert";
  129. if ((mode & warningFlag) != 0) return "Warning";
  130. return (mode & logFlag) != 0 ? "Log" :
  131. // It's possible for a log entry to have other modes (e.g., from packages)
  132. "Unknown";
  133. }
  134. }
  135. }