diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index eee9f141976..fbd203a00b9 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -31,3 +31,4 @@ HPCC_ADD_SUBDIRECTORY (pkgfiles)
HPCC_ADD_SUBDIRECTORY (workunit)
HPCC_ADD_SUBDIRECTORY (wuanalysis)
HPCC_ADD_SUBDIRECTORY (wuwebview "PLATFORM")
+HPCC_ADD_SUBDIRECTORY (sysinfologger)
diff --git a/common/sysinfologger/CMakeLists.txt b/common/sysinfologger/CMakeLists.txt
new file mode 100644
index 00000000000..8606af7f350
--- /dev/null
+++ b/common/sysinfologger/CMakeLists.txt
@@ -0,0 +1,58 @@
+################################################################################
+# HPCC SYSTEMS software Copyright (C) 2024 HPCC Systems®.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+
+# File : CMakeLists.txt
+# Component: sysinfologger
+#####################################################
+# Description:
+# ------------
+# Cmake input file for sysinfologger
+#####################################################
+
+project ( sysinfologger )
+
+set ( SRCS
+ sysinfologger.cpp
+
+ sysinfologger.hpp
+ )
+
+include_directories (
+ ${HPCC_SOURCE_DIR}/system/jlib
+ ${HPCC_SOURCE_DIR}/system/include
+ ${HPCC_SOURCE_DIR}/system/jlog
+ ${HPCC_SOURCE_DIR}/system/mp
+ ${HPCC_SOURCE_DIR}/dali/base
+ ${HPCC_SOURCE_DIR}/testing/unittests
+ )
+
+ADD_DEFINITIONS( -DSYSINFOMSG_EXPORTS )
+
+HPCC_ADD_LIBRARY( sysinfologger SHARED ${SRCS} )
+
+target_link_libraries (
+ sysinfologger
+ jlib
+ dalibase
+ ${CppUnit_LIBRARIES}
+ )
+
+if ( USE_CPPUNIT )
+ target_link_libraries ( sysinfologger )
+endif()
+
+install ( TARGETS sysinfologger RUNTIME DESTINATION ${EXEC_DIR} LIBRARY DESTINATION ${LIB_DIR} )
diff --git a/common/sysinfologger/sourcedoc.xml b/common/sysinfologger/sourcedoc.xml
new file mode 100644
index 00000000000..cd2f7e03e8c
--- /dev/null
+++ b/common/sysinfologger/sourcedoc.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ common/sysinfologger
+
+
+ The common/sysinfologger directory contains the sources for the common/sysinfologger library.
+
+
diff --git a/common/sysinfologger/sysinfologger.cpp b/common/sysinfologger/sysinfologger.cpp
new file mode 100644
index 00000000000..b14e70df587
--- /dev/null
+++ b/common/sysinfologger/sysinfologger.cpp
@@ -0,0 +1,560 @@
+/*##############################################################################
+
+ HPCC SYSTEMS software Copyright (C) 2024 HPCC Systems®.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+############################################################################## */
+
+#include "sysinfologger.hpp"
+#include "daclient.hpp"
+#include "jutil.hpp"
+
+#ifdef _USE_CPPUNIT
+#include
+#include
+#include
+#endif
+
+#define SDS_LOCK_TIMEOUT (5*60*1000) // 5 minutes
+#define SYS_INFO_VERSION "1.0"
+#define BOOL_STR(b) (b?"true":"false")
+
+#define SYS_INFO_ROOT "/SysInfo"
+#define MSG_NODE "msg"
+#define ATTR_TIMESTAMP "@ts"
+#define ATTR_AUDIENCE "@audience"
+#define ATTR_CLASS "@class"
+#define ATTR_CODE "@code"
+#define ATTR_HIDDEN "@hidden"
+
+class CSysInfoLoggerMsg : implements ISysInfoLoggerMsg
+{
+ Owned msgPtree = nullptr;
+
+public:
+ CSysInfoLoggerMsg & set(IPropertyTree & ptree)
+ {
+ msgPtree.setown(&ptree);
+ return * this;
+ }
+ unsigned __int64 queryTimeStamp() const override
+ {
+ return msgPtree->getPropInt64(ATTR_TIMESTAMP);
+ }
+ LogMsgAudience queryAudience() const override
+ {
+ return LogMsgAudFromAbbrev(msgPtree->queryProp(ATTR_AUDIENCE));
+ }
+ LogMsgClass queryClass() const override
+ {
+ return LogMsgClassFromAbbrev(msgPtree->queryProp(ATTR_CLASS));
+ }
+ LogMsgCode queryLogMsgCode() const override
+ {
+ return msgPtree->getPropInt(ATTR_CODE);
+ }
+ const char * queryMsg() const override
+ {
+ return msgPtree->queryProp(".");
+ }
+ bool queryIsHidden() const override
+ {
+ return msgPtree->getPropBool(ATTR_HIDDEN);
+ }
+ static IPropertyTree * createMsgPTree(const LogMsgCategory & cat, LogMsgCode code, const char * msg, unsigned __int64 ts, bool hidden)
+ {
+ Owned msgPtree = createPTree(MSG_NODE);
+ msgPtree->setPropBool(ATTR_HIDDEN, false);
+ msgPtree->setPropInt64(ATTR_TIMESTAMP, ts);
+ msgPtree->setPropInt(ATTR_CODE, code);
+ msgPtree->setProp(ATTR_AUDIENCE, LogMsgAudienceToFixString(cat.queryAudience()));
+ msgPtree->setProp(ATTR_CLASS, LogMsgClassToFixString(cat.queryClass()));
+ msgPtree->setProp(".", msg);
+ return msgPtree.getClear();
+ }
+ static StringBuffer &buildMsgMatchXpath(const LogMsgAudience aud, const LogMsgClass logClass, LogMsgCode code, unsigned __int64 ts, StringBuffer & xpath)
+ {
+ return xpath.appendf(MSG_NODE "[" ATTR_TIMESTAMP "='%" I64F "u'][" ATTR_CODE "='%d'][" ATTR_AUDIENCE "='%s'][" ATTR_CLASS "='%s']", ts, (int) code, LogMsgAudienceToFixString(aud), LogMsgClassToFixString(logClass) );
+ }
+};
+
+class CSysInfoLoggerMsgIterator : public CInterface, implements ISysInfoLoggerMsgIterator
+{
+ Owned conn;
+ CSysInfoLoggerMsg infoMsg;
+ Owned msgIter;
+ bool hiddenOnly = false;
+ bool visibleOnly = false;
+ unsigned year, month, day;
+
+ bool ensureMatch()
+ {
+ for (; msgIter && msgIter->isValid(); msgIter->next())
+ {
+ IPropertyTree & pt = msgIter->query();
+ if(year)
+ {
+ unsigned __int64 ts = pt.getPropInt64(ATTR_TIMESTAMP, 0);
+ if (!ts)
+ continue;
+ CDateTime dt;
+ dt.setTimeStamp(ts);
+ unsigned tyear, tmonth, tday;
+ dt.getDate(tyear, tmonth, tday);
+ if (year!=tyear || (month && tmonth!=month) || (day && tday!=day))
+ continue;
+ }
+ break;
+ }
+ return msgIter->isValid();
+ }
+
+public:
+ IMPLEMENT_IINTERFACE;
+
+ CSysInfoLoggerMsgIterator(bool _hiddenOnly, bool _visibleOnly, unsigned _year=0, unsigned _month=0, unsigned _day=0)
+ : hiddenOnly(_hiddenOnly), visibleOnly(_visibleOnly), year(_year), month(_month), day(_day)
+ {
+ if (hiddenOnly && visibleOnly)
+ throw makeStringExceptionV(-1, "CSysInfoLoggerMsgIterator: cannot filter by both hiddenOnly and visibleOnly");
+ if (!month && day)
+ throw makeStringExceptionV(-1, "CSysInfoLoggerMsgIterator: month and year must be provided when filtering by day");
+ if (!year && month)
+ throw makeStringExceptionV(-1, "CSysInfoLoggerMsgIterator: year must be provided when filtering by month");
+ }
+ virtual ISysInfoLoggerMsg & query() override
+ {
+ return infoMsg.set(msgIter->get());
+ }
+ virtual bool first() override
+ {
+ StringBuffer xpath(SYS_INFO_ROOT);
+ if (year && month)
+ {
+ xpath.appendf("/m%04d%02d", year, month);
+ if (day)
+ xpath.appendf("/d%02d", day);
+ }
+ conn.setown(querySDS().connect(xpath.str(), myProcessSession(), RTM_LOCK_READ, SDS_LOCK_TIMEOUT));
+
+ xpath.set("//" MSG_NODE);
+ if (hiddenOnly)
+ xpath.append("[@hidden='1')]");
+ if (visibleOnly)
+ xpath.append("[@hidden='0')]");
+
+ msgIter.setown(conn->queryRoot()->getElements(xpath.str()));
+ msgIter->first();
+ return ensureMatch();
+ }
+ virtual bool next() override
+ {
+ msgIter->next();
+ return ensureMatch();
+
+ }
+ virtual bool isValid() override
+ {
+ return msgIter ? msgIter->isValid() : false;
+ }
+};
+
+ISysInfoLoggerMsgIterator * createSysInfoLoggerMsgIterator(unsigned year, unsigned month, unsigned day, bool visibleOnly, bool hiddenOnly)
+{
+ return new CSysInfoLoggerMsgIterator(hiddenOnly, visibleOnly, year, month, day);
+}
+
+void logSysInfoError(const LogMsgCategory & cat, LogMsgCode code, const char * msg, unsigned __int64 ts)
+{
+ if (ts==0)
+ ts = getTimeStampNowValue();
+
+ CDateTime timeStamp;
+ timeStamp.setTimeStamp(ts);
+ unsigned year, month, day;
+ timeStamp.getDate(year, month, day);
+
+ StringBuffer xpath(SYS_INFO_ROOT);
+ xpath.appendf("/m%04d%02d/d%02d", year, month, day);
+ Owned conn = querySDS().connect(xpath.str(), myProcessSession(), RTM_LOCK_WRITE|RTM_CREATE_QUERY, SDS_LOCK_TIMEOUT);
+ if (!conn)
+ throw makeStringExceptionV(-1, "logSysInfoError: unable to create connection to '%s'", xpath.str());
+ IPropertyTree * root = conn->queryRoot();
+ if (!root->hasProp("@version"))
+ root->addProp("@version", SYS_INFO_VERSION);
+
+ root->addPropTree(MSG_NODE, CSysInfoLoggerMsg::createMsgPTree(cat, code, msg, ts, false));
+ conn->close();
+};
+
+bool hideLogSysInfoMsg(LogMsgCategory & cat, LogMsgCode code, unsigned __int64 ts)
+{
+ unsigned year, month, day;
+ CDateTime timeStamp;
+ timeStamp.setTimeStamp(ts);
+ timeStamp.getDate(year, month, day);
+
+ StringBuffer xpath(SYS_INFO_ROOT);
+ xpath.appendf("/m%04d%02d/d%02d/", year, month, day);
+ CSysInfoLoggerMsg::buildMsgMatchXpath(cat.queryAudience(), cat.queryClass(), code, ts, xpath);
+ xpath.append("[1]"); // Match first message only
+
+ Owned conn = querySDS().connect(xpath.str(), myProcessSession(), RTM_LOCK_READ | RTM_LOCK_WRITE, SDS_LOCK_TIMEOUT);
+ if (!conn)
+ throw makeStringExceptionV(-1, "logSysInfoError: unable to create connection to '%s'", xpath.str());
+ conn->queryRoot()->setPropBool(ATTR_HIDDEN, true);
+ return true;
+}
+
+bool deleteLogSysInfoMsg(LogMsgCategory & cat, LogMsgCode code, unsigned __int64 ts)
+{
+ unsigned year, month, day;
+ CDateTime timeStamp;
+ timeStamp.setTimeStamp(ts);
+ timeStamp.getDate(year, month, day);
+
+ StringBuffer xpath(SYS_INFO_ROOT);
+ xpath.appendf("/m%04d%02d/d%02d", year, month, day);
+ Owned conn = querySDS().connect(xpath.str(), myProcessSession(), RTM_LOCK_READ | RTM_LOCK_WRITE, SDS_LOCK_TIMEOUT);
+ if (!conn)
+ throw makeStringExceptionV(-1, "logSysInfoError: unable to create connection to '%s'", xpath.str());
+
+ IPropertyTree * root = conn->queryRoot();
+ CSysInfoLoggerMsg::buildMsgMatchXpath(cat.queryAudience(), cat.queryClass(), code, ts, xpath.clear());
+ xpath.append("[1]"); // Match first message only
+ IPropertyTree * msgPtree = root->queryPropTree(xpath.str());
+ if (msgPtree && root)
+ {
+ root->removeTree(msgPtree);
+ return true;
+ }
+ return false;
+}
+
+static unsigned readDigits(char const * & p, unsigned numDigits)
+{
+ unsigned num = 0;
+ for (unsigned n=0; n conn = querySDS().connect(SYS_INFO_ROOT, myProcessSession(), RTM_LOCK_WRITE, SDS_LOCK_TIMEOUT);
+ Owned monthIter = conn->queryRoot()->getElements("./*");
+
+ ForEach(*monthIter)
+ {
+ IPropertyTree & monthPT = monthIter->query();
+ unsigned msgYear = 0, msgMonth = 0;
+ const char *p = monthPT.queryName(); // should be in format 'myyyydd'
+ if (*p++ == 'm')
+ {
+ msgYear = readDigits(p, 4);
+ msgMonth= readDigits(p, 2);
+ }
+ if (msgYear == 0 || msgMonth == 0)
+ throw makeStringExceptionV(-1, "child of " SYS_INFO_ROOT " is invalid: %s", monthPT.queryName());
+ if (msgYear > year)
+ continue;
+ if (msgYear == year && (msgMonth > month))
+ continue;
+ if (msgMonth < month && !hiddenOnly && !visibleOnly)
+ {
+ printf("removing %s\n", monthPT.queryName());
+ conn->queryRoot()->removeTree(&monthPT);
+ ++removed;
+ }
+ else
+ {
+ Owned dayIter = monthPT.getElements("./*");
+ ForEach(*dayIter)
+ {
+ IPropertyTree & dayPT = dayIter->query();
+ unsigned msgDay = 0;
+ const char * d = dayPT.queryName();
+ if (*d++ == 'd')
+ msgDay = readDigits(d, 2);
+ if (msgDay == 0)
+ throw makeStringExceptionV(-1, "child of " SYS_INFO_ROOT "/%s is invalid: %s", monthPT.queryName(), dayPT.queryName());
+ if ((msgDay >= day))
+ continue;
+ if (hiddenOnly||visibleOnly)
+ {
+ IArrayOf delmsgs;
+ Owned msgIter = dayPT.getElements("./*");
+ ForEach(*msgIter)
+ {
+ IPropertyTree & msgPT = msgIter->query();
+ bool isHidden = msgPT.getPropBool(ATTR_HIDDEN);
+ if (hiddenOnly && !isHidden)
+ continue;
+ if (visibleOnly && isHidden)
+ continue;
+ delmsgs.append(msgIter->get());
+ }
+ ForEachItemIn(d, delmsgs)
+ {
+ IPropertyTree & msgPT = delmsgs.item(d);
+ dayPT.removeTree(&msgPT);
+ removed++;
+ }
+ }
+ else
+ {
+ monthPT.removeTree(&dayPT);
+ ++removed;
+ }
+ }
+ }
+ }
+ return removed;
+}
+
+#ifdef _USE_CPPUNIT
+#include "unittests.hpp"
+
+std::atomic_bool initialized {false};
+CriticalSection crit;
+
+void daliClientInit()
+{
+ CriticalBlock b(crit);
+ if (initialized.load()==true)
+ return;
+ InitModuleObjects();
+ SocketEndpoint ep;
+ ep.set(".", 7070);
+ SocketEndpointArray epa;
+ epa.append(ep);
+ Owned group = createIGroup(epa);
+ initClientProcess(group, DCR_Testing);
+ initialized.store(true);
+}
+
+void daliClientEnd()
+{
+ CriticalBlock b(crit);
+ if (initialized.load()==false)
+ return;
+ closedownClientProcess();
+ initialized.store(false);
+}
+
+class CSysInfoLoggerTester : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(CSysInfoLoggerTester);
+ CPPUNIT_TEST(test);
+ CPPUNIT_TEST_SUITE_END();
+
+ unsigned testRead(bool hiddenOnly=false, bool visibleOnly=false, unsigned year=0, unsigned month=0, unsigned day=0)
+ {
+ unsigned readCount=0;
+ try
+ {
+ std::set matchedMessages; // used to make sure every message written has been read back
+ // Test cases for this day only
+ Owned iter = new CSysInfoLoggerMsgIterator(hiddenOnly, visibleOnly, year, month, day);
+ ForEach(*iter)
+ {
+ const ISysInfoLoggerMsg & sysInfoMsg = iter->query();
+
+ WrittenLogMessage wm{sysInfoMsg.queryTimeStamp(), 0};
+ auto matched = std::find(writtenMessages.begin(), writtenMessages.end(), wm);
+ if (matched==writtenMessages.end())
+ continue; // not a message written by this unittest so ignore
+ matchedMessages.insert(matched->index);
+
+ TestCase & testCase = testCases[matched->index];
+ ASSERT(testCase.hidden==sysInfoMsg.queryIsHidden());
+ ASSERT(testCase.code==sysInfoMsg.queryLogMsgCode());
+ ASSERT(strcmp(testCase.msg,sysInfoMsg.queryMsg())==0);
+ ASSERT(testCase.cat.queryAudience()==sysInfoMsg.queryAudience());
+ ASSERT(testCase.cat.queryClass()==sysInfoMsg.queryClass());
+
+ readCount++;
+ }
+ ASSERT(readCount==matchedMessages.size()); // make sure there are not duplicates
+ }
+ catch (IException *e)
+ {
+ StringBuffer msg;
+ msg.appendf("testRead(hidden=%s, visible=%s) failed: ", BOOL_STR(hiddenOnly), BOOL_STR(visibleOnly));
+ e->errorMessage(msg);
+ msg.appendf("(code %d)", e->errorCode());
+ e->Release();
+ CPPUNIT_FAIL(msg.str());
+ }
+ return readCount;
+ }
+
+ struct TestCase
+ {
+ LogMsgCategory cat;
+ LogMsgCode code;
+ bool hidden;
+ const char * dateTimeStamp;
+ const char * msg;
+ };
+
+ std::vector testCases =
+ {
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42301,
+ false,
+ "2000-02-03T10:01:22.342343",
+ "CSysInfoLogger Unit test message 1"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42302,
+ false,
+ "2000-02-03T12:03:42.114233",
+ "CSysInfoLogger Unit test message 2"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42303,
+ true,
+ "2000-02-03T14:02:13.678443",
+ "CSysInfoLogger Unit test message 3"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42304,
+ true,
+ "2000-02-03T16:05:18.8324832",
+ "CSysInfoLogger Unit test message 4"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42301,
+ false,
+ "2000-02-04T03:01:42.5754",
+ "CSysInfoLogger Unit test message 5"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42302,
+ false,
+ "2000-02-04T09:06:25.133132",
+ "CSysInfoLogger Unit test message 6"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42303,
+ false,
+ "2000-02-04T11:09:32.78439",
+ "CSysInfoLogger Unit test message 7"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42304,
+ true,
+ "2000-02-04T13:02:12.82821",
+ "CSysInfoLogger Unit test message 8"
+ },
+ {
+ LogMsgCategory(MSGAUD_operator, MSGCLS_information, DefaultDetail),
+ 42304,
+ true,
+ "2000-02-04T18:32:11.23421",
+ "CSysInfoLogger Unit test message 9"
+ }
+ };
+
+ struct WrittenLogMessage
+ {
+ unsigned __int64 ts;
+ unsigned index;
+ inline bool operator==(const WrittenLogMessage& rhs)
+ {
+ return ts==rhs.ts;
+ }
+ };
+ std::vector writtenMessages;
+
+public:
+ CSysInfoLoggerTester()
+ {
+ try
+ {
+ daliClientInit();
+ }
+ catch (IException *e)
+ {
+ StringBuffer msg;
+ e->errorMessage(msg);
+ printf("daliClientInit failed: %s (code %d)", msg.str(), e->errorCode());
+ e->Release();
+ }
+ }
+ ~CSysInfoLoggerTester()
+ {
+ daliClientEnd();
+ }
+ void testWrite()
+ {
+ unsigned index=0;
+ for (auto testCase: testCases)
+ {
+ try
+ {
+ CDateTime dateTime;
+ dateTime.setString(testCase.dateTimeStamp);
+
+ unsigned __int64 ts = dateTime.getTimeStamp();
+ logSysInfoError(testCase.cat, testCase.code, testCase.msg, ts);
+ writtenMessages.push_back({ts, index++});
+ if (testCase.hidden)
+ ASSERT(hideLogSysInfoMsg(testCase.cat, testCase.code, ts)==true);
+ }
+ catch (IException *e)
+ {
+ StringBuffer msg;
+ msg.append("logSysInfoError failed: ");
+ e->errorMessage(msg);
+ msg.appendf("(code %d)", e->errorCode());
+ e->Release();
+ CPPUNIT_FAIL(msg.str());
+ }
+ }
+ ASSERT(testCases.size()==writtenMessages.size());
+ }
+ void test()
+ {
+ testWrite();
+ ASSERT(testRead(false, false)==9);
+ ASSERT(testRead(false, false, 2000, 02, 03)==4);
+ ASSERT(testRead(false, false, 2000, 02, 04)==5);
+ ASSERT(testRead(false, true)==5); //visible only
+ ASSERT(testRead(true, false)==4); //hidden only
+ ASSERT(deleteOlderThanLogSysInfoMsg(2000, 02, 04, false, true)==2);
+ ASSERT(deleteOlderThanLogSysInfoMsg(2000, 02, 05, true, false)==5);
+ ASSERT(deleteOlderThanLogSysInfoMsg(2000, 02, 05, false, false)==2);
+ writtenMessages.clear();
+ ASSERT(testRead(false, false)==0);
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION( CSysInfoLoggerTester );
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CSysInfoLoggerTester, "CSysInfoLoggerTester" );
+
+#endif
diff --git a/common/sysinfologger/sysinfologger.hpp b/common/sysinfologger/sysinfologger.hpp
new file mode 100644
index 00000000000..8e2d417a609
--- /dev/null
+++ b/common/sysinfologger/sysinfologger.hpp
@@ -0,0 +1,51 @@
+/*##############################################################################
+
+ HPCC SYSTEMS software Copyright (C) 2024 HPCC Systems®.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+############################################################################## */
+
+#ifndef SYSINFOLOGGER
+#define SYSINFOLOGGER
+
+#include "jlog.hpp"
+#include "jutil.hpp"
+
+#ifdef SYSINFOMSG_EXPORTS
+ #define SYSINFO_API DECL_EXPORT
+#else
+ #define SYSINFO_API DECL_IMPORT
+#endif
+
+interface ISysInfoLoggerMsg
+{
+ virtual unsigned __int64 queryTimeStamp() const = 0;
+ virtual LogMsgAudience queryAudience() const = 0;
+ virtual LogMsgClass queryClass() const = 0;
+ virtual LogMsgCode queryLogMsgCode() const = 0;
+ virtual const char * queryMsg() const = 0;
+ virtual bool queryIsHidden() const = 0;
+};
+
+interface ISysInfoLoggerMsgIterator : implements IScmIterator
+{
+ virtual ISysInfoLoggerMsg & query() = 0;
+};
+
+SYSINFO_API ISysInfoLoggerMsgIterator * createSysInfoLoggerMsgIterator(unsigned year=0, unsigned month=0, unsigned day=0, bool visibleOnly=true, bool hiddenOnly=false);
+SYSINFO_API void logSysInfoError(const LogMsgCategory & cat, LogMsgCode code, const char * msg, unsigned __int64 ts=0);
+SYSINFO_API bool hideLogSysInfoMsg(LogMsgCategory & cat, LogMsgCode code, unsigned __int64 ts);
+SYSINFO_API bool deleteLogSysInfoMsg(LogMsgCategory & cat, LogMsgCode code, unsigned __int64 ts);
+SYSINFO_API unsigned deleteOlderThanLogSysInfoMsg(unsigned year=0, unsigned month=0, unsigned day=0, bool visibleOnly=true, bool hiddenOnly=true);
+
+#endif