Factor out function to decode 2 hex digits
[xapian.git] / xapian-core / common / debuglog.h
blobc96e935dd2f98c9437b9052c699bd8989958f2eb
1 /** @file
2 * @brief Debug logging macros.
3 */
4 /* Copyright (C) 2008,2009,2010,2011,2014,2015,2021 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-internal.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;
99 /// File descriptor for debug logging.
100 int fd;
102 /// The current indent level.
103 int indent_level;
105 /// Initialise categories_mask.
106 void initialise_categories_mask();
108 public:
109 /// Constructor.
110 DebugLogger()
111 : categories_mask(1 << DEBUGLOG_CATEGORY_API), fd(-1), indent_level(0)
114 /// Destructor.
115 ~DebugLogger();
117 /// Check if the user wants debug log messages of category @a category.
118 bool is_category_wanted(debuglog_categories category) {
119 // The argument will almost always be constant, so these inline checks
120 // against DEBUGLOG_CATEGORY_ALWAYS and DEBUGLOG_CATEGORY_NEVER will
121 // usually be optimised away, or become the only code path.
122 if (category == DEBUGLOG_CATEGORY_ALWAYS) return true;
123 if (category == DEBUGLOG_CATEGORY_NEVER) return false;
124 if (fd == -1) initialise_categories_mask();
125 return (categories_mask >> category) & 1;
128 /// Log message @a msg of category @a category.
129 void log_line(debuglog_categories category, const std::string& msg);
131 void indent() { ++indent_level; }
133 void outdent() {
134 if (indent_level) --indent_level;
138 namespace Xapian {
139 /** Dummy type for "no arguments".
141 * We pull this into the global namespace, and overload operator<< so that
142 * writing it to a stream should generate no code.
144 typedef enum { NO_ARGS } NoArguments_;
147 inline std::ostream & operator<<(std::ostream &o, Xapian::NoArguments_) {
148 return o;
151 using Xapian::NO_ARGS;
153 extern DebugLogger xapian_debuglogger_;
155 /** Unconditionally log message @a MSG of category @a CATEGORY. */
156 // Note that MSG can contain '<<' so we don't "protect" it with brackets.
157 #define LOGLINE_ALWAYS_(CATEGORY, MSG) do { \
158 std::ostringstream xapian_debuglog_ostream_; \
159 xapian_debuglog_ostream_ << MSG; \
160 xapian_debuglogger_.log_line(CATEGORY, xapian_debuglog_ostream_.str()); \
161 } while (false)
163 /** Log message @a MSG of category @a CATEGORY. */
164 // Note that MSG can contain '<<' so we don't "protect" it with brackets.
165 #define LOGLINE_(CATEGORY, MSG) do { \
166 debuglog_categories xapian_debuglog_category_ = (CATEGORY); \
167 if (xapian_debuglogger_.is_category_wanted(xapian_debuglog_category_)) { \
168 LOGLINE_ALWAYS_(xapian_debuglog_category_, MSG); \
170 } while (false)
172 /** Helper class for debug logging of functions and methods.
174 * We instantiate a DebugLogFunc object at the start of each logged function
175 * and method. DebugLogFunc's constructor logs the parameters passed, the
176 * RETURN() macro sets the return value as a string, and DebugLogFunc's
177 * destructor logs this string. If an exception is thrown during the method
178 * and causes it to exit, DebugLogFunc's destructor detects and logs this
179 * fact.
181 class DebugLogFunc {
182 /// This pointer (or 0 if this is a static method or a non-class function).
183 const void* this_ptr;
185 /// The category of log message to use for this function/method.
186 debuglog_categories category;
188 /// Function/method name.
189 std::string func;
191 /// Number of uncaught exceptions when we entered this function.
192 int uncaught_exceptions;
194 static int get_uncaught_exceptions() {
195 #if __cplusplus >= 201703L
196 return std::uncaught_exceptions();
197 #else
198 return int(std::uncaught_exception());
199 #endif
202 public:
203 /// Constructor called when logging for a "normal" method or function.
204 DebugLogFunc(const void* this_ptr_, debuglog_categories category_,
205 const char* return_type, const char* func_name,
206 const std::string& params)
207 : this_ptr(this_ptr_), category(category_),
208 uncaught_exceptions(get_uncaught_exceptions())
210 if (is_category_wanted()) {
211 func.assign(return_type);
212 func += ' ';
213 func += func_name;
214 func += '(';
215 func += params;
216 func += ')';
217 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func);
218 xapian_debuglogger_.indent();
222 /// Log the returned value.
223 void log_return_value(const std::string& return_value) {
224 xapian_debuglogger_.outdent();
225 LOGLINE_(category, '[' << this_ptr << "] " << func << " returned: " <<
226 return_value);
228 // Flag that we've logged the return already.
229 category = DEBUGLOG_CATEGORY_NEVER;
232 /// Check if the current category of log message is wanted.
233 bool is_category_wanted() const {
234 return xapian_debuglogger_.is_category_wanted(category);
237 /** Destructor.
239 * This logs that the function/method has returned if this is due to an
240 * exception or if the RETURN() macro hasn't been used.
242 ~DebugLogFunc() {
243 if (!is_category_wanted()) return;
244 xapian_debuglogger_.outdent();
245 if (get_uncaught_exceptions() > uncaught_exceptions) {
246 // An exception is causing the stack to be unwound.
247 LOGLINE_(category, '[' << this_ptr << "] " << func <<
248 " exited due to exception");
249 } else {
250 LOGLINE_(category, '[' << this_ptr << "] " << func <<
251 " returned (not marked up for return logging)");
256 /** Helper class for debug logging of functions and methods returning void,
257 * and class constructors and destructors.
259 * We instantiate a DebugLogFuncVoid object at the start of each logged
260 * function and method. DebugLogFuncVoid's constructor logs the parameters
261 * passed, and DebugLogFunc's destructor logs that the function/method is
262 * returning. If an exception is thrown during the method and causes it to
263 * exit, DebugLogFunc's destructor detects and logs this fact.
265 class DebugLogFuncVoid {
266 /// This pointer (or 0 if this is a static method or a non-class function).
267 const void* this_ptr;
269 /// The category of log message to use for this function/method.
270 debuglog_categories category;
272 /// Function/method name.
273 std::string func;
275 /// Number of uncaught exceptions when we entered this function.
276 int uncaught_exceptions;
278 static int get_uncaught_exceptions() {
279 #if __cplusplus >= 201703L
280 return std::uncaught_exceptions();
281 #else
282 return int(std::uncaught_exception());
283 #endif
286 public:
287 /// Constructor called when logging for a "normal" method or function.
288 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_,
289 const char* func_name,
290 const std::string& params)
291 : this_ptr(this_ptr_), category(category_),
292 uncaught_exceptions(get_uncaught_exceptions())
294 if (is_category_wanted()) {
295 func.assign("void ");
296 func += func_name;
297 func += '(';
298 func += params;
299 func += ')';
300 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func);
301 xapian_debuglogger_.indent();
305 /// Constructor called when logging for a class constructor.
306 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_,
307 const std::string& params,
308 const char* class_name)
309 : this_ptr(this_ptr_), category(category_),
310 uncaught_exceptions(get_uncaught_exceptions())
312 if (is_category_wanted()) {
313 func.assign(class_name);
314 func += "::";
315 // The ctor name is the last component if there are colons (e.g.
316 // for Query::Internal, the ctor is Internal.
317 const char* ctor_name = std::strrchr(class_name, ':');
318 if (ctor_name)
319 ++ctor_name;
320 else
321 ctor_name = class_name;
322 func += ctor_name;
323 func += '(';
324 func += params;
325 func += ')';
326 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func);
327 xapian_debuglogger_.indent();
331 /// Constructor called when logging for a class destructor.
332 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_,
333 const char* class_name)
334 : this_ptr(this_ptr_), category(category_),
335 uncaught_exceptions(get_uncaught_exceptions())
337 if (is_category_wanted()) {
338 func.assign(class_name);
339 func += "::~";
340 // The dtor name is the last component if there are colons.
341 const char* dtor_name = std::strrchr(class_name, ':');
342 if (dtor_name)
343 ++dtor_name;
344 else
345 dtor_name = class_name;
346 func += dtor_name;
347 func += "()";
348 LOGLINE_(category, '[' << this_ptr << "] " << func);
349 xapian_debuglogger_.indent();
353 /// Check if the current category of log message is wanted.
354 bool is_category_wanted() const {
355 return xapian_debuglogger_.is_category_wanted(category);
358 /** Destructor.
360 * This logs that the function/method has returned and whether this was
361 * due to an exception.
363 ~DebugLogFuncVoid() {
364 if (!is_category_wanted()) return;
365 xapian_debuglogger_.outdent();
366 const char* reason;
367 if (get_uncaught_exceptions() > uncaught_exceptions) {
368 // An exception is causing the stack to be unwound.
369 reason = " exited due to exception";
370 } else {
371 reason = " returned";
373 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func << reason);
377 #ifdef __GNUC__
378 // __attribute__((unused)) supported since at least GCC 2.95.3.
379 # define XAPIAN_UNUSED __attribute__((unused))
380 #else
381 # define XAPIAN_UNUSED
382 #endif
384 /// Log a call to a method returning non-void.
385 #define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS) \
386 typedef TYPE xapian_logcall_return_type_ XAPIAN_UNUSED; \
387 std::string xapian_logcall_parameters_; \
388 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
389 std::ostringstream xapian_logcall_ostream_; \
390 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
391 xapian_logcall_stream_ << PARAMS; \
392 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
394 DebugLogFunc xapian_logcall_(static_cast<const void*>(this), \
395 DEBUGLOG_CATEGORY_##CATEGORY, #TYPE, FUNC, \
396 xapian_logcall_parameters_)
398 /// Log a call to a method returning void.
399 #define LOGCALL_VOID(CATEGORY, FUNC, PARAMS) \
400 std::string xapian_logcall_parameters_; \
401 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
402 std::ostringstream xapian_logcall_ostream_; \
403 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
404 xapian_logcall_stream_ << PARAMS; \
405 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
407 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \
408 DEBUGLOG_CATEGORY_##CATEGORY, FUNC, \
409 xapian_logcall_parameters_)
411 /// Log a constructor call.
412 #define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS) \
413 std::string xapian_logcall_parameters_; \
414 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
415 std::ostringstream xapian_logcall_ostream_; \
416 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
417 xapian_logcall_stream_ << PARAMS; \
418 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
420 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \
421 DEBUGLOG_CATEGORY_##CATEGORY, \
422 xapian_logcall_parameters_, CLASS)
424 /// Log a destructor call.
425 #define LOGCALL_DTOR(CATEGORY, CLASS) \
426 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \
427 DEBUGLOG_CATEGORY_##CATEGORY, CLASS)
429 /// Log a call to a static method returning a non-void type.
430 #define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS) \
431 typedef TYPE xapian_logcall_return_type_ XAPIAN_UNUSED; \
432 std::string xapian_logcall_parameters_; \
433 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
434 std::ostringstream xapian_logcall_ostream_; \
435 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
436 xapian_logcall_stream_ << PARAMS; \
437 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
439 DebugLogFunc xapian_logcall_(0, DEBUGLOG_CATEGORY_##CATEGORY, #TYPE, FUNC, xapian_logcall_parameters_)
441 /// Log a call to a static method returning void.
442 #define LOGCALL_STATIC_VOID(CATEGORY, FUNC, PARAMS) \
443 std::string xapian_logcall_parameters_; \
444 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \
445 std::ostringstream xapian_logcall_ostream_; \
446 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
447 xapian_logcall_stream_ << PARAMS; \
448 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \
450 DebugLogFuncVoid xapian_logcall_(0, DEBUGLOG_CATEGORY_##CATEGORY, FUNC, xapian_logcall_parameters_)
452 /// Log returning a value.
453 #define RETURN(A) do { \
454 xapian_logcall_return_type_ xapian_logcall_return_ = A; \
455 if (xapian_logcall_.is_category_wanted()) { \
456 std::ostringstream xapian_logcall_ostream_; \
457 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \
458 xapian_logcall_stream_ << xapian_logcall_return_; \
459 xapian_logcall_.log_return_value(xapian_logcall_ostream_.str()); \
461 return xapian_logcall_return_; \
462 } while (false)
464 /** Log message @a b of category @a a.
466 * The message is logged on a line by itself. To keep the debug log readable,
467 * it shouldn't have a trailing '\n', or contain an embedded '\n'.
469 #define LOGLINE(a,b) LOGLINE_(DEBUGLOG_CATEGORY_##a, b)
471 /** Log the value of variable or expression @a b. */
472 #define LOGVALUE(a,b) LOGLINE_(DEBUGLOG_CATEGORY_##a, #b" = " << b)
474 #else
476 #define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS) (void)0
477 #define LOGCALL_VOID(CATEGORY, FUNC, PARAMS) (void)0
478 #define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS) (void)0
479 #define LOGCALL_DTOR(CATEGORY, CLASS) (void)0
480 #define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS) (void)0
481 #define LOGCALL_STATIC_VOID(CATEGORY, FUNC, PARAMS) (void)0
482 #define RETURN(A) return A
483 #define LOGLINE(a,b) (void)0
484 #define LOGVALUE(a,b) (void)0
486 #endif
488 #endif // XAPIAN_INCLUDED_DEBUGLOG_H