321 lines
12 KiB
C
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
|
|
/*============================================================================*/ |