diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index d2d921b35..7aa50e250 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -15,6 +15,16 @@ EPICS Base 3.15.0.x releases are not intended for use in production systems.

Changes between 3.14.x and 3.15.0.x

+

+Added support for iocLogPrefix

+ +

+Added a iocLogPrefix command to iocsh. This adds a +prefix to all messages from this IOC (or other log client) as they get sent to the +iocLogServer. This lets sites use the "fac=<facility>" syntax for +displaying the facility, process name etc. in log viewers like the +cmlogviewer.

+

Reworked the epicsEvent C & C++ APIs

diff --git a/src/libCom/iocsh/libComRegister.c b/src/libCom/iocsh/libComRegister.c index 6cd79bb66..f6ce30284 100644 --- a/src/libCom/iocsh/libComRegister.c +++ b/src/libCom/iocsh/libComRegister.c @@ -191,6 +191,15 @@ static void errlogCallFunc(const iocshArgBuf *args) errlogPrintfNoConsole("%s\n", args[0].sval); } +/* iocLogPrefix */ +static const iocshArg iocLogPrefixArg0 = { "prefix",iocshArgString}; +static const iocshArg * const iocLogPrefixArgs[1] = {&iocLogPrefixArg0}; +static const iocshFuncDef iocLogPrefixFuncDef = {"iocLogPrefix",1,iocLogPrefixArgs}; +static void iocLogPrefixCallFunc(const iocshArgBuf *args) +{ + iocLogPrefix(args[0].sval); +} + /* epicsThreadShowAll */ static const iocshArg epicsThreadShowAllArg0 = { "level",iocshArgInt}; static const iocshArg * const epicsThreadShowAllArgs[1] = {&epicsThreadShowAllArg0}; @@ -355,6 +364,7 @@ void epicsShareAPI libComRegister(void) iocshRegister(&errlogInitFuncDef,errlogInitCallFunc); iocshRegister(&errlogInit2FuncDef,errlogInit2CallFunc); iocshRegister(&errlogFuncDef, errlogCallFunc); + iocshRegister(&iocLogPrefixFuncDef, iocLogPrefixCallFunc); iocshRegister(&epicsThreadShowAllFuncDef,epicsThreadShowAllCallFunc); iocshRegister(&threadFuncDef, threadCallFunc); diff --git a/src/libCom/log/logClient.c b/src/libCom/log/logClient.c index adaaa7f95..6aa223b5d 100644 --- a/src/libCom/log/logClient.c +++ b/src/libCom/log/logClient.c @@ -57,6 +57,11 @@ static const double LOG_RESTART_DELAY = 5.0; /* sec */ static const double LOG_SERVER_CREATE_CONNECT_SYNC_TIMEOUT = 5.0; /* sec */ static const double LOG_SERVER_SHUTDOWN_TIMEOUT = 30.0; /* sec */ +/* + * If set using iocLogPrefix() this string is prepended to all log messages: + */ +static char* logClientPrefix = NULL; + /* * logClientClose () */ @@ -159,22 +164,13 @@ static void logClientDestroy (logClientId id) free ( pClient ); } -/* - * logClientSend () +/* + * This method requires the pClient->mutex be owned already. */ -void epicsShareAPI logClientSend ( logClientId id, const char * message ) -{ - logClient * pClient = ( logClient * ) id; +static void sendMessageChunk(logClient * pClient, const char * message) { unsigned strSize; - if ( ! pClient || ! message ) { - return; - } - strSize = strlen ( message ); - - epicsMutexMustLock ( pClient->mutex ); - while ( strSize ) { unsigned msgBufBytesLeft = sizeof ( pClient->msgBuf ) - pClient->nextMsgIndex; @@ -231,10 +227,31 @@ void epicsShareAPI logClientSend ( logClientId id, const char * message ) break; } } - +} + + +/* + * logClientSend () + */ +void epicsShareAPI logClientSend ( logClientId id, const char * message ) +{ + logClient * pClient = ( logClient * ) id; + + if ( ! pClient || ! message ) { + return; + } + + epicsMutexMustLock ( pClient->mutex ); + + if (logClientPrefix) { + sendMessageChunk(pClient, logClientPrefix); + } + sendMessageChunk(pClient, message); + epicsMutexUnlock (pClient->mutex); } + void epicsShareAPI logClientFlush ( logClientId id ) { logClient * pClient = ( logClient * ) id; @@ -553,5 +570,36 @@ void epicsShareAPI logClientShow (logClientId id, unsigned level) pClient->sock==INVALID_SOCKET?"INVALID":"OK", pClient->connectCount); } + + if (logClientPrefix) { + printf ("log client: prefix is \"%s\"\n", logClientPrefix); + } } +/* + * iocLogPrefix() + */ +void epicsShareAPI iocLogPrefix(const char * prefix) +{ + + /* If we have already established a log prefix, don't let the user change + * it. The iocLogPrefix command is expected to be run from the IOC startup + * script during initialization; the prefix can't be changed once it has + * been set. + */ + + if (logClientPrefix) { + printf ("iocLogPrefix: The prefix was already set to \"%s\" " + "and can't be changed.\n", logClientPrefix); + return; + } + + if (prefix) { + unsigned prefixLen = strlen(prefix); + if (prefixLen > 0) { + char * localCopy = malloc(prefixLen+1); + strcpy(localCopy, prefix); + logClientPrefix = localCopy; + } + } +} diff --git a/src/libCom/log/logClient.h b/src/libCom/log/logClient.h index 8f1ec09d5..1797bbb20 100644 --- a/src/libCom/log/logClient.h +++ b/src/libCom/log/logClient.h @@ -33,6 +33,7 @@ epicsShareFunc logClientId epicsShareAPI logClientCreate ( epicsShareFunc void epicsShareAPI logClientSend (logClientId id, const char *message); epicsShareFunc void epicsShareAPI logClientShow (logClientId id, unsigned level); epicsShareFunc void epicsShareAPI logClientFlush (logClientId id); +epicsShareFunc void epicsShareAPI iocLogPrefix(const char* prefix); /* deprecated interface; retained for backward compatibility */ /* note: implementations are in iocLog.c, not logClient.c */ diff --git a/src/libCom/test/epicsErrlogTest.c b/src/libCom/test/epicsErrlogTest.c index 08389e438..e1729104e 100644 --- a/src/libCom/test/epicsErrlogTest.c +++ b/src/libCom/test/epicsErrlogTest.c @@ -22,6 +22,11 @@ #include "errlog.h" #include "epicsUnitTest.h" #include "testMain.h" +#include "iocLog.h" +#include "logClient.h" +#include "envDefs.h" +#include "osiSock.h" +#include "fdmgr.h" #define LOGBUFSIZE 2048 @@ -75,6 +80,28 @@ typedef struct { int jam; } clientPvt; +static void testLogPrefix(void); +static void acceptNewClient( void *pParam ); +static void readFromClient( void *pParam ); +static void testPrefixLogandCompare( const char* logmessage); + +static void *pfdctx; +static SOCKET sock; +static SOCKET insock; + +static const char* prefixactualmsg[]= { + "A message without prefix", + "A message with prefix", + "DONE" + }; +static const char *prefixstring = "fac=LI21 "; +static const char prefixexpectedmsg[] = "A message without prefix" + "fac=LI21 A message with prefix" + "fac=LI21 DONE" + ; +static char prefixmsgbuffer[1024]; + + static void logClient(void* raw, const char* msg) { @@ -115,7 +142,7 @@ MAIN(epicsErrlogTest) char msg[256]; clientPvt pvt, pvt2; - testPlan(25); + testPlan(29); strcpy(msg, truncmsg); @@ -143,7 +170,7 @@ MAIN(epicsErrlogTest) pvt.expect = "Testing"; pvt.checkLen = strlen(pvt.expect); - errlogPrintfNoConsole(pvt.expect); + errlogPrintfNoConsole("%s", pvt.expect); errlogFlush(); testOk1(pvt.count == 1); @@ -153,7 +180,7 @@ MAIN(epicsErrlogTest) pvt2.expect = pvt.expect = "Testing2"; pvt2.checkLen = pvt.checkLen = strlen(pvt.expect); - errlogPrintfNoConsole(pvt.expect); + errlogPrintfNoConsole("%s", pvt.expect); errlogFlush(); testOk1(pvt.count == 2); @@ -165,7 +192,7 @@ MAIN(epicsErrlogTest) pvt2.expect = "Testing3"; pvt2.checkLen = strlen(pvt2.expect); - errlogPrintfNoConsole(pvt2.expect); + errlogPrintfNoConsole("%s", pvt2.expect); errlogFlush(); testOk1(pvt.count == 2); @@ -227,7 +254,7 @@ MAIN(epicsErrlogTest) pvt.jam = 1; for (i = 0; i < N; i++) { - errlogPrintfNoConsole(msg); + errlogPrintfNoConsole("%s", msg); } epicsEventSignal(pvt.jammer); @@ -258,7 +285,7 @@ MAIN(epicsErrlogTest) testDiag("Filling with %d messages of size %d", (int) N, (int) mlen); for (i = 0; i < N; i++) { - errlogPrintfNoConsole(msg); + errlogPrintfNoConsole("%s", msg); } epicsThreadSleep(0.1); /* should really be a second Event */ @@ -273,10 +300,10 @@ MAIN(epicsErrlogTest) testOk1(pvt.count == 2); /* The buffer has space for 1 more message: sizeof(msgNode) + 256 bytes */ - errlogPrintfNoConsole(msg); /* Use up that space */ + errlogPrintfNoConsole("%s", msg); /* Use up that space */ testDiag("Overflow the buffer"); - errlogPrintfNoConsole(msg); + errlogPrintfNoConsole("%s", msg); testOk1(pvt.count == 2); @@ -289,5 +316,143 @@ MAIN(epicsErrlogTest) /* Clean up */ errlogRemoveListener(&logClient); + testLogPrefix(); + return testDone(); } +/* + * Tests the log prefix code + * The prefix is only applied to log messages as they go out to the socket, + * so we need to create a server listening on a port, accept connections etc. + * This code is a reduced version of the code in iocLogServer. + */ +static void testLogPrefix(void) { + struct sockaddr_in serverAddr; + int status; + struct timeval timeout; + struct sockaddr_in actualServerAddr; + osiSocklen_t actualServerAddrSize; + char portstring[16]; + + + testDiag("Testing iocLogPrefix"); + + timeout.tv_sec = 5; /* in seconds */ + timeout.tv_usec = 0; + + memset((void*)prefixmsgbuffer, 0, sizeof prefixmsgbuffer); + + /* Clear "errlog: messages were discarded" status */ + errlogPrintfNoConsole("."); + errlogFlush(); + + sock = epicsSocketCreate(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + testAbort("epicsSocketCreate failed."); + } + + /* We listen on a an available port. */ + memset((void *)&serverAddr, 0, sizeof serverAddr); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(0); + + status = bind (sock, (struct sockaddr *)&serverAddr, + sizeof (serverAddr) ); + if (status < 0) { + testAbort("bind failed; all ports in use?"); + } + + status = listen(sock, 10); + if (status < 0) { + testAbort("listen failed!"); + } + + /* Determine the port that the OS chose */ + actualServerAddrSize = sizeof actualServerAddr; + memset((void *)&actualServerAddr, 0, sizeof serverAddr); + status = getsockname(sock, (struct sockaddr *) &actualServerAddr, + &actualServerAddrSize); + if (status < 0) { + testAbort("Can't find port number!"); + } + + sprintf(portstring, "%d", ntohs(actualServerAddr.sin_port)); + testDiag("Listening on port %s", portstring); + + /* Set the EPICS environment variables for logging. */ + epicsEnvSet ( "EPICS_IOC_LOG_INET", "localhost" ); + epicsEnvSet ( "EPICS_IOC_LOG_PORT", portstring ); + + pfdctx = (void *) fdmgr_init(); + if (status < 0) { + testAbort("fdmgr_init failed!"); + } + + status = fdmgr_add_callback(pfdctx, sock, fdi_read, + acceptNewClient, &serverAddr); + + if (status < 0) { + testAbort("fdmgr_add_callback failed!"); + } + + testOk1(iocLogInit() == 0); + fdmgr_pend_event(pfdctx, &timeout); + + testPrefixLogandCompare(prefixactualmsg[0]); + + iocLogPrefix(prefixstring); + testPrefixLogandCompare(prefixactualmsg[1]); + testPrefixLogandCompare(prefixactualmsg[2]); + close(sock); +} + +static void testPrefixLogandCompare( const char* logmessage ) { + struct timeval timeout; + timeout.tv_sec = 5; /* in seconds */ + timeout.tv_usec = 0; + + errlogPrintfNoConsole("%s", logmessage); + errlogFlush(); + iocLogFlush(); + fdmgr_pend_event(pfdctx, &timeout); +} + +static void acceptNewClient ( void *pParam ) +{ + osiSocklen_t addrSize; + struct sockaddr_in addr; + int status; + + addrSize = sizeof ( addr ); + insock = epicsSocketAccept ( sock, (struct sockaddr *)&addr, &addrSize ); + testOk(insock != INVALID_SOCKET && addrSize >= sizeof (addr), + "Accepted new client"); + + status = fdmgr_add_callback(pfdctx, insock, fdi_read, + readFromClient, NULL); + + testOk(status >= 0, "Client read configured"); +} + +static void readFromClient(void *pParam) +{ + char recvbuf[1024]; + int recvLength; + + memset(&recvbuf, 0, 1024); + recvLength = recv(insock, &recvbuf, 1024, 0); + if (recvLength > 0) { + strcat(prefixmsgbuffer, recvbuf); + + /* If we have received all of the messages. */ + if (strstr(prefixmsgbuffer, "DONE") != NULL) { + size_t msglen = strlen(prefixexpectedmsg); + int prefixcmp = strncmp(prefixexpectedmsg, prefixmsgbuffer, msglen); + + if (!testOk(prefixcmp == 0, "prefix matches")) { + testDiag("Expected '%s'\n", prefixexpectedmsg); + testDiag("Obtained '%s'\n", prefixmsgbuffer); + } + } + } +}