diff --git a/UnityMcpBridge/Editor/Tools/ReadConsole.cs b/UnityMcpBridge/Editor/Tools/ReadConsole.cs index e3470cf3..4bd40090 100644 --- a/UnityMcpBridge/Editor/Tools/ReadConsole.cs +++ b/UnityMcpBridge/Editor/Tools/ReadConsole.cs @@ -16,6 +16,8 @@ namespace UnityMcpBridge.Editor.Tools /// public static class ReadConsole { + // (Calibration removed) + // Reflection members for accessing internal LogEntry data // private static MethodInfo _getEntriesMethod; // Removed as it's unused and fails reflection private static MethodInfo _startGettingEntriesMethod; @@ -41,6 +43,8 @@ static ReadConsole() ); if (logEntriesType == null) throw new Exception("Could not find internal type UnityEditor.LogEntries"); + + // Include NonPublic binding flags as internal APIs might change accessibility BindingFlags staticFlags = @@ -100,6 +104,9 @@ static ReadConsole() _instanceIdField = logEntryType.GetField("instanceID", instanceFlags); if (_instanceIdField == null) throw new Exception("Failed to reflect LogEntry.instanceID"); + + // (Calibration removed) + } catch (Exception e) { @@ -251,16 +258,38 @@ bool includeStacktrace // int instanceId = (int)_instanceIdField.GetValue(logEntryInstance); if (string.IsNullOrEmpty(message)) + { continue; // Skip empty messages + } + + // (Calibration removed) // --- Filtering --- - // Filter by type - LogType currentType = GetLogTypeFromMode(mode); - if (!types.Contains(currentType.ToString().ToLowerInvariant())) + // Prefer classifying severity from message/stacktrace; fallback to mode bits if needed + LogType unityType = InferTypeFromMessage(message); + bool isExplicitDebug = IsExplicitDebugLog(message); + if (!isExplicitDebug && unityType == LogType.Log) { - continue; + unityType = GetLogTypeFromMode(mode); } + bool want; + // Treat Exception/Assert as errors for filtering convenience + if (unityType == LogType.Exception) + { + want = types.Contains("error") || types.Contains("exception"); + } + else if (unityType == LogType.Assert) + { + want = types.Contains("error") || types.Contains("assert"); + } + else + { + want = types.Contains(unityType.ToString().ToLowerInvariant()); + } + + if (!want) continue; + // Filter by text (case-insensitive) if ( !string.IsNullOrEmpty(filterText) @@ -294,7 +323,7 @@ bool includeStacktrace default: formattedEntry = new { - type = currentType.ToString(), + type = unityType.ToString(), message = messageOnly, file = file, line = line, @@ -350,15 +379,12 @@ bool includeStacktrace // --- Internal Helpers --- - // Mapping from LogEntry.mode bits to LogType enum - // Based on decompiled UnityEditor code or common patterns. Precise bits might change between Unity versions. - // See comments below for LogEntry mode bits exploration. - // Note: This mapping is simplified and might not cover all edge cases or future Unity versions perfectly. + // Mapping bits from LogEntry.mode. These may vary by Unity version. private const int ModeBitError = 1 << 0; private const int ModeBitAssert = 1 << 1; private const int ModeBitWarning = 1 << 2; private const int ModeBitLog = 1 << 3; - private const int ModeBitException = 1 << 4; // Often combined with Error bits + private const int ModeBitException = 1 << 4; // often combined with Error bits private const int ModeBitScriptingError = 1 << 9; private const int ModeBitScriptingWarning = 1 << 10; private const int ModeBitScriptingLog = 1 << 11; @@ -367,46 +393,75 @@ bool includeStacktrace private static LogType GetLogTypeFromMode(int mode) { - // First, determine the type based on the original logic (most severe first) - LogType initialType; - if ( - ( - mode - & ( - ModeBitError - | ModeBitScriptingError - | ModeBitException - | ModeBitScriptingException - ) - ) != 0 - ) - { - initialType = LogType.Error; - } - else if ((mode & (ModeBitAssert | ModeBitScriptingAssertion)) != 0) - { - initialType = LogType.Assert; - } - else if ((mode & (ModeBitWarning | ModeBitScriptingWarning)) != 0) - { - initialType = LogType.Warning; - } - else - { - initialType = LogType.Log; - } + // Preserve Unity's real type (no remapping); bits may vary by version + if ((mode & (ModeBitException | ModeBitScriptingException)) != 0) return LogType.Exception; + if ((mode & (ModeBitError | ModeBitScriptingError)) != 0) return LogType.Error; + if ((mode & (ModeBitAssert | ModeBitScriptingAssertion)) != 0) return LogType.Assert; + if ((mode & (ModeBitWarning | ModeBitScriptingWarning)) != 0) return LogType.Warning; + return LogType.Log; + } + + // (Calibration helpers removed) + + /// + /// Classifies severity using message/stacktrace content. Works across Unity versions. + /// + private static LogType InferTypeFromMessage(string fullMessage) + { + if (string.IsNullOrEmpty(fullMessage)) return LogType.Log; + + // Fast path: look for explicit Debug API names in the appended stack trace + // e.g., "UnityEngine.Debug:LogError (object)" or "LogWarning" + if (fullMessage.IndexOf("LogError", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Error; + if (fullMessage.IndexOf("LogWarning", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Warning; + + // Compiler diagnostics (C#): "warning CSxxxx" / "error CSxxxx" + if (fullMessage.IndexOf(" warning CS", StringComparison.OrdinalIgnoreCase) >= 0 + || fullMessage.IndexOf(": warning CS", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Warning; + if (fullMessage.IndexOf(" error CS", StringComparison.OrdinalIgnoreCase) >= 0 + || fullMessage.IndexOf(": error CS", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Error; + + // Exceptions (avoid misclassifying compiler diagnostics) + if (fullMessage.IndexOf("Exception", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Exception; + + // Unity assertions + if (fullMessage.IndexOf("Assertion", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Assert; + + return LogType.Log; + } - // Apply the observed "one level lower" correction - switch (initialType) + private static bool IsExplicitDebugLog(string fullMessage) + { + if (string.IsNullOrEmpty(fullMessage)) return false; + if (fullMessage.IndexOf("Debug:Log (", StringComparison.OrdinalIgnoreCase) >= 0) return true; + if (fullMessage.IndexOf("UnityEngine.Debug:Log (", StringComparison.OrdinalIgnoreCase) >= 0) return true; + return false; + } + + /// + /// Applies the "one level lower" remapping for filtering, like the old version. + /// This ensures compatibility with the filtering logic that expects remapped types. + /// + private static LogType GetRemappedTypeForFiltering(LogType unityType) + { + switch (unityType) { case LogType.Error: return LogType.Warning; // Error becomes Warning case LogType.Warning: return LogType.Log; // Warning becomes Log case LogType.Assert: - return LogType.Assert; // Assert remains Assert (no lower level defined) + return LogType.Assert; // Assert remains Assert case LogType.Log: return LogType.Log; // Log remains Log + case LogType.Exception: + return LogType.Warning; // Exception becomes Warning default: return LogType.Log; // Default fallback }