//*============================================================================*/ // Title: logging.c // Author: RSH // Description: // General purpose logging function with timestamping and choice of output // streams: stout, stderr, or a supplied file (opened in append mode). // TODO: // * Have a logging receive server // * provide iso compliant option // * test output options // * useful print/formatting outputs (lines, tabs, etc.) //TODO: would be nice if logger auto chunked at 80 chars /*============================================================================*/ #include #include #include #include #include #include #include #include "logging_lib.h" // static const int tsBufLen = strlen("2025-01-01T01:50:01.234567890Z") + 1 ; // static const int tsTmpLen = strlen("2025-01-01T01:50:01.") + 1 ; // #define LOG_MSG getTimestamp(tsBuf); snprintf(msgBuffer, tsBufLen+2, "%s: ", tsBuf); sprintf(msgBuffer + tsBufLen +1, // #define RP ) // static int getTimestamp(char * tsBuf) // { // struct timespec now; // struct tm tm; // int retval = clock_gettime(CLOCK_REALTIME, &now); // gmtime_r(&now.tv_sec, &tm); // strftime(tsBuf, tsTmpLen, "%Y-%m-%dT%H:%M:%S.", &tm); // sprintf(tsBuf + tsTmpLen -1, "%09luZ", now.tv_nsec); // return retval; // } // Initialise buffers for the different severity strings static char logDbg[] = DEBUG_PREFIX; static char logInfo[] = INFO_PREFIX; static char logWarn[] = WARN_PREFIX; static char logErr[] = ERR_PREFIX; static char logInfoColour[] = COLOUR_INFO_PREFIX; static char logWarnColour[] = COLOUR_WARN_PREFIX; static char logErrColour[] = COLOUR_ERR_PREFIX; // Constants for the lenght of various elements of the timestamp and function ID static const int tsTmpLen = strlen(STD_TIMESTAMP_SEC)+1; static const int tsBufLen = strlen(STD_TIMESTAMP_WHOLE)+1; static const int tsFunLen = strlen(FUNC_STR); static char logFileName[MAX_LOG_FILE_NAME_LEN] = ""; static FILE *logOutput = NULL; static T_LogOutput outputState = notSet; static int envNoColour = 0; /*============================================================================*/ /* Logging_CheckInited */ /*----------------------------------------------------------------------------*/ // This checks that the library has been inited and is called by other // logging functions - throws a warning and inits the library to stdout if it // has not already been initialised. /*----------------------------------------------------------------------------*/ static void Logging_CheckInited(char *timestamp, const char *caller) { if ( (logOutput == NULL) || (outputState == notSet) ) { logOutput = stdout; outputState = standardOut; fprintf(logOutput , "%s%sLogging function %s() called without logging library being initialised >>>> defaulting to stdout\n" , timestamp, logWarn, caller); } } /*============================================================================*/ /*============================================================================*/ /* Logging_LogMsgTS */ /*----------------------------------------------------------------------------*/ // Example intended usage: // #define LOG_SOURCE "FileName" // #define ME someFunction // int Verbose = 1 // could be set at runtime via arg (e.g. ./prog -v) // void someFunc (void) // { // Logging_LogMsgTS(LOG_SOURCE, ME, LOG_DEBUG, "This is some noisy info"); // } // #undef // Output: // [ 2025-06-07 02:12:02.005 ] (DEBUG) | FileName in function: someFunction(): This is some noisy info // // // OR without custom defines: // void someFunc (void) // { // Logging_LogMsgTS(__FILE__, __func__, LOG_DEBUG, "This is some noisy info"); // } // Output: // [ 2025-06-07 02:12:02.005 ] (DEBUG) | logTest.c in function: someFunc(): This is some noisy info /*----------------------------------------------------------------------------*/ void Logging_LogMsgTS( const char * where , const char * caller , int severity , const char * message, ... ) { char buffer[MAX_LOG_BUF_LEN]; int callerLen = strlen(where) + strlen(caller) + tsFunLen; char *severityMsg = NULL; int severityLen = 0; int printfColour = 0; va_list args; va_start(args, message); // first get the current time and insert at the front of the buffer struct timespec now; struct tm tm; if ( clock_gettime(CLOCK_REALTIME, &now) < 0) { // Output the error to the file if used if(outputState == logFile) { LOGMSG_N_ERR("Logging", "Logging_LogMsgTS", "Unable to get system time [errno: %d (%s)]" , errno, strerror(errno)) } // Put error on stderr as well fprintf(logOutput , "%sLogging function Logging_LogMsgTS(): Unable to get system time [errno: %d (%s)]\n" , (envNoColour == 1) ? logErr : logErrColour , errno, strerror(errno)); return; } gmtime_r(&now.tv_sec, &tm); strftime(buffer, tsTmpLen, "[ %Y-%m-%d %H:%M:%S.", &tm); sprintf( (buffer + tsTmpLen -1), "%03lu ] ", (now.tv_nsec/1000000) % 1000000); // check if the function has been called (making use of the pre-assembled timestamp) Logging_CheckInited(buffer, __func__); // determine which string to use for the severity print out based on message // severity level, output stream and whether the environment has NO_COLOR // set [see Logging_Init()] printfColour = ( (envNoColour == 1) || (outputState == logFile) ) ? NO_COLOUR : PRINT_COLOUR; switch (severity) { case 0: severityMsg = logDbg; break; case 1: severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour; break; case 2: severityMsg = (printfColour == NO_COLOUR) ? logWarn : logWarnColour; break; case 3: severityMsg = (printfColour == NO_COLOUR) ? logErr : logErrColour; break; default: severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour; } severityLen = strlen(severityMsg); // add the severity to the buffer sprintf( (buffer + tsBufLen), "%s", severityMsg); // then add the process/file name and function name to the output buffer sprintf( (buffer + tsBufLen + severityLen), "%s in function: %s(): ", where, caller); // finally add the supplied message and format with args vsprintf( (buffer + tsBufLen + severityLen + callerLen), message, args ); // print the message to the output fprintf(logOutput, "%s\n", buffer); va_end(args); } /*============================================================================*/ /*============================================================================*/ /* Logging_LogMsg*/ /*----------------------------------------------------------------------------*/ // Like Logging_LogMsgTS above, but does not insert a timestamp /*----------------------------------------------------------------------------*/ void Logging_LogMsg( const char * where , const char * caller , int severity , const char * message, ... ) { char buffer[MAX_LOG_BUF_LEN]; int callerLen = strlen(where) + strlen(caller) + tsFunLen; char * severityMsg = NULL; int severityLen = 0; int printfColour = 0; va_list args; va_start(args, message); // check if the function has been called (making use of the pre-assembled timestamp) Logging_CheckInited(NULL, __func__); // determine which string to use for the severity print out based on message // severity level, output stream and whether the environment has NO_COLOR // set [see Logging_Init()] printfColour = ( (envNoColour == 1) || (outputState == logFile) ) ? NO_COLOUR : PRINT_COLOUR; switch (severity) { case 0: severityMsg = logDbg; break; case 1: severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour; break; case 2: severityMsg = (printfColour == NO_COLOUR) ? logWarn : logWarnColour; break; case 3: severityMsg = (printfColour == NO_COLOUR) ? logErr : logErrColour; break; default: severityMsg = (printfColour == NO_COLOUR) ? logInfo : logInfoColour; } severityLen = strlen(severityMsg); // add the severity to the buffer sprintf( buffer, "%s", severityMsg); // then add the process/file name and function name to the output buffer sprintf( (buffer + severityLen), "%s in function: %s(): ", where, caller); // finally add the supplied message and format with args vsprintf( (buffer + severityLen + callerLen), message, args ); // print the message to the output fprintf(logOutput, "%s\n", buffer); va_end(args); } /*============================================================================*/ /*============================================================================*/ /* Logging_Init */ /*----------------------------------------------------------------------------*/ // It is not necessary to call Logging_Init, but it is strongly recommended. // If it is not called, the first call to a logging function will set the // output to stdout as default and throw a warning. /*----------------------------------------------------------------------------*/ #define ME "Logging_Init" int Logging_Init(T_LogOutput output, char * fileName) { // check if the ENV variable NO_COLOR is set (https://no-color.org/) char *colorEnv = getenv("NO_COLOR"); if( (colorEnv != NULL) && (colorEnv[0] != '\0') ) { envNoColour = 1; } else { envNoColour = 0; } // if a file name has been supplied, attempt to open it, // else check if the user wants the output to be // stderr - otherwise the default output type is stdout if (fileName != NULL) { logOutput = fopen(fileName, "a"); if (logOutput == NULL) { logOutput = stdout; Logging_LogMsg( __FILE__, ME, LOG_ERR, "Can't open file: %s [errno: %d (%s)] >>>>>>> defaulting to stdout" , fileName, errno, strerror(errno) ); outputState = standardOut; //return a fail in case the caller wants to handle (retry, terminate, etc.) return 1; } else { //logOuput already set by successful fopen() outputState = logFile; memcpy(logFileName, fileName, (strlen(fileName) * sizeof(char))); Logging_LogMsgTS(__FILE__, ME, LOG_INFO, "Set log output to file: %s", logFileName); } } else if (output == standardErr) { logOutput = stderr; outputState = standardErr; Logging_LogMsgTS(__FILE__, ME, LOG_INFO, "Set log output to stderr"); } else { logOutput = stdout; outputState = standardOut; Logging_LogMsgTS(__FILE__, ME, LOG_INFO, "Set log output to stdout"); } return 0; } #undef ME /*============================================================================*/ /*============================================================================*/ /* Logging_Term */ /*----------------------------------------------------------------------------*/ // If Logging_Init was not called prior, or the output stream is set to stdout // or stderr, then there is strictly no need to call Logging_Term though it is // strongly recommended. // It is necessary to call Logging_Term if the logging output is set to a file! /*----------------------------------------------------------------------------*/ #define ME "Logging_Term" void Logging_Term( void ) { if (outputState == logFile) { fclose(logOutput); memset(logFileName, '\0', sizeof(logFileName)); } logOutput = NULL; outputState = notSet; } #undef ME /*============================================================================*/