Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // File: kjmj_log.h
- // SPDX-License-Identifier: Unlicensed
- // This is free and unencumbered software released into the public domain.
- /*!
- * \file kjmj_log.h
- *
- * \brief This file is a \e thin wrapper around the C Macro Collections
- * \c log.h header file.
- *
- * Defines the macro functions \c LOG_INIT, \c LOG_TO_FILE, LOG_TO_TERMINAL,
- * and \c LOG_TERM. These macros initialize, setup, and terminate logging.
- *
- * Defines the macro values \c LOG_LEVEL_TRACE, \c LOG_LEVEL_DEBUG,
- * \c LOG_LEVEL_INFO, \c LOG_LEVEL_WARN, \c LOG_LEVEL_ERROR, and
- * \c LOG_LEVEL_FATAL. These macro values are used to setup logging to the
- * given level or higher (i.e., \c LOG_LEVEL_TRACE is the lowest level, and
- * \c LOG_LEVEL_FATAL is the highest level).
- *
- * Defines the logging macro functions \c LOG_TRACE, \c LOG_DEBUG,
- * \c LOG_INFO, \c LOG_WARN, \c LOG_ERROR, and \c WARN_FATAL.
- *
- * \version 1.0
- * \date 2023
- */
- #ifndef KJMJ_LOG_H_
- #define KJMJ_LOG_H_
- #ifdef __cplusplus
- extern "C" {
- #endif
- #include <stdio.h> // for FILE and file-related functions, stdout, stderr
- #include <string.h> // for string manipulation functions
- #include <time.h> // for time_t, time, localtime
- #include <stdarg.h> // for va_list, va_start, va_end, vfprintf
- #include <assert.h> // for assert
- // ANSI color codes.
- #define ANSI_BLACK_FG "\e[0;30m"
- #define ANSI_RED_FG "\e[0;31m"
- #define ANSI_GREEN_FG "\e[0;32m"
- #define ANSI_YELLOW_FG "\e[0;33m"
- #define ANSI_BLUE_FG "\e[0;34m"
- #define ANSI_PURPLE_FG "\e[0;35m"
- #define ANSI_CYAN_FG "\e[0;36m"
- #define ANSI_WHITE_FG "\e[0;37m"
- #define ANSI_BOLD_BLACK_FG "\e[1;30m"
- #define ANSI_BOLD_RED_FG "\e[1;31m"
- #define ANSI_BOLD_GREEN_FG "\e[1;32m"
- #define ANSI_BOLD_YELLOW_FG "\e[1;33m"
- #define ANSI_BOLD_BLUE_FG "\e[1;34m"
- #define ANSI_BOLD_PURPLE_FG "\e[1;35m"
- #define ANSI_BOLD_CYAN_FG "\e[1;36m"
- #define ANSI_BOLD_WHITE_FG "\e[1;37m"
- #define ANSI_BRIGHT_BLACK_FG "\e[0;90m"
- #define ANSI_BRIGHT_RED_FG "\e[0;91m"
- #define ANSI_BRIGHT_GREEN_FG "\e[0;92m"
- #define ANSI_BRIGHT_YELLOW_FG "\e[0;93m"
- #define ANSI_BRIGHT_BLUE_FG "\e[0;94m"
- #define ANSI_BRIGHT_PURPLE_FG "\e[0;95m"
- #define ANSI_BRIGHT_CYAN_FG "\e[0;96m"
- #define ANSI_BRIGHT_WHITE_FG "\e[0;97m"
- #define ANSI_BOLD_BRIGHT_BLACK_FG "\e[1;90m"
- #define ANSI_BOLD_BRIGHT_RED_FG "\e[1;91m"
- #define ANSI_BOLD_BRIGHT_GREEN_FG "\e[1;92m"
- #define ANSI_BOLD_BRIGHT_YELLOW_FG "\e[1;93m"
- #define ANSI_BOLD_BRIGHT_BLUE_FG "\e[1;94m"
- #define ANSI_BOLD_BRIGHT_PURPLE_FG "\e[1;95m"
- #define ANSI_BOLD_BRIGHT_CYAN_FG "\e[1;96m"
- #define ANSI_BOLD_BRIGHT_WHITE_FG "\e[1;97m"
- #define ANSI_UNDERLINE_BLACK_FG "\e[4;30m"
- #define ANSI_UNDERLINE_RED_FG "\e[4;31m"
- #define ANSI_UNDERLINE_GREEN_FG "\e[4;32m"
- #define ANSI_UNDERLINE_YELLOW_FG "\e[4;33m"
- #define ANSI_UNDERLINE_BLUE_FG "\e[4;34m"
- #define ANSI_UNDERLINE_PURPLE_FG "\e[4;35m"
- #define ANSI_UNDERLINE_CYAN_FG "\e[4;36m"
- #define ANSI_UNDERLINE_WHITE_FG "\e[4;37m"
- #define ANSI_BLACK_BG "\e[40m"
- #define ANSI_RED_BG "\e[41m"
- #define ANSI_GREEN_BG "\e[42m"
- #define ANSI_YELLOW_BG "\e[43m"
- #define ANSI_BLUE_BG "\e[44m"
- #define ANSI_PURPLE_BG "\e[45m"
- #define ANSI_CYAN_BG "\e[46m"
- #define ANSI_WHITE_BG "\e[47m"
- #define ANSI_RESET "\e[0;0m"
- // Log colors.
- #ifndef ANSI_START_TRACE
- #define ANSI_START_TRACE ANSI_BRIGHT_WHITE_FG
- #endif
- #ifndef ANSI_START_DEBUG
- #define ANSI_START_DEBUG ANSI_BRIGHT_BLUE_FG
- #endif
- #ifndef ANSI_START_INFO
- #define ANSI_START_INFO ANSI_BRIGHT_CYAN_FG
- #endif
- #ifndef ANSI_START_WARN
- #define ANSI_START_WARN ANSI_BRIGHT_YELLOW_FG
- #endif
- #ifndef ANSI_START_ERROR
- #define ANSI_START_ERROR ANSI_BRIGHT_RED_FG
- #endif
- #ifndef ANSI_START_FATAL
- #define ANSI_START_FATAL ANSI_BRIGHT_PURPLE_FG
- #endif
- /*!
- * \enum kjmjLogLevelEnum
- *
- * \brief The possible logging levels.
- *
- * The log level \c KJMJ_LOG_LEVEL_FATAL can be used to specify that only the
- * most important (i.e., fatal) log messages shold be logged. Using this level
- * to specify the log level for \c kjmj_log_to_file, \c kjmj_log_to_stdout, or
- * \c kjmj_log_to_stderr means that only messages logged using the \c FATAL
- * macro will be logged.
- *
- * Conversely, the log level \c KJMJ_LOG_LEVEL_TRACE can be used to specify
- * that \em any logging message will be logged.
- *
- * The log level \c KJMJ_LOG_LEVEL_DISABLED can be used to specify that
- * logging should not occur.
- *
- * The log level \c KJMJ_LOG_LEVEL_ALL can be used to specify that all log
- * messages should be logged.
- *
- * \see kjmj_log_to_file
- * \see kjmj_tog_to_stdout
- * \see kjmj_log_to_stderr
- * \see FATAL
- */
- enum kjmjLogLevelEnum {
- KJMJ_LOG_LEVEL_DISABLED,
- KJMJ_LOG_LEVEL_FATAL,
- KJMJ_LOG_LEVEL_ERROR,
- KJMJ_LOG_LEVEL_WARN,
- KJMJ_LOG_LEVEL_INFO,
- KJMJ_LOG_LEVEL_DEBUG,
- KJMJ_LOG_LEVEL_TRACE,
- KJMJ_LOG_LEVEL_ALL
- };
- typedef enum kjmjLogLevelEnum kjmjLogLevel;
- /*!
- * \def KJMJ_LEVEL_IS_AT_LEAST
- *
- * \brief Returns true if a level is at least as important as the
- * other level.
- *
- * \param LEVEL the level to compare against \p OTHER.
- * \param OTHER the level that LEVEL should be at least as important as.
- *
- * \see kjmjLogLevelEnum
- *
- * \note Because the log levels are specified from the
- * most important (fatal) to the least important (trace)
- * the lower the log level the more important it is.
- */
- #define KJMJ_LEVEL_AT_LEAST( LEVEL, OTHER ) \
- ( LEVEL <= OTHER )
- /*!
- * \internal
- *
- * \def KJMJ_FNAME
- *
- * \brief Returns the filename suffix portion of \c __FILE__.
- *
- * \endinternal
- */
- #define KJMJ_FNAME \
- ( strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__ )
- /*!
- * \internal
- *
- * \def KJMJ_MACRO_START
- *
- * \brief Used at the beginning of macro function definitions that contain
- * more than one line of code.
- *
- * Used to wrap the macro function in a do { ... } while(0) block.
- *
- * \see KJMJ_MACRO_END
- *
- * \endinternal
- */
- #define KJMJ_MACRO_START do {
- /*!
- * \internal
- *
- * \def KJMJ_MACRO_END
- *
- * \brief Used at the end of macro function definitions that contain
- * more than one line of code.
- *
- * Used to wrap the macro function in a <tt>do { ... } while (0)</tt> block.
- *
- * \see KJMJ_MACRO_START
- *
- * \endinternal
- */
- #define KJMJ_MACRO_END } while (0)
- /*!
- * \def TRACE
- *
- * \brief Used to log at the trace level.
- *
- * Trace is used to describe the inner workings of a function or an
- * algorithm. It can be useful when trying to find an error in an
- * algorithm or for corner cases.
- *
- * \param FMT a C printf style format string.
- * \param ... any additional variadic arguments will be used like printf
- * variadic arguments.
- */
- #ifndef TRACE
- #define TRACE( FMT, ... ) \
- kjmj_log( KJMJ_LOG_LEVEL_TRACE, "[TRACE]", ANSI_START_TRACE, \
- __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
- #else // TRACE is defined
- #error "TRACE already defined"
- #endif
- /*!
- * \def DEBUG
- *
- * \brief Used to log at the debug level or higher.
- *
- * Mainly used for debugging, tracking certain variables or
- * anything useful to help visualizing what your program is doing.
- *
- * \param FMT a C printf style format string.
- * \param ... any additional variadic arguments will be used like printf
- * variadic arguments.
- */
- #ifndef DEBUG
- #define DEBUG( FMT, ... ) \
- kjmj_log( KJMJ_LOG_LEVEL_DEBUG, "[DEBUG]", ANSI_START_DEBUG, \
- __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
- #else
- #error "DEBUG already defined"
- #endif
- /*!
- * \def INFO
- * \brief Used to log at the info level.
- *
- * Info can be used as a heads up or for normal messages.
- *
- * \param FMT a C printf style format string.
- * \param ... any additional variadic arguments will be used like printf
- * variadic arguments.
- */
- #ifndef INFO
- #define INFO( FMT, ... ) \
- kjmj_log( KJMJ_LOG_LEVEL_INFO, "[INFO]", ANSI_START_INFO, \
- __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
- #else
- #error "INFO already defined"
- #endif
- /*!
- * \def WARN
- *
- * \brief Used to log at the warning level.
- *
- * Warnings are attributed to all cases where the code can recover from the
- * condition and they are usually treated by it. In most cases the user can
- * treat these warnings themselves.
- *
- * \param FMT a C printf style format string.
- * \param ... any additional variadic arguments will be used like printf
- * variadic arguments.
- */
- #ifndef WARN
- #define WARN( FMT, ... ) \
- kjmj_log( KJMJ_LOG_LEVEL_WARN, "[WARN]", ANSI_START_WARN, \
- __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
- #else
- #error "WARN already defined"
- #endif
- /*!
- * \def ERROR
- *
- * \brief Used to log at the error level.
- *
- * Something really bad happened. Your program will not crash yet but
- * depending on what comes next, it probably will.
- *
- * \param FMT a C printf style format string.
- * \param ... any additional variadic arguments will be used like printf
- * variadic arguments.
- */
- #ifndef ERROR
- #define ERROR( FMT, ... ) \
- kjmj_log( KJMJ_LOG_LEVEL_ERROR, "[ERROR]", ANSI_START_ERROR, \
- __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ )
- #else
- #error "ERROR already defined"
- #endif
- /*!
- * \def FATAL
- *
- * \brief Used to log at the fatal level.
- *
- * This will probably be the last logging message before your program
- * crashes or goes into madness. Fatal errors are commonly attributed
- * to dereferencing \c NULL pointers.
- *
- * \param FMT a C printf style format string.
- * \param ... any additional variadic arguments will be used like printf
- * variadic arguments.
- */
- #ifndef FATAL
- #define FATAL( FMT, ... ) \
- kjmj_log( KJMJ_LOG_LEVEL_FATAL, "[FATAL]", ANSI_START_FATAL, \
- __func__, KJMJ_FNAME, __LINE__, FMT, ##__VA_ARGS__ );
- #else
- #error "FATAL already defined"
- #endif
- /*!
- * \internal
- *
- * \struct kjmjLogStruct
- *
- * \brief The data structure that holds all information neccessary to
- * determine what to do when a message is logged.
- *
- * The one and only variable that uses this struct is the global variable
- * \c g_kjmj_log.
- *
- * \endinternal
- */
- static struct kjmjLogStruct {
- FILE* file;
- int file_log_level;
- int stdout_log_level;
- int stderr_log_level;
- } g_kjmj_log = {
- NULL,
- KJMJ_LOG_LEVEL_DISABLED,
- KJMJ_LOG_LEVEL_DISABLED,
- KJMJ_LOG_LEVEL_DISABLED
- };
- /*!
- * \fn kjmj_log_init
- *
- * \brief Prepares the logging system. Both file and terminal logging is
- * disabled.
- *
- * \note If the logging system is currently setup to log to a file, the
- * file will be closed.
- */
- static void kjmj_log_init()
- {
- if ( g_kjmj_log.file != NULL ) {
- fclose( g_kjmj_log.file );
- }
- g_kjmj_log.file = NULL,
- g_kjmj_log.file_log_level = KJMJ_LOG_LEVEL_DISABLED;
- g_kjmj_log.stdout_log_level = KJMJ_LOG_LEVEL_DISABLED,
- g_kjmj_log.stderr_log_level = KJMJ_LOG_LEVEL_DISABLED;
- }
- /*!
- * \fn kjmj_log_terminate
- *
- * \brief Should be called once to clean up.
- *
- * \see kjmj_log_init
- */
- static void kjmj_log_terminate()
- {
- kjmj_log_init();
- }
- /*!
- * \fn kjmj_log_to_file
- *
- * \brief Enables logging to \p file at the \p level logging level.
- *
- * \param file handle (pointer) to \c FILE. Must be open unless the
- * \p LEVEL is \c KJMJ_LOG_LEVEL_DISABLED.
- * \param level the level (or higher) at which logging is enabled.
- *
- * \see kjmjLogLevel
- */
- static void kjmj_log_to_file( FILE* file, kjmjLogLevel level )
- {
- assert( ( file != NULL ) ||
- ( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_TRACE, level ) ) );
- assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
- assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
- g_kjmj_log.file = file;
- g_kjmj_log.file_log_level = level;
- }
- /*!
- * \def kjmj_log_to_stdout
- *
- * \brief Enables logging to \c stdout at the \p level logging level.
- *
- * \param level the level (or more important) at which logging is enabled.
- *
- * \see kjmjLogLevel
- */
- static void kjmj_log_to_stdout( kjmjLogLevel level )
- {
- assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
- assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
- g_kjmj_log.stdout_log_level = level;
- }
- /*!
- * \def kjmj_log_to_stderr
- *
- * \brief Enables logging to \c stderr at the \p level logging level.
- *
- * \param level the level (or more important) at which logging is enabled.
- */
- static void kjmj_log_to_stderr( kjmjLogLevel level )
- {
- assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
- assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
- g_kjmj_log.stderr_log_level = level;
- }
- /*!
- * \internal
- *
- * \fn kjmj_log
- *
- * \brief Logs a message to a file, stdout, and/or stderr if enabled.
- *
- * \param level the level of the log message.
- * \param level_name a string representation of the level.
- * \param ansi_start_level the ANSI string that should be used to start
- * the log message if log colors are enabled.
- * \param function_name the name of the function currently executing.
- * \param filename the name of the file containing this function.
- * \param line the line number where the message is being
- * logged from.
- * \param fmt a C printf style format string.
- * \param ... any additional variadic arguments will be used
- * like printf variadic arguments.
- *
- * \internal
- */
- void kjmj_log( int level, const char* level_name,
- const char* ansi_start_level, const char* function_name,
- const char* filename, unsigned line, const char* fmt, ... )
- {
- assert( KJMJ_LEVEL_AT_LEAST( KJMJ_LOG_LEVEL_DISABLED, level ) );
- assert( KJMJ_LEVEL_AT_LEAST( level, KJMJ_LOG_LEVEL_ALL ) );
- assert( level_name != NULL );
- assert( ansi_start_level != NULL );
- assert( function_name != NULL );
- assert( filename != NULL );
- assert( fmt != NULL );
- _Bool log_to_file = ( g_kjmj_log.file != NULL )
- && ( KJMJ_LEVEL_AT_LEAST( level, g_kjmj_log.file_log_level ) );
- _Bool log_to_stdout =
- KJMJ_LEVEL_AT_LEAST( level, g_kjmj_log.stdout_log_level );
- _Bool log_to_stderr =
- KJMJ_LEVEL_AT_LEAST( level, g_kjmj_log.stderr_log_level );
- if ( !log_to_file && !log_to_stdout && !log_to_stderr ) {
- return;
- }
- // Determine the time string for the time now.
- time_t now;
- time( &now );
- struct tm* local;
- local = localtime( &now );
- char timestamp[ 0x20 ];
- timestamp[ strftime( timestamp, sizeof( timestamp ),
- "%Y/%m/%d %H:%M:%S", local ) ] = '\0';
- // Determine the prefix to be printed before the log message.
- char msg_prefix[ 0x100 ];
- snprintf( msg_prefix, sizeof( msg_prefix ),
- "%s %-7s %s (%s:%u) ", timestamp, level_name,
- function_name, filename, line );
- // Optionally log to file.
- if ( log_to_file ) {
- fprintf( g_kjmj_log.file, msg_prefix );
- va_list args;
- va_start( args, fmt );
- vfprintf( g_kjmj_log.file, fmt, args );
- va_end( args );
- fprintf( g_kjmj_log.file, "\n" );
- fflush( g_kjmj_log.file );
- }
- // Optionally log to stdout.
- if ( log_to_stdout ) {
- #ifndef KJMJ_LOG_NO_COLOR
- fprintf( stdout, ansi_start_level );
- #endif
- fprintf( stdout, msg_prefix );
- #ifndef KJMJ_LOG_NO_COLOR
- fprintf( stdout, ANSI_RESET );
- #endif
- va_list args;
- va_start( args, fmt );
- vfprintf( stdout, fmt, args );
- va_end( args );
- fprintf( stdout, "\n" );
- }
- // Optionally log to stderr.
- if ( log_to_stderr ) {
- #ifndef KJMJ_LOG_NO_COLOR
- fprintf( stderr, ansi_start_level );
- #endif
- fprintf( stderr, msg_prefix );
- #ifndef KJMJ_LOG_NO_COLOR
- fprintf( stderr, ANSI_RESET );
- #endif
- va_list args;
- va_start( args, fmt );
- vfprintf( stderr, fmt, args );
- va_end( args );
- fprintf( stderr, "\n" );
- }
- } // kjmj_log
- #ifdef __cplusplus
- }
- #endif
- #endif // LOG_H_
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement