Files
Utilities/Logging/logging_lib.c
2025-09-13 00:31:42 +01:00

321 lines
12 KiB
C

//*============================================================================*/
// 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 <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#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
/*============================================================================*/