Debug.cs 10 KB

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