TraceMessageHook.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <TraceMessageHook.h>
  9. #include <AzCore/std/smart_ptr/shared_ptr.h>
  10. #include <AzFramework/StringFunc/StringFunc.h>
  11. #include <AzToolsFramework/Debug/TraceContextLogFormatter.h>
  12. #include <AzCore/Debug/Trace.h>
  13. #include <AzCore/PlatformIncl.h>
  14. namespace AssetBuilder
  15. {
  16. TraceMessageHook::TraceMessageHook()
  17. : m_stacks(nullptr)
  18. , m_inDebugMode(false)
  19. , m_skipErrorsCount(0)
  20. , m_skipWarningsCount(0)
  21. , m_skipPrintfsCount(0)
  22. , m_totalWarningCount(0)
  23. , m_totalErrorCount(0)
  24. {
  25. AssetBuilderSDK::AssetBuilderTraceBus::Handler::BusConnect();
  26. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  27. }
  28. TraceMessageHook::~TraceMessageHook()
  29. {
  30. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  31. AssetBuilderSDK::AssetBuilderTraceBus::Handler::BusDisconnect();
  32. delete m_stacks;
  33. m_stacks = nullptr;
  34. }
  35. void TraceMessageHook::EnableTraceContext(bool enable)
  36. {
  37. if (enable)
  38. {
  39. if (!m_stacks)
  40. {
  41. m_stacks = new AzToolsFramework::Debug::TraceContextMultiStackHandler();
  42. }
  43. }
  44. else
  45. {
  46. delete m_stacks;
  47. m_stacks = nullptr;
  48. }
  49. }
  50. void TraceMessageHook::EnableDebugMode(bool enable)
  51. {
  52. m_inDebugMode = enable;
  53. }
  54. bool TraceMessageHook::OnAssert(const char* message)
  55. {
  56. if (m_skipErrorsCount == 0)
  57. {
  58. CleanMessage(stdout, "E", message, true);
  59. AZ::Debug::Trace::Instance().PrintCallstack("", 3); // Skip all the Trace.cpp function calls
  60. std::fflush(stdout);
  61. ++m_totalErrorCount;
  62. }
  63. else
  64. {
  65. --m_skipErrorsCount;
  66. }
  67. return !m_inDebugMode;
  68. }
  69. bool TraceMessageHook::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message)
  70. {
  71. if(m_skipErrorsCount == 0)
  72. {
  73. // Add the trace information and message type to context details to simplify the event log
  74. AZ_TraceContext("Trace", AZStd::string::format("%s(%d): '%s'", fileName, line, func));
  75. AZ_TraceContext("Type", "Trace::Error");
  76. CleanMessage(stdout, "E", AZStd::string::format("%s: %s", window, message).c_str(), true);
  77. ++m_totalErrorCount;
  78. }
  79. else
  80. {
  81. --m_skipErrorsCount;
  82. }
  83. return !m_inDebugMode;
  84. }
  85. bool TraceMessageHook::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message)
  86. {
  87. if (m_skipWarningsCount == 0)
  88. {
  89. // Add the trace information and message type to context details to simplify the event log
  90. AZ_TraceContext("Trace", AZStd::string::format("%s(%d): '%s'", fileName, line, func));
  91. AZ_TraceContext("Type", "Trace::Warning");
  92. CleanMessage(stdout, "W", AZStd::string::format("%s: %s", window, message).c_str(), true);
  93. ++m_totalWarningCount;
  94. }
  95. else
  96. {
  97. --m_skipWarningsCount;
  98. }
  99. return !m_inDebugMode;
  100. }
  101. bool TraceMessageHook::OnException(const char* message)
  102. {
  103. m_isInException = true;
  104. CleanMessage(stdout, "E", message, true);
  105. ++m_totalErrorCount;
  106. AZ::Debug::Trace::HandleExceptions(false);
  107. AZ::Debug::Trace::Instance().PrintCallstack("", 3); // Skip all the Trace.cpp function calls
  108. // note that the above call ultimately results in a whole bunch of TracePrint/Outputs, which will end up in OnOutput below.
  109. std::fflush(stdout);
  110. // if we don't terminate here, the user may get a dialog box from the OS saying that the program crashed.
  111. // we don't want this, because in this case, the program is one of potentially many, many background worker processes
  112. // that are continuously starting/stopping and they'd get flooded by those message boxes.
  113. AZ::Debug::Trace::Terminate(1);
  114. return false;
  115. }
  116. bool TraceMessageHook::OnOutput(const char* /*window*/, const char* message)
  117. {
  118. if (m_isInException) // all messages that occur during an exception should be considered an error.
  119. {
  120. CleanMessage(stdout, "E", message, true);
  121. return true;
  122. }
  123. return false;
  124. }
  125. bool TraceMessageHook::OnPrintf(const char* window, const char* message)
  126. {
  127. if (m_skipPrintfsCount == 0)
  128. {
  129. CleanMessage(stdout, window, message, false);
  130. }
  131. else
  132. {
  133. --m_skipPrintfsCount;
  134. }
  135. return true;
  136. }
  137. void TraceMessageHook::IgnoreNextErrors(AZ::u32 count)
  138. {
  139. m_skipErrorsCount += count;
  140. }
  141. void TraceMessageHook::IgnoreNextWarning(AZ::u32 count)
  142. {
  143. m_skipWarningsCount += count;
  144. }
  145. void TraceMessageHook::IgnoreNextPrintf(AZ::u32 count)
  146. {
  147. m_skipPrintfsCount += count;
  148. }
  149. void TraceMessageHook::ResetWarningCount()
  150. {
  151. m_totalWarningCount = 0;
  152. }
  153. void TraceMessageHook::ResetErrorCount()
  154. {
  155. m_totalErrorCount = 0;
  156. }
  157. AZ::u32 TraceMessageHook::GetWarningCount()
  158. {
  159. return m_totalWarningCount;
  160. }
  161. AZ::u32 TraceMessageHook::GetErrorCount()
  162. {
  163. return m_totalErrorCount;
  164. }
  165. void TraceMessageHook::DumpTraceContext(FILE* stream) const
  166. {
  167. if (m_stacks)
  168. {
  169. AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_stacks->GetCurrentStack();
  170. if (stack)
  171. {
  172. AZStd::string line;
  173. size_t stackSize = stack->GetStackCount();
  174. for (size_t i = 0; i < stackSize; ++i)
  175. {
  176. line.clear();
  177. AzToolsFramework::Debug::TraceContextLogFormatter::PrintLine(line, *stack, i);
  178. CleanMessage(stream, "C", line.c_str(), false, nullptr, false);
  179. }
  180. }
  181. }
  182. }
  183. void TraceMessageHook::CleanMessage(FILE* stream, const char* prefix, const char* message, bool forceFlush, const char* extraPrefix, bool includeTraceContext) const
  184. {
  185. if (message && message[0])
  186. {
  187. AZStd::vector<AZStd::string> lines;
  188. AzFramework::StringFunc::Tokenize(message, lines, '\n', true, true); // Make sure to keep empty lines because it could be intentional blank lines someone has added for formatting reasons
  189. // If the message ended with a newline, remove it, we're adding newlines to each line already
  190. if(lines.back().empty())
  191. {
  192. lines.pop_back();
  193. }
  194. for (const AZStd::string& line : lines)
  195. {
  196. if(includeTraceContext)
  197. {
  198. DumpTraceContext(stream);
  199. }
  200. if (prefix && prefix[0])
  201. {
  202. fprintf(stream, "%s: ", prefix);
  203. }
  204. if(extraPrefix && extraPrefix[0])
  205. {
  206. fprintf(stream, "%s", extraPrefix);
  207. }
  208. fprintf(stream, "%s\n", line.c_str());
  209. }
  210. // Make sure the message ends with a newline
  211. if (message[AZStd::char_traits<char>::length(message) - 1] != '\n')
  212. {
  213. fprintf(stream, "\n");
  214. }
  215. if (forceFlush)
  216. {
  217. fflush(stream);
  218. }
  219. }
  220. }
  221. } // namespace AssetBuilder