From a40db4813220b862041839a66dc5bd2631f449bb Mon Sep 17 00:00:00 2001 From: David Sarno Date: Sun, 10 Aug 2025 19:45:24 -0700 Subject: [PATCH 1/4] ReadConsole: stable severity classification and filtering across Unity versions - Classify severity via stacktrace/message first (LogError/LogWarning/Exception/Assertion), with safe fallback to mode-bit mapping - Fix error/warning/log mapping; treat Exception/Assert as errors for filtering - Return the current console buffer reliably and remove debug spam - No changes outside ReadConsole behavior --- UnityMcpBridge/Editor/Tools/ReadConsole.cs | 127 ++++++++++++++------- 1 file changed, 86 insertions(+), 41 deletions(-) diff --git a/UnityMcpBridge/Editor/Tools/ReadConsole.cs b/UnityMcpBridge/Editor/Tools/ReadConsole.cs index e3470cf3..eca81b8d 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,44 @@ 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); + if (unityType == LogType.Log) { - continue; + unityType = GetLogTypeFromMode(mode); } + bool want; + if (types.Contains("all")) + { + want = true; + } + else + { + // 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 +329,7 @@ bool includeStacktrace default: formattedEntry = new { - type = currentType.ToString(), + type = unityType.ToString(), message = messageOnly, file = file, line = line, @@ -350,15 +385,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 +399,59 @@ 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; + + // Exceptions often include the word "Exception" in the first lines + if (fullMessage.IndexOf("Exception", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Exception; - // Apply the observed "one level lower" correction - switch (initialType) + // Unity assertions + if (fullMessage.IndexOf("Assertion", StringComparison.OrdinalIgnoreCase) >= 0) + return LogType.Assert; + + return LogType.Log; + } + + /// + /// 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 } From dc6171dfe653aeb91e78f947100016cf835ad218 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Sun, 10 Aug 2025 20:12:45 -0700 Subject: [PATCH 2/4] ReadConsole: lock Debug.Log classification to Log; avoid bit-based fallback when stacktrace shows Debug:Log - Detect explicit Debug.Log in stacktrace (UnityEngine.Debug:Log) - Do not downgrade/upgrade to Warning via mode bits for editor-originated logs - Keeps informational setup lines (e.g., MCP registration, bridge start) as Log --- UnityMcpBridge/Editor/Tools/ReadConsole.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/UnityMcpBridge/Editor/Tools/ReadConsole.cs b/UnityMcpBridge/Editor/Tools/ReadConsole.cs index eca81b8d..12350d2e 100644 --- a/UnityMcpBridge/Editor/Tools/ReadConsole.cs +++ b/UnityMcpBridge/Editor/Tools/ReadConsole.cs @@ -267,7 +267,8 @@ bool includeStacktrace // --- Filtering --- // Prefer classifying severity from message/stacktrace; fallback to mode bits if needed LogType unityType = InferTypeFromMessage(message); - if (unityType == LogType.Log) + bool isExplicitDebugLog = IsExplicitDebugLog(message); + if (!isExplicitDebugLog && unityType == LogType.Log) { unityType = GetLogTypeFromMode(mode); } @@ -422,6 +423,8 @@ private static LogType InferTypeFromMessage(string fullMessage) return LogType.Error; if (fullMessage.IndexOf("LogWarning", StringComparison.OrdinalIgnoreCase) >= 0) return LogType.Warning; + if (IsExplicitDebugLog(fullMessage)) + return LogType.Log; // Exceptions often include the word "Exception" in the first lines if (fullMessage.IndexOf("Exception", StringComparison.OrdinalIgnoreCase) >= 0) @@ -434,6 +437,15 @@ private static LogType InferTypeFromMessage(string fullMessage) return LogType.Log; } + private static bool IsExplicitDebugLog(string fullMessage) + { + // Detect explicit Debug.Log in the stacktrace/message to lock type to Log + 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. From 46f616df909ceff470d9a87a1b5a942c7ab3c6c1 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 11 Aug 2025 16:52:42 -0700 Subject: [PATCH 3/4] read_console: correct compiler diagnostic categorization (CSxxxx), preserve Debug.Log as Log without mode fallback, add explicit Debug.Log detection helper --- UnityMcpBridge/Editor/Tools/ReadConsole.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/UnityMcpBridge/Editor/Tools/ReadConsole.cs b/UnityMcpBridge/Editor/Tools/ReadConsole.cs index 12350d2e..cbeb4e43 100644 --- a/UnityMcpBridge/Editor/Tools/ReadConsole.cs +++ b/UnityMcpBridge/Editor/Tools/ReadConsole.cs @@ -267,8 +267,8 @@ bool includeStacktrace // --- Filtering --- // Prefer classifying severity from message/stacktrace; fallback to mode bits if needed LogType unityType = InferTypeFromMessage(message); - bool isExplicitDebugLog = IsExplicitDebugLog(message); - if (!isExplicitDebugLog && unityType == LogType.Log) + bool isExplicitDebug = IsExplicitDebugLog(message); + if (!isExplicitDebug && unityType == LogType.Log) { unityType = GetLogTypeFromMode(mode); } @@ -423,10 +423,16 @@ private static LogType InferTypeFromMessage(string fullMessage) return LogType.Error; if (fullMessage.IndexOf("LogWarning", StringComparison.OrdinalIgnoreCase) >= 0) return LogType.Warning; - if (IsExplicitDebugLog(fullMessage)) - return LogType.Log; - // Exceptions often include the word "Exception" in the first lines + // 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; @@ -439,7 +445,6 @@ private static LogType InferTypeFromMessage(string fullMessage) private static bool IsExplicitDebugLog(string fullMessage) { - // Detect explicit Debug.Log in the stacktrace/message to lock type to Log 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; From ae87e3f3b2c6918c0d2dc1c7c5bee296f9a54d17 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 11 Aug 2025 17:26:51 -0700 Subject: [PATCH 4/4] read_console: remove dead types.Contains("all") branch; compute want directly from unityType (Exception/Assert treated as errors) --- UnityMcpBridge/Editor/Tools/ReadConsole.cs | 23 ++++++++-------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/UnityMcpBridge/Editor/Tools/ReadConsole.cs b/UnityMcpBridge/Editor/Tools/ReadConsole.cs index cbeb4e43..4bd40090 100644 --- a/UnityMcpBridge/Editor/Tools/ReadConsole.cs +++ b/UnityMcpBridge/Editor/Tools/ReadConsole.cs @@ -274,25 +274,18 @@ bool includeStacktrace } bool want; - if (types.Contains("all")) + // 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 = true; + want = types.Contains("error") || types.Contains("assert"); } else { - // 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()); - } + want = types.Contains(unityType.ToString().ToLowerInvariant()); } if (!want) continue;