Stroika Library 3.0d23
 
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
525 Debug::Private_::Emitter::DoEmit_ (const wchar_t* p, const wchar_t* e) noexcept
526{
527 try {
528 size_t len = e - p;
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 ());
533 }
534 catch (...) {
536 }
537}
538
539/*
540 ********************************************************************************
541 ************************** Debug::GetDbgTraceThreadName ************************
542 ********************************************************************************
543 */
544wstring Debug::GetDbgTraceThreadName (thread::id threadID)
545{
546 return String::FromNarrowSDKString (GetDbgTraceThreadName_A (threadID)).As<wstring> ();
547}
548string Debug::GetDbgTraceThreadName_A (thread::id threadID)
549{
550 return mkThreadLabelForThreadID_ (threadID).second;
551}
552
553#if qStroika_Foundation_Debug_TraceToFile
554/*
555 ********************************************************************************
556 ************************* Debug::GetTraceFileName ******************************
557 ********************************************************************************
558 */
559filesystem::path Debug::GetTraceFileName ()
560{
561 auto mkTraceFileName_ = [] () -> filesystem::path {
562 // Use TempDir instead of EXEDir because on vista, installation permissions prevent us from (easily) writing in EXEDir.
563 // (could fix of course, but I'm not sure desirable - reasonable defaults)
564 //
565 // Don't want to use TempFileLibrarian cuz we don't want these deleted on app exit
566 SDKString mfname;
567 {
568 try {
569 mfname = Execution::GetEXEPath ().native ();
570 }
571 catch (...) {
572 mfname = SDKSTR ("{unknown}");
573 }
574 {
575 size_t i = mfname.rfind (filesystem::path::preferred_separator);
576 if (i != SDKString::npos) {
577 mfname = mfname.substr (i + 1);
578 }
579 i = mfname.rfind ('.');
580 if (i != SDKString::npos) {
581 mfname.erase (i);
582 }
583 }
584 for (auto i = mfname.begin (); i != mfname.end (); ++i) {
585 if (*i == ' ') {
586 *i = '-';
587 }
588 }
589 }
590 SDKString nowstr = Time::DateTime::Now ().Format (Time::DateTime::kISO8601Format).AsSDKString (); // use eISO8601 instead of eCurrentLocale cuz locale CTOR not safe to construct before main
591 for (auto i = nowstr.begin (); i != nowstr.end (); ++i) {
592 if (*i == ':') {
593 *i = '-';
594 }
595 if (*i == '/' or *i == ' ') {
596 *i = '_';
597 }
598 }
600 CString::Format (SDKSTR ("TraceLog_%s_PID#%d-%s.txt"), mfname.c_str (), (int)Execution::GetCurrentProcessID (), nowstr.c_str ());
601 };
602 static filesystem::path sTraceFileName_ = mkTraceFileName_ ();
603 return sTraceFileName_;
604}
605#endif
606
607/*
608 ********************************************************************************
609 ****************************** TraceContextBumper ******************************
610 ********************************************************************************
611 */
612#if qStroika_Foundation_Debug_DefaultTracingOn
613TraceContextBumper::TraceContextBumper (CHAR_ARRAY_T mainName, CHAR_ARRAY_T extraTextAtTop) noexcept
614 : fDoEndMarker{true}
615{
616 Require (char_traits<wchar_t>::length (mainName.data ()) <= kMaxContextNameLen_); // assert NUL-terminated
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));
621 }
622 else {
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));
627 }
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);
632 }
633 fSavedContextName_[len] = '\0';
634 *(std::end (fSavedContextName_) - 1) = '\0';
635 IncCount_ ();
636}
637
638TraceContextBumper::TraceContextBumper (const wchar_t* contextName, const wchar_t* extraFmt, ...) noexcept
639 : fDoEndMarker{true}
640{
641 // ********************** DEPRECATED API *********************
642 try {
643 va_list argsList;
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));
648 va_end (argsList);
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);
653 }
654 *(std::end (fSavedContextName_) - 1) = '\0';
655 fSavedContextName_[len] = '\0';
656 IncCount_ ();
657 }
658 catch (...) {
659 }
660}
661
662unsigned int TraceContextBumper::GetCount ()
663{
664 return tTraceContextDepth_;
665}
666
667void TraceContextBumper::IncCount_ () noexcept
668{
669 ++tTraceContextDepth_;
670}
671
672void TraceContextBumper::DecrCount_ () noexcept
673{
674 --tTraceContextDepth_;
675}
676
677TraceContextBumper::~TraceContextBumper () noexcept
678{
679 DecrCount_ ();
680 if (fDoEndMarker) {
681 RequireNotNull (sModuleData_);
682 try {
683 [[maybe_unused]] lock_guard critSec{sModuleData_->fModuleMutex};
684 if (Emitter::Get ().UnputBufferedCharactersForMatchingToken (fLastWriteToken_)) {
685 Emitter::Get ().EmitUnadornedText ("/>");
686 Emitter::Get ().EmitUnadornedText (kEOL<char>);
687 }
688 else {
689 Emitter::Get ().EmitTraceMessage ("}} </{}>"_f, fSavedContextName_.data ());
690 }
691 }
692 catch (...) {
693 // not much of a chance of successfully reporting a problem here, but DTOR must be noexcept
694 }
695 }
696}
697#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:834
static String FromNarrowSDKString(const char *from)
Definition String.inl:470
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 ...