Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
Trace.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cmath>
7#include <cstdarg>
8#include <cstdio>
9#include <fstream>
10#include <map>
11#include <mutex>
12
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"
28
29#if qStroika_Foundation_Debug_TraceToFile
32#endif
33
34using namespace Stroika::Foundation;
35
36using namespace Characters;
37using namespace Debug;
38using namespace Execution;
39
41
42using Debug::Private_::Emitter;
43
44/*
45 * TODO:
46 *
47 * @todo The buffering code here maybe now correct, but isn't simple/clear, so rewrite/improve...
48 * -- LGP 2011-10-03
49 */
50
51CompileTimeFlagChecker_SOURCE (Stroika::Foundation::Debug, qTraceToFile, qStroika_Foundation_Debug_TraceToFile);
52CompileTimeFlagChecker_SOURCE (Stroika::Foundation::Debug, qDefaultTracingOn, qStroika_Foundation_Debug_DefaultTracingOn);
53
54namespace {
55 // This is MOSTLY to remove NEWLINES from the MIDDLE of a message - replace with kBadChar.
56 const char kBadChar_ = ' ';
57 void SquishBadCharacters_ (string* s) noexcept
58 {
60 size_t end = s->length ();
61 // ignore last 2 in case crlf
62 if (end > 2) {
63 end -= 2;
64 }
65 for (size_t i = 0; i < end; ++i) {
66 if ((*s)[i] == '\n' or (*s)[i] == '\r') {
67 (*s)[i] = kBadChar_;
68 }
69 }
70 }
71 void SquishBadCharacters_ (wstring* s) noexcept
72 {
74 size_t end = s->length ();
75 // ignore last 2 in case crlf
76 if (end > 2) {
77 end -= 2;
78 }
79 for (size_t i = 0; i < end; ++i) {
80 if ((*s)[i] == '\n' or (*s)[i] == '\r') {
81 (*s)[i] = kBadChar_;
82 }
83 }
84 }
85}
86
87namespace {
88#if qStroika_Foundation_Debug_DefaultTracingOn
89 thread_local unsigned int tTraceContextDepth_{0}; // no need for atomic access because thread_local
90#endif
91
92 // Declared HERE instead of the template so they get shared across TYPE values for CHARTYPE
93 Thread::IDType sMainThread_ = Execution::Thread::GetCurrentThreadID ();
94}
95
96/*
97 ********************************************************************************
98 ******************************* Debug::Private_::Emitter ***********************
99 ********************************************************************************
100 */
101template <typename CHARTYPE>
102inline void Debug::Private_::Emitter::EmitUnadornedText (const CHARTYPE* p)
103{
104 DoEmit_ (p);
105}
106
107/*
108 ********************************************************************************
109 ************************* Debug::Private_::ModuleInit_ *************************
110 ********************************************************************************
111 */
112namespace {
113 struct PrivateModuleData_ {
114 recursive_mutex fModuleMutex; // see GetEmitCritSection_
115 Emitter fEmitter;
116
117#if qStroika_Foundation_Debug_TraceToFile
118 ofstream fTraceFile;
119#endif
120
121#if qStroika_Foundation_Debug_TraceToFile
122 PrivateModuleData_ ()
123 {
124 fTraceFile.open (GetTraceFileName ().native ().c_str (), ios::out | ios::binary);
125 }
126#endif
127 };
128 PrivateModuleData_* sModuleData_{nullptr};
129}
130
131Debug::Private_::ModuleInit_::ModuleInit_ () noexcept
132{
133 Assert (sModuleData_ == nullptr);
134 sModuleData_ = new PrivateModuleData_ ();
135}
136Debug::Private_::ModuleInit_::~ModuleInit_ ()
137{
138 Assert (sModuleData_ != nullptr);
139 delete sModuleData_;
140#if qStroika_Foundation_Debug_AssertionsChecked
141 sModuleData_ = nullptr;
142#endif
143}
144
145/*
146 ********************************************************************************
147 ************************** Private_::Emitter ***********************************
148 ********************************************************************************
149 */
150auto Debug::Private_::Emitter::Get () noexcept -> Emitter&
151{
152 auto emitFirstTime = [] () {
153 // Cannot call DbgTrace or TraceContextBumper in this code (else hang cuz calls back to Emitter::Get ())
154 // which is why this function takes Emitter as argument!
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 ());
159#endif
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); // warning maybe falsely reported as false on gcc
166 sModuleData_->fEmitter.EmitTraceMessage (" Debug::IsRunningUnderValgrind () = {}"_f, Debug::IsRunningUnderValgrind ());
167 sModuleData_->fEmitter.EmitTraceMessage ("</debug-state>"_f);
168 };
169 static once_flag sOnceFlag_;
170 call_once (sOnceFlag_, [=] () { emitFirstTime (); });
171 return sModuleData_->fEmitter;
172}
173
174#if qStroika_Foundation_Debug_TraceToFile
175namespace {
176 void Emit2File_ (const char* text) noexcept
177 {
178 RequireNotNull (text);
179 AssertNotNull (sModuleData_);
180 try {
181 if (sModuleData_->fTraceFile.is_open ()) {
182 sModuleData_->fTraceFile << text;
183 sModuleData_->fTraceFile.flush ();
184 }
185 }
186 catch (...) {
188 }
189 }
190 void Emit2File_ (const wchar_t* text) noexcept
191 {
192 RequireNotNull (text);
193 try {
194 Emit2File_ (String{text}.AsNarrowSDKString (eIgnoreErrors).c_str ());
195 }
196 catch (...) {
198 }
199 }
200}
201#endif
202
203/*
204@DESCRIPTION: <p>This function takes a 'format' argument and then any number of additional arguments - exactly
205 like std::printf (). It calls std::vsprintf () internally. This can be called directly - regardless of the
206 @'qStroika_Foundation_Debug_DefaultTracingOn' flag - but is typically just called indirectly by calling
207 @'DbgTrace'.</p>
208*/
209void Debug::Private_::Emitter::EmitTraceMessage (const char* format, ...) noexcept
210{
211 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
212 return;
213 }
215 try {
216 va_list argsList;
217 va_start (argsList, format);
218 string tmp = Characters::CString::FormatV (format, argsList);
219 va_end (argsList);
220 SquishBadCharacters_ (&tmp);
221 AssureHasLineTermination (&tmp);
222 DoEmitMessage_ (0, Containers::Start (tmp), Containers::End (tmp));
223 }
224 catch (...) {
225 WeakAssert (false); // Should NEVER happen anymore becuase of new vsnprintf() stuff
226 // Most likely indicates invalid format string for varargs parameters
227 DoEmit_ (L"EmitTraceMessage FAILED internally (buffer overflow?)");
228 }
229}
230
231void Debug::Private_::Emitter::EmitTraceMessage (const wchar_t* format, ...) noexcept
232{
233 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
234 return;
235 }
237 try {
238 va_list argsList;
239 va_start (argsList, format);
240 wstring tmp = Characters::CString::FormatV (format, argsList);
241 va_end (argsList);
242 SquishBadCharacters_ (&tmp);
243 AssureHasLineTermination (&tmp);
244 DoEmitMessage_ (0, Containers::Start (tmp), Containers::End (tmp));
245 }
246 catch (...) {
247 WeakAssert (false); // Should NEVER happen anymore becuase of new vsnprintf() stuff
248 // Most likely indicates invalid format string for varargs parameters
249 DoEmit_ (L"EmitTraceMessage FAILED internally (buffer overflow?)");
250 }
251}
252
253auto Debug::Private_::Emitter::EmitTraceMessage_ (size_t bufferLastNChars, wstring_view format, Common::StdCompat::wformat_args&& args) noexcept
254 -> TraceLastBufferedWriteTokenType
255{
256 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
257 return 0;
258 }
260 try {
261 wstring tmp = Common::StdCompat::vformat (qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view{format}, args);
262 SquishBadCharacters_ (&tmp);
263 AssureHasLineTermination (&tmp);
264 return DoEmitMessage_ (bufferLastNChars, Containers::Start (tmp), Containers::End (tmp));
265 }
266 catch (...) {
267 WeakAssert (false); // Should NEVER happen anymore becuase of new vsnprintf() stuff
268 // Most likely indicates invalid format string for varargs parameters
269 DoEmit_ (L"EmitTraceMessage FAILED internally (buffer overflow?)");
270 return 0;
271 }
272}
273
274void Debug::Private_::Emitter::EmitTraceMessage_ (wstring_view format, Common::StdCompat::wformat_args&& args) noexcept
275{
276 try {
277 EmitTraceMessage_ (vformat (qStroika_Foundation_Characters_FMT_PREFIX_::wstring_view{format}, args));
278 }
279 catch (...) {
280 }
281}
282void Debug::Private_::Emitter::EmitTraceMessage_ (string_view format, Common::StdCompat::format_args&& args) noexcept
283{
284 try {
285 Debug::Private_::Emitter::EmitTraceMessage_ (
286 Characters::String::FromNarrowSDKString (vformat (qStroika_Foundation_Characters_FMT_PREFIX_::string_view{format}, args)).As<wstring> ());
287 }
288 catch (...) {
289 }
290}
291
292void Debug::Private_::Emitter::EmitTraceMessage_ (const wstring& raw) noexcept
293{
294 if (TraceContextSuppressor::GetSuppressTraceInThisThread ()) {
295 return;
296 }
298 try {
299 wstring tmp = raw;
300 SquishBadCharacters_ (&tmp);
301 AssureHasLineTermination (&tmp);
302 DoEmitMessage_ (0, Containers::Start (tmp), Containers::End (tmp));
303 }
304 catch (...) {
305 WeakAssert (false); // Should NEVER happen anymore becuase of new vsnprintf() stuff
306 // Most likely indicates invalid format string for varargs parameters
307 DoEmit_ (L"EmitTraceMessage FAILED internally (buffer overflow?)");
308 }
309}
310
311namespace {
312 // .first is true iff added, and false if already present
313 // .second is the threadid to display
314 pair<bool, string> mkThreadLabelForThreadID_ (const Thread::IDType& threadID)
315 {
317 static atomic<int> sMinWidth_ = 4; // for MAIN
318 bool wasNew = false;
319 unsigned int threadIndex2Show = Thread::IndexRegistrar::sThe.GetIndex (threadID, &wasNew);
320 if (wasNew) {
321 if (threadIndex2Show >= 10000) {
322 sMinWidth_.store (5); // could enhance if we anticipate more threads
323 }
324 }
325 if (threadID == sMainThread_) {
326 static const string kMAIN_{"MAIN"sv};
327 return make_pair (wasNew, kMAIN_);
328 }
329 else {
330 char buf[1024];
331 (void)::snprintf (buf, std::size (buf), "%.*d", sMinWidth_.load (), threadIndex2Show);
332 return make_pair (wasNew, buf);
333 }
334 }
335 else {
336 // If this is deemed useful, then re-instate the mapping of threadID == sMainThread_ to "MAIN" with appropriate -- around it
337 return make_pair (false, Thread::FormatThreadID_A (threadID));
338 }
339 }
340}
341template <typename CHARTYPE>
342auto Debug::Private_::Emitter::DoEmitMessage_ (size_t bufferLastNChars, const CHARTYPE* s, const CHARTYPE* e) -> TraceLastBufferedWriteTokenType
343{
344 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
345 FlushBufferedCharacters_ ();
346
347 auto curRelativeTime = Time::DisplayedRealtimeClock::now (); // same as Time::clock_cast<Time::DisplayedRealtimeClock> (Time::GetTickCount ())
348
349 {
350 char buf[1024];
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) {
356 char buf2[1024]; // intentionally un-initialized
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);
361#else
362 (void)::strcat (buf, buf2);
363#endif
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);
368#else
369 (void)::strcat (buf, buf2);
370#endif
371#endif
372 }
373 Assert (::strlen (buf) < std::size (buf) / 2); // really just needs to be <, but since this buffer unchecked, break if we get CLOSE
374 DoEmit_ (buf);
375 }
376#if qStroika_Foundation_Debug_DefaultTracingOn
377 unsigned int contextDepth = TraceContextBumper::GetCount ();
378 for (unsigned int i = 0; i < contextDepth; ++i) {
379 DoEmit_ (L"\t");
380 }
381#endif
382 if (bufferLastNChars == 0) {
383 DoEmit_ (s, e);
384 ++fLastNCharBuf_Token_; // even if not buffering, increment, so other buffers known to be invalid
385 }
386 else {
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_; // even if not buffering, increment, so other buffers known to be invalid
392 }
393 return fLastNCharBuf_Token_;
394}
395
396void Debug::Private_::Emitter::BufferNChars_ (size_t bufferLastNChars, const char* p)
397{
398 Assert (bufferLastNChars < std::size (fLastNCharBuf_CHAR_));
399 fLastNCharBufCharCount_ = bufferLastNChars;
400 (void)::memcpy (fLastNCharBuf_CHAR_, p, bufferLastNChars); // no need to nul-terminate because fLastNCharBufCharCount_ stores length
401 fLastNCharBuf_WCHARFlag_ = false;
402}
403
404void Debug::Private_::Emitter::BufferNChars_ (size_t bufferLastNChars, const wchar_t* p)
405{
406 Assert (bufferLastNChars < std::size (fLastNCharBuf_WCHAR_));
407 fLastNCharBufCharCount_ = bufferLastNChars;
408 (void)::memcpy (fLastNCharBuf_WCHAR_, p, bufferLastNChars * sizeof (wchar_t)); // no need to nul-terminate because fLastNCharBufCharCount_ stores length
409 fLastNCharBuf_WCHARFlag_ = true;
410}
411
412void Debug::Private_::Emitter::FlushBufferedCharacters_ ()
413{
414 if (fLastNCharBufCharCount_ != 0) {
415 if (fLastNCharBuf_WCHARFlag_) {
416 DoEmit_ (fLastNCharBuf_WCHAR_, fLastNCharBuf_WCHAR_ + fLastNCharBufCharCount_);
417 }
418 else {
419 DoEmit_ (fLastNCharBuf_CHAR_, fLastNCharBuf_CHAR_ + fLastNCharBufCharCount_);
420 }
421 fLastNCharBufCharCount_ = 0;
422 }
423}
424
425bool Debug::Private_::Emitter::UnputBufferedCharactersForMatchingToken (TraceLastBufferedWriteTokenType token)
426{
427 RequireNotNull (sModuleData_);
428 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
429 // If the fLastNCharBuf_Token_ matches (no new tokens written since the saved one) and the time
430 // hasn't been too long (we currently write 1/100th second timestamp resolution).
431 // then blank unput (ignore) buffered characters, and return true so caller knows to write
432 // funky replacement for those characters.
433 if (fLastNCharBuf_Token_ == token and (Time::DisplayedRealtimeClock::now () - fLastNCharBuf_WriteTickcount_ < 20ms)) {
434 fLastNCharBufCharCount_ = 0;
435 return true;
436 }
437 return false; // assume old behavior for now
438}
439
440void Debug::Private_::Emitter::DoEmit_ (const char* p) noexcept
441{
442#if qStroika_Foundation_Common_Platform_Windows
443 constexpr size_t kMaxLen_ = 1023; // no docs on limit, but various hints the limit is somewhere between 1k and 4k. Empirically - just chops off after a point...
444 if (::strlen (p) < kMaxLen_) {
445 ::OutputDebugStringA (p);
446 }
447 else {
448 char buf[1024]; // @todo if/when we always support constexpr can use that here!
449 (void)::memcpy (buf, p, sizeof (buf));
450 buf[std::size (buf) - 1] = 0;
451 ::OutputDebugStringA (buf);
452 ::OutputDebugStringA ("...");
453 ::OutputDebugStringA (kEOL<char>);
454 }
455#endif
456#if qStroika_Foundation_Debug_TraceToFile
457 Emit2File_ (p);
458#endif
459}
460
461void Debug::Private_::Emitter::DoEmit_ (const wchar_t* p) noexcept
462{
463#if qStroika_Foundation_Common_Platform_Windows
464 constexpr size_t kMaxLen_ = 1023; // no docs on limit, but various hints the limit is somewhere between 1k and 4k. Empirically - just chops off after a point...
465 if (::wcslen (p) < kMaxLen_) {
466 ::OutputDebugStringW (p);
467 }
468 else {
469 wchar_t buf[1024]; // @todo if/when we always support constexpr can use that here!
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>);
475 }
476#endif
477#if qStroika_Foundation_Debug_TraceToFile
478 Emit2File_ (p);
479#endif
480}
481
482void Debug::Private_::Emitter::DoEmit_ (const char* p, const char* e) noexcept
483{
484 try {
485 size_t len = e - p;
486 StackBuffer<char> buf{Memory::eUninitialized, len + 1};
487 (void)::memcpy (buf.begin (), p, len);
488 buf.begin ()[len] = '\0';
489 DoEmit_ (buf.begin ());
490 }
491 catch (...) {
493 }
494}
495
496/*
497 [Succeeded] (1 seconds) [42] Foundation::Execution::Other (scp ../Builds/raspberrypi-g++-12-release-sanitize_address/Tests/Test42...; ssh lewis@192.168.244.20 /tmp/Test42)
498=================================================================
499==24967==ERROR: AddressSanitizer: stack-use-after-scope on address 0xbefb9b80 at pc 0x0048c10f bp 0xbefb9688 sp 0xbefb9694
500WRITE of size 100 at 0xbefb9b80 thread T0
501 #0 0x48c10c in __interceptor_memcpy (/tmp/Test43+0x6c10c)
502 #1 0x6189ee in memcpy /usr/arm-linux-gnueabihf/include/bits/string_fortified.h:29
503 #2 0x6189ee in Stroika::Foundation::Debug::Private_::Emitter::DoEmit_(wchar_t const*, wchar_t const*) /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Library/Sources/Stroika/Foundation/Debug/Trace.cpp:553
504 #3 0x6236c8 in int Stroika::Foundation::Debug::Private_::Emitter::DoEmitMessage_<wchar_t>(unsigned int, wchar_t const*, wchar_t const*) /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Library/Sources/Stroika/Foundation/Debug/Trace.cpp:432
505 #4 0x61928e in Stroika::Foundation::Debug::Private_::Emitter::EmitTraceMessage(wchar_t const*, ...) /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Library/Sources/Stroika/Foundation/Debug/Trace.cpp:299
506 #5 0x6198ba in Stroika::Foundation::Debug::TraceContextBumper::~TraceContextBumper() /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Library/Sources/Stroika/Foundation/Debug/Trace.cpp:645
507 #6 0x6198ba in Stroika::Foundation::Debug::TraceContextBumper::~TraceContextBumper() /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Library/Sources/Stroika/Foundation/Debug/Trace.cpp:634
508 #7 0x5423cc in DoTests_ /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Tests/43/Test.cpp:376
509 #8 0x54cb52 in DoRegressionTests_ /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Tests/43/Test.cpp:568
510 #9 0x56ac3c in Stroika::TestHarness::PrintPassOrFail(void (*)()) ../TestHarness/TestHarness.cpp:89
511 #10 0xb6d1a3bc in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
512 #11 0xb6d1a4c4 in __libc_start_main_impl csu/libc-start.c:360
513
514Address 0xbefb9b80 is located in stack of thread T0 at offset 128 in frame
515 #0 0x6188e8 in Stroika::Foundation::Debug::Private_::Emitter::DoEmit_(wchar_t const*, wchar_t const*) /home/lewis/Sandbox/Stroika-Build-Dir-Ubuntu2204-Cross-Compile2RaspberryPi/Library/Sources/Stroika/Foundation/Debug/Trace.cpp:549
516
517 This frame has 1 object(s):
518 [48, 4152) 'buf' (line 552) <== Memory access at offset 128 is inside this variable
519HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
520*/
521#if qCompilerAndStdLib_arm_asan_FaultStackUseAfterScope_Buggy
522Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
523#endif
524 void Debug::Private_::Emitter::DoEmit_ (const wchar_t* p, const wchar_t* e) noexcept
525{
526 try {
527 size_t len = e - p;
528 StackBuffer<wchar_t> buf{Memory::eUninitialized, len + 1};
529 (void)::memcpy (buf.begin (), p, len * sizeof (wchar_t));
530 buf.begin ()[len] = '\0';
531 DoEmit_ (buf.begin ());
532 }
533 catch (...) {
535 }
536}
537
538/*
539 ********************************************************************************
540 ************************** Debug::GetDbgTraceThreadName ************************
541 ********************************************************************************
542 */
543wstring Debug::GetDbgTraceThreadName (thread::id threadID)
544{
545 return String::FromNarrowSDKString (GetDbgTraceThreadName_A (threadID)).As<wstring> ();
546}
547string Debug::GetDbgTraceThreadName_A (thread::id threadID)
548{
549 return mkThreadLabelForThreadID_ (threadID).second;
550}
551
552#if qStroika_Foundation_Debug_TraceToFile
553/*
554 ********************************************************************************
555 ************************* Debug::GetTraceFileName ******************************
556 ********************************************************************************
557 */
558filesystem::path Debug::GetTraceFileName ()
559{
560 auto mkTraceFileName_ = [] () -> filesystem::path {
561 // Use TempDir instead of EXEDir because on vista, installation permissions prevent us from (easily) writing in EXEDir.
562 // (could fix of course, but I'm not sure desirable - reasonable defaults)
563 //
564 // Don't want to use TempFileLibrarian cuz we don't want these deleted on app exit
565 SDKString mfname;
566 {
567 try {
568 mfname = Execution::GetEXEPath ().native ();
569 }
570 catch (...) {
571 mfname = SDKSTR ("{unknown}");
572 }
573 {
574 size_t i = mfname.rfind (filesystem::path::preferred_separator);
575 if (i != SDKString::npos) {
576 mfname = mfname.substr (i + 1);
577 }
578 i = mfname.rfind ('.');
579 if (i != SDKString::npos) {
580 mfname.erase (i);
581 }
582 }
583 for (auto i = mfname.begin (); i != mfname.end (); ++i) {
584 if (*i == ' ') {
585 *i = '-';
586 }
587 }
588 }
589 SDKString nowstr = Time::DateTime::Now ().Format (Time::DateTime::kISO8601Format).AsSDKString (); // use eISO8601 instead of eCurrentLocale cuz locale CTOR not safe to construct before main
590 for (auto i = nowstr.begin (); i != nowstr.end (); ++i) {
591 if (*i == ':') {
592 *i = '-';
593 }
594 if (*i == '/' or *i == ' ') {
595 *i = '_';
596 }
597 }
599 CString::Format (SDKSTR ("TraceLog_%s_PID#%d-%s.txt"), mfname.c_str (), (int)Execution::GetCurrentProcessID (), nowstr.c_str ());
600 };
601 static filesystem::path sTraceFileName_ = mkTraceFileName_ ();
602 return sTraceFileName_;
603}
604#endif
605
606/*
607 ********************************************************************************
608 ****************************** TraceContextBumper ******************************
609 ********************************************************************************
610 */
611#if qStroika_Foundation_Debug_DefaultTracingOn
612TraceContextBumper::TraceContextBumper (CHAR_ARRAY_T mainName, CHAR_ARRAY_T extraTextAtTop) noexcept
613 : fDoEndMarker{true}
614{
615 Require (char_traits<wchar_t>::length (mainName.data ()) <= kMaxContextNameLen_); // assert NUL-terminated
616 if (extraTextAtTop.empty () or extraTextAtTop[0] == '\0') {
617 auto mainNameData = mainName.data ();
618 fLastWriteToken_ = Private_::Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L"<{}> {{"sv,
619 Common::StdCompat::make_wformat_args (mainNameData));
620 }
621 else {
622 auto mainNameData = mainName.data ();
623 auto extraTextAtTopData = extraTextAtTop.data ();
624 fLastWriteToken_ = Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L"<{} ({})> {{"sv,
625 Common::StdCompat::make_wformat_args (mainNameData, extraTextAtTopData));
626 }
627 size_t len = char_traits<wchar_t>::length (mainName.data ());
628 char_traits<wchar_t>::copy (fSavedContextName_.data (), mainName.data (), len);
629 if (len >= kMaxContextNameLen_ - 1) {
630 char_traits<wchar_t>::copy (&fSavedContextName_.data ()[len - 3], L"...", 3);
631 }
632 fSavedContextName_[len] = '\0';
633 *(std::end (fSavedContextName_) - 1) = '\0';
634 IncCount_ ();
635}
636
637TraceContextBumper::TraceContextBumper (const wchar_t* contextName, const wchar_t* extraFmt, ...) noexcept
638 : fDoEndMarker{true}
639{
640 // ********************** DEPRECATED API *********************
641 try {
642 va_list argsList;
643 va_start (argsList, extraFmt);
644 wstring tmpFmtV = Characters::CString::FormatV (extraFmt, argsList);
645 fLastWriteToken_ = Emitter::Get ().EmitTraceMessage_ (3 + ::wcslen (kEOL<wchar_t>), L"<{} ({})> {{"sv,
646 Common::StdCompat::make_wformat_args (contextName, tmpFmtV));
647 va_end (argsList);
648 size_t len = min (kMaxContextNameLen_ - 1, char_traits<wchar_t>::length (contextName));
649 char_traits<wchar_t>::copy (fSavedContextName_.data (), contextName, len);
650 if (len >= kMaxContextNameLen_ - 1) {
651 char_traits<wchar_t>::copy (&fSavedContextName_.data ()[len - 3], L"...", 3);
652 }
653 *(std::end (fSavedContextName_) - 1) = '\0';
654 fSavedContextName_[len] = '\0';
655 IncCount_ ();
656 }
657 catch (...) {
658 }
659}
660
661unsigned int TraceContextBumper::GetCount ()
662{
663 return tTraceContextDepth_;
664}
665
666void TraceContextBumper::IncCount_ () noexcept
667{
668 ++tTraceContextDepth_;
669}
670
671void TraceContextBumper::DecrCount_ () noexcept
672{
673 --tTraceContextDepth_;
674}
675
676TraceContextBumper::~TraceContextBumper () noexcept
677{
678 DecrCount_ ();
679 if (fDoEndMarker) {
680 RequireNotNull (sModuleData_);
681 try {
682 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
683 if (Emitter::Get ().UnputBufferedCharactersForMatchingToken (fLastWriteToken_)) {
684 Emitter::Get ().EmitUnadornedText ("/>");
685 Emitter::Get ().EmitUnadornedText (kEOL<char>);
686 }
687 else {
688 Emitter::Get ().EmitTraceMessage ("}} </{}>"_f, fSavedContextName_.data ());
689 }
690 }
691 catch (...) {
692 // not much of a chance of successfully reporting a problem here, but DTOR must be noexcept
693 }
694 }
695}
696#endif
#define AssertNotNull(p)
Definition Assertions.h:334
#define RequireNotNull(p)
Definition Assertions.h:348
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:439
#define AssertNotReached()
Definition Assertions.h:356
#define Verify(c)
Definition Assertions.h:420
#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...
Definition Trace.h:84
#define qStroika_Foundation_Debug_TraceToFile
Definition Trace.h:46
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,...
Definition String.h:201
nonvirtual string AsNarrowSDKString() const
Definition String.inl:836
static String FromNarrowSDKString(const char *from)
Definition String.inl:472
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
basic_string< SDKChar > SDKString
Definition SDKString.h:38
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 ...