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