693 lines
18 KiB
C
693 lines
18 KiB
C
/*
|
|
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 <string.h>
|
|
#include <time.h>
|
|
#include <logv2.h>
|
|
#include <sics.h>
|
|
#include <splitter.h>
|
|
#include <lld_str.h>
|
|
#include <lld.h>
|
|
#include <dynstring.h>
|
|
#include <hexString.h>
|
|
/*==============================================================================
|
|
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;
|
|
}
|
|
}
|