Files
sics/loglisten.c
2024-11-14 09:08:20 +01:00

428 lines
12 KiB
C

/**
* This is a means to listen into the stream of log messages from the logv2
* logging system.
*
* Mark Koennecke, May 2016
*
* added more useful stuff like the old "config listen" and "commandlog tail"
* features. Markus Zolliker April 2017
*/
#include <ctype.h>
#include <sics.h>
#include <logv2.h>
#include <lld.h>
#include <dynstring.h>
#include <tcl.h>
/*
From logv2.c
*/
extern int subsystemFromText(const char *text);
/*=============================================================================*/
static int listenerList;
static int callbackRegistered = 0;
typedef struct {
SConnection *pCon;
char subsystem[64];
} ListenEntry;
/*-----------------------------------------------------------------------------*/
/* NEW STUFF */
static int newCallbackRegistered = 0;
static char timestampMode = 'h';
typedef struct ListenerItem_s {
struct ListenerItem_s *next;
SConnection *pCon;
} ListenerItem;
ListenerItem *listeners = NULL;
typedef struct {
int len;
int pos;
int size;
char **history;
} Commandlog;
static Commandlog *commandlog = NULL;
/*-----------------------------------------------------------------------------*/
Commandlog *CommandlogMake(int size) {
Commandlog *c;
int i;
c = malloc(sizeof(*commandlog));
if (c) {
c->size = size;
c->pos = 0;
c->len = 0;
c->history = calloc(size, sizeof(char *));
if (!c->history) {
free(c);
return NULL;
}
for (i = 0; i < size; i++) {
c->history[i] = NULL;
}
}
return c;
}
/*-----------------------------------------------------------------------------*/
void CommandlogFree(Commandlog *c) {
int i;
for (i = 0; i < c->size; i++) {
if (c->history[i]) free(c->history[i]);
}
free(c->history);
free(c);
}
/*-----------------------------------------------------------------------------*/
void CommandlogAdd(Commandlog *c, char *line) {
int l=strlen(line);
line = strdup(line);
if (l > 0 && line[l-1] == '\n') {
line[l-1] = 0;
}
if (c->len < c->size) {
c->len++;
}
if (c->history[c->pos]) free(c->history[c->pos]);
c->history[c->pos] = line;
c->pos++;
if (c->pos >= c->size) c->pos = 0;
}
/*-----------------------------------------------------------------------------*/
char *CommandlogTail(Commandlog *c, int maxlen) {
int len;
pDynString dyn = CreateDynString(1000,1000);
int pos;
if (maxlen > c->len) {
maxlen = c->len;
}
pos = c->pos - maxlen;
if (pos < 0) {
pos += c->size;
}
for (len = maxlen; len > 0; len--) {
if (c->history[pos]) {
DynStringConcat(dyn, c->history[pos]);
DynStringConcatChar(dyn , '\n');
}
pos++;
if (pos >= c->size) pos = 0;
}
return Dyn2Cstring(dyn);
}
/*-----------------------------------------------------------------------------*/
int AnalyzeCommand(char *command) {
/* guess if a command does modify anything. returns 1 if yes, 0 else */
int writable = 1;
CONST char * CONST *argv;
char *endptr;
int argc;
int arg0;
pObjectDescriptor pDes;
Tcl_SplitList(NULL, command, &argc, &argv);
arg0 = 0;
if (argc > 0 && (strcasecmp(argv[0], "fulltransact") == 0
|| strcasecmp(argv[0], "transact") == 0)) {
arg0 = 1;
}
if (argc <= arg0 + 1) {
writable = 0; /* single word -> guess read only */
goto Free;
}
if (strcmp(argv[arg0], "hval") == 0
|| strcmp(argv[arg0], "sicsdescriptor") == 0
|| strcmp(argv[arg0], "_tcl") == 0) {
writable = 0;
goto Free;
}
pDes = FindCommandDescriptor(pServ->pSics, argv[arg0]);
if (pDes == NULL) goto Free;
if (pDes->parNode == NULL && GetDescriptorKey(pDes, "pardef") == NULL) goto Free;
if (argc == arg0 + 2) {
strtof(argv[arg0+1], &endptr);
if (endptr == argv[arg0+1]) {
/* second word of two is not a number */
writable = 0;
}
}
Free:
Tcl_Free((char *)argv);
return writable;
}
/*-----------------------------------------------------------------------------*/
static void LogListenCallbackNew(
unsigned int severity, const char *timeStamp, const char *subsystem,
const char *message, void *userData)
{
ListenerItem *item, **endp;
char *command = NULL;
int sockHandle;
static char lastDate[12]="", lastTime[24]="";
static int lastHandle=0;
static int readonlySocket = 0;
char buffer[80];
char conn[12];
static char lastTag=0;
int lcmp;
if (strncmp(timeStamp, lastDate, 10) != 0) {
/* force new log file at midnight */
if (lastDate[0] != 0) {
LogClose(NULL);
}
strncpy(lastDate, timeStamp, 10);
lastDate[10] = 0;
}
if (severity <= INFO) {
if (strcmp(subsystem, "com") == 0) {
command = strstr((char *)message, "sock");
if (command) {
sockHandle = atoi(command+4);
if (command == message) { /* it is a response */
command = strchr(command, ':');
if (command) {
command++;
if (*command == 0) command = NULL;
}
if (command && sockHandle != readonlySocket) {
readonlySocket = 0;
}
} else { /* it is a command */
command = strstr(command, ":in:");
if (command) {
command += 4;
if (*command == 0) command = NULL;
}
if (command) {
if (AnalyzeCommand(command)) {
readonlySocket = 0;
} else {
readonlySocket = sockHandle; /* do not show commands probably not changing anything */
}
}
}
}
if (command && sockHandle != readonlySocket) { /* do not show commands probably not changing anything */
switch (tolower(timestampMode)) {
case 'h': lcmp = 13; break;
case 'm': lcmp = 16; break;
case 's': lcmp = 19; break;
default: lcmp = 10; break;
}
if (strncmp(timeStamp, lastTime, lcmp) != 0 || (sockHandle >=0 && sockHandle != lastHandle)) {
/* make a timestamp for every changed socket and at least all 10 mins. */
strncpy(lastTime, timeStamp, 19);
lastTime[19] = 0;
if (sockHandle >= 0) lastHandle = sockHandle;
if (sockHandle > 0) {
snprintf(conn, sizeof conn, " con%d", sockHandle);
} else {
conn[0] = 0;
}
snprintf(buffer, sizeof buffer, "=== %.10s %.2s:%.2s:%.2s ===%s", lastTime, lastTime+11, lastTime+14, lastTime+17, conn);
if (commandlog) {
CommandlogAdd(commandlog, buffer);
}
} else {
buffer[0] = 0;
}
if (commandlog) {
CommandlogAdd(commandlog, command);
}
endp = &listeners;
item = listeners;
while (item) {
if (item->pCon && SCisConnected(item->pCon)) {
if (item->pCon->listening) {
if (buffer[0]) {
SCPureSockWrite(item->pCon, buffer, eValue);
}
if (item->pCon->sockHandle != sockHandle) {
SCPureSockWrite(item->pCon, (char *)command, eValue);
}
}
endp = &item->next;
} else {
SCDeleteConnection(item->pCon);
*endp = item->next;
free(item);
}
item = *endp;
}
}
}
}
}
/*-----------------------------------------------------------------------------*/
ListenerItem *LogListenRegister(SConnection * pCon) {
ListenerItem *item;
/* find registered */
for (item = listeners; item != NULL; item=item->next) {
if (item->pCon->sockHandle == pCon->sockHandle) {
item->pCon->listening = pCon->listening;
return item;
}
}
item = malloc(sizeof *item);
if (!item) return NULL;
item->next = listeners;
item->pCon = SCCopyConnection(pCon);
listeners = item;
if(!newCallbackRegistered){
RegisterLogCallback(LogListenCallbackNew,NULL);
newCallbackRegistered = 1;
}
return item;
}
/* old stuff */
/*-----------------------------------------------------------------------------*/
static void LogListenCallback(unsigned int severity, const char *timeStamp,
const char *subsystem,
const char *message, void *userData)
{
ListenEntry current;
int status, cleanupNeeded = 0;
if(logFilter(severity,subsystem,message) == 1) {
return;
}
status = LLDnodePtr2First(listenerList);
while(status != 0){
LLDnodeDataTo(listenerList,&current);
if(strcmp(current.subsystem,subsystem) == 0) {
if(SCisConnected(current.pCon)){
SCPureSockWrite(current.pCon,(char *)message,eValue);
} else {
cleanupNeeded = 1;
}
}
status = LLDnodePtr2Next(listenerList);
}
/*
lld lists sometimes get confused when deleting nodes on the fly.
This is why this has been put into a separate loop
*/
if(cleanupNeeded){
status = LLDnodePtr2First(listenerList);
while(status != 0){
LLDnodeDataTo(listenerList,&current);
if(!SCisConnected(current.pCon)){
SCDeleteConnection(current.pCon);
LLDnodeDelete(listenerList);
}
status = LLDnodePtr2Next(listenerList);
}
}
}
/* modified stuff */
/*-----------------------------------------------------------------------------*/
static int LogListenAction(SConnection * pCon, SicsInterp * pSics,
void *pData, int argc, char *argv[])
{
ListenEntry listLog, current;
int status;
pDynString dyn;
int size;
char *tail;
if (argc < 2) goto Error;
if (strcmp(argv[1], "1") == 0) {
pCon->listening = 1;
LogListenRegister(pCon);
SCSendOK(pCon);
return 1;
}
if (strcmp(argv[1], "0") == 0) {
pCon->listening = 0;
LogListenRegister(pCon);
SCSendOK(pCon);
return 1;
}
if (strcmp(argv[1], "commandlog") == 0) {
if(!newCallbackRegistered){
RegisterLogCallback(LogListenCallbackNew,NULL);
newCallbackRegistered = 1;
}
if (commandlog) CommandlogFree(commandlog);
if (argc >= 3) {
size = atoi(argv[2]);
} else {
size = 1000;
}
commandlog = CommandlogMake(size);
SCSendOK(pCon);
return 1;
}
if (strcmp(argv[1], "tail") == 0) {
if (commandlog) {
if (argc >= 3) {
size = atoi(argv[2]);
} else {
size = commandlog->size;
}
tail = CommandlogTail(commandlog, size);
SCDoSockWrite(pCon,tail);
free(tail);
} else {
SCWrite(pCon,"ERROR: commandlog not initialized",eError);
}
return 1;
}
if (strcmp(argv[1], "timestamps") == 0) {
/* LogListenRegister(pCon); */
if (argc > 2) {
timestampMode = argv[2][0];
}
SCPrintf(pCon, eValue, "loglisten timestamps = %c", timestampMode);
return 1;
}
if (subsystemFromText(argv[1]) < 0){
strncpy(listLog.subsystem,argv[1],sizeof(listLog.subsystem));
listLog.pCon = SCCopyConnection(pCon);
strncpy(listLog.subsystem,argv[1],sizeof(listLog.subsystem));
LLDnodeAppendFrom(listenerList,&listLog);
if(!callbackRegistered){
RegisterLogCallback(LogListenCallback,NULL);
callbackRegistered = 1;
}
SCSendOK(pCon);
return 1;
}
Error:
SCPrintf(pCon,eError, "ERROR: Usage:");
SCPrintf(pCon,eError, "ERROR: loglisten 1 | 0 ");
SCPrintf(pCon,eError, "ERROR: loglisten <subsystem>");
SCPrintf(pCon,eError, "ERROR: loglisten commandlog <size>");
SCPrintf(pCon,eError, "ERROR: loglisten tail <len>");
SCPrintf(pCon,eError, "ERROR: loglisten timestamps d | h | m | s");
return 0;
}
/*-----------------------------------------------------------------------------*/
void LogListenInit(void)
{
listenerList = LLDcreate(sizeof(ListenEntry));
AddCmd("loglisten", LogListenAction);
}