Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
Trace.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Debug_Trace_h_
5#define _Stroika_Foundation_Debug_Trace_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <array>
10#include <filesystem>
11#include <span>
12#include <thread>
13
14#include "Stroika/Foundation/Common/Common.h"
17
18/**
19 * \file
20 *
21 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
22 *
23 */
24
26 template <typename CHAR_T>
27 struct FormatString;
28}
29
30namespace Stroika::Foundation::Debug {
31
32 /**
33 * If qStroika_Foundation_Debug_TraceToFile is set true, then DbgTrace () and other Trace calls all go both to the usual debugger output
34 * screen, and to an auto-generated logfile.
35 *
36 * This can be handy for building a version of code to hand to customers with an irreproducible bug to get a detailed
37 * report of what happened. Its also handy for embedded or timing sensative programs where capturing the log
38 * of exactly what happened is helpful.
39 */
40#if !defined(qStroika_Foundation_Debug_TraceToFile)
41#if defined(qTraceToFile)
42#warning "use qStroika_Foundation_Debug_TraceToFile since Stroika v3.0d4 "
43#define qStroika_Foundation_Debug_TraceToFile qTraceToFile
44#define qTraceToFile 0
45#else
46#define qStroika_Foundation_Debug_TraceToFile 0
47#endif
48#endif
49
50 /*
51 * qStroika_Foundation_Debug_DefaultTracingOn provides the default configuration for whether or not DbgTrace logs
52 * or just is 'compiled out' of target programs.
53 *
54 * Note ALSO - many Stroika modules (CPP files) contain a private
55 * // Comment this in to turn on tracing in this module
56 * //#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
57 * define.
58 *
59 * This is often not enabled by default because it could produce lots of uninteresting noise in logfiles
60 * (when tracing on).
61 *
62 * Turn per-module USE_NOISY_TRACE_IN_THIS_MODULE_ flags on selectively just to enable extra detailed logging on a per module basis.
63 */
64#if !defined(qStroika_Foundation_Debug_DefaultTracingOn)
65
66#if defined(qDefaultTracingOn)
67#warning "use qStroika_Foundation_Debug_DefaultTracingOn since Stroika v3.0d4 "
68#define qStroika_Foundation_Debug_DefaultTracingOn qDefaultTracingOn
69#else
70#define qStroika_Foundation_Debug_DefaultTracingOn qStroika_Foundation_Debug_AssertionsChecked
71#endif
72#endif
73
74 /**
75 * \brief if true, emit a much shorter thread ID, making - I suspect (testing) for terser and clearer tracelogs.
76 * Only downside is that you must find first occurrence of that index to find real threadId, and use that in waits, etc.
77 *
78 * This is defined as a #define, so you can turn it off when building Stroika. I'm unsure if the showing of thread indexes
79 * helps readability of the tracelog, but I think it does.
80 *
81 * This is somewhat helpful on windows, but much more so on 64-bit linux with very long fairly random looking thread ids.
82 */
83#ifndef qStroika_Foundation_Debug_ShowThreadIndex
84#define qStroika_Foundation_Debug_ShowThreadIndex 1
85#endif
86
87 namespace Private_ {
88 // The 'TraceLastBufferedWriteTokenType' overload of EmitTraceMessage allows you to specify a set of (trailing) characters to
89 // be temporarily buffered. These characters are not immediately emitted, and can be cleared via UnputBufferedCharactersForMatchingToken ().
90 // They will eventually be flushed out on the next call to EmitTraceMessage ().
91 using TraceLastBufferedWriteTokenType = int;
92 class Emitter;
93 }
94
95 /**
96 * Just for debugging purposes, get the name displayed in the trace log for the given thread-id.
97 */
98 wstring GetDbgTraceThreadName (thread::id threadID);
99 string GetDbgTraceThreadName_A (thread::id threadID);
100
101#if qStroika_Foundation_Debug_TraceToFile
102 /**
103 * @brief Return the path where the tracefile exists. Note this is only available if the qStroika_Foundation_Debug_TraceToFile function is enabled.
104 */
105 filesystem::path GetTraceFileName ();
106#endif
107
108 /**
109 * Define a new start/end context (with optional label) for trace messages emitted with DbgTrace (), and indent future
110 * DbgTrace () messages (from this thread) during the lifetime of TraceContextBumper.
111 *
112 * \par Example Usage
113 * \code
114 * Debug::TraceContextBumper ctx{"MyXercesMemMgr_::DUMPCurMemStats"};
115 * DbgTrace (L"x");
116 * \endcode
117 *
118 * Generates log output:
119 * <MyXercesMemMgr_::DUMPCurMemStats>
120 * x
121 * </MyXercesMemMgr_::DUMPCurMemStats>
122 *
123 * \par Example Usage
124 * \code
125 * Debug::TraceContextBumper ctx{"OptionsFile::ReadRaw", "readfilename={}"_f, GetReadFilePath_ ()};
126 * \endcode
127 *
128 * Generates log output (assuming ReadRaw is quick and doesn't do more DbgTrace calls):
129 * <OptionsFile::ReadRaw (readfilename=C:\Users\Lewis\AppData\Local\Temp\MyModule.json)/>
130 *
131 * \par Example Usage
132 * \code
133 * Debug::TraceContextBumper ctx { Stroika_Foundation_Debug_OptionalizeTraceArgs ("OptionsFile::ReadRaw", L"readfilename={}"_f, GetReadFilePath_ ().c_str ()) };
134 * DbgTrace (L"x");
135 * \endcode
136 *
137 * Generates log output:
138 * <OptionsFile::ReadRaw (readfilename=C:\Users\Lewis\AppData\Local\Temp\MyModule.json)>
139 * x
140 * </OptionsFile::ReadRaw>
141 *
142 * \par Example Usage
143 * \code
144 * struct X {
145 * // ... lots of data members - and you get a crash between constructor or destruction of some of them
146 * // ... use this trick to see dbgmessages BETWEEN construction and destruction of each member
147 * ComplexObject fComplexObject1;
148 * TraceContextBumper tmpNoteAfterComplexObj1 {"after fComplexObject1"};
149 * ComplexObject fComplexObject2;
150 * TraceContextBumper tmpNoteAfterComplexObj2 {"after fComplexObject2"};
151 * };
152 * \endcode
153 *
154 * \note ***Not Cancelation Point*** - and uses noexcept
155 * \note safe to call and does nothing if !qStroika_Foundation_Debug_DefaultTracingOn
156 */
157 class TraceContextBumper final {
158 public:
159 /**
160 *
161 * @todo REDO ALL THESE DOCS --- AS OF 2024-03-22
162 *
163 * If constructor taking const char* used, the argument must be ASCII characters.
164 *
165 * The constructor with 'extraFmt', emits the extra data in the heading of the trace message, but
166 * not the close brace. This can allow for more terse TraceContextBumper messages, and more terse
167 * calling usage.
168 *
169 * For TraceContextBumper (const wchar_t* contextName, const wchar_t* extraFmt, ...) usage, @see Stroika_Foundation_Debug_OptionalizeTraceArgs
170 * to optionally suppress side-effects.
171 *
172 * \note ***Not Cancelation Point*** - and uses noexcept
173 */
174 TraceContextBumper () noexcept;
175 template <typename CHAR_T>
176 TraceContextBumper (const CHAR_T* contextName) noexcept
177 requires (same_as<CHAR_T, char> or same_as<CHAR_T, wchar_t>);
178 template <typename CHAR_T, typename FCHAR_T, typename... ARGS>
179 TraceContextBumper (const CHAR_T* contextName, Characters::FormatString<FCHAR_T> fmt, ARGS&&... args) noexcept
180 requires ((same_as<CHAR_T, char> or same_as<CHAR_T, wchar_t>) and (same_as<FCHAR_T, char> or same_as<FCHAR_T, wchar_t>));
181 TraceContextBumper (const TraceContextBumper&) = delete;
182
183 public:
184 // legacy API
185 [[deprecated ("Since Stroika v3.0d6 - use _f format strings")]] TraceContextBumper (const wchar_t* contextName,
186 const wchar_t* extraFmt, ...) noexcept;
187
188#if qStroika_Foundation_Debug_DefaultTracingOn
189 private:
190 // Nothing too important about this constant, but not so long as to be hard to read
191 static constexpr size_t kMaxContextNameLen_{80};
192
193 using CHAR_ARRAY_T = array<wchar_t, kMaxContextNameLen_ + 1>; // enforce always nul-terminated strings - but can copy by value
194
195 private:
196 // spans are outer bounds of strings - also look for NUL-term to find string length - require they are nul-terminated (so not sure why span is useful - @todo rethink)
197 TraceContextBumper (CHAR_ARRAY_T mainName, CHAR_ARRAY_T extraTextAtTop = {}) noexcept;
198#endif
199
200 public:
201#if qStroika_Foundation_Debug_DefaultTracingOn
202 ~TraceContextBumper () noexcept;
203#else
204 ~TraceContextBumper () noexcept = default;
205#endif
206
207 public:
208 nonvirtual TraceContextBumper& operator= (const TraceContextBumper&) = delete;
209
210#if qStroika_Foundation_Debug_DefaultTracingOn
211 public:
212 bool fDoEndMarker{false};
213
214 public:
215 static unsigned int GetCount ();
216
217 private:
218 CHAR_ARRAY_T fSavedContextName_{};
219 Private_::TraceLastBufferedWriteTokenType fLastWriteToken_{}; // used to COMBINE items into a single line if they happen quickly enuf
220
221 private:
222 template <typename CHAR_T>
223 static CHAR_ARRAY_T cvt2WChartArrayTrunc_ (span<const CHAR_T> contextName);
224 template <typename CHAR_T>
225 static CHAR_ARRAY_T cvt2WChartArrayTrunc_ (const CHAR_T* contextName);
226 template <typename CHAR_T, typename... ARGS>
227 CHAR_ARRAY_T ProcessFmtString_ (Characters::FormatString<CHAR_T> fmt, ARGS&&... args) noexcept
228 requires (same_as<CHAR_T, char> or same_as<CHAR_T, wchar_t>);
229
230 private:
231 static void IncCount_ () noexcept;
232 static void DecrCount_ () noexcept;
233#endif
234 };
235
236 /**
237 * Suppress trace messages (in this thread) for the lifetime of this object.
238 *
239 * \par Example Usage
240 * \code
241 * {
242 * Debug::TraceContextSuppressor suppressTraceInThisBlock;
243 * DbgTrace (L"x"); // suppressed
244 * }
245 * DbgTrace (L"x"); // emitted
246 * \endcode
247 */
249 public:
250 /**
251 */
252 TraceContextSuppressor () noexcept;
254 TraceContextSuppressor& operator= (const TraceContextSuppressor&) = delete;
255
256 public:
258
259 public:
260 static bool GetSuppressTraceInThisThread ();
261
262 private:
263 static inline thread_local unsigned int tSuppressCnt_;
264 };
265
266 /**
267 * \def Stroika_Foundation_Debug_OptionalizeTraceArgs
268 *
269 * This is meant to be used with the 2+ argument Debug::TraceContextBumper constructor (_f format string), to optionally suppress side-effects
270 * of trace arguments when tracing is disabled (at compile time).
271 *
272 * \note this may still be useful in Stroika v3 and later, but was mostly useful in before, switching to use of variadic templates, since now
273 * the compiler can see the variadic and format parameters are unused for anything, so probably is able to optimize them away.
274 */
275#if qStroika_Foundation_Debug_DefaultTracingOn
276#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...) __VA_ARGS__
277#else
278#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
279#endif
280
281 /**
282 * \def DbgTrace
283 *
284 * This function either does NOTHING (trying to not even evaluate its arguments)
285 * or does a printf style PRINT function by delegating to @'EmitTraceMessage'. Which of
286 * these two behaviors you see is conditioned on @'qStroika_Foundation_Debug_DefaultTracingOn'</p>
287 *
288 * \note ***Not Cancelation Point*** - and uses noexcept
289 * So you can call this freely without worrying about Throw (ThreadAbortException) etc
290 * (though beware of passing arguments to DbgTrace() which may be cancelation points)
291 *
292 * \note - DbgTrace can take either printf style format strings, or _f format strings.
293 *
294 * \par Example Usage
295 * \code
296 * auto url = IO::Network::URI{"http://www.sophists.com"};
297 * DbgTrace ("u = {}"_f, url); // output "u = 'http://www.sophists.com'"
298 * \endcode
299 *
300 * @todo - redo NOT as MACRO but as FUNCTION call (to emittracemessage)
301 *
302 * template <typename CHAR_T, typename... Args>
303 nonvirtual void DbgTrace (Characters::FormatString<CHAR_T> fmt, Args&&... args) noexcept
304 {
305 or some such
306 try {
307 Stroika::Foundation::Debug::Private_::Emitter::Get ().EmitTraceMessage (fmt.get (), Common::StdCompat::make_wformat_args (args...));
308 }
309 catch (...) {
310 }
311 }
312 but only after 'd' stage development so I can lose C-style FMT string overloads...
313 */
314#if qStroika_Foundation_Debug_DefaultTracingOn
315#define DbgTrace Stroika::Foundation::Debug::Private_::Emitter::Get ().EmitTraceMessage
316#else
317#define DbgTrace _NoOp_
318#endif
319
320}
321
322/*
323 ********************************************************************************
324 ***************************** Implementation Details ***************************
325 ********************************************************************************
326 */
327#include "Trace.inl"
328
329#endif /*_Stroika_Foundation_Debug_Trace_h_*/
wstring GetDbgTraceThreadName(thread::id threadID)
Definition Trace.cpp:544
Roughly equivalent to std::wformat_string, except that it can be constructed from 'char' string,...