Debug.cs 9.1 KB

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