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";
}
}
}