[ci] Update macos jobs
[xapian.git] / xapian-core / common / debuglog.h
blob2fe483584cf46d4c490240d9249bc719cb2215a5
1 /** @file
2 * @brief Debug logging macros.
3 */
4 /* Copyright (C) 2008,2009,2010,2011,2014,2015,2021,2023 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifndef XAPIAN_INCLUDED_DEBUGLOG_H
22 #define XAPIAN_INCLUDED_DEBUGLOG_H
24 // In places where we include library code in non-library contexts, we can't
25 // have debug logging enabled, as the support functions aren't visible, so
26 // we define XAPIAN_REALLY_NO_DEBUG_LOG there.
27 #ifdef XAPIAN_REALLY_NO_DEBUG_LOG
28 # ifdef XAPIAN_DEBUG_LOG
29 # undef XAPIAN_DEBUG_LOG
30 # endif
31 #endif
33 #ifdef XAPIAN_DEBUG_LOG
35 #include "output.h"
36 #include "pretty.h"
38 #include <cstring>
39 #include <ostream>
40 #include <sstream>
41 #include <string>
43 /// A categorisation of debug log messages.
44 enum debuglog_categories {
45 // Never wanted.
46 DEBUGLOG_CATEGORY_NEVER = 0,
48 /// Public API method and function calls.
49 DEBUGLOG_CATEGORY_API = ('A' - '@'),
51 /// Related to database backends.
52 DEBUGLOG_CATEGORY_DB = ('D' - '@'),
54 /// Related to exception handling.
55 DEBUGLOG_CATEGORY_EXCEPTION = ('X' - '@'),
57 /// Query expansion.
58 DEBUGLOG_CATEGORY_EXPAND = ('E' - '@'),
60 /// Matcher.
61 DEBUGLOG_CATEGORY_MATCH = ('M' - '@'),
63 /// Debug output from the lemon-generated QueryParser code.
64 DEBUGLOG_CATEGORY_QUERYPARSER = ('Q' - '@'),
66 /// Related to the remote backend.
67 DEBUGLOG_CATEGORY_REMOTE = ('R' - '@'),
69 /// Related to replication.
70 DEBUGLOG_CATEGORY_REPLICA = ('C' - '@'),
72 /// Spelling correction.
73 DEBUGLOG_CATEGORY_SPELLING = ('S' - '@'),
75 /// Uncategorised.
76 DEBUGLOG_CATEGORY_UNKNOWN = ('U' - '@'),
78 /// Weight calculations.
79 DEBUGLOG_CATEGORY_WTCALC = ('W' - '@'),
81 /// Query stuff.
82 DEBUGLOG_CATEGORY_QUERY = ('Y' - '@'),
84 /// Messages which are always logged.
85 DEBUGLOG_CATEGORY_ALWAYS = 31
88 /// Class to actually do the logging.
89 class DebugLogger {
90 /// Don't allow assignment.
91 void operator=(const DebugLogger&);
93 /// Don't allow copying.
94 DebugLogger(const DebugLogger&);
96 /// Mask bitmap of categories the user wants log messages for.
97 unsigned int categories_mask = 1 << DEBUGLOG_CATEGORY_API;
99 /// File descriptor for debug logging.
100 int fd = -1;
102 /// The current indent level.
103 int indent_level = 0;
105 /// Initialise categories_mask.
106 void initialise_categories_mask();
108 public:
109 /// Constructor.
110 DebugLogger() { }
112 /// Destructor.
113 ~DebugLogger();
115 /// Check if the user wants debug log messages of category @a category.
116 bool is_category_wanted(debuglog_categories category) {
117 // The argument will almost always be constant, so these inline checks
118 // against DEBUGLOG_CATEGORY_ALWAYS and DEBUGLOG_CATEGORY_NEVER will
119 // usually be optimised away, or become the only code path.
120 if (category == DEBUGLOG_CATEGORY_ALWAYS) return true;
121 if (category == DEBUGLOG_CATEGORY_NEVER) return false;
122 if (fd == -1) initialise_categories_mask();
123 return (categories_mask >> category) & 1;
126 /// Log message @a msg of category @a category.
127 void log_line(debuglog_categories category, const std::string& msg);
129 void indent() { ++indent_level; }
131 void outdent() {
132 if (indent_level) --indent_level;
136 namespace Xapian {
137 /** Dummy type for "no arguments".
139 * We pull this into the global namespace, and overload operator<< so that
140 * writing it to a stream should generate no code.
142 typedef enum { NO_ARGS } NoArguments_;
145 inline std::ostream & operator<<(std::ostream &o, Xapian::NoArguments_) {
146 return o;
149 using Xapian::NO_ARGS;
151 extern DebugLogger xapian_debuglogger_;
153 /** Unconditionally log message @a MSG of category @a CATEGORY. */
154 // Note that MSG can contain '<<' so we don't "protect" it with brackets.
155 #define LOGLINE_ALWAYS_(CATEGORY, MSG) do { \
156 std::ostringstream xapian_debuglog_ostream_; \
157 xapian_debuglog_ostream_ << MSG; \
158 xapian_debuglogger_.log_line(CATEGORY, xapian_debuglog_ostream_.str()); \
159 } while (false)
161 /** Log message @a MSG of category @a CATEGORY. */
162 // Note that MSG can contain '<<' so we don't "protect" it with brackets.
163 #define LOGLINE_(CATEGORY, MSG) do { \
164 debuglog_categories xapian_debuglog_category_ = (CATEGORY); \
165 if (xapian_debuglogger_.is_category_wanted(xapian_debuglog_category_)) { \
166 LOGLINE_ALWAYS_(xapian_debuglog_category_, MSG); \
168 } while (false)
170 /** Helper class for debug logging of functions and methods.
172 * We instantiate a DebugLogFunc object at the start of each logged function
173 * and method. DebugLogFunc's constructor logs the parameters passed, the
174 * RETURN() macro sets the return value as a string, and DebugLogFunc's
175 * destructor logs this string. If an exception is thrown during the method
176 * and causes it to exit, DebugLogFunc's destructor detects and logs this
177 * fact.
179 class DebugLogFunc {
180 /// This pointer (or 0 if this is a static method or a non-class function).
181 const void* this_ptr;
183 /// The category of log message to use for this function/method.
184 debuglog_categories category;
186 /// Function/method name.
187 std::string func;
189 /// Number of uncaught exceptions when we entered this function.
190 int uncaught_exceptions;
192 static int get_uncaught_exceptions() {
193 return std::uncaught_exceptions();
196 public:
197 /// Constructor called when logging for a "normal" method or function.
198 DebugLogFunc(const void* this_ptr_, debuglog_categories category_,
199 const char* return_type, const char* func_name,
200 const std::string& params)
201 : this_ptr(this_ptr_), category(category_),
202 uncaught_exceptions(get_uncaught_exceptions())
204 if (is_category_wanted()) {
205 func.assign(return_type);
206 func += ' ';
207 func += func_name;
208 func += '(';
209 func += params;
210 func += ')';
211 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func);
212 xapian_debuglogger_.indent();
216 /// Log the returned value.
217 void log_return_value(const std::string& return_value) {
218 xapian_debuglogger_.outdent();
219 LOGLINE_(category, '[' << this_ptr << "] " << func << " returned: " <<
220 return_value);
222 // Flag that we've logged the return already.
223 category = DEBUGLOG_CATEGORY_NEVER;
226 /// Check if the current category of log message is wanted.
227 bool is_category_wanted() const {
228 return xapian_debuglogger_.is_category_wanted(category);
231 /** Destructor.
233 * This logs that the function/method has returned if this is due to an
234 * exception or if the RETURN() macro hasn't been used.
236 ~DebugLogFunc() {
237 if (!is_category_wanted()) return;
238 xapian_debuglogger_.outdent();
239 if (get_uncaught_exceptions() > uncaught_exceptions) {
240 // An exception is causing the stack to be unwound.
241 LOGLINE_(category, '[' << this_ptr << "] " << func <<
242 " exited due to exception");
243 } else {
244 LOGLINE_(category, '[' << this_ptr << "] " << func <<
245 " returned (not marked up for return logging)");
250 /** Helper class for debug logging of functions and methods returning void,
251 * and class constructors and destructors.
253 * We instantiate a DebugLogFuncVoid object at the start of each logged
254 * function and method. DebugLogFuncVoid's constructor logs the parameters
255 * passed, and DebugLogFunc's destructor logs that the function/method is
256 * returning. If an exception is thrown during the method and causes it to
257 * exit, DebugLogFunc's destructor detects and logs this fact.
259 class DebugLogFuncVoid {
260 /// This pointer (or 0 if this is a static method or a non-class function).
261 const void* this_ptr;
263 /// The category of log message to use for this function/method.
264 debuglog_categories category;
266 /// Function/method name.
267 std::string func;
269 /// Number of uncaught exceptions when we entered this function.
270 int uncaught_exceptions;
272 static int get_uncaught_exceptions() {
273 return std::uncaught_exceptions();
276 public:
277 /// Constructor called when logging for a "normal" method or function.
278 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_,
279 const char* func_name,
280 const std::string& params)
281 : this_ptr(this_ptr_), category(category_),
282 uncaught_exceptions(get_uncaught_exceptions())
284 if (is_category_wanted()) {
285 func.assign("void ");
286 func += func_name;
287 func += '(';
288 func += params;
289 func += ')';
290 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func);
291 xapian_debuglogger_.indent();
295 /// Constructor called when logging for a class constructor.
296 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_,
297 const std::string& params,
298 const char* class_name)
299 : this_ptr(this_ptr_), category(category_),
300 uncaught_exceptions(get_uncaught_exceptions())
302 if (is_category_wanted()) {
303 func.assign(class_name);
304 func += "::";
305 // The ctor name is the last component if there are colons (e.g.
306 // for Query::Internal, the ctor is Internal.
307 const char* ctor_name = std::strrchr(class_name, ':');
308 if (ctor_name)
309 ++ctor_name;
310 else
311 ctor_name = class_name;
312 func += ctor_name;
313 func += '(';
314 func += params;
315 func += ')';
316 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func);
317 xapian_debuglogger_.indent();
321 /// Constructor called when logging for a class destructor.
322 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_,
323 const char* class_name)
324 : this_ptr(this_ptr_), category(category_),
325 uncaught_exceptions(get_uncaught_exceptions())
327 if (is_category_wanted()) {
328 func.assign(class_name);
329 func += "::~";
330 // The dtor name is the last component if there are colons.
331 const char* dtor_name = std::strrchr(class_name, ':');
332 if (dtor_name)
333 ++dtor_name;
334 else
335 dtor_name = class_name;
336 func += dtor_name;
337 func += "()";
338 LOGLINE_(category, '[' << this_ptr << "] " << func);
339 xapian_debuglogger_.indent();
343 /// Check if the current category of log message is wanted.
344 bool is_category_wanted() const {
345 return xapian_debuglogger_.is_category_wanted(category);
348 /** Destructor.
350 * This logs that the function/method has returned and whether this was
351 * due to an exception.
353 ~DebugLogFuncVoid() {
354 if (!is_category_wanted()) return;
355 xapian_debuglogger_.outdent();
356 const char* reason;
357 if (get_uncaught_exceptions() > uncaught_exceptions) {
358 // An exception is causing the stack to be unwound.
359 reason = " exited due to exception";
360 } else {
361 reason = " returned";
363 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func << reason);
367 #ifdef __GNUC__
368 // __attribute__((unused)) supported since at least GCC 2.95.3.
369 # define XAPIAN_UNUSED __attribute__((unused))
370 #else
371 # define XAPIAN_UNUSED
372 #endif
374 /// Log a call to a method returning non-void.
375 #define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS) \
376 typedef TYPE xapian_logcall_return_type_ XAPIAN_UNUSED; \
377 std::string xapian_logcall_parameters_; \
378 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
379 std::ostringstream xapian_logcall_ostream_; \
380 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
381 xapian_logcall_stream_ << PARAMS; \
382 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
384 DebugLogFunc xapian_logcall_(static_cast<const void*>(this), \
385 DEBUGLOG_CATEGORY_##CATEGORY, #TYPE, FUNC, \
386 xapian_logcall_parameters_)
388 /// Log a call to a method returning void.
389 #define LOGCALL_VOID(CATEGORY, FUNC, PARAMS) \
390 std::string xapian_logcall_parameters_; \
391 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
392 std::ostringstream xapian_logcall_ostream_; \
393 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
394 xapian_logcall_stream_ << PARAMS; \
395 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
397 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \
398 DEBUGLOG_CATEGORY_##CATEGORY, FUNC, \
399 xapian_logcall_parameters_)
401 /// Log a constructor call.
402 #define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS) \
403 std::string xapian_logcall_parameters_; \
404 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
405 std::ostringstream xapian_logcall_ostream_; \
406 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
407 xapian_logcall_stream_ << PARAMS; \
408 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
410 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \
411 DEBUGLOG_CATEGORY_##CATEGORY, \
412 xapian_logcall_parameters_, CLASS)
414 /// Log a destructor call.
415 #define LOGCALL_DTOR(CATEGORY, CLASS) \
416 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \
417 DEBUGLOG_CATEGORY_##CATEGORY, CLASS)
419 /// Log a call to a static method returning a non-void type.
420 #define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS) \
421 typedef TYPE xapian_logcall_return_type_ XAPIAN_UNUSED; \
422 std::string xapian_logcall_parameters_; \
423 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
424 std::ostringstream xapian_logcall_ostream_; \
425 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
426 xapian_logcall_stream_ << PARAMS; \
427 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
429 DebugLogFunc xapian_logcall_(0, DEBUGLOG_CATEGORY_##CATEGORY, #TYPE, FUNC, xapian_logcall_parameters_)
431 /// Log a call to a static method returning void.
432 #define LOGCALL_STATIC_VOID(CATEGORY, FUNC, PARAMS) \
433 std::string xapian_logcall_parameters_; \
434 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
435 std::ostringstream xapian_logcall_ostream_; \
436 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
437 xapian_logcall_stream_ << PARAMS; \
438 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
440 DebugLogFuncVoid xapian_logcall_(0, DEBUGLOG_CATEGORY_##CATEGORY, FUNC, xapian_logcall_parameters_)
442 /// Log returning a value.
443 /* Use __VA_ARGS__ so things like `RETURN({1, 2})` work. */
444 #define RETURN(...) do { \
445 xapian_logcall_return_type_ xapian_logcall_return_ = __VA_ARGS__; \
446 if (xapian_logcall_.is_category_wanted()) { \
447 std::ostringstream xapian_logcall_ostream_; \
448 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
449 xapian_logcall_stream_ << xapian_logcall_return_; \
450 xapian_logcall_.log_return_value(xapian_logcall_ostream_.str()); \
452 return xapian_logcall_return_; \
453 } while (false)
455 /** Log message @a b of category @a a.
457 * The message is logged on a line by itself. To keep the debug log readable,
458 * it shouldn't have a trailing '\n', or contain an embedded '\n'.
460 #define LOGLINE(a,b) LOGLINE_(DEBUGLOG_CATEGORY_##a, b)
462 /** Log the value of variable or expression @a b. */
463 #define LOGVALUE(a,b) LOGLINE_(DEBUGLOG_CATEGORY_##a, #b" = " << b)
465 /** Wrapper macro for return types containing a comma.
467 * Use like:
469 * LOGCALL_STATIC(DB, RETURN_TYPE(pair<int, string>), "ProgClient::run_program", progname | args | Literal("[&child]"));
471 * This also works for return types without a comma, though it's not necessary
472 * there and should be avoided for verbosity reasons.
474 #define RETURN_TYPE(...) __VA_ARGS__
476 #else
478 #define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS) (void)0
479 #define LOGCALL_VOID(CATEGORY, FUNC, PARAMS) (void)0
480 #define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS) (void)0
481 #define LOGCALL_DTOR(CATEGORY, CLASS) (void)0
482 #define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS) (void)0
483 #define LOGCALL_STATIC_VOID(CATEGORY, FUNC, PARAMS) (void)0
484 #define RETURN(...) return __VA_ARGS__
485 #define LOGLINE(a,b) (void)0
486 #define LOGVALUE(a,b) (void)0
488 #endif
490 #endif // XAPIAN_INCLUDED_DEBUGLOG_H