A simple logging library based on Python's logging library
/**
* @file logging.h
* @author Charles Averill
* @brief Function headers, ANSI defines, and enums for logging and raising warnings and errors
* @date 08-Sep-2022
*/
#ifndef LOGGING_H
#define LOGGING_H
#include
#include
#include
#define ANSI_BOLD "\033[1m"
#define ANSI_RED "\033[38:5:196m"
#define ANSI_ORANGE "\033[38:5:208m"
#define ANSI_YELLOW "\033[38:5:178m"
#define ANSI_RESET "\033[0m"
#define ERROR_RED ANSI_RED ANSI_BOLD
#define ERROR_ORANGE ANSI_ORANGE ANSI_BOLD
#define ERROR_YELLOW ANSI_YELLOW ANSI_BOLD
/**
* @brief Severity levels of logging statements emitted by the compiler
*/
typedef enum {
LOG_NONE,
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
LOG_CRITICAL
} LogLevel;
typedef struct {
LogLevel level;
char* name;
char* color;
} LogInfo;
const LogInfo logInfoLevels[] = {
{LOG_NONE, "", ANSI_RESET}, {LOG_DEBUG, "[DEBUG]", ANSI_BOLD},
{LOG_INFO, "[INFO]", ANSI_BOLD}, {LOG_WARNING, "[WARNING]", ANSI_YELLOW},
{LOG_ERROR, "[ERROR]", ANSI_ORANGE}, {LOG_CRITICAL, "[CRITICAL]", ANSI_RED}};
/**
* @brief Return codes used in different scenarios
*/
typedef enum {
RC_OK,
RC_ERROR,
// Put more return codes here if needed. I wrote this for a compiler,
// so I have return codes for syntax errors, memory errors, file errors, etc.
} ReturnCode;
/**
* @brief String representation of return codes
*/
const char* returnCodeStrings[] = {
"OK", "ERROR", // add more for extra return codes defined above
};
void fatal(ReturnCode rc, const char* fmt, ...);
void log(LogLevel level, const char* fmt, ...);
#endif /* LOGGING_H */
/**
* @file logging.c
* @author Charles Averill
* @brief Logging, warnings, and fatal error handling
* @date 08-Sep-2022
*/
#include "logging.h"
/**
* @brief Raises a fatal error that will exit the program
*
* @param rc Return code to exit with
* @param fmt Format string for printed error
* @param ... Varargs for printed error
*/
void fatal(ReturnCode rc, const char* fmt, ...)
{
va_list func_args;
va_start(func_args, fmt);
fprintf(stderr, "%s%s%s", ERROR_RED "[", returnCodeStrings[rc], "] - " ANSI_RESET);
vfprintf(stderr, fmt, func_args);
va_end(func_args);
// Print fence for error distinguishing
fprintf(stderr, "\n----------------------------------------\n");
if (D_INPUT_FILE) {
fclose(D_INPUT_FILE);
}
exit(rc);
}
/**
* @brief Raises a non-fatal logging statement
*
* @param level Severity of statement
* @param fmt Format string for printed statement
* @param ... Varargs for printed statement
*/
void log(LogLevel level, const char* fmt, ...)
{
va_list func_args;
FILE *output_stream;
/* If using argp.h, you can add a `logging` field to your arguments to control the level of statements that are printed
if(args->logging == LOG_NONE || args->logging > level) {
return;
}
if(args->logging > LOG_INFO) {
output_stream = stderr;
} else {
output_stream = stdout;
}
*/
output_stream = stdout;
va_start(func_args, fmt);
fprintf(output_stream, "LOG:%s%s%s", logInfoLevels[level].color, logInfoLevels[level].name,
" - " ANSI_RESET);
vfprintf(output_stream, fmt, func_args);
fprintf(output_stream, "\n");
va_end(func_args);
}