using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Threading; using UnityEngine; namespace DebuggingEssentials { [DefaultExecutionOrder(-5000000)] public class HtmlDebug : MonoBehaviour { public static HtmlDebug instance; public static Stopwatch timeSinceStartup = new Stopwatch(); public static float frameTime; public static int currentFrame = 0; public static int mainThreadId; [Tooltip("Automatically deletes build Log files that are older than x days.")] public bool deleteBuildLogs = true; public int deleteBuildLogsAfterDays = 7; [Tooltip("Open the HTML Log file manually by code if you want to give your own path and file name")] public bool openLogManually; [Tooltip("Only shows the first line of the stack trace with Debub.Log.\n\n(Only if Stack Trace is enabled in the Player Settings)")] public bool normalLogOnlyFirstLineStackTrace = true; public int titleFontSize = 30; public int frameFontSize = 16; public int logFontSize = 15; public int stackFontSize = 13; [NonSerialized] public string logPathIncludingFilename; [NonSerialized] public string logPath; private FastList logsThread = new FastList(); private FastList logs = new FastList(); private int lastFrame = -1; private bool isLogEnabled; private bool isEditor; private bool isDebugBuild; private StackTraceLogType logStackTraceLogType; private StackTraceLogType assertStackTraceLogType; private StackTraceLogType warningStackTraceLogType; private StackTraceLogType errorStackTraceLogType; private StackTraceLogType exceptionStackTraceLogType; private string frameFontSizeString; private string stackFontSizeString; private string logFontSizeString; private WaitCallback logCallBack; private StreamWriter sw; private bool isLogging; private bool updateLogCallFromMainThread = true; private bool isQuitting; private const int skipFrames = 6; public static void ResetStatic() { frameTime = 0f; currentFrame = 0; mainThreadId = Thread.CurrentThread.ManagedThreadId; } private void Awake() { if (instance != null) { UnityEngine.Debug.LogError("You have more than 1 HtmlDebug GameObject, make sure to only use 1"); return; } instance = this; logCallBack = WriteLogs; if (!openLogManually) { OpenLog(Helper.GetConsoleLogPath()); } } private void OnDestroy() { if (instance == this) { instance = null; } if (!isQuitting) { CloseLog("OnDestroy"); } } private void OnApplicationQuit() { isQuitting = true; CloseLog("OnApplicationQuit"); } private void CloseLog(string closeReason) { while (isLogging) { } updateLogCallFromMainThread = false; UnityDebugLogThread(closeReason, string.Empty, LogType.Log); WriteLogs(logCallBack); CloseLog(); } public static int GetThreadId() { int managedThreadId = Thread.CurrentThread.ManagedThreadId; if (managedThreadId != mainThreadId) { return managedThreadId; } return -1; } private void DeleteBuildLogsAfterXDays(string logPath) { if (!deleteBuildLogs) { return; } string[] files = Directory.GetFiles(logPath); for (int i = 0; i < files.Length; i++) { FileInfo fileInfo = new FileInfo(files[i]); if (fileInfo.LastWriteTime < DateTime.Now.AddDays(-deleteBuildLogsAfterDays) && (fileInfo.Name.Contains(".html") || fileInfo.Name.Contains(".log"))) { fileInfo.Delete(); } } } public static void OpenLog(string logPathIncludingFileName) { instance.OpenLogInternal(logPathIncludingFileName); } private void OpenLogInternal(string logPathIncludingFileName) { if (isLogEnabled) { UnityEngine.Debug.LogError("Html Debug Logs is already opened. Make sure if you call OpenLog manually to enable 'Open Log Manually' in the HTML Debug Inspector"); return; } isLogEnabled = true; logStackTraceLogType = Application.GetStackTraceLogType(LogType.Log); assertStackTraceLogType = Application.GetStackTraceLogType(LogType.Assert); warningStackTraceLogType = Application.GetStackTraceLogType(LogType.Warning); errorStackTraceLogType = Application.GetStackTraceLogType(LogType.Error); exceptionStackTraceLogType = Application.GetStackTraceLogType(LogType.Exception); isEditor = Application.isEditor; isDebugBuild = UnityEngine.Debug.isDebugBuild; frameFontSizeString = "font-size:" + frameFontSize + "px;\">"; stackFontSizeString = "font-size:" + stackFontSize + "px;\">"; logFontSizeString = "font-size:" + logFontSize + "px;\">"; try { logPathIncludingFileName = logPathIncludingFileName.Replace(".log", ".html"); logPathIncludingFilename = logPathIncludingFileName; logPath = logPathIncludingFileName.Substring(0, logPathIncludingFileName.LastIndexOf("/")); Directory.CreateDirectory(Path.GetDirectoryName(logPathIncludingFileName)); sw = new StreamWriter(logPathIncludingFileName, append: false, Encoding.ASCII, 8192); sw.Write(""); sw.Write(""); sw.Write(""); sw.Write(Helper.GetApplicationInfo()); sw.Write("


"); sw.Write("
    "); Application.logMessageReceivedThreaded += UnityDebugLogThread; DeleteBuildLogsAfterXDays(logPath); } catch (Exception ex) { UnityEngine.Debug.LogError(ex.ToString()); } } private void CloseLog() { isLogEnabled = false; Application.logMessageReceivedThreaded -= UnityDebugLogThread; if (sw != null) { sw.Write("
"); sw.Write(""); sw.Close(); } } private bool UseStackTrace(LogType logType) { if (logType == LogType.Log && logStackTraceLogType != StackTraceLogType.None) { return true; } if (logType == LogType.Assert && assertStackTraceLogType != StackTraceLogType.None) { return true; } if (logType == LogType.Warning && warningStackTraceLogType != StackTraceLogType.None) { return true; } if (logType == LogType.Error && errorStackTraceLogType != StackTraceLogType.None) { return true; } if (logType == LogType.Exception && exceptionStackTraceLogType != StackTraceLogType.None) { return true; } return false; } public void UpdateLogs() { if (!isLogging && logsThread.Count != 0) { isLogging = true; ThreadPool.QueueUserWorkItem(logCallBack); } } private void WriteLogs(object callback) { try { logs.GrabListThreadSafe(logsThread, fastClear: true); for (int i = 0; i < logs.Count; i++) { Log log = logs.items[i]; UnityDebugLog(log.logString, log.stackTraceString, log.logType, log.isMainThread, log.threadId, log.stackTrace); } logs.FastClear(); } catch (Exception exception) { UnityEngine.Debug.LogException(exception); } finally { isLogging = false; } } private void UnityDebugLogThread(string logString, string stackTraceString, LogType logType) { if (isLogEnabled && sw != null) { int managedThreadId = Thread.CurrentThread.ManagedThreadId; bool flag = managedThreadId == mainThreadId; bool flag2 = UseStackTrace(logType); logsThread.AddThreadSafe(new Log { logString = logString, stackTraceString = stackTraceString, logType = logType, isMainThread = flag, threadId = managedThreadId, stackTrace = (flag2 ? new StackTrace(6, fNeedFileInfo: true) : null) }); if (flag && updateLogCallFromMainThread) { UpdateLogs(); } } } public void UnityDebugLog(string logString, string stackTraceString, LogType logType, bool isMainThread, int threadId = -1, StackTrace stackTrace = null, EntryType2 entryType = EntryType2.Unity, bool closeLi = true) { if (!isLogEnabled || sw == null) { return; } if (currentFrame != lastFrame) { lastFrame = currentFrame; sw.Write("
    "); } switch (logType) { case LogType.Error: sw.Write("
  • "); } sw.Write(logString); if (entryType != EntryType2.Unity) { sw.Write(""); } if (!isMainThread) { sw.Write(" [Thread "); sw.Write(threadId); sw.Write("]"); } sw.Write("
    "); string[] array = null; if (logType == LogType.Exception) { sw.Write(""); } } sw.Write(""); } else if (entryType == EntryType2.Unity && UseStackTrace(logType)) { switch (logType) { case LogType.Error: sw.Write(""); } sw.Write(""); } if (entryType == EntryType2.Unity && (bool)RuntimeConsole.instance) { RuntimeConsole.Log(logString, array, logType, Color.white, threadId); } if (closeLi) { sw.Write("
  • "); sw.Write("
    "); } else { sw.Write(""); } sw.Flush(); } } }