/** * @file Trace.hh * * Tracing utility * * Copyright (c) 2016 - 2018, Peter Helfer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef TRACE_HH #define TRACE_HH #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> /** * Trace class */ class Trace { public: enum TraceLevel { TRACE_Flow, TRACE_Debug3, TRACE_Debug2, TRACE_Debug1, TRACE_Debug, TRACE_Info, TRACE_Warn, TRACE_Error, TRACE_Fatal, TRACE_Maxval }; static void setTraceLevel(TraceLevel level) { traceLevel = level; } static bool setTraceLevel(const char *levelString) { for (uint i = 0; i < TRACE_Maxval; i++) { if (strcasecmp(levelString, traceLevelString((TraceLevel) i)) == 0) { setTraceLevel((TraceLevel) i); return true; } } fprintf(stderr, "Unknown trace level '%s'\n", levelString); return false; } static TraceLevel getTraceLevel() { return traceLevel; } static const char *getTraceLevelString() { return traceLevelString(traceLevel); } #ifdef TRACE_ON #define TRACE(lvl, fmt, ...) \ do { \ if (lvl >= Trace::getTraceLevel()) { \ Trace::trace(lvl, __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__); \ } \ } while(0); #define TRACE_FLOW_IS_ON (Trace::TRACE_Flow >= Trace::getTraceLevel()) #define TRACE_DEBUG3_IS_ON (Trace::TRACE_Debug3 >= Trace::getTraceLevel()) #define TRACE_DEBUG2_IS_ON (Trace::TRACE_Debug2 >= Trace::getTraceLevel()) #define TRACE_DEBUG1_IS_ON (Trace::TRACE_Debug1 >= Trace::getTraceLevel()) #define TRACE_DEBUG_IS_ON (Trace::TRACE_Debug >= Trace::getTraceLevel()) #define TRACE_INFO_IS_ON (Trace::TRACE_Info >= Trace::getTraceLevel()) #define TRACE_WARN_IS_ON (Trace::TRACE_Warn >= Trace::getTraceLevel()) #define TRACE_ERROR_IS_ON (Trace::TRACE_Error >= Trace::getTraceLevel()) #else #define TRACE(lvl, fmt, ...) #define TRACE_FLOW_IS_ON false #define TRACE_DEBUG3_IS_ON false #define TRACE_DEBUG2_IS_ON false #define TRACE_DEBUG1_IS_ON false #define TRACE_DEBUG_IS_ON false #define TRACE_INFO_IS_ON false #define TRACE_WARN_IS_ON false #define TRACE_ERROR_IS_ON false #endif #define TRACE_FLOW(fmt, ...) TRACE(Trace::TRACE_Flow, fmt, ##__VA_ARGS__) #define TRACE_DEBUG3(fmt, ...) TRACE(Trace::TRACE_Debug3, fmt, ##__VA_ARGS__) #define TRACE_DEBUG2(fmt, ...) TRACE(Trace::TRACE_Debug2, fmt, ##__VA_ARGS__) #define TRACE_DEBUG1(fmt, ...) TRACE(Trace::TRACE_Debug1, fmt, ##__VA_ARGS__) #define TRACE_DEBUG(fmt, ...) TRACE(Trace::TRACE_Debug, fmt, ##__VA_ARGS__) #define TRACE_INFO(fmt, ...) TRACE(Trace::TRACE_Info, fmt, ##__VA_ARGS__) #define TRACE_WARN(fmt, ...) TRACE(Trace::TRACE_Warn, fmt, ##__VA_ARGS__) #define TRACE_ERROR(fmt, ...) TRACE(Trace::TRACE_Error, fmt, ##__VA_ARGS__) #define TRACE_FATAL(fmt, ...) \ do { \ Trace::trace(Trace::TRACE_Fatal, __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__); \ abort(); \ } while (0) // TRACE_FATAL will always abort the process, even when compiled without TRACE_ON /** * Abort if a condition is true. Unlike assert(3), ABORT_IF cannot be * disabled by a compile option like -DNDEBUG. * @param cond The condition * @param fmt printf-style format string for error message * @param ... Optional parameters values for error message */ #define ABORT_IF(cond, fmt, ...) \ if (cond) { \ TRACE_DEBUG("Aborting because: %s", #cond); \ TRACE_FATAL(fmt, ##__VA_ARGS__); \ } #define ABORT_UNLESS(cond, fmt, ...) \ ABORT_IF(!(cond), fmt, ##__VA_ARGS__) #define TRACE_ENTER() do { TRACE_FLOW("-->"); Trace::incrIndent(); } while (0); #define TRACE_EXIT() do { Trace::decrIndent(); TRACE_FLOW("<--"); } while (0); #define TRACE_RETURN(x) do { TRACE_EXIT(); return x; } while (0) static void trace(TraceLevel lvl, const char *file, int line, const char *func, const char *fmt, ...) { va_list ap; va_start(ap, fmt); FILE *dest = lvl >= TRACE_Warn ? stderr : stdout; fprintf(dest, "%s%s %s[%d] %s(): ", indentStr(), traceLevelString(lvl), file, line, func); vfprintf(dest, fmt, ap); fprintf(dest, "\n"); va_end(ap); } static void incrIndent() { if (indentLevel < MAXINDENT) indentLevel++; } static void decrIndent() { if (indentLevel > 0) indentLevel--; } static const char *traceLevelString(TraceLevel lvl) { switch (lvl) { case TRACE_Flow: return "FLOW"; case TRACE_Debug3: return "DEBUG3"; case TRACE_Debug2: return "DEBUG2"; case TRACE_Debug1: return "DEBUG1"; case TRACE_Debug: return "DEBUG"; case TRACE_Info: return "INFO"; case TRACE_Warn: return "WARN"; case TRACE_Error: return "ERROR"; case TRACE_Fatal: return "FATAL"; default: return "UNKNOWN TRACE LEVEL"; } } private: static TraceLevel traceLevel; static uint indentLevel; enum { MAXINDENT = 128 }; static const char *indentStr() { if (traceLevel <= TRACE_Flow) { static char buf[MAXINDENT + 1]; static bool firstTime = true; if (firstTime) { for (uint i = 0; i < MAXINDENT; i++) { buf[i] = ' '; } buf[MAXINDENT] = 0; firstTime = false; } return buf + MAXINDENT - indentLevel; } else { return ""; } } }; #endif // TRACE_HH