2 * @brief Debug logging macros.
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
33 #ifdef XAPIAN_DEBUG_LOG
43 /// A categorisation of debug log messages.
44 enum debuglog_categories
{
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' - '@'),
58 DEBUGLOG_CATEGORY_EXPAND
= ('E' - '@'),
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' - '@'),
76 DEBUGLOG_CATEGORY_UNKNOWN
= ('U' - '@'),
78 /// Weight calculations.
79 DEBUGLOG_CATEGORY_WTCALC
= ('W' - '@'),
82 DEBUGLOG_CATEGORY_QUERY
= ('Y' - '@'),
84 /// Messages which are always logged.
85 DEBUGLOG_CATEGORY_ALWAYS
= 31
88 /// Class to actually do the logging.
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.
102 /// The current indent level.
103 int indent_level
= 0;
105 /// Initialise categories_mask.
106 void initialise_categories_mask();
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
; }
132 if (indent_level
) --indent_level
;
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_
) {
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()); \
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); \
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
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.
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();
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
);
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: " <<
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
);
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.
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");
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.
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();
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 ");
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
);
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
, ':');
311 ctor_name
= class_name
;
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
);
330 // The dtor name is the last component if there are colons.
331 const char* dtor_name
= std::strrchr(class_name
, ':');
335 dtor_name
= class_name
;
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
);
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();
357 if (get_uncaught_exceptions() > uncaught_exceptions
) {
358 // An exception is causing the stack to be unwound.
359 reason
= " exited due to exception";
361 reason
= " returned";
363 LOGLINE_ALWAYS_(category
, '[' << this_ptr
<< "] " << func
<< reason
);
368 // __attribute__((unused)) supported since at least GCC 2.95.3.
369 # define XAPIAN_UNUSED __attribute__((unused))
371 # define XAPIAN_UNUSED
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_; \
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.
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__
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
490 #endif // XAPIAN_INCLUDED_DEBUGLOG_H