4#include "Stroika/Foundation/StroikaPreComp.h"
15#include "Stroika/Foundation/Characters/LineEndings.h"
20#include "Stroika/Foundation/Execution/Common.h"
21#include "Stroika/Foundation/Execution/Module.h"
22#include "Stroika/Foundation/Execution/Process.h"
24#include "Stroika/Foundation/Memory/Common.h"
29#if qStroika_Foundation_Debug_TraceToFile
36using namespace Characters;
38using namespace Execution;
42using Debug::Private_::Emitter;
56 const char kBadChar_ =
' ';
57 void SquishBadCharacters_ (
string* s)
noexcept
60 size_t end = s->length ();
65 for (
size_t i = 0; i < end; ++i) {
66 if ((*s)[i] ==
'\n' or (*s)[i] ==
'\r') {
71 void SquishBadCharacters_ (wstring* s)
noexcept
74 size_t end = s->length ();
79 for (
size_t i = 0; i < end; ++i) {
80 if ((*s)[i] ==
'\n' or (*s)[i] ==
'\r') {
88#if qStroika_Foundation_Debug_DefaultTracingOn
89 thread_local unsigned int tTraceContextDepth_{0};
93 Thread::IDType sMainThread_ = Execution::Thread::GetCurrentThreadID ();
101template <
typename CHARTYPE>
102inline void Debug::Private_::Emitter::EmitUnadornedText (
const CHARTYPE* p)
113 struct PrivateModuleData_ {
114 recursive_mutex fModuleMutex;
117#if qStroika_Foundation_Debug_TraceToFile
121#if qStroika_Foundation_Debug_TraceToFile
122 PrivateModuleData_ ()
124 fTraceFile.open (GetTraceFileName ().native ().c_str (), ios::out | ios::binary);
128 PrivateModuleData_* sModuleData_{
nullptr};
131Debug::Private_::ModuleInit_::ModuleInit_ () noexcept
133 Assert (sModuleData_ ==
nullptr);
134 sModuleData_ =
new PrivateModuleData_ ();
136Debug::Private_::ModuleInit_::~ModuleInit_ ()
138 Assert (sModuleData_ !=
nullptr);
140#if qStroika_Foundation_Debug_AssertionsChecked
141 sModuleData_ =
nullptr;
150auto Debug::Private_::Emitter::Get () noexcept -> Emitter&
152 auto emitFirstTime = [] () {
155 sModuleData_->fEmitter.EmitTraceMessage (
"***Starting TraceLog***"_f);
156 sModuleData_->fEmitter.EmitTraceMessage (
"Starting at {}"_f, Time::DateTime::Now ().Format ());
157#if qStroika_Foundation_Debug_TraceToFile
158 sModuleData_->fEmitter.EmitTraceMessage (
"TraceFileName: {}"_f, GetTraceFileName ());
160 sModuleData_->fEmitter.EmitTraceMessage (
"EXEPath={}"_f, Execution::GetEXEPath ());
161 sModuleData_->fEmitter.EmitTraceMessage (
"<debug-state>"_f);
162 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::kBuiltWithAddressSanitizer = {}"_f, Debug::kBuiltWithAddressSanitizer);
163 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::kBuiltWithThreadSanitizer = {}"_f, Debug::kBuiltWithThreadSanitizer);
164 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::kBuiltWithUndefinedBehaviorSanitizer = {}(?)"_f,
165 Debug::kBuiltWithUndefinedBehaviorSanitizer);
166 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::IsRunningUnderValgrind () = {}"_f, Debug::IsRunningUnderValgrind ());
167 sModuleData_->fEmitter.EmitTraceMessage (
"</debug-state>"_f);
169 static once_flag sOnceFlag_;
170 call_once (sOnceFlag_, [=] () { emitFirstTime (); });
171 return sModuleData_->fEmitter;
174#if qStroika_Foundation_Debug_TraceToFile
176 void Emit2File_ (
const char* text)
noexcept
181 if (sModuleData_->fTraceFile.is_open ()) {
182 sModuleData_->fTraceFile << text;
183 sModuleData_->fTraceFile.flush ();
190 void Emit2File_ (
const wchar_t* text)
noexcept
209void Debug::Private_::Emitter::EmitTraceMessage (
const char* format, ...) noexcept
211 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
217 va_start (argsList, format);
218 string tmp = Characters::CString::FormatV (format, argsList);
220 SquishBadCharacters_ (&tmp);
221 AssureHasLineTermination (&tmp);
227 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
231void Debug::Private_::Emitter::EmitTraceMessage (
const wchar_t* format, ...) noexcept
233 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
239 va_start (argsList, format);
240 wstring tmp = Characters::CString::FormatV (format, argsList);
242 SquishBadCharacters_ (&tmp);
243 AssureHasLineTermination (&tmp);
249 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
253auto Debug::Private_::Emitter::EmitTraceMessage_ (
size_t bufferLastNChars, wstring_view format, Common::StdCompat::wformat_args&& args)
noexcept
254 -> TraceLastBufferedWriteTokenType
256 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
261 wstring tmp = Common::StdCompat::vformat (qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view{format}, args);
262 SquishBadCharacters_ (&tmp);
263 AssureHasLineTermination (&tmp);
269 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
274void Debug::Private_::Emitter::EmitTraceMessage_ (wstring_view format, Common::StdCompat::wformat_args&& args)
noexcept
277 EmitTraceMessage_ (vformat (qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view{format}, args));
282void Debug::Private_::Emitter::EmitTraceMessage_ (string_view format, Common::StdCompat::format_args&& args)
noexcept
285 Debug::Private_::Emitter::EmitTraceMessage_ (
292void Debug::Private_::Emitter::EmitTraceMessage_ (
const wstring& raw)
noexcept
294 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
300 SquishBadCharacters_ (&tmp);
301 AssureHasLineTermination (&tmp);
307 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
314 pair<bool, string> mkThreadLabelForThreadID_ (
const Thread::IDType& threadID)
317 static atomic<int> sMinWidth_ = 4;
319 unsigned int threadIndex2Show = Thread::IndexRegistrar::sThe.GetIndex (threadID, &wasNew);
321 if (threadIndex2Show >= 10000) {
322 sMinWidth_.store (5);
325 if (threadID == sMainThread_) {
326 static const string kMAIN_{
"MAIN"sv};
327 return make_pair (wasNew, kMAIN_);
331 (void)::snprintf (buf, std::size (buf),
"%.*d", sMinWidth_.load (), threadIndex2Show);
332 return make_pair (wasNew, buf);
337 return make_pair (
false, Thread::FormatThreadID_A (threadID));
341template <
typename CHARTYPE>
342auto Debug::Private_::Emitter::DoEmitMessage_ (
size_t bufferLastNChars,
const CHARTYPE* s,
const CHARTYPE* e) -> TraceLastBufferedWriteTokenType
344 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
345 FlushBufferedCharacters_ ();
347 auto curRelativeTime = Time::DisplayedRealtimeClock::now ();
351 Thread::IDType threadID = Execution::Thread::GetCurrentThreadID ();
352 pair<bool, string> threadIDInfo = mkThreadLabelForThreadID_ (threadID);
353 Verify (::snprintf (buf, std::size (buf),
"[%s][%08.3f]\t", threadIDInfo.second.c_str (),
354 static_cast<double> (curRelativeTime.time_since_epoch ().count ())) > 0);
355 if (threadIDInfo.first) {
357 Verify (snprintf (buf2, std::size (buf2),
"(NEW THREAD, index=%s Real Thread ID=%s)\t", threadIDInfo.second.c_str (),
358 Thread::FormatThreadID_A (threadID).c_str ()) > 0);
359#if __STDC_WANT_SECURE_LIB__
360 (void)::strcat_s (buf, buf2);
362 (void)::strcat (buf, buf2);
364#if qStroika_Foundation_Common_Platform_POSIX
365 Verify (::snprintf (buf2, std::size (buf2),
"(pthread_self=0x%lx)\t", (
unsigned long)pthread_self ()) > 0);
366#if __STDC_WANT_SECURE_LIB__
367 (void)::strcat_s (buf, buf2);
369 (void)::strcat (buf, buf2);
373 Assert (::strlen (buf) < std::size (buf) / 2);
376#if qStroika_Foundation_Debug_DefaultTracingOn
377 unsigned int contextDepth = TraceContextBumper::GetCount ();
378 for (
unsigned int i = 0; i < contextDepth; ++i) {
382 if (bufferLastNChars == 0) {
384 ++fLastNCharBuf_Token_;
387 Assert ((e - s) >
static_cast<ptrdiff_t
> (bufferLastNChars));
388 BufferNChars_ (bufferLastNChars, e - bufferLastNChars);
389 DoEmit_ (s, e - bufferLastNChars);
390 fLastNCharBuf_WriteTickcount_ = curRelativeTime;
391 ++fLastNCharBuf_Token_;
393 return fLastNCharBuf_Token_;
396void Debug::Private_::Emitter::BufferNChars_ (
size_t bufferLastNChars,
const char* p)
398 Assert (bufferLastNChars < std::size (fLastNCharBuf_CHAR_));
399 fLastNCharBufCharCount_ = bufferLastNChars;
400 (void)::memcpy (fLastNCharBuf_CHAR_, p, bufferLastNChars);
401 fLastNCharBuf_WCHARFlag_ =
false;
404void Debug::Private_::Emitter::BufferNChars_ (
size_t bufferLastNChars,
const wchar_t* p)
406 Assert (bufferLastNChars < std::size (fLastNCharBuf_WCHAR_));
407 fLastNCharBufCharCount_ = bufferLastNChars;
408 (void)::memcpy (fLastNCharBuf_WCHAR_, p, bufferLastNChars *
sizeof (
wchar_t));
409 fLastNCharBuf_WCHARFlag_ =
true;
412void Debug::Private_::Emitter::FlushBufferedCharacters_ ()
414 if (fLastNCharBufCharCount_ != 0) {
415 if (fLastNCharBuf_WCHARFlag_) {
416 DoEmit_ (fLastNCharBuf_WCHAR_, fLastNCharBuf_WCHAR_ + fLastNCharBufCharCount_);
419 DoEmit_ (fLastNCharBuf_CHAR_, fLastNCharBuf_CHAR_ + fLastNCharBufCharCount_);
421 fLastNCharBufCharCount_ = 0;
425bool Debug::Private_::Emitter::UnputBufferedCharactersForMatchingToken (TraceLastBufferedWriteTokenType token)
428 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
433 if (fLastNCharBuf_Token_ == token and (Time::DisplayedRealtimeClock::now () - fLastNCharBuf_WriteTickcount_ < 20ms)) {
434 fLastNCharBufCharCount_ = 0;
440void Debug::Private_::Emitter::DoEmit_ (
const char* p)
noexcept
442#if qStroika_Foundation_Common_Platform_Windows
443 constexpr size_t kMaxLen_ = 1023;
444 if (::strlen (p) < kMaxLen_) {
445 ::OutputDebugStringA (p);
449 (void)::memcpy (buf, p,
sizeof (buf));
450 buf[std::size (buf) - 1] = 0;
451 ::OutputDebugStringA (buf);
452 ::OutputDebugStringA (
"...");
453 ::OutputDebugStringA (kEOL<char>);
456#if qStroika_Foundation_Debug_TraceToFile
461void Debug::Private_::Emitter::DoEmit_ (
const wchar_t* p)
noexcept
463#if qStroika_Foundation_Common_Platform_Windows
464 constexpr size_t kMaxLen_ = 1023;
465 if (::wcslen (p) < kMaxLen_) {
466 ::OutputDebugStringW (p);
470 (void)::memcpy (buf, p,
sizeof (buf));
471 buf[std::size (buf) - 1] = 0;
472 ::OutputDebugStringW (buf);
473 ::OutputDebugStringW (L
"...");
474 ::OutputDebugStringW (kEOL<wchar_t>);
477#if qStroika_Foundation_Debug_TraceToFile
482void Debug::Private_::Emitter::DoEmit_ (
const char* p,
const char* e)
noexcept
486 StackBuffer<char> buf{Memory::eUninitialized, len + 1};
487 (void)::memcpy (buf.begin (), p, len);
488 buf.begin ()[len] =
'\0';
489 DoEmit_ (buf.begin ());
521#if qCompilerAndStdLib_arm_asan_FaultStackUseAfterScope_Buggy
522Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
525 Debug::Private_::Emitter::DoEmit_ (
const wchar_t* p,
const wchar_t* e)
noexcept
529 StackBuffer<wchar_t> buf{Memory::eUninitialized, len + 1};
530 (void)::memcpy (buf.begin (), p, len *
sizeof (wchar_t));
531 buf.begin ()[len] =
'\0';
532 DoEmit_ (buf.begin ());
544wstring Debug::GetDbgTraceThreadName (thread::id threadID)
546 return String::FromNarrowSDKString (GetDbgTraceThreadName_A (threadID)).As<wstring> ();
548string Debug::GetDbgTraceThreadName_A (thread::id threadID)
550 return mkThreadLabelForThreadID_ (threadID).second;
553#if qStroika_Foundation_Debug_TraceToFile
559filesystem::path Debug::GetTraceFileName ()
561 auto mkTraceFileName_ = [] () -> filesystem::path {
569 mfname = Execution::GetEXEPath ().native ();
572 mfname = SDKSTR (
"{unknown}");
575 size_t i = mfname.rfind (filesystem::path::preferred_separator);
576 if (i != SDKString::npos) {
577 mfname = mfname.substr (i + 1);
579 i = mfname.rfind (
'.');
580 if (i != SDKString::npos) {
584 for (
auto i = mfname.begin (); i != mfname.end (); ++i) {
590 SDKString nowstr = Time::DateTime::Now ().Format (Time::DateTime::kISO8601Format).AsSDKString ();
591 for (
auto i = nowstr.begin (); i != nowstr.end (); ++i) {
595 if (*i ==
'/' or *i ==
' ') {
600 CString::Format (SDKSTR (
"TraceLog_%s_PID#%d-%s.txt"), mfname.c_str (), (
int)Execution::GetCurrentProcessID (), nowstr.c_str ());
602 static filesystem::path sTraceFileName_ = mkTraceFileName_ ();
603 return sTraceFileName_;
612#if qStroika_Foundation_Debug_DefaultTracingOn
616 Require (char_traits<wchar_t>::length (mainName.data ()) <= kMaxContextNameLen_);
617 if (extraTextAtTop.empty () or extraTextAtTop[0] ==
'\0') {
618 auto mainNameData = mainName.data ();
619 fLastWriteToken_ = Private_::Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L
"<{}> {{"sv,
620 Common::StdCompat::make_wformat_args (mainNameData));
623 auto mainNameData = mainName.data ();
624 auto extraTextAtTopData = extraTextAtTop.data ();
625 fLastWriteToken_ = Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L
"<{} ({})> {{"sv,
626 Common::StdCompat::make_wformat_args (mainNameData, extraTextAtTopData));
628 size_t len = char_traits<wchar_t>::length (mainName.data ());
629 char_traits<wchar_t>::copy (fSavedContextName_.data (), mainName.data (), len);
630 if (len >= kMaxContextNameLen_ - 1) {
631 char_traits<wchar_t>::copy (&fSavedContextName_.data ()[len - 3], L
"...", 3);
633 fSavedContextName_[len] =
'\0';
634 *(std::end (fSavedContextName_) - 1) =
'\0';
644 va_start (argsList, extraFmt);
645 wstring tmpFmtV = Characters::CString::FormatV (extraFmt, argsList);
646 fLastWriteToken_ = Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L
"<{} ({})> {{"sv,
647 Common::StdCompat::make_wformat_args (contextName, tmpFmtV));
649 size_t len = min (kMaxContextNameLen_ - 1, char_traits<wchar_t>::length (contextName));
650 char_traits<wchar_t>::copy (fSavedContextName_.data (), contextName, len);
651 if (len >= kMaxContextNameLen_ - 1) {
652 char_traits<wchar_t>::copy (&fSavedContextName_.data ()[len - 3], L
"...", 3);
654 *(std::end (fSavedContextName_) - 1) =
'\0';
655 fSavedContextName_[len] =
'\0';
662unsigned int TraceContextBumper::GetCount ()
664 return tTraceContextDepth_;
667void TraceContextBumper::IncCount_ () noexcept
669 ++tTraceContextDepth_;
672void TraceContextBumper::DecrCount_ () noexcept
674 --tTraceContextDepth_;
677TraceContextBumper::~TraceContextBumper () noexcept
683 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
684 if (Emitter::Get ().UnputBufferedCharactersForMatchingToken (fLastWriteToken_)) {
685 Emitter::Get ().EmitUnadornedText (
"/>");
686 Emitter::Get ().EmitUnadornedText (kEOL<char>);
689 Emitter::Get ().EmitTraceMessage (
"}} </{}>"_f, fSavedContextName_.data ());
#define RequireNotNull(p)
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
#define AssertNotReached()
#define CompileTimeFlagChecker_SOURCE(NS_PREFIX, NAME, VALUE)
#define qStroika_Foundation_Debug_ShowThreadIndex
if true, emit a much shorter thread ID, making - I suspect (testing) for terser and clearer tracelogs...
#define qStroika_Foundation_Debug_TraceToFile
Include this file VERY EARLY ON - before including stuff like <cstdio> - to allow use of Valgrind (so...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual string AsNarrowSDKString() const
static String FromNarrowSDKString(const char *from)
TraceContextBumper() noexcept
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
basic_string< SDKChar > SDKString
CONTAINER::value_type * End(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the end of the co...
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...
filesystem::path GetTemporary()