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