瀏覽代碼

Merge pull request #2522 from wangkx/gh2476a

Fix gh-2476 get thor slave log on node getting wrong log

Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 13 年之前
父節點
當前提交
1929dbd966

+ 12 - 0
common/workunit/workunit.cpp

@@ -4043,6 +4043,18 @@ extern WORKUNIT_API StringBuffer &getClusterThorQueueName(StringBuffer &ret, con
     return ret.append(cluster).append(THOR_QUEUE_EXT);
 }
 
+extern WORKUNIT_API StringBuffer &getClusterThorGroupName(StringBuffer &ret, const char *cluster)
+{
+    StringBuffer path;
+    Owned<IRemoteConnection> conn = querySDS().connect(path.append("Environment/Software/ThorCluster[@name=\"").append(cluster).append("\"]").str(), myProcessSession(), RTM_LOCK_READ, SDS_LOCK_TIMEOUT);
+    if (conn)
+    {
+        getClusterGroupName(*conn->queryRoot(), ret);
+    }
+
+    return ret;
+}
+
 extern WORKUNIT_API StringBuffer &getClusterRoxieQueueName(StringBuffer &ret, const char *cluster)
 {
     return ret.append(cluster).append(ROXIE_QUEUE_EXT);

+ 1 - 0
common/workunit/workunit.hpp

@@ -1140,6 +1140,7 @@ extern WORKUNIT_API IStringVal &getAgentQueueNames(IStringVal &ret, const char *
 extern WORKUNIT_API IStringVal &getRoxieQueueNames(IStringVal &ret, const char *process);
 extern WORKUNIT_API IStringVal &getThorQueueNames(IStringVal &ret, const char *process);
 extern WORKUNIT_API StringBuffer &getClusterThorQueueName(StringBuffer &ret, const char *cluster);
+extern WORKUNIT_API StringBuffer &getClusterThorGroupName(StringBuffer &ret, const char *cluster);
 extern WORKUNIT_API StringBuffer &getClusterRoxieQueueName(StringBuffer &ret, const char *cluster);
 extern WORKUNIT_API StringBuffer &getClusterEclCCServerQueueName(StringBuffer &ret, const char *cluster);
 extern WORKUNIT_API StringBuffer &getClusterEclServerQueueName(StringBuffer &ret, const char *cluster);

+ 120 - 28
esp/eclwatch/ws_XSLT/wuid.xslt

@@ -63,30 +63,128 @@
             var Jobname = '<xsl:value-of select="$jobName"/>';
 
           <xsl:text disable-output-escaping="yes"><![CDATA[
-           var url0 = '';
-           var reloadTimer = null;
-                     var reloadTimeout = 0;
-           var sections = new Array("Exceptions","Graphs","SourceFiles","Results","Variables","Timers","DebugValues","ApplicationValues","Workflows");
-           var activeSections = new Array();
+            var url0 = '';
+            var reloadTimer = null;
+            var reloadTimeout = 0;
+            var sections = new Array("Exceptions","Graphs","SourceFiles","Results","Variables","Timers","DebugValues","ApplicationValues","Workflows");
+            var activeSections = new Array();
+            var thorProcess;
+            var thorGroup;
+            var thorLogDate;
+            var numberOfSlaves;
          
-                     // This function gets called when the window has completely loaded.
-                     // It starts the reload timer with a default time value.
+            function CheckSlaveNum(e)
+            {
+                if (document.getElementById('NumberSlaves').disabled == 'true')
+                    return false;
+
+                var key;
+                if (window.event)
+                   key = window.event.keyCode;
+                else if (e)
+                   key = e.which;
+                else
+                   return true;
 
-     function onLoad()
-     {
-        /*
-        initialize();
-            
-          if (isarchived)
-        {
-          return;
-        }
-        UpdateAutoRefresh();
-        //reloadSection('Exceptions');
-        checkPreloadedSections();
-        */
-        return;
-     } 
+                var keychar = String.fromCharCode(key);
+
+                if ((("0123456789").indexOf(keychar) > -1))
+                   return true;
+                else
+                   return false;
+            }
+
+            function CheckSlaveAddress(e)
+            {
+                if (document.getElementById('SlaveAddress').disabled == 'true')
+                    return false;
+
+                var key;
+                if (window.event)
+                   key = window.event.keyCode;
+                else if (e)
+                   key = e.which;
+                else
+                   return true;
+
+                var keychar = String.fromCharCode(key);
+
+                if ((("0123456789_.").indexOf(keychar) > -1))
+                   return true;
+                else
+                   return false;
+            }
+
+            function thorProcessChanged(value)
+            {
+                pos = value.indexOf('@');
+                thorLogDate = value.substring(pos+1);
+                numberOfSlaves = value.substring(0, pos);
+                pos1 = thorLogDate.indexOf('@');
+                thorProcess = thorLogDate.substring(pos1+1);
+                thorLogDate = thorLogDate.substring(0, pos1);
+                pos2 = thorProcess.indexOf('@');
+                thorGroup = thorProcess.substring(pos2+1);
+                thorProcess = thorProcess.substring(0, pos2);
+
+                var el = document.getElementById('NumberSlaves');
+                if (el == undefined)
+                    return;
+
+                if (numberOfSlaves == '1')
+                {
+                    el.innerText = '';
+                    document.getElementById('SlaveNum').disabled=true;
+                }
+                else
+                {
+                    el.innerText = ' (from 1 to ' + numberOfSlaves + ')';
+                    document.getElementById('SlaveNum').disabled = false;
+                }
+                document.getElementById('SlaveNum').value = '1';
+            }
+
+            function GetThorSlaveLog()
+            {
+                if (document.getElementById('NumberSlaves') != undefined)
+                {
+                    var slaveNum = document.getElementById('SlaveNum').value;
+                    if (slaveNum > numberOfSlaves)
+                    {
+                        alert('Slave Number cannot be greater than ' + numberOfSlaves);
+                        return;
+                    }
+
+                    getOptions('ThorSlave.log', '/WsWorkunits/WUFile?Wuid='+wid+'&Type=ThorSlaveLog&Process='
+                    +thorProcess+'&ClusterGroup='+thorGroup+'&LogDate='+thorLogDate+'&SlaveNumber='+slaveNum, true);
+                }
+                else
+                {
+                    var el = document.getElementById('SlaveAddress');
+                    if (el.value == '')
+                    {
+                        alert('Slave address not specified');
+                        return;
+                    }
+
+                    getOptions('ThorSlave.log', '/WsWorkunits/WUFile?Wuid='+wid+'&Type=ThorSlaveLog&SlaveNumber=0&Process='
+                    +document.getElementById('ProcessName').value+'&IPAddress='+el.value+'&LogDate='
+                    +document.getElementById('LogDate').value, true);
+                }
+            }
+
+            // This function gets called when the window has completely loaded.
+            // It starts the reload timer with a default time value.
+            function onLoad()
+            {
+                var thorProcessDropDown = document.getElementById('ThorProcess');
+                if (thorProcessDropDown == undefined)
+                    return;
+
+                thorProcessChanged(thorProcessDropDown.options[thorProcessDropDown.selectedIndex].value);
+
+                return;
+            }
 
    function onUnload()
    {
@@ -275,12 +373,6 @@
                             logBtn.disabled = true;
                    }
 
-                   function GetThorSlaveLog() 
-                   {
-                      //document.location.href='/WsWorkunits/WUFile?Wuid='+wid+'&Type=ThorSlaveLog&SlaveIP='+document.getElementById('ThorSlaveIP').value;
-            getOptions('ThorSlave.log', '/WsWorkunits/WUFile?Wuid='+wid+'&Type=ThorSlaveLog&SlaveIP='+document.getElementById('ThorSlaveIP').value, true); 
-                   }
-
            var downloadWnds = new Array();
          
            function getLink(ParentElement, Link)

+ 40 - 19
esp/eclwatch/ws_XSLT/wuidcommon.xslt

@@ -721,21 +721,41 @@
           </div>
         </xsl:if>
 
-        <xsl:if test="number(ClusterFlag)=1">
-        <table class="workunit">
-          <colgroup>
-            <col width="20%"/>
-            <col width="80%"/>
-          </colgroup>
-                 <tr>
-                    <td></td>
-                    <td>
-                      <input id="getthorslavelog" type="button" value="GetThorSlaveLog on >>" onclick="GetThorSlaveLog()" disabled="true"> </input>
-                      <input type="text" id="ThorSlaveIP" name="ThorSlaveIP" value="{$thorSlaveIP}" size="40" onkeyup="CheckIPInput();"/>
+        <xsl:if test="(number(ClusterFlag)=1) and (count(ThorLogList/ThorLogInfo) > 0)">
+            <table class="workunit">
+                <colgroup>
+                    <col width="20%"/>
+                    <col width="80%"/>
+                </colgroup>
+                <tr>
+                    <td colspan="3">
+                        <div style="border:1px solid grey;">
+                            <input id="getthorslavelog" type="button" value="Get slave log" onclick="GetThorSlaveLog()"> </input>
+                            <xsl:choose>
+                                <xsl:when test="number(ThorLogList/ThorLogInfo[1]/NumberSlaves) != 0">
+                                    Thor Process: <select id="ThorProcess" name="ThorProcess" onchange="thorProcessChanged(options[selectedIndex].value)">
+                                    <xsl:for-each select="ThorLogList/ThorLogInfo">
+                                        <xsl:variable name="val">
+                                            <xsl:value-of select="./NumberSlaves"/>@<xsl:value-of select="./LogDate"/>@<xsl:value-of select="./ProcessName"/>@<xsl:value-of select="./ClusterGroup"/>
+                                        </xsl:variable>
+                                        <option value="{$val}">
+                                            <xsl:value-of select="./ProcessName"/>
+                                        </option>
+                                    </xsl:for-each>
+                                    </select>
+                                    Slave Number<span id="NumberSlaves"></span>: <input type="text" id="SlaveNum" name="SlaveNum" value="1" size="4" onkeypress="return CheckSlaveNum(event);"/>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <input type="hidden" id="ProcessName" value="{ThorLogList/ThorLogInfo[1]/ProcessName}"/>
+                                    <input type="hidden" id="LogDate" value="{ThorLogList/ThorLogInfo[1]/LogDate}"/>
+                                    on: <input type="text" id="SlaveAddress" name="SlaveAddress" title="Type in NetworkAddress or NetworkAddress_Port where the slave run" value="" size="16" onkeypress="return CheckSlaveAddress(event);"/>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </div>
                     </td>
-                 </tr>
-        </table>
-          <br/>
+                </tr>
+            </table>
+            <br/>
         </xsl:if>
         <table class="workunit">
           <colgroup>
@@ -1227,24 +1247,25 @@
       </xsl:if>
       <xsl:if test="starts-with(Type, 'ThorLog')">
         <td>
-          <a href="/WsWorkunits/WUFile/ThorLog?Wuid={$wuid}&amp;Type={Type}"
+          <a href="/WsWorkunits/WUFile/ThorLog?Wuid={$wuid}&amp;Process={Description}&amp;Type={Type}"
                         >
             thormaster.log: <xsl:value-of select="Name"/>
           </a>
         </td>
         <td>
-          <a href="javascript:void(0)" onclick="getOptions('thormaster.log', '/WsWorkunits/WUFile/ThorLog?Wuid={$wuid}&amp;Type={Type}', false); return false;">
+          <a href="javascript:void(0)" onclick="getOptions('thormaster.log', '/WsWorkunits/WUFile/ThorLog?Wuid={$wuid}&amp;Process={Description}&amp;Type={Type}', false); return false;">
             download
           </a>
         </td>
       </xsl:if>
       <xsl:if test="Type = 'EclAgentLog'">
         <td>
-          <a href="/WsWorkunits/WUFile/EclAgentLog?Wuid={$wuid}&amp;Type=EclAgentLog"
-                        >eclagent.log</a>
+          <a href="/WsWorkunits/WUFile/EclAgentLog?Wuid={$wuid}&amp;Process={Description}&amp;Type=EclAgentLog">
+              eclagent.log: <xsl:value-of select="Name"/>
+          </a>
         </td>
         <td>
-          <a href="javascript:void(0)" onclick="getOptions('eclagent.log', '/WsWorkunits/WUFile/EclAgentLog?Wuid={$wuid}&amp;Type=EclAgentLog', false); return false;">
+          <a href="javascript:void(0)" onclick="getOptions('eclagent.log', '/WsWorkunits/WUFile/EclAgentLog?Wuid={$wuid}&amp;Process={Description}&amp;Type=EclAgentLog', false); return false;">
             download
           </a>
         </td>

+ 14 - 2
esp/scm/ws_workunits.ecm

@@ -155,6 +155,14 @@ ESPStruct [nil_remove] ECLWorkflow
     int CountRemaining(-1);
 };
 
+ESPStruct [nil_remove] ThorLogInfo
+{
+    string ProcessName;
+    string ClusterGroup;
+    string LogDate;
+    int    NumberSlaves;
+};
+
 ESPStruct [nil_remove] ECLWorkunit
 {
     string Wuid;
@@ -220,6 +228,7 @@ ESPStruct [nil_remove] ECLWorkunit
     [min_ver("1.29")] string ApplicationValuesDesc;
     [min_ver("1.29")] string WorkflowsDesc;
     [min_ver("1.31")] bool HasArchiveQuery(false);
+    [min_ver("1.38")] ESParray<ESPstruct ThorLogInfo> ThorLogList;
 
 };
 
@@ -568,7 +577,6 @@ ESPrequest WUInfoRequest
     [min_ver("1.25")] string ThorSlaveIP;
 };
 
-
 ESPresponse [exceptions_inline] WUInfoResponse
 {
     ESPstruct ECLWorkunit Workunit;
@@ -604,6 +612,10 @@ ESPrequest WULogFileRequest
     [min_ver("1.32")] string Description;
     [min_ver("1.36")] string QuerySet;
     [min_ver("1.36")] string Query;
+    [min_ver("1.38")] string Process;
+    [min_ver("1.38")] string ClusterGroup;
+    [min_ver("1.38")] string LogDate;
+    [min_ver("1.38")] int SlaveNumber(1);
     string PlainText;
 };
 
@@ -1239,7 +1251,7 @@ ESPresponse [exceptions_inline] WUQuerySetCopyQueryResponse
 };
 
 ESPservice [
-    version("1.37"), default_client_version("1.37"),
+    version("1.38"), default_client_version("1.38"),
     noforms,exceptions_inline("./smc_xslt/exceptions.xslt"),use_method_name] WsWorkunits
 {
     ESPmethod [resp_xsl_default("/esp/xslt/workunits.xslt")]     WUQuery(WUQueryRequest, WUQueryResponse);

+ 291 - 42
esp/services/ws_workunits/ws_workunitsHelpers.cpp

@@ -465,36 +465,55 @@ bool WsWuInfo::getHelpers(IEspECLWorkunit &info, unsigned flags)
             getHelpFiles(query, FileTypeDll, helpers);
             getHelpFiles(query, FileTypeResText, helpers);
 
-            SCMStringBuffer name;
-            for (int i0 = 1; i0 < MAXTHORS; i0++)
+            getWorkunitThorLogInfo(helpers, info);
+
+            if (cw->getWuidVersion() > 0)
             {
-                StringBuffer fileType;
-                if (i0 < 2)
-                    fileType.append(File_ThorLog);
-                else
-                    fileType.appendf("%s%d", File_ThorLog, i0);
-                cw->getDebugValue(fileType.str(), name);
-                if(name.length() < 1)
-                    break;
+                Owned<IStringIterator> eclAgentInstances = cw->getProcesses("EclAgent");
+                ForEach (*eclAgentInstances)
+                {
+                    SCMStringBuffer processName;
+                    eclAgentInstances->str(processName);
+                    if (processName.length() < 1)
+                        continue;
 
-                Owned<IEspECLHelpFile> h= createECLHelpFile("","");
-                h->setName(name.str());
-                h->setType(fileType.str());
-                helpers.append(*h.getLink());
-                name.clear();
-            }
+                    Owned<IStringIterator> eclAgentLogs = cw->getLogs("EclAgent", processName.str());
+                    ForEach (*eclAgentLogs)
+                    {
+                        SCMStringBuffer logName;
+                        eclAgentLogs->str(logName);
+                        if (logName.length() < 1)
+                            continue;
 
-            cw->getDebugValue("EclAgentLog", name);
-            if(name.length())
+                        Owned<IEspECLHelpFile> h= createECLHelpFile("","");
+                        h->setName(logName.str());
+                        h->setDescription(processName.str());
+                        h->setType(File_EclAgentLog);
+                        helpers.append(*h.getLink());
+                    }
+                }
+            }
+            else // legacy wuid
             {
-                Owned<IEspECLHelpFile> h= createECLHelpFile("","");
-                h->setName(name.str());
-                h->setType("EclAgentLog");
-                helpers.append(*h.getLink());
-                name.clear();
+                Owned<IStringIterator> eclAgentLogs = cw->getLogs("EclAgent");
+                ForEach (*eclAgentLogs)
+                {
+                    SCMStringBuffer name;
+                    eclAgentLogs->str(name);
+                    if (name.length() < 1)
+                        continue;
+
+                    Owned<IEspECLHelpFile> h= createECLHelpFile("","");
+                    h->setName(name.str());
+                    h->setType(File_EclAgentLog);
+                    helpers.append(*h.getLink());
+                    break;
+                }
             }
-         info.setHelpers(helpers);
-         return true;
+
+            info.setHelpers(helpers);
+
+            return true;
         }
     }
      catch(IException* e)
@@ -875,6 +894,152 @@ void WsWuInfo::getInfo(IEspECLWorkunit &info, unsigned flags)
         info.setWorkflowsDesc(msg);
 }
 
+unsigned WsWuInfo::getWorkunitThorLogInfo(IArrayOf<IEspECLHelpFile>& helpers, IEspECLWorkunit &info)
+{
+    unsigned countThorLog = 0;
+
+    IArrayOf<IConstThorLogInfo> thorLogList;
+    if (cw->getWuidVersion() > 0)
+    {
+        Owned<IConstWUClusterInfo> clusterInfo = getTargetClusterInfo("thor");
+        unsigned numberOfSlaves = clusterInfo->getSize();
+
+        Owned<IStringIterator> thorInstances = cw->getProcesses("Thor");
+        ForEach (*thorInstances)
+        {
+            SCMStringBuffer processName;
+            thorInstances->str(processName);
+            if (processName.length() < 1)
+                continue;
+
+            StringBuffer groupName;
+            getClusterThorGroupName(groupName, processName.str());
+
+            Owned<IStringIterator> thorLogs = cw->getLogs("Thor", processName.str());
+            ForEach (*thorLogs)
+            {
+                SCMStringBuffer logName;
+                thorLogs->str(logName);
+                if (logName.length() < 1)
+                    continue;
+
+                countThorLog++;
+
+                StringBuffer fileType;
+                if (countThorLog < 2)
+                    fileType.append(File_ThorLog);
+                else
+                    fileType.appendf("%s%d", File_ThorLog, countThorLog);
+
+                Owned<IEspECLHelpFile> h= createECLHelpFile("","");
+                h->setName(logName.str());
+                h->setDescription(processName.str());
+                h->setType(fileType.str());
+                helpers.append(*h.getLink());
+
+                if (version < 1.38)
+                    continue;
+
+                const char* pStr = logName.str();
+                const char* ppStr = strstr(pStr, "/thormaster.");
+                if (!ppStr)
+                {
+                    WARNLOG("Invalid thorlog entry in workunit xml: %s", logName.str());
+                    continue;
+                }
+
+                ppStr += 12;
+                StringBuffer logDate = ppStr;
+                logDate.setLength(10);
+
+                Owned<IEspThorLogInfo> thorLog = createThorLogInfo("","");
+                thorLog->setProcessName(processName.str());
+                thorLog->setClusterGroup(groupName.str());
+                thorLog->setLogDate(logDate.str());
+                thorLog->setNumberSlaves(numberOfSlaves);
+                thorLogList.append(*thorLog.getLink());
+            }
+        }
+    }
+    else //legacy wuid
+    {
+        SCMStringBuffer name;
+        for (int i0 = 1; i0 < MAXTHORS; i0++)
+        {
+            StringBuffer fileType;
+            if (i0 < 2)
+                fileType.append(File_ThorLog);
+            else
+                fileType.appendf("%s%d", File_ThorLog, i0);
+            cw->getDebugValue(fileType.str(), name);
+            if(name.length() < 1)
+                break;
+
+            Owned<IEspECLHelpFile> h= createECLHelpFile("","");
+            h->setName(name.str());
+            h->setType(fileType.str());
+            helpers.append(*h.getLink());
+            name.clear();
+        }
+
+        StringBuffer logDir;
+        Owned<IEnvironmentFactory> envFactory = getEnvironmentFactory();
+        Owned<IConstEnvironment> constEnv = envFactory->openEnvironmentByFile();
+        constEnv->getPTree().getProp("EnvSettings/log", logDir);
+        if (logDir.length() > 0)
+        {
+            Owned<IStringIterator> debugs = cw->getLogs("Thor");
+            ForEach(*debugs)
+            {
+                SCMStringBuffer val;
+                debugs->str(val);
+                if (val.length() < 1)
+                    continue;
+
+                const char* pStr = val.str();
+                const char* ppStr = strstr(pStr, logDir.str());
+                if (!ppStr)
+                {
+                    WARNLOG("Invalid thorlog entry in workunit xml: %s", val.str());
+                    continue;
+                }
+
+                const char* pProcessName = ppStr + logDir.length();
+                char sep = pProcessName[0];
+                StringBuffer processName = pProcessName + 1;
+                ppStr = strchr(pProcessName + 1, sep);
+                if (!ppStr)
+                {
+                    WARNLOG("Invalid thorlog entry in workunit xml: %s", val.str());
+                    continue;
+                }
+                processName.setLength(ppStr - pProcessName - 1);
+
+                StringBuffer groupName;
+                getClusterThorGroupName(groupName, processName.str());
+
+                StringBuffer logDate = ppStr + 12;
+                logDate.setLength(10);
+
+                Owned<IEspThorLogInfo> thorLog = createThorLogInfo("","");
+                thorLog->setProcessName(processName.str());
+                thorLog->setClusterGroup(groupName.str());
+                thorLog->setLogDate(logDate.str());
+                //for legacy wuid, the log name does not contain slaveNum. So, a user may not specify
+                //a slaveNum and we only display the first slave log if > 1 per IP.
+                thorLog->setNumberSlaves(0);
+                thorLogList.append(*thorLog.getLink());
+            }
+        }
+    }
+
+    if (thorLogList.length() > 0)
+        info.setThorLogList(thorLogList);
+    thorLogList.kill();
+
+    return countThorLog;
+}
+
 bool WsWuInfo::getClusterInfo(IEspECLWorkunit &info, unsigned flags)
 {
     if (version > 1.04)
@@ -1429,10 +1594,16 @@ void appendIOStreamContent(MemoryBuffer &mb, IFileIOStream *ios, bool forDownloa
     }
 }
 
-void WsWuInfo::getWorkunitEclAgentLog(MemoryBuffer& buf)
+void WsWuInfo::getWorkunitEclAgentLog(const char* eclAgentInstance, MemoryBuffer& buf)
 {
     SCMStringBuffer logname;
-    cw->getDebugValue("EclAgentLog", logname);
+    Owned<IStringIterator> eclAgentLogs = cw->getLogs("EclAgent", eclAgentInstance);
+    ForEach (*eclAgentLogs)
+    {
+        eclAgentLogs->str(logname);
+        if (logname.length() > 0)
+            break;
+    }
 
     unsigned pid = cw->getAgentPID();
     if(logname.length() == 0)
@@ -1490,10 +1661,16 @@ void WsWuInfo::getWorkunitEclAgentLog(MemoryBuffer& buf)
     }
 }
 
-void WsWuInfo::getWorkunitThorLog(MemoryBuffer& buf)
+void WsWuInfo::getWorkunitThorLog(const char* processName, MemoryBuffer& buf)
 {
     SCMStringBuffer logname;
-    cw->getDebugValue(File_ThorLog, logname);
+    Owned<IStringIterator> thorLogs = cw->getLogs("Thor", processName);
+    ForEach (*thorLogs)
+    {
+        thorLogs->str(logname);
+        if (logname.length() > 0)
+            break;
+    }
 
     Owned<IFile> rFile = createIFile(logname.str());
     if (!rFile)
@@ -1538,26 +1715,59 @@ void WsWuInfo::getWorkunitThorLog(MemoryBuffer& buf)
     }
 }
 
-void WsWuInfo::getWorkunitThorSlaveLog(const char *slaveip, MemoryBuffer& buf, bool forDownload)
+void WsWuInfo::getWorkunitThorSlaveLog(const char *groupName, const char *ipAddress, const char* logDate, const char* logDir, int slaveNum, MemoryBuffer& buf, bool forDownload)
 {
-   if (isEmpty(slaveip))
-      throw MakeStringException(ECLWATCH_INVALID_INPUT,"ThorSlave IP not specified.");
+    if (isEmpty(logDir))
+      throw MakeStringException(ECLWATCH_INVALID_INPUT,"ThorSlave log path not specified.");
+    if (isEmpty(logDate))
+        throw MakeStringException(ECLWATCH_INVALID_INPUT,"ThorSlave log date not specified.");
 
-    SCMStringBuffer logname;
-    cw->getDebugValue(File_ThorLog, logname);
+    StringBuffer slaveIPAddress, logName;
+    if (slaveNum > 0)
+    {
+        if (isEmpty(groupName))
+          throw MakeStringException(ECLWATCH_INVALID_INPUT,"Thor group not specified.");
 
-    StringBuffer logdir;
-    splitDirTail(logname.str(),logdir);
+        Owned<IGroup> nodeGroup = queryNamedGroupStore().lookup(groupName);
+        if (!nodeGroup || (nodeGroup->ordinality() == 0))
+        {
+            WARNLOG("Node group %s not found", groupName);
+            return;
+        }
+
+        nodeGroup->queryNode(slaveNum-1).endpoint().getIpText(slaveIPAddress);
+        if (slaveIPAddress.length() < 1)
+            throw MakeStringException(ECLWATCH_INVALID_INPUT,"ThorSlave log network address not found.");
+
+        logName.appendf("thorslave.%d.%s.log", slaveNum, logDate);
+    }
+    else
+    {//legacy wuid: a user types in an IP address for a thor slave
+        if (isEmpty(ipAddress))
+            throw MakeStringException(ECLWATCH_INVALID_INPUT,"ThorSlave address not specified.");
+
+        //thorslave.10.239.219.6_20100.2012_05_23.log
+        logName.appendf("thorslave.%s*.%s.log", ipAddress, logDate);
+        const char* portPtr = strchr(ipAddress, '_');
+        if (!portPtr)
+            slaveIPAddress.append(ipAddress);
+        else
+        {
+            StringBuffer ipAddressStr = ipAddress;
+            ipAddressStr.setLength(portPtr - ipAddress);
+            slaveIPAddress.append(ipAddressStr.str());
+        }
+    }
 
     RemoteFilename rfn;
-    rfn.setRemotePath(logdir.str());
-    SocketEndpoint ep(slaveip);
+    rfn.setRemotePath(logDir);
+    SocketEndpoint ep(slaveIPAddress.str());
     rfn.setIp(ep);
 
     Owned<IFile> dir = createIFile(rfn);
-    Owned<IDirectoryIterator> diriter = dir->directoryFiles("*.log");
+    Owned<IDirectoryIterator> diriter = dir->directoryFiles(logName.str());
     if (!diriter->first())
-      throw MakeStringException(ECLWATCH_FILE_NOT_EXIST,"Cannot find Thor slave log file %s.", logdir.str());
+      throw MakeStringException(ECLWATCH_FILE_NOT_EXIST,"Cannot find Thor slave log file %s.", logName.str());
 
     Linked<IFile> logfile = &diriter->query();
     diriter.clear();
@@ -1566,10 +1776,49 @@ void WsWuInfo::getWorkunitThorSlaveLog(const char *slaveip, MemoryBuffer& buf, b
 
     OwnedIFileIO rIO = logfile->openShared(IFOread,IFSHfull);
     if (!rIO)
-        throw MakeStringException(ECLWATCH_CANNOT_READ_FILE,"Cannot read file %s.",logdir.str());
+        throw MakeStringException(ECLWATCH_CANNOT_READ_FILE,"Cannot read file %s.",logName.str());
 
     OwnedIFileIOStream ios = createBufferedIOStream(rIO);
-    appendIOStreamContent(buf, ios.get(), forDownload);
+    if (slaveNum > 0)
+    {
+        StringBuffer line;
+        bool eof = false;
+        bool include = false;
+
+        VStringBuffer startwuid("Started wuid=%s", wuid.str());
+        VStringBuffer endwuid("Finished wuid=%s", wuid.str());
+
+        const char *sw = startwuid.str();
+        const char *ew = endwuid.str();
+
+        while (!eof)
+        {
+            line.clear();
+            loop
+            {
+                char c;
+                size32_t numRead = ios->read(1, &c);
+                if (!numRead)
+                {
+                    eof = true;
+                    break;
+                }
+                line.append(c);
+                if (c=='\n')
+                    break;
+            }
+            if (strstr(line.str(), sw))
+                include = true;
+            if (include)
+                buf.append(line.length(), line.str());
+            if (strstr(line.str(), ew))
+                include = false;
+        }
+    }
+    else
+    {//legacy wuid
+        appendIOStreamContent(buf, ios.get(), forDownload);
+    }
 }
 
 void WsWuInfo::getWorkunitResTxt(MemoryBuffer& buf)

+ 4 - 3
esp/services/ws_workunits/ws_workunitsHelpers.hpp

@@ -164,15 +164,16 @@ public:
     bool getResultEclSchemas(IConstWUResult &r, IArrayOf<IEspECLSchemaItem>& schemas);
     void getResult(IConstWUResult &r, IArrayOf<IEspECLResult>& results, unsigned flags);
 
-    void getWorkunitEclAgentLog(MemoryBuffer& buf);
-    void getWorkunitThorLog(MemoryBuffer& buf);
-    void getWorkunitThorSlaveLog(const char *slaveip, MemoryBuffer& buf, bool forDownload);
+    void getWorkunitEclAgentLog(const char* eclAgentInstance, MemoryBuffer& buf);
+    void getWorkunitThorLog(const char *processName, MemoryBuffer& buf);
+    void getWorkunitThorSlaveLog(const char *groupName, const char *ipAddress, const char* logDate, const char* logDir, int slaveNum, MemoryBuffer& buf, bool forDownload);
     void getWorkunitResTxt(MemoryBuffer& buf);
     void getWorkunitArchiveQuery(MemoryBuffer& buf);
     void getWorkunitDll(StringBuffer &name, MemoryBuffer& buf);
     void getWorkunitXml(const char* plainText, MemoryBuffer& buf);
     void getWorkunitCpp(const char* cppname, const char* description, const char* ipAddress, MemoryBuffer& buf, bool forDownload);
     void getEventScheduleFlag(IEspECLWorkunit &info);
+    unsigned getWorkunitThorLogInfo(IArrayOf<IEspECLHelpFile>& helpers, IEspECLWorkunit &info);
 
 public:
     IEspContext &context;

+ 9 - 4
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -551,8 +551,10 @@ void CWsWorkunitsEx::init(IPropertyTree *cfg, const char *process, const char *s
     VStringBuffer xpath("Software/EspProcess[@name=\"%s\"]/EspService[@name=\"%s\"]/AWUsCacheMinutes", process, service);
     cfg->getPropInt(xpath.str(), awusCacheMinutes);
 
+    directories.set(cfg->queryPropTree("Software/Directories"));
+
     const char *name = cfg->queryProp("Software/EspProcess/@name");
-    getConfigurationDirectory(cfg->queryPropTree("Software/Directories"), "query", "esp", name ? name : "esp", queryDirectory);
+    getConfigurationDirectory(directories, "query", "esp", name ? name : "esp", queryDirectory);
     recursiveCreateDirectory(queryDirectory.str());
 
     dataCache.setown(new DataCache(DATA_SIZE));
@@ -2421,17 +2423,20 @@ bool CWsWorkunitsEx::onWUFile(IEspContext &context,IEspWULogFileRequest &req, IE
             }
             else if (strncmp(req.getType(), File_ThorLog, 7) == 0)
             {
-                winfo.getWorkunitThorLog(mb);
+                winfo.getWorkunitThorLog(req.getProcess(), mb);
                 openSaveFile(context, opt, "thormaster.log", HTTP_TYPE_TEXT_PLAIN, mb, resp);
             }
             else if (strieq(File_ThorSlaveLog,req.getType()))
             {
-                winfo.getWorkunitThorSlaveLog(req.getSlaveIP(), mb, opt > 0);
+                StringBuffer logDir;
+                getConfigurationDirectory(directories, "log", "thor", req.getProcess(), logDir);
+
+                winfo.getWorkunitThorSlaveLog(req.getClusterGroup(), req.getIPAddress(), req.getLogDate(), logDir.str(), req.getSlaveNumber(), mb, false);
                 openSaveFile(context, opt, "ThorSlave.log", HTTP_TYPE_TEXT_PLAIN, mb, resp);
             }
             else if (strieq(File_EclAgentLog,req.getType()))
             {
-                winfo.getWorkunitEclAgentLog(mb);
+                winfo.getWorkunitEclAgentLog(req.getProcess(), mb);
                 openSaveFile(context, opt, "eclagent.log", HTTP_TYPE_TEXT_PLAIN, mb, resp);
             }
             else if (strieq(File_XML,req.getType()))

+ 1 - 0
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -113,6 +113,7 @@ private:
     CriticalSection crit;
     WUSchedule m_sched;
     unsigned short port;
+    Owned<IPropertyTree> directories;
 };
 
 class CWsWorkunitsSoapBindingEx : public CWsWorkunitsSoapBinding