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 ();
102template <
typename CHARTYPE>
103inline void Debug::Private_::Emitter::EmitUnadornedText (
const CHARTYPE* p)
114 struct PrivateModuleData_ {
115 recursive_mutex fModuleMutex;
118#if qStroika_Foundation_Debug_TraceToFile
122#if qStroika_Foundation_Debug_TraceToFile
123 PrivateModuleData_ ()
125 fTraceFile.open (Debug::Private_::Emitter::GetTraceFileName ().native ().c_str (), ios::out | ios::binary);
129 PrivateModuleData_* sModuleData_{
nullptr};
132Debug::Private_::ModuleInit_::ModuleInit_ () noexcept
134 Assert (sModuleData_ ==
nullptr);
135 sModuleData_ =
new PrivateModuleData_ ();
137Debug::Private_::ModuleInit_::~ModuleInit_ ()
139 Assert (sModuleData_ !=
nullptr);
141#if qStroika_Foundation_Debug_AssertionsChecked
142 sModuleData_ =
nullptr;
151auto Debug::Private_::Emitter::Get () noexcept -> Emitter&
153 auto emitFirstTime = [] () {
156 sModuleData_->fEmitter.EmitTraceMessage (
"***Starting TraceLog***"_f);
157 sModuleData_->fEmitter.EmitTraceMessage (
"Starting at {}"_f, Time::DateTime::Now ().Format ());
158#if qStroika_Foundation_Debug_TraceToFile
159 sModuleData_->fEmitter.EmitTraceMessage (
"TraceFileName: {}"_f, Emitter::GetTraceFileName ());
162 sModuleData_->fEmitter.EmitTraceMessage (
"<debug-state>"_f);
163 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::kBuiltWithAddressSanitizer = {}"_f, Debug::kBuiltWithAddressSanitizer);
164 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::kBuiltWithThreadSanitizer = {}"_f, Debug::kBuiltWithThreadSanitizer);
165 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::kBuiltWithUndefinedBehaviorSanitizer = {}(?)"_f,
166 Debug::kBuiltWithUndefinedBehaviorSanitizer);
167 sModuleData_->fEmitter.EmitTraceMessage (
" Debug::IsRunningUnderValgrind () = {}"_f, Debug::IsRunningUnderValgrind ());
168 sModuleData_->fEmitter.EmitTraceMessage (
"</debug-state>"_f);
170 static once_flag sOnceFlag_;
171 call_once (sOnceFlag_, [=] () { emitFirstTime (); });
172 return sModuleData_->fEmitter;
175#if qStroika_Foundation_Debug_TraceToFile
176filesystem::path Debug::Private_::Emitter::GetTraceFileName ()
178 auto mkTraceFileName_ = [] () -> filesystem::path {
189 mfname = SDKSTR (
"{unknown}");
192 size_t i = mfname.rfind (filesystem::path::preferred_separator);
193 if (i != SDKString::npos) {
194 mfname = mfname.substr (i + 1);
196 i = mfname.rfind (
'.');
197 if (i != SDKString::npos) {
201 for (
auto i = mfname.begin (); i != mfname.end (); ++i) {
207 SDKString nowstr = Time::DateTime::Now ().Format (Time::DateTime::kISO8601Format).AsSDKString ();
208 for (
auto i = nowstr.begin (); i != nowstr.end (); ++i) {
212 if (*i ==
'/' or *i ==
' ') {
217 CString::Format (SDKSTR (
"TraceLog_%s_PID#%d-%s.txt"), mfname.c_str (), (
int)Execution::GetCurrentProcessID (), nowstr.c_str ());
219 static filesystem::path sTraceFileName_ = mkTraceFileName_ ();
220 return sTraceFileName_;
224#if qStroika_Foundation_Debug_TraceToFile
226 void Emit2File_ (
const char* text)
noexcept
231 if (sModuleData_->fTraceFile.is_open ()) {
232 sModuleData_->fTraceFile << text;
233 sModuleData_->fTraceFile.flush ();
240 void Emit2File_ (
const wchar_t* text)
noexcept
259void Debug::Private_::Emitter::EmitTraceMessage (
const char* format, ...) noexcept
261 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
267 va_start (argsList, format);
268 string tmp = Characters::CString::FormatV (format, argsList);
270 SquishBadCharacters_ (&tmp);
271 AssureHasLineTermination (&tmp);
277 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
281void Debug::Private_::Emitter::EmitTraceMessage (
const wchar_t* format, ...) noexcept
283 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
289 va_start (argsList, format);
290 wstring tmp = Characters::CString::FormatV (format, argsList);
292 SquishBadCharacters_ (&tmp);
293 AssureHasLineTermination (&tmp);
299 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
304auto Debug::Private_::Emitter::EmitTraceMessage_ (
size_t bufferLastNChars,
const wchar_t* format, ...) noexcept -> TraceLastBufferedWriteTokenType
306 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
312 va_start (argsList, format);
313 wstring tmp = Characters::CString::FormatV (format, argsList);
315 SquishBadCharacters_ (&tmp);
316 AssureHasLineTermination (&tmp);
322 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
328auto Debug::Private_::Emitter::EmitTraceMessage_ (
size_t bufferLastNChars, wstring_view format, Common::StdCompat::wformat_args&& args)
noexcept
329 -> TraceLastBufferedWriteTokenType
331 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
336 wstring tmp = Common::StdCompat::vformat (qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view{format}, args);
337 SquishBadCharacters_ (&tmp);
338 AssureHasLineTermination (&tmp);
344 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
349void Debug::Private_::Emitter::EmitTraceMessage_ (wstring_view format, Common::StdCompat::wformat_args&& args)
noexcept
352 EmitTraceMessage_ (vformat (qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view{format}, args));
357void Debug::Private_::Emitter::EmitTraceMessage_ (string_view format, Common::StdCompat::format_args&& args)
noexcept
360 Debug::Private_::Emitter::EmitTraceMessage_ (
367void Debug::Private_::Emitter::EmitTraceMessage_ (
const wstring& raw)
noexcept
369 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
375 SquishBadCharacters_ (&tmp);
376 AssureHasLineTermination (&tmp);
382 DoEmit_ (L
"EmitTraceMessage FAILED internally (buffer overflow?)");
389 pair<bool, string> mkThreadLabelForThreadID_ (
const Thread::IDType& threadID)
392 static atomic<int> sMinWidth_ = 4;
394 unsigned int threadIndex2Show = Thread::IndexRegistrar::sThe.GetIndex (threadID, &wasNew);
396 if (threadIndex2Show >= 10000) {
397 sMinWidth_.store (5);
400 if (threadID == sMainThread_) {
401 static const string kMAIN_{
"MAIN"sv};
402 return make_pair (wasNew, kMAIN_);
406 (void)::snprintf (buf, Memory::NEltsOf (buf),
"%.*d", sMinWidth_.load (), threadIndex2Show);
407 return make_pair (wasNew, buf);
412 return make_pair (
false, Thread::FormatThreadID_A (threadID));
416template <
typename CHARTYPE>
417auto Debug::Private_::Emitter::DoEmitMessage_ (
size_t bufferLastNChars,
const CHARTYPE* s,
const CHARTYPE* e) -> TraceLastBufferedWriteTokenType
419 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
420 FlushBufferedCharacters_ ();
422 auto curRelativeTime = Time::DisplayedRealtimeClock::now ();
426 Thread::IDType threadID = Execution::Thread::GetCurrentThreadID ();
427 pair<bool, string> threadIDInfo = mkThreadLabelForThreadID_ (threadID);
428 Verify (::snprintf (buf, Memory::NEltsOf (buf),
"[%s][%08.3f]\t", threadIDInfo.second.c_str (),
429 static_cast<double> (curRelativeTime.time_since_epoch ().count ())) > 0);
430 if (threadIDInfo.first) {
432 Verify (snprintf (buf2, Memory::NEltsOf (buf2),
"(NEW THREAD, index=%s Real Thread ID=%s)\t", threadIDInfo.second.c_str (),
433 Thread::FormatThreadID_A (threadID).c_str ()) > 0);
434#if __STDC_WANT_SECURE_LIB__
435 (void)::strcat_s (buf, buf2);
437 (void)::strcat (buf, buf2);
439#if qStroika_Foundation_Common_Platform_POSIX
440 Verify (::snprintf (buf2, Memory::NEltsOf (buf2),
"(pthread_self=0x%lx)\t", (
unsigned long)pthread_self ()) > 0);
441#if __STDC_WANT_SECURE_LIB__
442 (void)::strcat_s (buf, buf2);
444 (void)::strcat (buf, buf2);
448 Assert (::strlen (buf) < Memory::NEltsOf (buf) / 2);
451#if qStroika_Foundation_Debug_DefaultTracingOn
452 unsigned int contextDepth = TraceContextBumper::GetCount ();
453 for (
unsigned int i = 0; i < contextDepth; ++i) {
457 if (bufferLastNChars == 0) {
459 ++fLastNCharBuf_Token_;
462 Assert ((e - s) >
static_cast<ptrdiff_t
> (bufferLastNChars));
463 BufferNChars_ (bufferLastNChars, e - bufferLastNChars);
464 DoEmit_ (s, e - bufferLastNChars);
465 fLastNCharBuf_WriteTickcount_ = curRelativeTime;
466 ++fLastNCharBuf_Token_;
468 return fLastNCharBuf_Token_;
471void Debug::Private_::Emitter::BufferNChars_ (
size_t bufferLastNChars,
const char* p)
473 Assert (bufferLastNChars < Memory::NEltsOf (fLastNCharBuf_CHAR_));
474 fLastNCharBufCharCount_ = bufferLastNChars;
475 (void)::memcpy (fLastNCharBuf_CHAR_, p, bufferLastNChars);
476 fLastNCharBuf_WCHARFlag_ =
false;
479void Debug::Private_::Emitter::BufferNChars_ (
size_t bufferLastNChars,
const wchar_t* p)
481 Assert (bufferLastNChars < Memory::NEltsOf (fLastNCharBuf_WCHAR_));
482 fLastNCharBufCharCount_ = bufferLastNChars;
483 (void)::memcpy (fLastNCharBuf_WCHAR_, p, bufferLastNChars *
sizeof (
wchar_t));
484 fLastNCharBuf_WCHARFlag_ =
true;
487void Debug::Private_::Emitter::FlushBufferedCharacters_ ()
489 if (fLastNCharBufCharCount_ != 0) {
490 if (fLastNCharBuf_WCHARFlag_) {
491 DoEmit_ (fLastNCharBuf_WCHAR_, fLastNCharBuf_WCHAR_ + fLastNCharBufCharCount_);
494 DoEmit_ (fLastNCharBuf_CHAR_, fLastNCharBuf_CHAR_ + fLastNCharBufCharCount_);
496 fLastNCharBufCharCount_ = 0;
500bool Debug::Private_::Emitter::UnputBufferedCharactersForMatchingToken (TraceLastBufferedWriteTokenType token)
503 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
508 if (fLastNCharBuf_Token_ == token and (Time::DisplayedRealtimeClock::now () - fLastNCharBuf_WriteTickcount_ < 20ms)) {
509 fLastNCharBufCharCount_ = 0;
515void Debug::Private_::Emitter::DoEmit_ (
const char* p)
noexcept
517#if qStroika_Foundation_Common_Platform_Windows
518 constexpr size_t kMaxLen_ = 1023;
519 if (::strlen (p) < kMaxLen_) {
520 ::OutputDebugStringA (p);
524 (void)::memcpy (buf, p,
sizeof (buf));
525 buf[Memory::NEltsOf (buf) - 1] = 0;
526 ::OutputDebugStringA (buf);
527 ::OutputDebugStringA (
"...");
528 ::OutputDebugStringA (kEOL<char>);
531#if qStroika_Foundation_Debug_TraceToFile
536void Debug::Private_::Emitter::DoEmit_ (
const wchar_t* p)
noexcept
538#if qStroika_Foundation_Common_Platform_Windows
539 constexpr size_t kMaxLen_ = 1023;
540 if (::wcslen (p) < kMaxLen_) {
541 ::OutputDebugStringW (p);
545 (void)::memcpy (buf, p,
sizeof (buf));
546 buf[Memory::NEltsOf (buf) - 1] = 0;
547 ::OutputDebugStringW (buf);
548 ::OutputDebugStringW (L
"...");
549 ::OutputDebugStringW (kEOL<wchar_t>);
552#if qStroika_Foundation_Debug_TraceToFile
557void Debug::Private_::Emitter::DoEmit_ (
const char* p,
const char* e)
noexcept
561 StackBuffer<char> buf{Memory::eUninitialized, len + 1};
562 (void)::memcpy (buf.begin (), p, len);
563 buf.begin ()[len] =
'\0';
564 DoEmit_ (buf.begin ());
596#if qCompilerAndStdLib_arm_asan_FaultStackUseAfterScope_Buggy
597Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
600 Debug::Private_::Emitter::DoEmit_ (
const wchar_t* p,
const wchar_t* e)
noexcept
604 StackBuffer<wchar_t> buf{Memory::eUninitialized, len + 1};
605 (void)::memcpy (buf.begin (), p, len *
sizeof (wchar_t));
606 buf.begin ()[len] =
'\0';
607 DoEmit_ (buf.begin ());
619wstring Debug::GetDbgTraceThreadName (thread::id threadID)
621 return String::FromNarrowSDKString (GetDbgTraceThreadName_A (threadID)).As<wstring> ();
623string Debug::GetDbgTraceThreadName_A (thread::id threadID)
625 return mkThreadLabelForThreadID_ (threadID).second;
633#if qStroika_Foundation_Debug_DefaultTracingOn
637 Require (char_traits<wchar_t>::length (mainName.data ()) <= kMaxContextNameLen_);
638 if (extraTextAtTop.empty () or extraTextAtTop[0] ==
'\0') {
639 auto mainNameData = mainName.data ();
640 fLastWriteToken_ = Private_::Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L
"<{}> {{"sv,
641 Common::StdCompat::make_wformat_args (mainNameData));
644 auto mainNameData = mainName.data ();
645 auto extraTextAtTopData = extraTextAtTop.data ();
646 fLastWriteToken_ = Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L
"<{} ({})> {{"sv,
647 Common::StdCompat::make_wformat_args (mainNameData, extraTextAtTopData));
649 size_t len = char_traits<wchar_t>::length (mainName.data ());
650 char_traits<wchar_t>::copy (fSavedContextName_.data (), mainName.data (), len);
651 if (len >= kMaxContextNameLen_ - 1) {
652 char_traits<wchar_t>::copy (&fSavedContextName_.data ()[len - 3], L
"...", 3);
654 fSavedContextName_[len] =
'\0';
655 *(std::end (fSavedContextName_) - 1) =
'\0';
665 va_start (argsList, extraFmt);
666 wstring tmpFmtV = Characters::CString::FormatV (extraFmt, argsList);
667 fLastWriteToken_ = Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L
"<{} ({})> {{"sv,
668 Common::StdCompat::make_wformat_args (contextName, tmpFmtV));
670 size_t len = min (kMaxContextNameLen_ - 1, char_traits<wchar_t>::length (contextName));
671 char_traits<wchar_t>::copy (fSavedContextName_.data (), contextName, len);
672 if (len >= kMaxContextNameLen_ - 1) {
673 char_traits<wchar_t>::copy (&fSavedContextName_.data ()[len - 3], L
"...", 3);
675 *(std::end (fSavedContextName_) - 1) =
'\0';
676 fSavedContextName_[len] =
'\0';
683unsigned int TraceContextBumper::GetCount ()
685 return tTraceContextDepth_;
688void TraceContextBumper::IncCount_ () noexcept
690 ++tTraceContextDepth_;
693void TraceContextBumper::DecrCount_ () noexcept
695 --tTraceContextDepth_;
698TraceContextBumper::~TraceContextBumper () noexcept
704 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
705 if (Emitter::Get ().UnputBufferedCharactersForMatchingToken (fLastWriteToken_)) {
706 Emitter::Get ().EmitUnadornedText (
"/>");
707 Emitter::Get ().EmitUnadornedText (kEOL<char>);
710 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 GetEXEPath()
filesystem::path GetTemporary()