Files
2026-03-04 09:37:33 +08:00

467 lines
12 KiB
C#

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<Log> logsThread = new FastList<Log>();
private FastList<Log> logs = new FastList<Log>();
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("<html>");
sw.Write("<body style=\"font-family:consolas; font-size:100%; background-color:#1E1E1E;\" >");
sw.Write("<strong><span style=\"color:#DCDCDC;");
sw.Write("font-size:");
sw.Write(titleFontSize.ToString());
sw.Write("px;\">");
sw.Write(Helper.GetApplicationInfo());
sw.Write("</span></strong><br><br><br>");
sw.Write("<span style=\"color:#DCDCDC;");
sw.Write(frameFontSizeString);
sw.Write("Unity version ");
sw.Write(Application.unityVersion);
sw.Write("</span><ul>");
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("</ul></body>");
sw.Write("</html>");
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("</ul><br><strong><span style=\"color:#508EA1;");
sw.Write(frameFontSizeString);
sw.Write("[Frame ");
sw.Write(currentFrame.ToString("D6"));
sw.Write("][Time ");
sw.Write(Helper.ToTimeFormat(frameTime));
sw.Write("] -----------------------------------------------------------------------------------------------");
sw.Write("</span></strong><ul>");
}
switch (logType)
{
case LogType.Error:
sw.Write("<li style =\"color:#FF0000;");
break;
case LogType.Exception:
sw.Write("<li style =\"color:#9D00FF;");
break;
case LogType.Warning:
sw.Write("<li style =\"color:#FFFF00;");
break;
default:
if (entryType == EntryType2.Unity)
{
sw.Write("<li style =\"color:#F0F0F0;");
break;
}
if (!closeLi)
{
sw.Write("<li style =\"color:#");
}
else
{
sw.Write("<span style =\"color:#");
}
switch (entryType)
{
case EntryType2.Command:
sw.Write(ColorUtility.ToHtmlStringRGB(Color.green) + ";");
break;
case EntryType2.CommandResult:
sw.Write(ColorUtility.ToHtmlStringRGB(Helper.colCommandResult) + ";");
break;
case EntryType2.CommandFault:
sw.Write(ColorUtility.ToHtmlStringRGB(Helper.colCommandResultFailed) + ";");
break;
}
break;
}
sw.Write(logFontSizeString);
if (entryType != EntryType2.Unity)
{
sw.Write("<strong>");
}
sw.Write(logString);
if (entryType != EntryType2.Unity)
{
sw.Write("</strong>");
}
if (!isMainThread)
{
sw.Write(" <i>[Thread ");
sw.Write(threadId);
sw.Write("]</i>");
}
sw.Write("</br>");
string[] array = null;
if (logType == LogType.Exception)
{
sw.Write("<span style=\"color:#7D00DF;");
sw.Write(stackFontSizeString);
array = stackTraceString.Split('\n');
for (int i = 0; i < array.Length; i++)
{
sw.Write(array[i]);
if (i < array.Length - 1)
{
sw.Write("<br>");
}
}
sw.Write("</span>");
}
else if (entryType == EntryType2.Unity && UseStackTrace(logType))
{
switch (logType)
{
case LogType.Error:
sw.Write("<span style=\"color:#A00000;");
break;
case LogType.Warning:
sw.Write("<span style=\"color:#A0A000;");
break;
default:
sw.Write("<span style=\"color:#909090;");
break;
}
sw.Write(stackFontSizeString);
int num = ((logType == LogType.Log && normalLogOnlyFirstLineStackTrace) ? 1 : stackTrace.FrameCount);
if ((bool)RuntimeConsole.instance)
{
array = new string[num];
}
for (int j = 0; j < num; j++)
{
StackFrame frame = stackTrace.GetFrame(j);
if (frame == null)
{
continue;
}
MethodBase method = frame.GetMethod();
string text = method.DeclaringType.Name;
sw.Write(text);
sw.Write(".");
sw.Write(method);
if (isEditor || isDebugBuild)
{
sw.Write(":");
int fileLineNumber = frame.GetFileLineNumber();
sw.Write(fileLineNumber);
if ((bool)RuntimeConsole.instance)
{
array[j] = text + "." + method?.ToString() + ":" + fileLineNumber;
}
}
else if ((bool)RuntimeConsole.instance)
{
array[j] = text + "." + method;
}
sw.Write("<br>");
}
sw.Write("</style></span>");
}
if (entryType == EntryType2.Unity && (bool)RuntimeConsole.instance)
{
RuntimeConsole.Log(logString, array, logType, Color.white, threadId);
}
if (closeLi)
{
sw.Write("</li>");
sw.Write("<span style=\"font-size:9px;\"><br></span>");
}
else
{
sw.Write("</span>");
}
sw.Flush();
}
}
}