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);
+ }
+ }
+ }
+}