├── LICENSE ├── LogCatWindow.cs ├── README.md └── screenshots ├── FilterByErrorTypes.png ├── FilterByErrorTypesAndString.png └── InitialWindow.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 Rokas Brazdzionis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LogCatWindow.cs: -------------------------------------------------------------------------------- 1 | #if PLATFORM_ANDROID 2 | using UnityEngine; 3 | using System.Collections; 4 | using UnityEditor; 5 | using UnityEditor.Android; 6 | using System.Diagnostics; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System; 10 | using System.Text; 11 | using System.Text.RegularExpressions; 12 | using System.IO; 13 | #if UNITY_2017_3_OR_NEWER 14 | using UnityEditor.Compilation; 15 | #endif 16 | 17 | public class LogCatWindow : EditorWindow 18 | { 19 | // How many log entries to store in memory. Keep it low for better performance. 20 | private const int memoryLimit = 2000; 21 | 22 | // How many log entries to show in unity3D editor. Keep it low for better performance. 23 | private const int showLimit = 200; 24 | 25 | // Filters 26 | private bool prefilterOnlyUnity = true; 27 | private bool filterOnlyError = false; 28 | private bool filterOnlyWarning = false; 29 | private bool filterOnlyDebug = false; 30 | private bool filterOnlyInfo = false; 31 | private bool filterOnlyVerbose = false; 32 | private string filterByString = String.Empty; 33 | 34 | // Android adb logcat process 35 | private Process logCatProcess; 36 | 37 | // Log entries 38 | private List logsList = new List(); 39 | private List filteredList = new List(memoryLimit); 40 | private const string LogcatPattern = @"([0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9]{3}) ([WIEDV])/(.*)"; 41 | private static readonly Regex LogcatRegex = new Regex(LogcatPattern, RegexOptions.Compiled); 42 | 43 | // Filtered GUI list scroll position 44 | private Vector2 scrollPosition = new Vector2(0, 0); 45 | 46 | // Add menu item named "LogCat" to the Window menu 47 | [MenuItem("Window/LogCat - Android Logger")] 48 | public static void ShowWindow() 49 | { 50 | // Show existing window instance. If one doesn't exist, make one. 51 | EditorWindow.GetWindow(typeof(LogCatWindow), false, "Logcat"); 52 | } 53 | 54 | void Update() 55 | { 56 | if (logsList.Count == 0) 57 | return; 58 | 59 | lock (logsList) 60 | { 61 | // Filter 62 | filteredList = logsList.Where(log => (filterByString.Length <= 2 || log.Message.ToLower().Contains(filterByString.ToLower())) && 63 | ((!filterOnlyError && !filterOnlyWarning && !filterOnlyDebug && !filterOnlyInfo && !filterOnlyVerbose) 64 | || filterOnlyError && log.Type == 'E' 65 | || filterOnlyWarning && log.Type == 'W' 66 | || filterOnlyDebug && log.Type == 'D' 67 | || filterOnlyInfo && log.Type == 'I' 68 | || filterOnlyVerbose && log.Type == 'V')).ToList(); 69 | } 70 | 71 | if (logCatProcess != null) 72 | { 73 | Repaint(); 74 | } 75 | } 76 | 77 | void OnGUI() 78 | { 79 | GUILayout.BeginHorizontal(); 80 | 81 | // Enable pre-filter if process is not started 82 | GUI.enabled = logCatProcess == null; 83 | prefilterOnlyUnity = GUILayout.Toggle(prefilterOnlyUnity, "Only Unity", "Button", GUILayout.Width(80)); 84 | 85 | // Enable button if process is not started 86 | GUI.enabled = logCatProcess == null; 87 | if (GUILayout.Button("Start", GUILayout.Width(60))) 88 | { 89 | string adbPath = GetAdbPath(); 90 | 91 | // Start `adb logcat -c` to clear the log buffer 92 | ProcessStartInfo clearProcessInfo = new ProcessStartInfo(); 93 | clearProcessInfo.WindowStyle = ProcessWindowStyle.Hidden; 94 | clearProcessInfo.CreateNoWindow = true; 95 | clearProcessInfo.UseShellExecute = false; 96 | clearProcessInfo.FileName = adbPath; 97 | clearProcessInfo.Arguments = @"logcat -c"; 98 | Process.Start(clearProcessInfo); 99 | 100 | // Start `adb logcat` (with additional optional arguments) process for filtering 101 | ProcessStartInfo logProcessInfo = new ProcessStartInfo(); 102 | logProcessInfo.CreateNoWindow = true; 103 | logProcessInfo.UseShellExecute = false; 104 | logProcessInfo.RedirectStandardOutput = true; 105 | logProcessInfo.RedirectStandardError = true; 106 | logProcessInfo.StandardOutputEncoding = Encoding.UTF8; 107 | logProcessInfo.FileName = adbPath; 108 | logProcessInfo.WindowStyle = ProcessWindowStyle.Hidden; 109 | 110 | // Add additional -s argument for filtering by Unity tag. 111 | logProcessInfo.Arguments = "logcat -v time"+(prefilterOnlyUnity ? " -s \"Unity\"": ""); 112 | 113 | logCatProcess = Process.Start(logProcessInfo); 114 | 115 | logCatProcess.ErrorDataReceived += (sender, errorLine) => { 116 | if (errorLine.Data != null && errorLine.Data.Length > 2) 117 | AddLog(new LogCatLog(errorLine.Data)); 118 | }; 119 | logCatProcess.OutputDataReceived += (sender, outputLine) => { 120 | if (outputLine.Data != null && outputLine.Data.Length > 2) 121 | AddLog(new LogCatLog(outputLine.Data)); 122 | }; 123 | logCatProcess.BeginErrorReadLine(); 124 | logCatProcess.BeginOutputReadLine(); 125 | } 126 | 127 | // Disable button if process is already started 128 | GUI.enabled = logCatProcess != null; 129 | if (GUILayout.Button("Stop", GUILayout.Width(60))) 130 | { 131 | StopLogCatProcess(); 132 | } 133 | 134 | GUI.enabled = true; 135 | if (GUILayout.Button("Clear", GUILayout.Width(60))) 136 | { 137 | lock (logsList) 138 | { 139 | logsList.Clear(); 140 | filteredList.Clear(); 141 | } 142 | } 143 | 144 | GUILayout.Label(filteredList.Count + " matching logs", GUILayout.Height(20)); 145 | 146 | // Create filters 147 | filterByString = GUILayout.TextField(filterByString, GUILayout.Height(20)); 148 | GUI.color = new Color(0.75f, 0.5f, 0.5f, 1f); 149 | filterOnlyError = GUILayout.Toggle(filterOnlyError, "Error", "Button", GUILayout.Width(80)); 150 | GUI.color = new Color(0.95f, 0.95f, 0.3f, 1f); 151 | filterOnlyWarning = GUILayout.Toggle(filterOnlyWarning, "Warning", "Button", GUILayout.Width(80)); 152 | GUI.color = new Color(0.5f, 0.5f, 0.75f, 1f); 153 | filterOnlyDebug = GUILayout.Toggle(filterOnlyDebug, "Debug", "Button", GUILayout.Width(80)); 154 | GUI.color = new Color(0.5f, 0.75f, 0.5f, 1f); 155 | filterOnlyInfo = GUILayout.Toggle(filterOnlyInfo, "Info", "Button", GUILayout.Width(80)); 156 | GUI.color = Color.white; 157 | filterOnlyVerbose = GUILayout.Toggle(filterOnlyVerbose, "Verbose", "Button", GUILayout.Width(80)); 158 | 159 | GUILayout.EndHorizontal(); 160 | 161 | GUIStyle lineStyle = new GUIStyle(); 162 | lineStyle.normal.background = MakeTexture(600, 1, new Color(1.0f, 1.0f, 1.0f, 0.1f)); 163 | 164 | scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(Screen.height - 45)); 165 | 166 | // Show only top `showingLimit` log entries 167 | int fromIndex = filteredList.Count - showLimit; 168 | if (fromIndex < 0) 169 | fromIndex = 0; 170 | 171 | for (int i = fromIndex; i < filteredList.Count; i++) 172 | { 173 | LogCatLog log = filteredList[i]; 174 | GUI.backgroundColor = log.GetBgColor(); 175 | GUILayout.BeginHorizontal(lineStyle); 176 | GUILayout.Label(log.CreationDate + " | " + log.Message); 177 | GUILayout.EndHorizontal(); 178 | } 179 | 180 | GUILayout.EndScrollView(); 181 | } 182 | 183 | private Texture2D MakeTexture(int width, int height, Color col) 184 | { 185 | Color[] pix = new Color[width * height]; 186 | 187 | for (int i = 0; i < pix.Length; i++) 188 | pix [i] = col; 189 | 190 | Texture2D result = new Texture2D(width, height); 191 | result.SetPixels(pix); 192 | result.Apply(); 193 | 194 | return result; 195 | } 196 | 197 | private void AddLog(LogCatLog log) 198 | { 199 | lock (logsList) 200 | { 201 | if (logsList.Count > memoryLimit + 1) 202 | logsList.RemoveRange(0, logsList.Count - memoryLimit + 1); 203 | 204 | logsList.Add(log); 205 | } 206 | } 207 | 208 | void OnEnable() 209 | { 210 | #if UNITY_2017_3_OR_NEWER 211 | CompilationPipeline.assemblyCompilationStarted += OnAssemblyCompilationStarted; 212 | #endif 213 | } 214 | 215 | void OnDisable() 216 | { 217 | #if UNITY_2017_3_OR_NEWER 218 | CompilationPipeline.assemblyCompilationStarted -= OnAssemblyCompilationStarted; 219 | #endif 220 | } 221 | 222 | void OnDestroy() 223 | { 224 | StopLogCatProcess(); 225 | } 226 | 227 | private void StopLogCatProcess() 228 | { 229 | if (logCatProcess == null) 230 | { 231 | return; 232 | } 233 | try 234 | { 235 | if (!logCatProcess.HasExited) 236 | { 237 | logCatProcess.Kill(); 238 | } 239 | } 240 | catch(InvalidOperationException) 241 | { 242 | // Just ignore it. 243 | } 244 | finally 245 | { 246 | logCatProcess.Dispose(); 247 | logCatProcess = null; 248 | } 249 | } 250 | 251 | private void OnAssemblyCompilationStarted(string _) 252 | { 253 | StopLogCatProcess(); 254 | } 255 | 256 | private class LogCatLog 257 | { 258 | public LogCatLog(string data) 259 | { 260 | // First char indicates error type: 261 | // W - warning 262 | // E - error 263 | // D - debug 264 | // I - info 265 | // V - verbose 266 | Match match = LogcatRegex.Match(data); 267 | if (match.Success) 268 | { 269 | Type = match.Groups[2].Value[0]; 270 | 271 | Message = match.Groups[3].Value; 272 | CreationDate = match.Groups[1].Value; 273 | } 274 | else 275 | { 276 | Type = 'V'; 277 | 278 | Message = data; 279 | CreationDate = DateTime.Now.ToString("MM-dd HH:mm:ss.fff"); 280 | } 281 | } 282 | 283 | public string CreationDate 284 | { 285 | get; 286 | set; 287 | } 288 | 289 | public char Type 290 | { 291 | get; 292 | set; 293 | } 294 | 295 | public string Message 296 | { 297 | get; 298 | set; 299 | } 300 | 301 | public Color GetBgColor() 302 | { 303 | switch (Type) 304 | { 305 | case 'W': 306 | return Color.yellow; 307 | 308 | case 'I': 309 | return Color.green; 310 | 311 | case 'E': 312 | return Color.red; 313 | 314 | case 'D': 315 | return Color.blue; 316 | 317 | case 'V': 318 | default: 319 | return Color.grey; 320 | } 321 | } 322 | } 323 | 324 | private static string GetAdbPath() 325 | { 326 | #if UNITY_2019_1_OR_NEWER 327 | ADB adb = ADB.GetInstance(); 328 | return adb == null ? string.Empty : adb.GetADBPath(); 329 | #else 330 | string androidSdkRoot = EditorPrefs.GetString("AndroidSdkRoot"); 331 | if (string.IsNullOrEmpty(androidSdkRoot)) 332 | { 333 | return string.Empty; 334 | } 335 | return Path.Combine(androidSdkRoot, Path.Combine("platform-tools", "adb")); 336 | #endif 337 | } 338 | } 339 | #endif 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Unity3D LogCat extension 2 | ======================== 3 | 4 | Logcat (android logging) extension for unity3D. Forget about using android studio or worthless terminal window. 5 | Log and filter your android messages directly from Unity3D! 6 | 7 | Works both on **windows** & **mac OS** and **Unity Free** & **Pro** versions! 8 | 9 | Functions 10 | --------------------- 11 | - Start / stop logging 12 | - Clear logs 13 | - "Only Unity" pre-filter 14 | - Filter by log type (error, warning, debug, info, verbose) 15 | - Filter by text (case-insensitive) 16 | 17 | How to use 18 | --------------------- 19 | 1. Clone `LogCatWindow.cs` from this repository into a folder named `Editor` within your `Assets` folder 20 | 2. Plug in the android device to the usb 21 | 3. Click Window -> LogCat - Android Logger 22 | 4. Start logging by clicking "Start logging" button! 23 | 24 | Screenshots 25 | --------------------- 26 | 27 | ### Initial window 28 | ![Initial window](/screenshots/InitialWindow.png) 29 | 30 | ### Filter by log type(s) 31 | ![Filter by string](/screenshots/FilterByErrorTypes.png) 32 | 33 | ### Filter by log type(s) & text 34 | ![Filter by log type & string](/screenshots/FilterByErrorTypesAndString.png) 35 | 36 | Contribution 37 | --------------------- 38 | Feel free to contribute! 39 | -------------------------------------------------------------------------------- /screenshots/FilterByErrorTypes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzonatan/Unity3D-LogCat-extension/432f320224919745eee7d008a5bb4b1a17fc2500/screenshots/FilterByErrorTypes.png -------------------------------------------------------------------------------- /screenshots/FilterByErrorTypesAndString.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzonatan/Unity3D-LogCat-extension/432f320224919745eee7d008a5bb4b1a17fc2500/screenshots/FilterByErrorTypesAndString.png -------------------------------------------------------------------------------- /screenshots/InitialWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzonatan/Unity3D-LogCat-extension/432f320224919745eee7d008a5bb4b1a17fc2500/screenshots/InitialWindow.png --------------------------------------------------------------------------------