2
0

Debug.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Reflection;
  7. using System.Runtime.CompilerServices;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11. namespace BansheeEngine
  12. {
  13. /// <summary>
  14. /// Possible types of debug messages.
  15. /// </summary>
  16. public enum DebugMessageType // Note: Must match C++ enum DebugChannel
  17. {
  18. Info, Warning, Error, CompilerWarning, CompilerError
  19. }
  20. /// <summary>
  21. /// Contains data for a single entry in a call stack associated with a log entry.
  22. /// </summary>
  23. public class CallStackEntry
  24. {
  25. public string method;
  26. public string file;
  27. public int line;
  28. }
  29. /// <summary>
  30. /// Contains data for a single log entry. Contained data was parsed from the message string
  31. /// to better organize the provided information.
  32. /// </summary>
  33. public class ParsedLogEntry
  34. {
  35. public string message;
  36. public CallStackEntry[] callstack;
  37. }
  38. /// <summary>
  39. /// Contains data for a single log entry.
  40. /// </summary>
  41. [StructLayout(LayoutKind.Sequential)]
  42. public struct LogEntry // Note: Must match C++ struct ScriptLogEntryData
  43. {
  44. public DebugMessageType type;
  45. public string message;
  46. }
  47. /// <summary>
  48. /// Utility class providing various debug functionality.
  49. /// </summary>
  50. public sealed class Debug
  51. {
  52. /// <summary>
  53. /// Triggered when a new message is added to the debug log.
  54. /// </summary>
  55. public static Action<DebugMessageType, string> OnAdded;
  56. /// <summary>
  57. /// Returns a list of all messages in the debug log.
  58. /// </summary>
  59. public static LogEntry[] Messages
  60. {
  61. get { return Internal_GetMessages(); }
  62. }
  63. /// <summary>
  64. /// Logs a new informative message to the global debug log.
  65. /// </summary>
  66. /// <param name="message">Message to log.</param>
  67. public static void Log(object message)
  68. {
  69. StringBuilder sb = new StringBuilder();
  70. sb.AppendLine(message.ToString());
  71. sb.Append(GetStackTrace(1));
  72. Internal_Log(sb.ToString());
  73. }
  74. /// <summary>
  75. /// Logs a new warning message to the global debug log.
  76. /// </summary>
  77. /// <param name="message">Message to log.</param>
  78. public static void LogWarning(object message)
  79. {
  80. StringBuilder sb = new StringBuilder();
  81. sb.AppendLine(message.ToString());
  82. sb.Append(GetStackTrace(1));
  83. Internal_LogWarning(sb.ToString());
  84. }
  85. /// <summary>
  86. /// Logs a new error message to the global debug log.
  87. /// </summary>
  88. /// <param name="message">Message to log.</param>
  89. public static void LogError(object message)
  90. {
  91. StringBuilder sb = new StringBuilder();
  92. sb.AppendLine(message.ToString());
  93. sb.Append(GetStackTrace(1));
  94. Internal_LogError(sb.ToString());
  95. }
  96. /// <summary>
  97. /// Logs a new message to the global debug log using the provided type.
  98. /// </summary>
  99. /// <param name="message">Message to log.</param>
  100. /// <param name="type">Type of the message to log.</param>
  101. internal static void LogMessage(object message, DebugMessageType type)
  102. {
  103. StringBuilder sb = new StringBuilder();
  104. sb.AppendLine(message.ToString());
  105. Internal_LogMessage(sb.ToString(), type);
  106. }
  107. /// <summary>
  108. /// Clears all messages from the debug log.
  109. /// </summary>
  110. internal static void Clear()
  111. {
  112. Internal_Clear();
  113. }
  114. /// <summary>
  115. /// Clears all messages of the specified type from the debug log.
  116. /// </summary>
  117. internal static void Clear(DebugMessageType type)
  118. {
  119. Internal_ClearType(type);
  120. }
  121. /// <summary>
  122. /// Returns the stack trace of the current point in code.
  123. /// </summary>
  124. /// <param name="ignoreFirstN">Determines how many of the top-level entries should be ignored.</param>
  125. /// <returns>String containing the stack trace.</returns>
  126. public static string GetStackTrace(int ignoreFirstN = 0)
  127. {
  128. StackTrace stackTrace = new StackTrace(1, true);
  129. StackFrame[] frames = stackTrace.GetFrames();
  130. if (frames == null)
  131. return "";
  132. StringBuilder sb = new StringBuilder();
  133. for(int i = 0; i < frames.Length; i++)
  134. {
  135. if (i < ignoreFirstN)
  136. continue;
  137. StackFrame frame = frames[i];
  138. MethodBase method = frame.GetMethod();
  139. if (method == null)
  140. continue;
  141. Type parentType = method.DeclaringType;
  142. if (parentType == null)
  143. continue;
  144. sb.Append("\tat " + parentType.Name + "." + method.Name + "(");
  145. ParameterInfo[] methodParams = method.GetParameters();
  146. for(int j = 0; j < methodParams.Length; j++)
  147. {
  148. if (j > 0)
  149. sb.Append(", ");
  150. sb.Append(methodParams[j].ParameterType.Name);
  151. }
  152. sb.Append(")");
  153. string ns = parentType.Namespace;
  154. string fileName = frame.GetFileName();
  155. if (!string.IsNullOrEmpty(fileName))
  156. {
  157. int line = frame.GetFileLineNumber();
  158. int column = frame.GetFileColumnNumber();
  159. sb.Append(" in " + fileName + ", line " + line + ", column " + column + ", namespace " + ns);
  160. }
  161. else
  162. {
  163. if (!string.IsNullOrEmpty(ns))
  164. sb.Append(" in namespace " + ns);
  165. }
  166. sb.AppendLine();
  167. }
  168. return sb.ToString();
  169. }
  170. /// <summary>
  171. /// Parses a log message and outputs a data object with a separate message and callstack entries.
  172. /// </summary>
  173. /// <param name="message">Message to parse.</param>
  174. /// <returns>Parsed log message.</returns>
  175. public static ParsedLogEntry ParseLogMessage(string message)
  176. {
  177. // Note: If you are modifying GetStackTrace method make sure to also update this one to match the formattting
  178. int firstMatchIdx = -1;
  179. Regex regex = new Regex(@"\tat (.*) in (.*), line (\d*), column .*, namespace .*");
  180. var matches = regex.Matches(message);
  181. ParsedLogEntry newEntry = new ParsedLogEntry();
  182. newEntry.callstack = new CallStackEntry[matches.Count];
  183. for (int i = 0; i < matches.Count; i++)
  184. {
  185. CallStackEntry callstackEntry = new CallStackEntry();
  186. callstackEntry.method = matches[i].Groups[1].Value;
  187. callstackEntry.file = matches[i].Groups[2].Value;
  188. int.TryParse(matches[i].Groups[3].Value, out callstackEntry.line);
  189. newEntry.callstack[i] = callstackEntry;
  190. if (firstMatchIdx == -1)
  191. firstMatchIdx = matches[i].Index;
  192. }
  193. if (firstMatchIdx != -1)
  194. newEntry.message = message.Substring(0, firstMatchIdx);
  195. else
  196. newEntry.message = message;
  197. return newEntry;
  198. }
  199. /// <summary>
  200. /// Parses a managed exception message and outputs a data object with a separate message and callstack entries.
  201. /// </summary>
  202. /// <param name="message">Message to parse.</param>
  203. /// <returns>Parsed log message.</returns>
  204. public static ParsedLogEntry ParseExceptionMessage(string message)
  205. {
  206. Regex headerRegex = new Regex(@"Managed exception: (.*)\n");
  207. var headerMatch = headerRegex.Match(message);
  208. if (!headerMatch.Success)
  209. return null;
  210. Regex regex = new Regex(@" at (.*) \[.*\] in (.*):(\d*)");
  211. var matches = regex.Matches(message);
  212. ParsedLogEntry newEntry = new ParsedLogEntry();
  213. newEntry.callstack = new CallStackEntry[matches.Count];
  214. for (int i = 0; i < matches.Count; i++)
  215. {
  216. CallStackEntry callstackEntry = new CallStackEntry();
  217. callstackEntry.method = matches[i].Groups[1].Value;
  218. callstackEntry.file = matches[i].Groups[2].Value;
  219. int.TryParse(matches[i].Groups[3].Value, out callstackEntry.line);
  220. newEntry.callstack[i] = callstackEntry;
  221. }
  222. newEntry.message = headerMatch.Groups[1].Value;
  223. return newEntry;
  224. }
  225. /// <summary>
  226. /// Triggered by the runtime when a new message is added to the debug log.
  227. /// </summary>
  228. /// <param name="type">Type of the message that was added.</param>
  229. /// <param name="message">Text of the message.</param>
  230. private static void Internal_OnAdded(DebugMessageType type, string message)
  231. {
  232. if (OnAdded != null)
  233. OnAdded(type, message);
  234. }
  235. [MethodImpl(MethodImplOptions.InternalCall)]
  236. internal static extern void Internal_Log(string message);
  237. [MethodImpl(MethodImplOptions.InternalCall)]
  238. internal static extern void Internal_LogWarning(string message);
  239. [MethodImpl(MethodImplOptions.InternalCall)]
  240. internal static extern void Internal_LogError(string message);
  241. [MethodImpl(MethodImplOptions.InternalCall)]
  242. internal static extern void Internal_LogMessage(string message, DebugMessageType type);
  243. [MethodImpl(MethodImplOptions.InternalCall)]
  244. internal static extern void Internal_Clear();
  245. [MethodImpl(MethodImplOptions.InternalCall)]
  246. internal static extern void Internal_ClearType(DebugMessageType type);
  247. [MethodImpl(MethodImplOptions.InternalCall)]
  248. internal static extern LogEntry[] Internal_GetMessages();
  249. }
  250. }