1 /** @file 2 * @brief Debug logging macros. 3 */ 4 /* Copyright (C) 2008,2009,2010,2011,2014,2015 Olly Betts 5 * 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. 10 * 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. 15 * 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 19 */ 20 21 #ifndef XAPIAN_INCLUDED_DEBUGLOG_H 22 #define XAPIAN_INCLUDED_DEBUGLOG_H 23 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 32 33 #ifdef XAPIAN_DEBUG_LOG 34 35 #include "output-internal.h" 36 #include "pretty.h" 37 38 #include <cstring> 39 #include <ostream> 40 #include <sstream> 41 #include <string> 42 43 /// A categorisation of debug log messages. 44 enum debuglog_categories { 45 // Never wanted. 46 DEBUGLOG_CATEGORY_NEVER = 0, 47 48 /// Public API method and function calls. 49 DEBUGLOG_CATEGORY_API = ('A' - '@'), 50 51 /// Related to database backends. 52 DEBUGLOG_CATEGORY_DB = ('D' - '@'), 53 54 /// Related to exception handling. 55 DEBUGLOG_CATEGORY_EXCEPTION = ('X' - '@'), 56 57 /// Query expansion. 58 DEBUGLOG_CATEGORY_EXPAND = ('E' - '@'), 59 60 /// Matcher. 61 DEBUGLOG_CATEGORY_MATCH = ('M' - '@'), 62 63 /// Debug output from the lemon-generated QueryParser code. 64 DEBUGLOG_CATEGORY_QUERYPARSER = ('Q' - '@'), 65 66 /// Related to the remote backend. 67 DEBUGLOG_CATEGORY_REMOTE = ('R' - '@'), 68 69 /// Related to replication. 70 DEBUGLOG_CATEGORY_REPLICA = ('C' - '@'), 71 72 /// Spelling correction. 73 DEBUGLOG_CATEGORY_SPELLING = ('S' - '@'), 74 75 /// Uncategorised. 76 DEBUGLOG_CATEGORY_UNKNOWN = ('U' - '@'), 77 78 /// Weight calculations. 79 DEBUGLOG_CATEGORY_WTCALC = ('W' - '@'), 80 81 /// Query stuff. 82 DEBUGLOG_CATEGORY_QUERY = ('Y' - '@'), 83 84 /// Messages which are always logged. 85 DEBUGLOG_CATEGORY_ALWAYS = 31 86 }; 87 88 /// Class to actually do the logging. 89 class DebugLogger { 90 /// Don't allow assignment. 91 void operator=(const DebugLogger&); 92 93 /// Don't allow copying. 94 DebugLogger(const DebugLogger&); 95 96 /// Mask bitmap of categories the user wants log messages for. 97 unsigned int categories_mask; 98 99 /// File descriptor for debug logging. 100 int fd; 101 102 /// The current indent level. 103 int indent_level; 104 105 /// Initialise categories_mask. 106 void initialise_categories_mask(); 107 108 public: 109 /// Constructor. DebugLogger()110 DebugLogger() 111 : categories_mask(1 << DEBUGLOG_CATEGORY_API), fd(-1), indent_level(0) 112 { } 113 114 /// Destructor. 115 ~DebugLogger(); 116 117 /// Check if the user wants debug log messages of category @a category. is_category_wanted(debuglog_categories 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; 126 } 127 128 /// Log message @a msg of category @a category. 129 void log_line(debuglog_categories category, const std::string& msg); 130 indent()131 void indent() { ++indent_level; } 132 outdent()133 void outdent() { 134 if (indent_level) --indent_level; 135 } 136 }; 137 138 namespace Xapian { 139 /** Dummy type for "no arguments". 140 * 141 * We pull this into the global namespace, and overload operator<< so that 142 * writing it to a stream should generate no code. 143 */ 144 typedef enum { NO_ARGS } NoArguments_; 145 } 146 147 inline std::ostream & operator<<(std::ostream &o, Xapian::NoArguments_) { 148 return o; 149 } 150 151 using Xapian::NO_ARGS; 152 153 extern DebugLogger xapian_debuglogger_; 154 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) 162 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); \ 169 } \ 170 } while (false) 171 172 /** Helper class for debug logging of functions and methods. 173 * 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. 180 */ 181 class DebugLogFunc { 182 /// This pointer (or 0 if this is a static method or a non-class function). 183 const void* this_ptr; 184 185 /// The category of log message to use for this function/method. 186 debuglog_categories category; 187 188 /// Function/method name. 189 std::string func; 190 191 /// Was an uncaught exception active when we entered this function? 192 bool uncaught_exception; 193 194 public: 195 /// Constructor called when logging for a "normal" method or function. DebugLogFunc(const void * this_ptr_,debuglog_categories category_,const char * return_type,const char * func_name,const std::string & params)196 DebugLogFunc(const void* this_ptr_, debuglog_categories category_, 197 const char* return_type, const char* func_name, 198 const std::string& params) 199 : this_ptr(this_ptr_), category(category_), 200 uncaught_exception(std::uncaught_exception()) 201 { 202 if (is_category_wanted()) { 203 func.assign(return_type); 204 func += ' '; 205 func += func_name; 206 func += '('; 207 func += params; 208 func += ')'; 209 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func); 210 xapian_debuglogger_.indent(); 211 } 212 } 213 214 /// Log the returned value. log_return_value(const std::string & return_value)215 void log_return_value(const std::string& return_value) { 216 xapian_debuglogger_.outdent(); 217 LOGLINE_(category, '[' << this_ptr << "] " << func << " returned: " << 218 return_value); 219 220 // Flag that we've logged the return already. 221 category = DEBUGLOG_CATEGORY_NEVER; 222 } 223 224 /// Check if the current category of log message is wanted. is_category_wanted()225 bool is_category_wanted() const { 226 return xapian_debuglogger_.is_category_wanted(category); 227 } 228 229 /** Destructor. 230 * 231 * This logs that the function/method has returned if this is due to an 232 * exception or if the RETURN() macro hasn't been used. 233 */ ~DebugLogFunc()234 ~DebugLogFunc() { 235 if (!is_category_wanted()) return; 236 xapian_debuglogger_.outdent(); 237 if (!uncaught_exception && std::uncaught_exception()) { 238 // An exception is causing the stack to be unwound. 239 LOGLINE_(category, '[' << this_ptr << "] " << func << 240 " exited due to exception"); 241 } else { 242 LOGLINE_(category, '[' << this_ptr << "] " << func << 243 " returned (not marked up for return logging)"); 244 } 245 } 246 }; 247 248 /** Helper class for debug logging of functions and methods returning void, 249 * and class constructors and destructors. 250 * 251 * We instantiate a DebugLogFuncVoid object at the start of each logged 252 * function and method. DebugLogFuncVoid's constructor logs the parameters 253 * passed, and DebugLogFunc's destructor logs that the function/method is 254 * returning. If an exception is thrown during the method and causes it to 255 * exit, DebugLogFunc's destructor detects and logs this fact. 256 */ 257 class DebugLogFuncVoid { 258 /// This pointer (or 0 if this is a static method or a non-class function). 259 const void* this_ptr; 260 261 /// The category of log message to use for this function/method. 262 debuglog_categories category; 263 264 /// Function/method name. 265 std::string func; 266 267 /// Was an uncaught exception active when we entered this function? 268 bool uncaught_exception; 269 270 public: 271 /// Constructor called when logging for a "normal" method or function. DebugLogFuncVoid(const void * this_ptr_,debuglog_categories category_,const char * func_name,const std::string & params)272 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_, 273 const char* func_name, 274 const std::string& params) 275 : this_ptr(this_ptr_), category(category_), 276 uncaught_exception(std::uncaught_exception()) 277 { 278 if (is_category_wanted()) { 279 func.assign("void "); 280 func += func_name; 281 func += '('; 282 func += params; 283 func += ')'; 284 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func); 285 xapian_debuglogger_.indent(); 286 } 287 } 288 289 /// Constructor called when logging for a class constructor. DebugLogFuncVoid(const void * this_ptr_,debuglog_categories category_,const std::string & params,const char * class_name)290 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_, 291 const std::string& params, 292 const char* class_name) 293 : this_ptr(this_ptr_), category(category_), 294 uncaught_exception(std::uncaught_exception()) 295 { 296 if (is_category_wanted()) { 297 func.assign(class_name); 298 func += "::"; 299 // The ctor name is the last component if there are colons (e.g. 300 // for Query::Internal, the ctor is Internal. 301 const char* ctor_name = std::strrchr(class_name, ':'); 302 if (ctor_name) 303 ++ctor_name; 304 else 305 ctor_name = class_name; 306 func += ctor_name; 307 func += '('; 308 func += params; 309 func += ')'; 310 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func); 311 xapian_debuglogger_.indent(); 312 } 313 } 314 315 /// Constructor called when logging for a class destructor. DebugLogFuncVoid(const void * this_ptr_,debuglog_categories category_,const char * class_name)316 DebugLogFuncVoid(const void* this_ptr_, debuglog_categories category_, 317 const char* class_name) 318 : this_ptr(this_ptr_), category(category_), 319 uncaught_exception(std::uncaught_exception()) 320 { 321 if (is_category_wanted()) { 322 func.assign(class_name); 323 func += "::~"; 324 // The dtor name is the last component if there are colons. 325 const char* dtor_name = std::strrchr(class_name, ':'); 326 if (dtor_name) 327 ++dtor_name; 328 else 329 dtor_name = class_name; 330 func += dtor_name; 331 func += "()"; 332 LOGLINE_(category, '[' << this_ptr << "] " << func); 333 xapian_debuglogger_.indent(); 334 } 335 } 336 337 /// Check if the current category of log message is wanted. is_category_wanted()338 bool is_category_wanted() const { 339 return xapian_debuglogger_.is_category_wanted(category); 340 } 341 342 /** Destructor. 343 * 344 * This logs that the function/method has returned and whether this was 345 * due to an exception. 346 */ ~DebugLogFuncVoid()347 ~DebugLogFuncVoid() { 348 if (!is_category_wanted()) return; 349 xapian_debuglogger_.outdent(); 350 const char* reason; 351 if (!uncaught_exception && std::uncaught_exception()) { 352 // An exception is causing the stack to be unwound. 353 reason = " exited due to exception"; 354 } else { 355 reason = " returned"; 356 } 357 LOGLINE_ALWAYS_(category, '[' << this_ptr << "] " << func << reason); 358 } 359 }; 360 361 #ifdef __GNUC__ 362 // __attribute__((unused)) supported since at least GCC 2.95.3. 363 # define XAPIAN_UNUSED __attribute__((unused)) 364 #else 365 # define XAPIAN_UNUSED 366 #endif 367 368 /// Log a call to a method returning non-void. 369 #define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS) \ 370 typedef TYPE xapian_logcall_return_type_ XAPIAN_UNUSED; \ 371 std::string xapian_logcall_parameters_; \ 372 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \ 373 std::ostringstream xapian_logcall_ostream_; \ 374 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \ 375 xapian_logcall_stream_ << PARAMS; \ 376 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \ 377 } \ 378 DebugLogFunc xapian_logcall_(static_cast<const void*>(this), \ 379 DEBUGLOG_CATEGORY_##CATEGORY, #TYPE, FUNC, \ 380 xapian_logcall_parameters_) 381 382 /// Log a call to a method returning void. 383 #define LOGCALL_VOID(CATEGORY, FUNC, PARAMS) \ 384 std::string xapian_logcall_parameters_; \ 385 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \ 386 std::ostringstream xapian_logcall_ostream_; \ 387 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \ 388 xapian_logcall_stream_ << PARAMS; \ 389 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \ 390 } \ 391 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \ 392 DEBUGLOG_CATEGORY_##CATEGORY, FUNC, \ 393 xapian_logcall_parameters_) 394 395 /// Log a constructor call. 396 #define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS) \ 397 std::string xapian_logcall_parameters_; \ 398 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \ 399 std::ostringstream xapian_logcall_ostream_; \ 400 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \ 401 xapian_logcall_stream_ << PARAMS; \ 402 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \ 403 } \ 404 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \ 405 DEBUGLOG_CATEGORY_##CATEGORY, \ 406 xapian_logcall_parameters_, CLASS) 407 408 /// Log a destructor call. 409 #define LOGCALL_DTOR(CATEGORY, CLASS) \ 410 DebugLogFuncVoid xapian_logcall_(static_cast<const void*>(this), \ 411 DEBUGLOG_CATEGORY_##CATEGORY, CLASS) 412 413 /// Log a call to a static method returning a non-void type. 414 #define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS) \ 415 typedef TYPE xapian_logcall_return_type_ XAPIAN_UNUSED; \ 416 std::string xapian_logcall_parameters_; \ 417 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \ 418 std::ostringstream xapian_logcall_ostream_; \ 419 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \ 420 xapian_logcall_stream_ << PARAMS; \ 421 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \ 422 } \ 423 DebugLogFunc xapian_logcall_(0, DEBUGLOG_CATEGORY_##CATEGORY, #TYPE, FUNC, xapian_logcall_parameters_) 424 425 /// Log a call to a static method returning void. 426 #define LOGCALL_STATIC_VOID(CATEGORY, FUNC, PARAMS) \ 427 std::string xapian_logcall_parameters_; \ 428 if (xapian_debuglogger_.is_category_wanted(DEBUGLOG_CATEGORY_##CATEGORY)) { \ 429 std::ostringstream xapian_logcall_ostream_; \ 430 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \ 431 xapian_logcall_stream_ << PARAMS; \ 432 xapian_logcall_parameters_ = xapian_logcall_ostream_.str(); \ 433 } \ 434 DebugLogFuncVoid xapian_logcall_(0, DEBUGLOG_CATEGORY_##CATEGORY, FUNC, xapian_logcall_parameters_) 435 436 /// Log returning a value. 437 #define RETURN(A) do { \ 438 xapian_logcall_return_type_ xapian_logcall_return_ = A; \ 439 if (xapian_logcall_.is_category_wanted()) { \ 440 std::ostringstream xapian_logcall_ostream_; \ 441 PrettyOStream<std::ostringstream> xapian_logcall_stream_(xapian_logcall_ostream_); \ 442 xapian_logcall_stream_ << xapian_logcall_return_; \ 443 xapian_logcall_.log_return_value(xapian_logcall_ostream_.str()); \ 444 } \ 445 return xapian_logcall_return_; \ 446 } while (false) 447 448 /** Log message @a b of category @a a. 449 * 450 * The message is logged on a line by itself. To keep the debug log readable, 451 * it shouldn't have a trailing '\n', or contain an embedded '\n'. 452 */ 453 #define LOGLINE(a,b) LOGLINE_(DEBUGLOG_CATEGORY_##a, b) 454 455 /** Log the value of variable or expression @a b. */ 456 #define LOGVALUE(a,b) LOGLINE_(DEBUGLOG_CATEGORY_##a, #b" = " << b) 457 458 #else 459 460 #define LOGCALL(CATEGORY, TYPE, FUNC, PARAMS) (void)0 461 #define LOGCALL_VOID(CATEGORY, FUNC, PARAMS) (void)0 462 #define LOGCALL_CTOR(CATEGORY, CLASS, PARAMS) (void)0 463 #define LOGCALL_DTOR(CATEGORY, CLASS) (void)0 464 #define LOGCALL_STATIC(CATEGORY, TYPE, FUNC, PARAMS) (void)0 465 #define LOGCALL_STATIC_VOID(CATEGORY, FUNC, PARAMS) (void)0 466 #define RETURN(A) return A 467 #define LOGLINE(a,b) (void)0 468 #define LOGVALUE(a,b) (void)0 469 470 #endif 471 472 #endif // XAPIAN_INCLUDED_DEBUGLOG_H 473