/* This is a reimplementation of SICS logging. The aim is to merge all the different logs within SICS to the new logging system COPYRIGHT: see file COPYRIGHT Mark Koennecke, February 2016 */ #include #include #include #include #include #include #include #include #include /*============================================================================== some static fields which control log file rotation ===============================================================================*/ static FILE *logFile = NULL; static unsigned int MAXFILELINES = 100000; static unsigned int lineCount = 0; static char logTemplate[1024]; static char logFilename[1024]; static unsigned int logDisabled = 0; static int unsigned logConfigured = 0; /*================ The default log level =======================================*/ static unsigned int globalLogLevel = INFO; /*========= The list of sub systems for which full logging is enabled ==========*/ unsigned int logEnabledArray[MAXSUB ]; /*================== Callback management =======================================*/ typedef struct { LogCallback func; void *userData; } LogCBData; static int callbackList; /*----------------------------------------------------------------------------*/ static void formatTimeStamp(struct timeval *tp, char *buffer, unsigned int bufferLength, unsigned int small) { struct tm *time; int year, month, day, hour, min, sec, usec; char delim = '-'; struct timeval tv; if(tp == NULL){ gettimeofday(&tv, NULL); } else { tv = *tp; } time = localtime(&tv.tv_sec); year = 1900 + time->tm_year; month = time->tm_mon + 1; day = time->tm_mday; hour = time->tm_hour; min = time->tm_min; sec = time->tm_sec; usec = (int) tv.tv_usec; if(small == 1){ snprintf(buffer,bufferLength, "%04d-%02d-%02dT%02d%c%02d%c%02d", year, month, day, hour, delim, min, delim, sec); } else { snprintf(buffer,bufferLength, "%04d-%02d-%02dT%02d%c%02d%c%02d.%06d", year, month, day, hour, delim, min, delim, sec, usec); } } /*----------------------------------------------------------------------------*/ static char *makeLogFileName(void) { char timeStamp[256]; char *filename = NULL; unsigned int length; formatTimeStamp(NULL,timeStamp,sizeof(timeStamp),1); length = strlen(logTemplate) + strlen(timeStamp) + 30; filename = malloc(length*sizeof(char)); if(filename != NULL){ sprintf(filename,"%s-%s.log",logTemplate,timeStamp); } return filename; } /*----------------------------------------------------------------------------*/ void LogClose(void *data) { if(logFile != NULL){ fclose(logFile); } logFile = NULL; lineCount = 0; strcpy(logFilename,"undefined"); } /*----------------------------------------------------------------------------*/ static void checkLogFile(void) { char *filename = NULL; char *oldLogFile = NULL; char timeStamp[132]; if(logDisabled || logConfigured == 0){ return; } /* close when full */ if(logFile != NULL && lineCount >= MAXFILELINES){ oldLogFile = strdup(logFilename); filename = makeLogFileName(); formatTimeStamp(NULL,timeStamp,sizeof(timeStamp),0); fprintf(logFile,"%s:sys:INFO:Next logfile %s\n",timeStamp,filename); LogClose(NULL); } /* start a new log file */ if(logFile == NULL){ if(filename == NULL){ filename = makeLogFileName(); } if(filename != NULL){ logFile = fopen(filename,"w"); strncpy(logFilename,filename,sizeof(logFilename)); if(oldLogFile != NULL){ fprintf(logFile,"%s:sys:INFO:Previous logfile %s\n",timeStamp,oldLogFile); free(oldLogFile); } free(filename); } } } /*----------------------------------------------------------------------------*/ static void writeLog(char *logMessage) { int nChar; unsigned int end; char *pPtr; const char delim = '\n'; if(logMessage == NULL){ return; } /* remove a trailing newline */ end = strlen(logMessage); if(logMessage[end-1] == delim){ logMessage[end-1] = '\0'; } if(logFile != NULL){ nChar = fprintf(logFile,"%s\n",logMessage); lineCount++; } } /*=============================================================================*/ void formatSeverity(unsigned int severity, char *buffer, unsigned int bufferLength) { static const char *severityText[] = {"FATAL", "ERROR", "WARNING", "INFO", "VERBOSE", "DEBUG", "INVALID" }; if(severity > DEBUG){ severity = INVALID; } strncpy(buffer,severityText[severity-1],bufferLength); } /*--------------------------------------------------------------------------------*/ static unsigned int sevFromText(const char *txt) { static const char *severityText[] = {"fatal", "error", "warn", "info", "verbose", "debug", NULL }; int sev = 0; while(severityText[sev] != NULL){ if(strcmp(txt,severityText[sev]) == 0){ break; } sev++; } sev++; /* starting at 1 rather then 0 */ return sev; } /*=============================================================================*/ int subsystemFromText(const char *text) { int i; static const char *subText[] = {"sys", "com", "asquio", "io", "dev", "par", "notify", "history", "INVALID", }; for(i = 0; i < MAXSUB; i++){ if(strcmp(subText[i],text) == 0){ return i; } } return SINVALID; } /*----------------------------------------------------------------------------*/ void formatSubsystem(unsigned int sub, char *buffer, unsigned int bufferLength) { static const char *subText[] = {"sys", "com", "asquio", "io", "dev", "par", "notify", "history", "INVALID", }; if(sub > SPAR){ sub = SINVALID; } strncpy(buffer,subText[sub],bufferLength); } /*----------------------------------------------------------------------------*/ unsigned int logFilter(unsigned int severity, const char *subsystem, const char *logMessage) { int status; char buffer[1024]; unsigned int sub; if(logDisabled || logConfigured == 0){ return 1; } /* suppress empty messages */ if(strlen(logMessage) < 1) { return 1; } /* If it is in the list of enabled subsystems, everything is logged */ sub = subsystemFromText(subsystem); if((sub >= 0 && sub <= SINVALID) && severity <= logEnabledArray[sub]){ return 0; } /* test against the global log level */ if(severity > globalLogLevel){ return 1; } return 0; } /*---------------------------------------------------------------------------- I wish to avoid excessive dynamic memory allocation in logging. Thus I pass in generous preallocated buffers and allocate only when the passed in buffer is not big enough. This trick is used througout this code. ------------------------------------------------------------------------------*/ static char *formatLogLine(char *timeStamp,char *severityTXT, const char* subsystem, char *logData, char *logLine, unsigned int logLineLength) { unsigned int totalLength; char *lineTxt = NULL; totalLength = strlen(timeStamp) + strlen(severityTXT) + strlen(subsystem) + strlen(logData) + 10; if(totalLength > logLineLength){ lineTxt = malloc((totalLength+1)*sizeof(char)); } else { lineTxt = logLine; } if(lineTxt != NULL){ sprintf(lineTxt,"%s:%s:%s:%s", timeStamp, subsystem, severityTXT, logData); } return lineTxt; } /*----------------------------------------------------------------------------*/ void RegisterLogCallback(LogCallback func, void *userData) { LogCBData lcb; lcb.func = func; lcb.userData = userData; LLDnodeAppendFrom(callbackList,&lcb); } /*---------------------------------------------------------------------------*/ void RemoveLogCallback(LogCallback func) { int status; LogCBData lcb; status = LLDnodePtr2First(callbackList); while(status == 1){ LLDnodeDataTo(callbackList,&lcb); if(lcb.func == func){ LLDnodeDelete(callbackList); return; } status = LLDnodePtr2Next(callbackList); } } /*----------------------------------------------------------------------------*/ static void notifyListeners(unsigned int severity, const char *subsystem, const char *timeStamp, char *logData) { int status; LogCBData lcb; status = LLDnodePtr2First(callbackList); while(status == 1){ LLDnodeDataTo(callbackList,&lcb); lcb.func(severity, timeStamp, subsystem,logData,lcb.userData); status = LLDnodePtr2Next(callbackList); } } /*-------------------------------------------------------------------------------*/ static void LogInternal(char *timeText, unsigned int severity, const char *subsystem, char *logMessage) { char severityTXT[60], logLine[3072], *fullMessage = NULL; checkLogFile(); formatSeverity(severity,severityTXT,sizeof(severityTXT)); notifyListeners(severity,subsystem,timeText, logMessage); if(logFilter(severity,subsystem,(const char *)logMessage) == 1){ return; } fullMessage = formatLogLine(timeText,severityTXT,subsystem, logMessage, logLine, sizeof(logLine)); writeLog(fullMessage); /* clean up */ if(fullMessage != logLine){ free(fullMessage); } } /*-------------------------------------------------------------------------------*/ void LogIS(unsigned int severity, unsigned int subsystem,const char *format,...) { char timeStamp[132]; char subText[30]; char logData[2024], *logMessage = NULL; char *dynMessage = NULL; va_list ap; int dataLength; formatSubsystem(subsystem,subText,sizeof(subText)); formatTimeStamp(NULL, timeStamp,sizeof(timeStamp),0); /* If we have enough space put the logData into our generous buffer. Else allocate a dynamic buffer. I cannot do this in a subroutine as I need to restart the vararg stuff. */ va_start(ap,format); dataLength = vsnprintf(logData,sizeof(logData),format,ap); logMessage = logData; va_end(ap); if(dataLength > sizeof(logData)){ dynMessage = malloc((dataLength+1)*sizeof(char)); if(dynMessage != NULL){ va_start(ap,format); vsnprintf(dynMessage,(dataLength+1)*sizeof(char),format,ap); va_end(ap); logMessage = dynMessage; } } LogInternal(timeStamp, severity, subText,logMessage); if(dynMessage != NULL){ free(dynMessage); } } /*-------------------------------------------------------------------------------*/ void LogTS(struct timeval *tv, unsigned int severity, unsigned int subsystem,const char *format,...) { char subText[30], timeStamp[256]; char logData[2024], *logMessage = NULL; char *dynMessage = NULL; va_list ap; int dataLength; formatSubsystem(subsystem,subText,sizeof(subText)); formatTimeStamp(tv, timeStamp,sizeof(timeStamp),0); /* If we have enough space put the logData into our generous buffer. Else allocate a dynamic buffer. I cannot do this in a subroutine as I need to restart the vararg stuff. */ va_start(ap,format); dataLength = vsnprintf(logData,sizeof(logData),format,ap); logMessage = logData; va_end(ap); if(dataLength > sizeof(logData)){ dynMessage = malloc((dataLength+1)*sizeof(char)); if(dynMessage != NULL){ va_start(ap,format); vsnprintf(dynMessage,(dataLength+1)*sizeof(char),format,ap); va_end(ap); logMessage = dynMessage; } } LogInternal((char *)timeStamp, severity, subText,logMessage); if(dynMessage != NULL){ free(dynMessage); } } /*----------------------------------------------------------------------------*/ void LogHex(struct timeval*tv, unsigned int severity, unsigned int subsystem, char *buffer, int bufferLength) { char subText[30], timeStamp[256], *hexData; formatSubsystem(subsystem,subText,sizeof(subText)); formatTimeStamp(tv, timeStamp,sizeof(timeStamp),0); hexData = bytesToHexString((uint8_t *)buffer,(size_t)bufferLength); if(hexData != NULL){ LogInternal((char *)timeStamp, severity, subText,hexData); free(hexData); } } /*----------------------------------------------------------------------------*/ void Log(unsigned int severity, const char *subsystem,const char *format,...) { char timeStamp[132]; char logData[2024], *logMessage = NULL; char *dynMessage = NULL; va_list ap; int dataLength; formatTimeStamp(NULL,timeStamp,sizeof(timeStamp),0); /* If we have enough space put the logData into our generous buffer. Else allocate a dynamic buffer. I cannot do this in a subroutine as I need to restart the vararg stuff. */ va_start(ap,format); dataLength = vsnprintf(logData,sizeof(logData),format,ap); logMessage = logData; va_end(ap); if(dataLength > sizeof(logData)){ dynMessage = malloc((dataLength+1)*sizeof(char)); if(dynMessage != NULL){ va_start(ap,format); vsnprintf(dynMessage,(dataLength+1)*sizeof(char),format,ap); va_end(ap); logMessage = dynMessage; } } LogInternal(timeStamp, severity, subsystem,logMessage); if(dynMessage != NULL){ free(dynMessage); } } /*------------------------------------------------------------------------------*/ static pDynString ListEnabled(void) { int status, i; char buffer[1024], sub[30], sev[30]; pDynString result = CreateDynString(64,64); if(result == NULL){ return NULL; } for(i = 0; i < MAXSUB; i++){ formatSeverity(logEnabledArray[i],sev,sizeof(sev)); formatSubsystem(i,sub,sizeof(sub)); DynStringConcat(result,sub); DynStringConcatChar(result,':'); DynStringConcat(result,sev); DynStringConcatChar(result,'\n'); } return result; } /*============================================================================ The Interpreter Interface ==============================================================================*/ static int LogAction(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { char message[1024]; unsigned int sev; if(argc < 4) { SCWrite(pCon,"ERROR: need severity subsystem message parameters", eError); return 0; } /* interpret severity */ strtolower(argv[1]); sev = sevFromText(argv[1]); if(sev >= INVALID){ SCPrintf(pCon,eError,"ERROR: %s is no valid severity code", argv[1]); return 0; } Arg2Text(argc-3,&argv[3],message,sizeof(message)); Log(sev,argv[2],"%s",message); return 1; } /*----------------------------------------------------------------------------*/ static int LogConfigAction(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { unsigned int sev, sub; char buffer[64]; pDynString result = NULL; if(argc < 2) { SCWrite(pCon,"ERROR: need keyword", eError); return 0; } strtolower(argv[1]); if(strcmp(argv[1],"maxlines") == 0){ if(argc > 2){ MAXFILELINES = atoi(argv[2]); SCSendOK(pCon); } else { SCPrintf(pCon,eValue,"logconfig.maxlines = %d", MAXFILELINES); } } else if(strcmp(argv[1],"logtemplate") == 0){ if(argc > 2){ strncpy(logTemplate,argv[2],sizeof(logTemplate)); logConfigured = 1; SCSendOK(pCon); } else { SCPrintf(pCon,eValue,"logconfig.logtemplate = %s", logTemplate); } }else if(strcmp(argv[1],"flush") == 0){ fflush(logFile); SCSendOK(pCon); } else if(strcmp(argv[1],"filename") == 0) { SCPrintf(pCon,eValue,"logconfig.filename = %s", logFilename); } else if(strcmp(argv[1],"close") == 0) { LogClose(NULL); SCSendOK(pCon); }else if(strcmp(argv[1],"level") == 0 ) { if(argc > 2) { sev = sevFromText(argv[2]); if(sev >= INVALID) { SCPrintf(pCon,eError,"ERROR: %s is no valid log level", argv[2]); return 0; } globalLogLevel = sev; SCSendOK(pCon); } else { formatSeverity(globalLogLevel,buffer,sizeof(buffer)); SCPrintf(pCon,eValue,"logconfig.level = %s", buffer); } }else if (strcmp(argv[1],"enable") == 0) { if(argc > 2){ if(strlen(argv[2]) >= 1024) { SCWrite(pCon,"ERROR: subsystem name to long",eError); return 0; } sub = subsystemFromText(argv[2]); if(sub >= 0){ logEnabledArray[sub] = DEBUG; } else { SCPrintf(pCon,eError,"ERROR: invalid subsystem %s requested", argv[2]); return 0; } SCSendOK(pCon); } else { SCWrite(pCon,"ERROR: need subsystem argument for enable",eError); return 0; } }else if (strcmp(argv[1],"disable") == 0) { if(argc > 2){ sub = subsystemFromText(argv[2]); if(sub >= 0){ logEnabledArray[sub] = ERROR; } else { SCPrintf(pCon,eError,"ERROR: invalid subsystem %s requested", argv[2]); } SCSendOK(pCon); } else { SCWrite(pCon,"ERROR: need subsystem argument for disable",eError); return 0; } } else if(strcmp(argv[1],"listenabled") == 0) { result = ListEnabled(); if(result != NULL){ SCWrite(pCon,GetCharArray(result),eValue); DeleteDynString(result); } } else { SCPrintf(pCon,eError,"ERROR: unknown keyword %s",argv[1]); return 0; } return 1; } /*-----------------------------------------------------------------------------*/ void DisableLog(void) { logDisabled = 1; } /*-----------------------------------------------------------------------------*/ void Logv2Init(void) { int i; callbackList = LLDcreate(sizeof(LogCBData)); strcpy(logTemplate,"unconfiguredLogTemplate"); AddCmd("log", LogAction); AddCommand(pServ->pSics,"logconfig", LogConfigAction, LogClose, NULL); for(i = 0; i < MAXSUB; i++){ logEnabledArray[i] = ERROR; } }