Advertisement
justin_hanekom

kjmj_log.h logging for C/C++

Feb 1st, 2025
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 17.14 KB | None | 0 0
  1. // File: kjmj_log.h
  2. // SPDX-License-Identifier: Unlicensed
  3. // This is free and unencumbered software released into the public domain.
  4.  
  5. /*!
  6.  * \file kjmj_log.h
  7.  *
  8.  * \brief   This file is a \e thin wrapper around the C Macro Collections
  9.  *          \c log.h header file.
  10.  *
  11.  * Defines the macro functions \c LOG_INIT, \c LOG_TO_FILE, LOG_TO_TERMINAL,
  12.  * and \c LOG_TERM. These macros initialize, setup, and terminate logging.
  13.  *
  14.  * Defines the macro values \c LOG_LEVEL_TRACE, \c LOG_LEVEL_DEBUG,
  15.  * \c LOG_LEVEL_INFO, \c LOG_LEVEL_WARN, \c LOG_LEVEL_ERROR, and
  16.  * \c LOG_LEVEL_FATAL. These macro values are used to setup logging to the
  17.  * given level or higher (i.e., \c LOG_LEVEL_TRACE is the lowest level, and
  18.  * \c LOG_LEVEL_FATAL is the highest level).
  19.  *
  20.  * Defines the logging macro functions \c LOG_TRACE, \c LOG_DEBUG,
  21.  * \c LOG_INFO, \c LOG_WARN, \c LOG_ERROR, and \c WARN_FATAL.
  22.  *
  23.  * \version 1.0
  24.  * \date    2023
  25.  */
  26.  
  27. #ifndef KJMJ_LOG_H_
  28. #define KJMJ_LOG_H_
  29.  
  30. #ifdef __cplusplus
  31. extern "C" {
  32. #endif
  33.  
  34. #include <stdio.h>      // for FILE and file-related functions, stdout, stderr
  35. #include <string.h>     // for string manipulation functions
  36. #include <time.h>       // for time_t, time, localtime
  37. #include <stdarg.h>     // for va_list, va_start, va_end, vfprintf
  38. #include <assert.h>     // for assert
  39.  
  40. // ANSI color codes.
  41. #define ANSI_BLACK_FG                   "\e[0;30m"
  42. #define ANSI_RED_FG                     "\e[0;31m"
  43. #define ANSI_GREEN_FG                   "\e[0;32m"
  44. #define ANSI_YELLOW_FG                  "\e[0;33m"
  45. #define ANSI_BLUE_FG                    "\e[0;34m"
  46. #define ANSI_PURPLE_FG                  "\e[0;35m"
  47. #define ANSI_CYAN_FG                    "\e[0;36m"
  48. #define ANSI_WHITE_FG                   "\e[0;37m"
  49.  
  50. #define ANSI_BOLD_BLACK_FG              "\e[1;30m"
  51. #define ANSI_BOLD_RED_FG                "\e[1;31m"
  52. #define ANSI_BOLD_GREEN_FG              "\e[1;32m"
  53. #define ANSI_BOLD_YELLOW_FG             "\e[1;33m"
  54. #define ANSI_BOLD_BLUE_FG               "\e[1;34m"
  55. #define ANSI_BOLD_PURPLE_FG             "\e[1;35m"
  56. #define ANSI_BOLD_CYAN_FG               "\e[1;36m"
  57. #define ANSI_BOLD_WHITE_FG              "\e[1;37m"
  58.  
  59. #define ANSI_BRIGHT_BLACK_FG            "\e[0;90m"
  60. #define ANSI_BRIGHT_RED_FG              "\e[0;91m"
  61. #define ANSI_BRIGHT_GREEN_FG            "\e[0;92m"
  62. #define ANSI_BRIGHT_YELLOW_FG           "\e[0;93m"
  63. #define ANSI_BRIGHT_BLUE_FG             "\e[0;94m"
  64. #define ANSI_BRIGHT_PURPLE_FG           "\e[0;95m"
  65. #define ANSI_BRIGHT_CYAN_FG             "\e[0;96m"
  66. #define ANSI_BRIGHT_WHITE_FG            "\e[0;97m"
  67.  
  68. #define ANSI_BOLD_BRIGHT_BLACK_FG       "\e[1;90m"
  69. #define ANSI_BOLD_BRIGHT_RED_FG         "\e[1;91m"
  70. #define ANSI_BOLD_BRIGHT_GREEN_FG       "\e[1;92m"
  71. #define ANSI_BOLD_BRIGHT_YELLOW_FG      "\e[1;93m"
  72. #define ANSI_BOLD_BRIGHT_BLUE_FG        "\e[1;94m"
  73. #define ANSI_BOLD_BRIGHT_PURPLE_FG      "\e[1;95m"
  74. #define ANSI_BOLD_BRIGHT_CYAN_FG        "\e[1;96m"
  75. #define ANSI_BOLD_BRIGHT_WHITE_FG       "\e[1;97m"
  76.  
  77. #define ANSI_UNDERLINE_BLACK_FG         "\e[4;30m"
  78. #define ANSI_UNDERLINE_RED_FG           "\e[4;31m"
  79. #define ANSI_UNDERLINE_GREEN_FG         "\e[4;32m"
  80. #define ANSI_UNDERLINE_YELLOW_FG        "\e[4;33m"
  81. #define ANSI_UNDERLINE_BLUE_FG          "\e[4;34m"
  82. #define ANSI_UNDERLINE_PURPLE_FG        "\e[4;35m"
  83. #define ANSI_UNDERLINE_CYAN_FG          "\e[4;36m"
  84. #define ANSI_UNDERLINE_WHITE_FG         "\e[4;37m"
  85.  
  86. #define ANSI_BLACK_BG                   "\e[40m"
  87. #define ANSI_RED_BG                     "\e[41m"
  88. #define ANSI_GREEN_BG                   "\e[42m"
  89. #define ANSI_YELLOW_BG                  "\e[43m"
  90. #define ANSI_BLUE_BG                    "\e[44m"
  91. #define ANSI_PURPLE_BG                  "\e[45m"
  92. #define ANSI_CYAN_BG                    "\e[46m"
  93. #define ANSI_WHITE_BG                   "\e[47m"
  94.  
  95. #define ANSI_RESET                      "\e[0;0m"
  96.  
  97. // Log colors.
  98. #ifndef ANSI_START_TRACE
  99. #define ANSI_START_TRACE                ANSI_BRIGHT_WHITE_FG
  100. #endif
  101. #ifndef ANSI_START_DEBUG
  102. #define ANSI_START_DEBUG                ANSI_BRIGHT_BLUE_FG
  103. #endif
  104. #ifndef ANSI_START_INFO
  105. #define ANSI_START_INFO                 ANSI_BRIGHT_CYAN_FG
  106. #endif
  107. #ifndef ANSI_START_WARN
  108. #define ANSI_START_WARN                 ANSI_BRIGHT_YELLOW_FG
  109. #endif
  110. #ifndef ANSI_START_ERROR
  111. #define ANSI_START_ERROR                ANSI_BRIGHT_RED_FG
  112. #endif
  113. #ifndef ANSI_START_FATAL
  114. #define ANSI_START_FATAL                ANSI_BRIGHT_PURPLE_FG
  115. #endif
  116.  
  117. /*!
  118.  * \enum    kjmjLogLevelEnum
  119.  *
  120.  * \brief   The possible logging levels.
  121.  *
  122.  * The log level \c KJMJ_LOG_LEVEL_FATAL can be used to specify that only the
  123.  * most important (i.e., fatal) log messages shold be logged. Using this level
  124.  * to specify the log level for \c kjmj_log_to_file, \c kjmj_log_to_stdout, or
  125.  * \c kjmj_log_to_stderr means that only messages logged using the \c FATAL
  126.  * macro will be logged.
  127.  *
  128.  * Conversely, the log level \c KJMJ_LOG_LEVEL_TRACE can be used to specify
  129.  * that \em any logging message will be logged.
  130.  *
  131.  * The log level \c KJMJ_LOG_LEVEL_DISABLED can be used to specify that
  132.  * logging should not occur.
  133.  *
  134.  * The log level \c KJMJ_LOG_LEVEL_ALL can be used to specify that all log
  135.  * messages should be logged.
  136.  *
  137.  * \see     kjmj_log_to_file
  138.  * \see     kjmj_tog_to_stdout
  139.  * \see     kjmj_log_to_stderr
  140.  * \see     FATAL
  141. */
  142. enum kjmjLogLevelEnum {
  143.     KJMJ_LOG_LEVEL_DISABLED,
  144.     KJMJ_LOG_LEVEL_FATAL,
  145.     KJMJ_LOG_LEVEL_ERROR,
  146.     KJMJ_LOG_LEVEL_WARN,
  147.     KJMJ_LOG_LEVEL_INFO,
  148.     KJMJ_LOG_LEVEL_DEBUG,
  149.     KJMJ_LOG_LEVEL_TRACE,
  150.     KJMJ_LOG_LEVEL_ALL
  151. };
  152. typedef enum kjmjLogLevelEnum kjmjLogLevel;
  153.  
  154. /*!
  155.  * \def     KJMJ_LEVEL_IS_AT_LEAST
  156.  *
  157.  * \brief   Returns true if a level is at least as important as the
  158.  *          other level.
  159.  *
  160.  * \param   LEVEL the level to compare against \p OTHER.
  161.  * \param   OTHER the level that LEVEL should be at least as important as.
  162.  *
  163.  * \see     kjmjLogLevelEnum
  164.  *
  165.  * \note    Because the log levels are specified from the
  166.  *          most important (fatal) to the least important (trace)
  167.  *          the lower the log level the more important it is.
  168.  */
  169. #define KJMJ_LEVEL_AT_LEAST( LEVEL, OTHER ) \
  170.     ( LEVEL <= OTHER )
  171.  
  172. /*!
  173.  * \internal
  174.  *
  175.  * \def     KJMJ_FNAME
  176.  *
  177.  * \brief   Returns the filename suffix portion of \c __FILE__.
  178.  *
  179.  * \endinternal
  180.  */
  181. #define KJMJ_FNAME \
  182.     ( strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__ )
  183.  
  184. /*!
  185.  * \internal
  186.  *
  187.  * \def KJMJ_MACRO_START
  188.  *
  189.  * \brief   Used at the beginning of macro function definitions that contain
  190.  *          more than one line of code.
  191.  *
  192.  * Used to wrap the macro function in a do { ... } while(0) block.
  193.  *
  194.  * \see     KJMJ_MACRO_END
  195.  *
  196.  * \endinternal
  197.  */
  198. #define KJMJ_MACRO_START do {
  199.  
  200. /*!
  201.  * \internal
  202.  *
  203.  * \def     KJMJ_MACRO_END
  204.  *
  205.  * \brief   Used at the end of macro function definitions that contain
  206.  *          more than one line of code.
  207.  *
  208.  * Used to wrap the macro function in a <tt>do { ... } while (0)</tt> block.
  209.  *
  210.  * \see     KJMJ_MACRO_START
  211.  *
  212.  * \endinternal
  213.  */
  214. #define KJMJ_MACRO_END } while (0)
  215.  
  216. /*!
  217.  * \def     TRACE
  218.  *
  219.  * \brief   Used to log at the trace level.
  220.  *
  221.  * Trace is used to describe the inner workings of a function or an
  222.  * algorithm. It can be useful when trying to find an error in an
  223.  * algorithm or for corner cases.
  224.  *
  225.  * \param FMT   a C printf style format string.
  226.  * \param ...   any additional variadic arguments will be used like printf
  227.  *              variadic arguments.
  228.  */
  229. #ifndef TRACE
  230. #define TRACE( FMT, ... ) \
  231.     kjmj_log( KJMJ_LOG_LEVEL_TRACE, "[TRACE]", ANSI_START_TRACE, \
  232.         __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
  233. #else   // TRACE is defined
  234. #error "TRACE already defined"
  235. #endif
  236.  
  237. /*!
  238.  * \def     DEBUG
  239.  *
  240.  * \brief   Used to log at the debug level or higher.
  241.  *
  242.  * Mainly used for debugging, tracking certain variables or
  243.  * anything useful to help visualizing what your program is doing.
  244.  *
  245.  * \param FMT   a C printf style format string.
  246.  * \param ...   any additional variadic arguments will be used like printf
  247.  *              variadic arguments.
  248.  */
  249. #ifndef DEBUG
  250. #define DEBUG( FMT, ... ) \
  251.     kjmj_log( KJMJ_LOG_LEVEL_DEBUG, "[DEBUG]", ANSI_START_DEBUG, \
  252.         __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
  253. #else
  254. #error "DEBUG already defined"
  255. #endif
  256.  
  257. /*!
  258.  * \def     INFO
  259.  * \brief   Used to log at the info level.
  260.  *
  261.  * Info can be used as a heads up or for normal messages.
  262.  *
  263.  * \param FMT   a C printf style format string.
  264.  * \param ...   any additional variadic arguments will be used like printf
  265.  *              variadic arguments.
  266.  */
  267. #ifndef INFO
  268. #define INFO( FMT, ... ) \
  269.     kjmj_log( KJMJ_LOG_LEVEL_INFO, "[INFO]", ANSI_START_INFO, \
  270.         __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
  271. #else
  272. #error "INFO already defined"
  273. #endif
  274.  
  275. /*!
  276.  * \def     WARN
  277.  *
  278.  * \brief   Used to log at the warning level.
  279.  *
  280.  * Warnings are attributed to all cases where the code can recover from the
  281.  * condition and they are usually treated by it. In most cases the user can
  282.  * treat these warnings themselves.
  283.  *
  284.  * \param FMT   a C printf style format string.
  285.  * \param ...   any additional variadic arguments will be used like printf
  286.  *              variadic arguments.
  287.  */
  288. #ifndef WARN
  289. #define WARN( FMT, ... ) \
  290.     kjmj_log( KJMJ_LOG_LEVEL_WARN, "[WARN]", ANSI_START_WARN, \
  291.         __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
  292. #else
  293. #error "WARN already defined"
  294. #endif
  295.  
  296. /*!
  297.  * \def     ERROR
  298.  *
  299.  * \brief   Used to log at the error level.
  300.  *
  301.  * Something really bad happened. Your program will not crash yet but
  302.  * depending on what comes next, it probably will.
  303.  *
  304.  * \param FMT   a C printf style format string.
  305.  * \param ...   any additional variadic arguments will be used like printf
  306.  *              variadic arguments.
  307.  */
  308. #ifndef ERROR
  309. #define ERROR( FMT, ... ) \
  310.     kjmj_log( KJMJ_LOG_LEVEL_ERROR, "[ERROR]", ANSI_START_ERROR, \
  311.         __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
  312. #else
  313. #error "ERROR already defined"
  314. #endif
  315.  
  316. /*!
  317.  * \def     FATAL
  318.  *
  319.  * \brief   Used to log at the fatal level.
  320.  *
  321.  * This will probably be the last logging message before your program
  322.  * crashes or goes into madness. Fatal errors are commonly attributed
  323.  * to dereferencing \c NULL pointers.
  324.  *
  325.  * \param FMT   a C printf style format string.
  326.  * \param ...   any additional variadic arguments will be used like printf
  327.  *              variadic arguments.
  328.  */
  329. #ifndef FATAL
  330. #define FATAL( FMT, ... ) \
  331.     kjmj_log( KJMJ_LOG_LEVEL_FATAL, "[FATAL]", ANSI_START_FATAL, \
  332.         __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ );
  333. #else
  334.     #error "FATAL already defined"
  335. #endif
  336.  
  337. /*!
  338.  * \internal
  339.  *
  340.  * \struct  kjmjLogStruct
  341.  *
  342.  * \brief   The data structure that holds all information neccessary to
  343.  *          determine what to do when a message is logged.
  344.  *
  345.  * The one and only variable that uses this struct is the global variable
  346.  * \c g_kjmj_log.
  347.  *
  348.  * \endinternal
  349.  */
  350. static struct kjmjLogStruct {
  351.     FILE* file;
  352.     int file_log_level;
  353.     int stdout_log_level;
  354.     int stderr_log_level;
  355. } g_kjmj_log = {
  356.     NULL,
  357.     KJMJ_LOG_LEVEL_DISABLED,
  358.     KJMJ_LOG_LEVEL_DISABLED,
  359.     KJMJ_LOG_LEVEL_DISABLED
  360. };
  361.  
  362. /*!
  363.  * \fn      kjmj_log_init
  364.  *
  365.  * \brief   Prepares the logging system. Both file and terminal logging is
  366.  *          disabled.
  367.  *
  368.  * \note    If the logging system is currently setup to log to a file, the
  369.  *          file will be closed.
  370.  */
  371. static void kjmj_log_init()
  372. {
  373.     if ( g_kjmj_log.file != NULL ) {
  374.         fclose( g_kjmj_log.file );
  375.     }
  376.  
  377.     g_kjmj_log.file = NULL,
  378.     g_kjmj_log.file_log_level = KJMJ_LOG_LEVEL_DISABLED;
  379.     g_kjmj_log.stdout_log_level = KJMJ_LOG_LEVEL_DISABLED,
  380.     g_kjmj_log.stderr_log_level = KJMJ_LOG_LEVEL_DISABLED;
  381. }
  382.  
  383. /*!
  384.  * \fn      kjmj_log_terminate
  385.  *
  386.  * \brief   Should be called once to clean up.
  387.  *
  388.  * \see     kjmj_log_init
  389.  */
  390. static void kjmj_log_terminate()
  391. {
  392.     kjmj_log_init();
  393. }
  394.  
  395. /*!
  396.  * \fn      kjmj_log_to_file
  397.  *
  398.  * \brief   Enables logging to \p file at the \p level logging level.
  399.  *
  400.  * \param   file    handle (pointer) to \c FILE. Must be open unless the
  401.  *                  \p LEVEL is \c KJMJ_LOG_LEVEL_DISABLED.
  402.  * \param   level   the level (or higher) at which logging is enabled.
  403.  *
  404.  * \see     kjmjLogLevel
  405.  */
  406. static void kjmj_log_to_file( FILE* file, kjmjLogLevel level )
  407. {
  408.     assert( ( file != NULL ) ||
  409.         ( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_TRACE, level ) ) );
  410.     assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
  411.     assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
  412.  
  413.     g_kjmj_log.file = file;
  414.     g_kjmj_log.file_log_level = level;
  415. }
  416.  
  417. /*!
  418.  * \def     kjmj_log_to_stdout
  419.  *
  420.  * \brief   Enables logging to \c stdout at the \p level logging level.
  421.  *
  422.  * \param   level   the level (or more important) at which logging is enabled.
  423.  *
  424.  * \see     kjmjLogLevel
  425.  */
  426. static void kjmj_log_to_stdout( kjmjLogLevel level )
  427. {
  428.     assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
  429.     assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
  430.  
  431.     g_kjmj_log.stdout_log_level = level;
  432. }
  433.  
  434. /*!
  435.  * \def     kjmj_log_to_stderr
  436.  *
  437.  * \brief   Enables logging to \c stderr at the \p level logging level.
  438.  *
  439.  * \param   level   the level (or more important) at which logging is enabled.
  440.  */
  441. static void kjmj_log_to_stderr( kjmjLogLevel level )
  442. {
  443.     assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
  444.     assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
  445.  
  446.     g_kjmj_log.stderr_log_level = level;
  447. }
  448.  
  449. /*!
  450.  * \internal
  451.  *
  452.  * \fn      kjmj_log
  453.  *
  454.  * \brief   Logs a message to a file, stdout, and/or stderr if enabled.
  455.  *
  456.  * \param   level               the level of the log message.
  457.  * \param   level_name          a string representation of the level.
  458.  * \param   ansi_start_level    the ANSI string that should be used to start
  459.  *                              the log message if log colors are enabled.
  460.  * \param   function_name       the name of the function currently executing.
  461.  * \param   filename            the name of the file containing this function.
  462.  * \param   line                the line number where the message is being
  463.  *                              logged from.
  464.  * \param   fmt                 a C printf style format string.
  465.  * \param ...                   any additional variadic arguments will be used
  466.  *                              like printf variadic arguments.
  467.  *
  468.  * \internal
  469.  */
  470. void kjmj_log( int level, const char* level_name,
  471.     const char* ansi_start_level, const char* function_name,
  472.     const char* filename, unsigned line, const char* fmt, ... )
  473. {
  474.     assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
  475.     assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
  476.     assert( level_name != NULL );
  477.     assert( ansi_start_level != NULL );
  478.     assert( function_name != NULL );
  479.     assert( filename != NULL );
  480.     assert( fmt != NULL );
  481.  
  482.     _Bool log_to_file = ( g_kjmj_log.file != NULL )
  483.         && ( KJMJ_LEVEL_AT_LEAST( level, g_kjmj_log.file_log_level ) );
  484.     _Bool log_to_stdout =
  485.         KJMJ_LEVEL_AT_LEAST( level, g_kjmj_log.stdout_log_level );
  486.     _Bool log_to_stderr =
  487.         KJMJ_LEVEL_AT_LEAST( level, g_kjmj_log.stderr_log_level );
  488.  
  489.     if ( !log_to_file && !log_to_stdout && !log_to_stderr ) {
  490.         return;
  491.     }
  492.  
  493.     // Determine the time string for the time now.
  494.     time_t now;
  495.     time( &now );
  496.     struct tm* local;
  497.     local = localtime( &now );
  498.     char timestamp[ 0x20 ];
  499.     timestamp[ strftime( timestamp, sizeof( timestamp ),
  500.             "%Y/%m/%d %H:%M:%S", local ) ] = '\0';
  501.  
  502.     // Determine the prefix to be printed before the log message.
  503.     char msg_prefix[ 0x100 ];
  504.     snprintf( msg_prefix, sizeof( msg_prefix ),
  505.         "%s %-7s %s (%s:%u) ", timestamp, level_name,
  506.         function_name, filename, line );
  507.  
  508.     // Optionally log to file.
  509.     if ( log_to_file ) {
  510.         fprintf( g_kjmj_log.file, msg_prefix );
  511.  
  512.         va_list args;
  513.         va_start( args, fmt );
  514.         vfprintf( g_kjmj_log.file, fmt, args );
  515.         va_end( args );
  516.         fprintf( g_kjmj_log.file, "\n" );
  517.  
  518.         fflush( g_kjmj_log.file );
  519.     }
  520.  
  521.     // Optionally log to stdout.
  522.     if ( log_to_stdout ) {
  523.         #ifndef KJMJ_LOG_NO_COLOR
  524.         fprintf( stdout, ansi_start_level );
  525.         #endif
  526.  
  527.         fprintf( stdout, msg_prefix );
  528.  
  529.         #ifndef KJMJ_LOG_NO_COLOR
  530.         fprintf( stdout, ANSI_RESET );
  531.         #endif
  532.  
  533.         va_list args;
  534.         va_start( args, fmt );
  535.         vfprintf( stdout, fmt, args );
  536.         va_end( args );
  537.         fprintf( stdout, "\n" );
  538.     }
  539.  
  540.     // Optionally log to stderr.
  541.     if ( log_to_stderr ) {
  542.         #ifndef KJMJ_LOG_NO_COLOR
  543.         fprintf( stderr, ansi_start_level );
  544.         #endif
  545.  
  546.         fprintf( stderr, msg_prefix );
  547.  
  548.         #ifndef KJMJ_LOG_NO_COLOR
  549.         fprintf( stderr, ANSI_RESET );
  550.         #endif
  551.  
  552.         va_list args;
  553.         va_start( args, fmt );
  554.         vfprintf( stderr, fmt, args );
  555.         va_end( args );
  556.         fprintf( stderr, "\n" );
  557.     }
  558. } // kjmj_log
  559.  
  560. #ifdef __cplusplus
  561. }
  562. #endif
  563.  
  564. #endif // LOG_H_
  565.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement