浏览代码

Merge pull request #12939 from wangkx/h22584

HPCC-22584 Support new log format in WsTopology

Reviewed-By: Shamser Ahmed <shamser.ahmed@lexisnexis.co.uk>
Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 5 年之前
父节点
当前提交
d2722c3f4c
共有 2 个文件被更改,包括 271 次插入133 次删除
  1. 256 131
      esp/services/ws_topology/ws_topologyService.cpp
  2. 15 2
      esp/services/ws_topology/ws_topologyService.hpp

+ 256 - 131
esp/services/ws_topology/ws_topologyService.cpp

@@ -43,6 +43,10 @@ static const char* MACHINE_URL = "MachineInfoAccess";
 static const unsigned THORSTATUSDETAILS_REFRESH_MINS = 1;
 static const long LOGFILESIZELIMIT = 100000; //Limit page size to 100k
 static const long AVERAGELOGROWSIZE = 10000;
+static const unsigned LOG_DATE_STRING_LENGTH = 10;
+static const unsigned LOG_TIME_STRING_LENGTH = 8;
+static const unsigned LOG_LEGACY_DATE_POS = 9;
+static const unsigned LOG_LEGACY_TIME_POS = 20;
 const char* TEMPZIPDIR = "tempzipfiles";
 
 void CWsTopologyEx::init(IPropertyTree *cfg, const char *process, const char *service)
@@ -520,6 +524,30 @@ bool CWsTopologyEx::readLogTime(char* pTr, int start, int length, CDateTime& dt)
     return bRet;
 }
 
+void CWsTopologyEx::readLogMessageFields(char* logStart, size32_t bytesRemaining, ReadLog& readLogReq)
+{
+    //Find out the first log line
+    char* finger = logStart;
+    while (bytesRemaining > 0)
+    {
+        if (isLineTerminator(finger, bytesRemaining, readLogReq.ltBytes))
+            break;
+
+        finger++;
+        bytesRemaining--;
+    }
+
+    //Read LogMessageFields from the first log line
+    if (finger > logStart)
+    {
+        StringBuffer logLine;
+        logLine.append(finger - logStart, logStart);
+        readLogReq.logfields = getMessageFieldsFromHeader(logLine);
+        if (readLogReq.logfields == 0)
+            readLogReq.logfields = MSGFIELD_LEGACY;
+    }
+}
+
 bool CWsTopologyEx::findTimestampAndLT(const char *logname, IFile* rFile, ReadLog& readLogReq, CDateTime& latestLogTime)
 {
     OwnedIFileIO rIO = rFile->openShared(IFOread,IFSHfull);
@@ -539,192 +567,286 @@ bool CWsTopologyEx::findTimestampAndLT(const char *logname, IFile* rFile, ReadLo
 
     char* pTr = (char*) dataBuffer.str();
     readLogReq.ltBytes = findLineTerminator(pTr, bytesRead);
+    readLogMessageFields(pTr, bytesRead, readLogReq);
+
+    if (readLogReq.logfields == MSGFIELD_LEGACY)
+    {
+        unsigned logDateTimeStringLength = LOG_DATE_STRING_LENGTH + 1 + LOG_TIME_STRING_LENGTH;
+        if (rFile->size() < LOG_LEGACY_DATE_POS + logDateTimeStringLength) //lineID + timestamp
+            return false;//no timestamp in this log
+        if (!readLogTime(pTr, LOG_LEGACY_DATE_POS, logDateTimeStringLength, latestLogTime))
+            return false;//no timestamp in this log
+    }
+    else
+    {
+        readLogReq.columnNumDate = getPositionOfField(readLogReq.logfields, MSGFIELD_date);
+        if (readLogReq.columnNumDate == 0)
+            return false;//no log date in this log
 
-    if (rFile->size() < 28) //lineID + timestamp
-        return false;//no timestamp in this log
-    if (!readLogTime(pTr, 9, 19, latestLogTime))
-        return false;//no timestamp in this log
+        readLogReq.columnNumTime = getPositionOfField(readLogReq.logfields, MSGFIELD_milliTime);
+        if (readLogReq.columnNumTime == 0)
+            readLogReq.columnNumTime = getPositionOfField(readLogReq.logfields, MSGFIELD_time);
+        if (readLogReq.columnNumTime == 0)
+            readLogReq.columnNumTime = getPositionOfField(readLogReq.logfields, MSGFIELD_microTime);
+        if (readLogReq.columnNumTime == 0)
+            return false;//no log time in this log
+    }
 
     if ((readLogReq.filterType != GLOLastNHours) && (readLogReq.filterType != GLOTimeRange))
-        return true;//Not interested in the last timestamp this time
+        return true;//The last timestamp is only needed for time related filterType.
 
-    //Search the last chuck to find out the latest timestamp
+    return readLastLogDateTime(logname, rIO, fileSize, readSize, readLogReq, latestLogTime);
+}
+
+bool CWsTopologyEx::readLastLogDateTime(const char *logName, IFileIO* rIO, size32_t fileSize, size32_t readSize,
+    ReadLog& readLogReq, CDateTime& latestLogTime)
+{
+    size32_t readFrom = 0;
     if (readSize < fileSize)
+        readFrom = fileSize - readSize; //Search LastLogTime from the last chuck
+
+    StringBuffer contentBuffer, previousPartialLine;
+    while (1)
     {
-        bytesRead = rIO->read(fileSize - readSize, readSize, dataBuffer.clear().reserve(readSize));
+        size32_t bytesRead = rIO->read(readFrom, readSize, contentBuffer.clear().reserve(readSize));
         if (bytesRead != readSize)
-            throw MakeStringException(ECLWATCH_CANNOT_READ_FILE, "Failed to read file %s.", logname);
-        pTr = (char*) dataBuffer.str();
-    }
+            throw MakeStringException(ECLWATCH_CANNOT_READ_FILE, "Failed to read file %s.", logName);
+            
+        if (!previousPartialLine.isEmpty())
+            contentBuffer.append(previousPartialLine); //Append the leftover from the last chuck
 
-    //skip the first line which may not contain the entire log line
-    StringBuffer logLine;
-    size32_t bytesRemaining = bytesRead;
-    bool hasAnotherLine = false;
-    pTr = readALogLine(pTr, bytesRemaining, readLogReq.ltBytes, logLine, hasAnotherLine);
-    if (!hasAnotherLine || (bytesRemaining < 28))
-        return true;
+        const char* partialLineEndPtr = readLastLogDateTimeFromContentBuffer(contentBuffer, readLogReq, latestLogTime);
+        if (!partialLineEndPtr)
+            return true; //Found LastLogTime
 
-    //Find out the last timestamp
-    while (hasAnotherLine && bytesRemaining > 27)
-    {
-        CDateTime dt;
-        pTr = readALogLine(pTr, bytesRemaining, readLogReq.ltBytes, logLine, hasAnotherLine);
-        if ((logLine.length() > 27) && readLogTime((char*) logLine.str(), 9, 19, dt))
-            latestLogTime = dt;
+        if (readFrom == 0)
+            break;
+
+        previousPartialLine.clear().append(partialLineEndPtr - contentBuffer.str(), contentBuffer.str());
+        readFrom -= readSize;
+        if (readFrom < 0)
+            readFrom = 0;
     }
+    return false;
+}
 
-    return true;
+const char* CWsTopologyEx::readLastLogDateTimeFromContentBuffer(StringBuffer& contentBuffer, ReadLog& readLogReq, CDateTime& latestLogTime)
+{
+    //Search the contentBuffer from the bottom up.
+    //If find out a LineTerminator, check if the line after the LineTerminator has
+    //a log timestamp. If yes, it is the LastLogDateTime. If not, continue searching
+    //a log timestamp backwards. If there is no log timestamp in this contentBuffer,
+    //return the lineEndPtr which points to the end of the partial line possibly at the
+    //beginning of this contentBuffer.
+    StringBuffer logFieldTime;
+    const char* startPtr = contentBuffer.str();
+    const char* lineEndPtr = startPtr + contentBuffer.length();
+    const char* finger = lineEndPtr - 1;
+    while (finger > startPtr)
+    { 
+        if (isLineTerminator(finger, finger - lineEndPtr, readLogReq.ltBytes))
+        {
+            const char* lineStartPtr = finger + readLogReq.ltBytes;
+            if (readLogDateTimeFromLogLine(lineStartPtr, lineEndPtr - lineStartPtr, readLogReq, latestLogTime, logFieldTime.clear()))
+                return nullptr; //Found log date and time
+            lineEndPtr = finger; //Switch to the end of previous line
+        }
+        finger--;
+    }
+    return lineEndPtr;
 }
 
-char* CWsTopologyEx::readALogLine(char* dataPtr, size32_t& bytesRemaining, unsigned ltLength, StringBuffer& logLine, bool& hasLineTerminator)
+bool CWsTopologyEx::readLogDateTimeFromLogLine(const char* lineStart, const unsigned lineLength, ReadLog& readLogReq,
+    CDateTime& dt, StringBuffer& logFieldTime)
 {
-    char* pTr = dataPtr;
+    if ((readLogReq.logfields == MSGFIELD_LEGACY) && (lineLength < (LOG_LEGACY_TIME_POS + LOG_TIME_STRING_LENGTH)))
+        return false;
 
-    CDateTime dt;
-    hasLineTerminator = false;
-    while(bytesRemaining > 0)
-    {
-        hasLineTerminator = isLineTerminator(pTr, bytesRemaining, ltLength);
-        if (hasLineTerminator && (bytesRemaining > ltLength + 27) && readLogTime(pTr+ltLength, 9, 19, dt))
-            break;
+    StringBuffer dtStringBuffer;
+    if (readLogReq.logfields != MSGFIELD_LEGACY)
+        readLogField(lineStart, lineLength, readLogReq.columnNumDate, LOG_DATE_STRING_LENGTH, dtStringBuffer);
+    else
+        dtStringBuffer.append(LOG_DATE_STRING_LENGTH, lineStart + LOG_LEGACY_DATE_POS);
+    if (dtStringBuffer.isEmpty())
+        return false;
 
-        pTr++;
-        bytesRemaining--;
-    }
+    if (readLogReq.logfields != MSGFIELD_LEGACY)
+        readLogField(lineStart, lineLength, readLogReq.columnNumTime, LOG_TIME_STRING_LENGTH, logFieldTime);
+    else
+        logFieldTime.append(LOG_TIME_STRING_LENGTH, lineStart + LOG_LEGACY_TIME_POS);
+    if (logFieldTime.isEmpty())
+        return false;
+
+    dtStringBuffer.append('T').append(logFieldTime);
 
-    if (hasLineTerminator && (bytesRemaining > 0))
+    try
+    {
+        dt.setString(dtStringBuffer.str(), NULL, false);
+        return true;
+    }
+    catch(IException* e)
     {
-        pTr += ltLength;
-        bytesRemaining -= ltLength;
+        e->Release();
     }
+    return false;
+}
 
-    logLine.clear();
-    if (pTr > dataPtr)
-        logLine.append(dataPtr, 0, pTr - dataPtr);
+void CWsTopologyEx::readLogField(const char* lineStart, const unsigned lineLength, const unsigned columnNum,
+    const unsigned columnLength, StringBuffer& logField)
+{
+    const char* ptr = lineStart;
+    unsigned byteChecked = 0;
+    for (unsigned cur = 0;  cur < columnNum && (byteChecked < lineLength); ++cur)
+    {
+        while ((byteChecked < lineLength) && (*ptr != ' '))   // Skip field
+        {
+            ++ptr; ++byteChecked;
+        }
+        while ((byteChecked < lineLength) && (*ptr == ' '))   // Skip spaces
+        {
+            ++ptr; ++byteChecked;
+        }
+    }
 
-    return pTr;
+    if ((lineLength - byteChecked) >= columnLength)
+        logField.append(columnLength, ptr);
 }
 
-void CWsTopologyEx::addALogLine(offset_t& readFrom, unsigned& locationFlag, const char * dataRow, ReadLog& readLogReq, StringArray& returnbuff)
-{
+void CWsTopologyEx::addALogLine(offset_t& readFrom, unsigned& logFlag, const char * dataRow, ReadLog& readLogReq,
+    StringBuffer& logTimeString, StringArray& returnBuf)
+{//readLogReq.filterType: GLOFirstNRows, GLOLastNHours or GLOTimeRange
     size_t len = strlen(dataRow);
     if (readLogReq.filterType == GLOFirstNRows)
     {
-        locationFlag = 1; //enter the area to be retrieved
-        returnbuff.append(dataRow);
-        if (returnbuff.length() == readLogReq.firstRows)
-            locationFlag = 2; //stop now since we have enough rows
-    }
-    else if (readLogReq.filterType == GLOLastNRows)
-    {
-        if (returnbuff.length() == readLogReq.lastRows)
-            returnbuff.remove(readLogReq.lastRows - 1);
-
-        returnbuff.add(dataRow, 0);
-        locationFlag = 1; //always in the area to be retrieved, but extra lines may be pushed out later
+        returnBuf.append(dataRow);
+        if (returnBuf.length() == readLogReq.firstRows)
+            logFlag = 2; //stop now since we have enough rows
     }
     else
-    {
-        unsigned long rowID;
-        if (!readLogLineID(dataRow, rowID)) //row id (and timestamp) not found in this log line
-        {
-            if (locationFlag > 0)
-                returnbuff.append(dataRow);
-            readFrom += len;
-            return;
-        }
-
-        StringBuffer str;
-        str.append(dataRow, 20, 8); //Read time
-        if (readLogReq.endDate.length() > 0 && strcmp(str.str(), readLogReq.endDate.str()) > 0)
-            locationFlag = 2; //out of the area to be retrieved
-        else if (readLogReq.startDate.length() > 1 && strcmp(str.str(), readLogReq.startDate.str()) < 0)
+    {   //readLogReq.filterType: GLOLastNHours or GLOTimeRange
+        //Check time
+        if (!readLogReq.endDate.isEmpty() && !logTimeString.isEmpty() && strcmp(logTimeString.str(), readLogReq.endDate.str()) > 0)
+            logFlag = 2; //out of the area to be retrieved
+        else if (!readLogReq.startDate.isEmpty() && !logTimeString.isEmpty() && strcmp(logTimeString.str(), readLogReq.startDate.str()) < 0)
             readFrom += len; //skip this line
         else
         {
-            returnbuff.append(dataRow);
-            if ((locationFlag < 1) && (readLogReq.filterType == GLOTimeRange))
+            returnBuf.append(dataRow);
+            if ((logFlag < 1) && (readLogReq.filterType == GLOTimeRange))
                 readLogReq.pageFrom = readFrom;
 
             readFrom += len;
-            locationFlag = 1; //enter the area to be retrieved
+            logFlag = 1; //enter the area to be retrieved
         }
     }
     return;
 }
 
-void CWsTopologyEx::readLogFileToArray(const char *logname, OwnedIFileIO rIO, ReadLog& readLogReq, StringArray& returnbuf)
-{
-    bool firstChuck = true;
-    bool lastChuck = false;
+void CWsTopologyEx::readLogFileToArray(const char *logname, OwnedIFileIO rIO, ReadLog& readLogReq, StringArray& returnBuf)
+{//readLogReq.filterType: GLOFirstNRows, GLOLastNRows, GLOLastNHours or GLOTimeRange
+    if (readLogReq.filterType == GLOLastNRows)
+    {
+        readLastNRowsToArray(logname, rIO, readLogReq, returnBuf);
+        return;
+    }
 
-    offset_t readFrom = 0;
-    offset_t logLineFrom = 0;
-    unsigned locationFlag = 0; //0: before the interested log lines are reached, 1: found the log lines, 2: out
-    StringBuffer logLine;
+    StringBuffer logLine, textLine, logFieldTime, logFieldTimeNextLine; //A logLine may has multiple textLines.
+    Owned<IFileIOStream> ios = createIOStream(rIO);
+    Owned<IStreamLineReader> lineReader = createLineReader(ios, true);
+    bool eof = lineReader->readLine(logLine);
+    if (eof)
+        return;
 
-    offset_t bytesLeft = readLogReq.fileSize;
-    while (bytesLeft > 0)
-    {
-        size32_t readSize;
-        StringBuffer dataBuffer;
+    if ((readLogReq.logfields != MSGFIELD_LEGACY) && (readLogReq.filterType != GLOFirstNRows))
+    { //skip the title line
+        if (lineReader->readLine(logLine.clear()))
+            return;
+    }
+
+    CDateTime dt;
+    readLogDateTimeFromLogLine(logLine, logLine.length(), readLogReq, dt, logFieldTime);
 
-        //Find out where to read and how many bytes to read
-        if (!firstChuck || (readLogReq.filterType != GLOLastNRows))
+    offset_t logLinePos = 0;
+    unsigned logFlag = 0; //0: before the interested log lines are reached, 1: found the lines to be logged, 2: no more line should be logged.
+    while (!eof)
+    {
+        eof = lineReader->readLine(textLine.clear());
+        if (!eof)
         {
-            if (bytesLeft > LOGFILESIZELIMIT)
-                readSize = LOGFILESIZELIMIT;
-            else
+            if (!readLogDateTimeFromLogLine(textLine, textLine.length(), readLogReq, dt, logFieldTimeNextLine.clear()))
             {
-                readSize = (size32_t) bytesLeft;
-                lastChuck = true;
+                logLine.append(textLine); //This line does not contain a timestamp, so it is not a new log line.
+                continue;
             }
-            bytesLeft -= readSize;
         }
-        else
+
+        if (!logLine.isEmpty())
+            addALogLine(logLinePos, logFlag, logLine, readLogReq, logFieldTime, returnBuf);
+        if (logFlag > 1) //interested log lines have been finished
+            break;
+        if (!eof)
         {
-            //read the last chuck since the file is too big
-            size32_t estimateSize = AVERAGELOGROWSIZE*readLogReq.lastRows;
-            if (readLogReq.fileSize < estimateSize)
-                readSize = (size32_t) readLogReq.fileSize;
-            else
-                readSize = estimateSize;
-            readFrom = (size32_t) readLogReq.fileSize-readSize;
-            bytesLeft = 0;
-            lastChuck = true;
+            logLine.set(textLine); //The textLine has a new timestamp. It is a part of new log line.
+            logFieldTime.set(logFieldTimeNextLine);
         }
+    }
+}
 
-        //read a chuck of log to a buffer
-        if (logLine.length() > 0) //check any left from a previous chunk
-            dataBuffer.append(logLine.str());
-        size32_t nRead = rIO->read(readFrom, readSize, dataBuffer.reserve(readSize));
-        if (nRead != readSize)
-            throw MakeStringException(ECLWATCH_CANNOT_READ_FILE, "Failed to read file %s.", logname);
-
-        logLineFrom = readFrom;
-        readFrom += nRead;
-
-        //get the log lines from the buffer
-        size32_t bytesRemaining = dataBuffer.length();
-        char* pTr = (char*) dataBuffer.str();
-        bool hasAnotherLine = true;
-        while (hasAnotherLine && (bytesRemaining > 0) && (locationFlag < 2))
+void CWsTopologyEx::readLastNRowsToArray(const char* logName, OwnedIFileIO rIO, ReadLog& readLogReq, StringArray& returnBuf)
+{
+    size32_t readSize = AVERAGELOGROWSIZE*readLogReq.lastRows;
+    if (readLogReq.fileSize < readSize)
+        readSize = (size32_t) readLogReq.fileSize;
+
+    CDateTime dt; //Not used for readLastNRows
+    StringBuffer logFieldTime; //Not used for readLastNRows
+    StringBuffer contentBuffer, logLine, previousPartialLine;
+    bool nRowsLogged = false;
+
+    //read the last chuck since the file may be too big
+    size32_t readFrom = (size32_t) readLogReq.fileSize-readSize;
+    while (1)
+    {
+        size32_t bytesRead = rIO->read(readFrom, readSize, contentBuffer.clear().reserve(readSize));
+        if (bytesRead != readSize)
+            throw MakeStringException(ECLWATCH_CANNOT_READ_FILE, "Failed to read file %s.", logName);
+            
+        if (!previousPartialLine.isEmpty())
+            contentBuffer.append(previousPartialLine); //Add the leftover from previous contentBuffer
+
+        //Read log lines backwards until nRowsLogged
+        const char* startPtr = contentBuffer.str();
+        const char* lineEndPtr = startPtr + contentBuffer.length();
+        const char* finger = lineEndPtr - 1;
+        while (finger > startPtr)
         {
-            pTr = readALogLine(pTr, bytesRemaining, readLogReq.ltBytes, logLine.clear(), hasAnotherLine);
-            if (logLine.length() < 1)
-                continue;
-            if (lastChuck || hasAnotherLine)
-                addALogLine(logLineFrom, locationFlag, logLine, readLogReq, returnbuf);
+            if (isLineTerminator(finger, finger - lineEndPtr, readLogReq.ltBytes))
+            {
+                const char* textLineStart = finger + readLogReq.ltBytes;
+                if (readLogDateTimeFromLogLine(textLineStart, lineEndPtr - textLineStart, readLogReq, dt, logFieldTime.clear()))
+                { //Found log date and time. This text line is the beginning of a log line.
+                    logLine.clear().append(lineEndPtr - textLineStart, textLineStart);
+                    returnBuf.append(logLine);
+                    if (returnBuf.length() == readLogReq.lastRows)
+                    {
+                        nRowsLogged = true;
+                        break;
+                    }
+                    lineEndPtr = finger + readLogReq.ltBytes; //Switch to the end of previous line with LineTerminator
+                }
+            }
+
+            finger--;
         }
 
-        if (locationFlag > 1) //interested log lines have been finished
+        if (nRowsLogged || (readFrom == 0))
             break;
 
-        firstChuck = false;
+        previousPartialLine.clear().append(lineEndPtr - startPtr, startPtr);
+        readFrom -= readSize;
+        if (readFrom < 0)
+            readFrom = 0;
     }
-
-    return;
 }
 
 void CWsTopologyEx::readLogFile(const char *logname, IFile* rFile, ReadLog& readLogReq, StringBuffer& returnbuff)
@@ -798,6 +920,9 @@ void CWsTopologyEx::readTpLogFileRequest(IEspContext &context, const char* fileN
     readLogReq.prevPage = -1;
     readLogReq.nextPage = -1;
     readLogReq.lastHours = 0;
+    readLogReq.logfields = MSGFIELD_LEGACY;//legacy log format
+    readLogReq.columnNumDate = 0;
+    readLogReq.columnNumTime = 0;
 
     CDateTime latestLogTime;
     readLogReq.hasTimestamp = findTimestampAndLT(fileName, rFile, readLogReq, latestLogTime);

+ 15 - 2
esp/services/ws_topology/ws_topologyService.hpp

@@ -54,6 +54,9 @@ struct ReadLog
     unsigned prevPage;
     unsigned nextPage;
     unsigned TotalPages;
+    unsigned logfields;
+    unsigned columnNumDate;
+    unsigned columnNumTime;
 };
 
 class CWsTopologySoapBindingEx : public CWsTopologySoapBinding
@@ -101,8 +104,8 @@ private:
     bool findTimestampAndLT(const char * logname, IFile* pFile, ReadLog& readLogReq, CDateTime& latestLogTime);
     unsigned findLineTerminator(const char* dataPtr, const size32_t dataSize);
     bool isLineTerminator(const char* dataPtr, const size32_t dataSize, unsigned ltLength);
-    char* readALogLine(char* dataPtr, size32_t& dataSize, unsigned ltLength, StringBuffer& logLine, bool& hasLineTerminator);
-    void addALogLine(offset_t& readFrom, unsigned& locationFlag, const char *dataRow, ReadLog& readLogReq, StringArray& returnbuff);
+    char* readALogLine(char* dataPtr, size32_t& dataSize, ReadLog& readLogReq, StringBuffer& logLine, bool& hasLineTerminator);
+    void addALogLine(offset_t& readFrom, unsigned& locationFlag, const char *dataRow, ReadLog& readLogReq, StringBuffer& logTimeString, StringArray& returnbuff);
     void readTpLogFileRequest(IEspContext &context, const char* fileName, IFile* rFile, IEspTpLogFileRequest  &req, ReadLog& readLogReq);
     void setTpLogFileResponse(IEspContext &context, ReadLog& readLogReq, const char* fileName,
                                          const char* fileType, StringBuffer& returnbuf, IEspTpLogFileResponse &resp);
@@ -112,6 +115,16 @@ private:
                                     bool& bThresholdIsPercentage);
 
     StringBuffer& getAcceptLanguage(IEspContext& context, StringBuffer& acceptLanguage);
+    void readLogMessageFields(char* logStart, size32_t bytesRemaining, ReadLog& readLogReq);
+    const char* readLastLogDateTimeFromContentBuffer(StringBuffer& content, ReadLog& readLogReq, CDateTime& latestLogTime);
+    bool readLastLogDateTime(const char* logName, IFileIO* rIO, size32_t fileSize, size32_t readSize,
+        ReadLog& readLogReq, CDateTime& latestLogTime);
+    void readLogField(const char* lineStart, const unsigned lineLength, const unsigned columnNum,
+        const unsigned columnLength, StringBuffer& logField);
+    bool readLogDateTimeFromLogLine(const char* lineStart, const unsigned lineLength, ReadLog& readLogReq,
+        CDateTime& dt, StringBuffer& logFieldTime);
+    void readLastNRowsToArray(const char* logName, OwnedIFileIO rIO, ReadLog& readLogReq, StringArray& returnBuf);
+
 public:
     IMPLEMENT_IINTERFACE;
     virtual ~CWsTopologyEx(){};