Debug.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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
  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. /// Clears all messages from the debug log.
  96. /// </summary>
  97. internal static void Clear()
  98. {
  99. Internal_Clear();
  100. }
  101. /// <summary>
  102. /// Returns the stack trace of the current point in code.
  103. /// </summary>
  104. /// <param name="ignoreFirstN">Determines how many of the top-level entries should be ignored.</param>
  105. /// <returns>String containing the stack trace.</returns>
  106. public static string GetStackTrace(int ignoreFirstN = 0)
  107. {
  108. StackTrace stackTrace = new StackTrace(1, true);
  109. StackFrame[] frames = stackTrace.GetFrames();
  110. if (frames == null)
  111. return "";
  112. StringBuilder sb = new StringBuilder();
  113. for(int i = 0; i < frames.Length; i++)
  114. {
  115. if (i < ignoreFirstN)
  116. continue;
  117. StackFrame frame = frames[i];
  118. MethodBase method = frame.GetMethod();
  119. if (method == null)
  120. continue;
  121. Type parentType = method.DeclaringType;
  122. if (parentType == null)
  123. continue;
  124. sb.Append("\tat " + parentType.Name + "." + method.Name + "(");
  125. ParameterInfo[] methodParams = method.GetParameters();
  126. for(int j = 0; j < methodParams.Length; j++)
  127. {
  128. if (j > 0)
  129. sb.Append(", ");
  130. sb.Append(methodParams[j].ParameterType.Name);
  131. }
  132. sb.Append(")");
  133. string ns = parentType.Namespace;
  134. string fileName = frame.GetFileName();
  135. if (!string.IsNullOrEmpty(fileName))
  136. {
  137. int line = frame.GetFileLineNumber();
  138. int column = frame.GetFileColumnNumber();
  139. sb.Append(" in " + fileName + ", line " + line + ", column " + column + ", namespace " + ns);
  140. }
  141. else
  142. {
  143. if (!string.IsNullOrEmpty(ns))
  144. sb.Append(" in namespace " + ns);
  145. }
  146. sb.AppendLine();
  147. }
  148. return sb.ToString();
  149. }
  150. /// <summary>
  151. /// Parses a log message and outputs a data object with a separate message and callstack entries.
  152. /// </summary>
  153. /// <param name="message">Message to parse.</param>
  154. /// <returns>Parsed log message.</returns>
  155. public static ParsedLogEntry ParseLogMessage(string message)
  156. {
  157. // Note: If you are modifying GetStackTrace method make sure to also update this one to match the formattting
  158. int firstMatchIdx = -1;
  159. Regex regex = new Regex(@"\tat (.*) in (.*), line (\d*), column .*, namespace .*");
  160. var matches = regex.Matches(message);
  161. ParsedLogEntry newEntry = new ParsedLogEntry();
  162. newEntry.callstack = new CallStackEntry[matches.Count];
  163. for (int i = 0; i < matches.Count; i++)
  164. {
  165. CallStackEntry callstackEntry = new CallStackEntry();
  166. callstackEntry.method = matches[i].Groups[1].Value;
  167. callstackEntry.file = matches[i].Groups[2].Value;
  168. int.TryParse(matches[i].Groups[3].Value, out callstackEntry.line);
  169. newEntry.callstack[i] = callstackEntry;
  170. if (firstMatchIdx == -1)
  171. firstMatchIdx = matches[i].Index;
  172. }
  173. if (firstMatchIdx != -1)
  174. newEntry.message = message.Substring(0, firstMatchIdx);
  175. else
  176. newEntry.message = message;
  177. return newEntry;
  178. }
  179. /// <summary>
  180. /// Triggered by the runtime when a new message is added to the debug log.
  181. /// </summary>
  182. /// <param name="type">Type of the message that was added.</param>
  183. /// <param name="message">Text of the message.</param>
  184. private static void Internal_OnAdded(DebugMessageType type, string message)
  185. {
  186. if (OnAdded != null)
  187. OnAdded(type, message);
  188. }
  189. [MethodImpl(MethodImplOptions.InternalCall)]
  190. internal static extern void Internal_Log(string message);
  191. [MethodImpl(MethodImplOptions.InternalCall)]
  192. internal static extern void Internal_LogWarning(string message);
  193. [MethodImpl(MethodImplOptions.InternalCall)]
  194. internal static extern void Internal_LogError(string message);
  195. [MethodImpl(MethodImplOptions.InternalCall)]
  196. internal static extern void Internal_Clear();
  197. [MethodImpl(MethodImplOptions.InternalCall)]
  198. internal static extern LogEntry[] Internal_GetMessages();
  199. }
  200. }