瀏覽代碼

HPCC-20133 Upgrade ESDL transaction log for Royalty logging, etc

1. Pass royalty dataset to log content in order to support
Royalty logging;
2. the transaction ID created by the existing code always has '-'
or '-X' between transaction seed and sequence number. Now, the
characters are configurable;
3. Create new class CLogContentFilter. Move the content of
CESPServerLoggingAgent.filterLogContent() into CLogContentFilter
.filterLogContent() so that it can be shared with other log
agents, such as WsLogServiceESPAgent. Also move the following
methods from CESPServerLoggingAgent to CLogContentFilter:
readAllLogFilters(), readLogFilters, addLogContentBranch,
filterAndAddLogContentBranch, and filterLogContentTree;
4. Move the following items from espserviceloggingagent.hpp to
loggingagentbase.hpp so that they can be shared for other log
agents: enum ESPLogContentGroup, const espLogContentGroupNames[],
class CTransIDBuilder, and class CESPLogContentGroupFilters.
5. Move wslogserviceespagent.xsl from LN repo to here because it
is needed by esp_service_DynamicESDL.xsl. Add code into
esp_service_DynamicESDL.xsl to call the template in
wslogserviceespagent.xsl
6. Move the xsl code of the transactionId/seed from logging_agent.xsl
to new esp_log_transid.xsl so that it can be shared by other log
agents.

Signed-off-by: wangkx <kevin.wang@lexisnexis.com>
wangkx 7 年之前
父節點
當前提交
a5ede269b7

+ 2 - 0
esp/esdllib/esdl_transformer2.cpp

@@ -1831,6 +1831,8 @@ void Esdl2Transformer::processHPCCResult(IEspContext &ctx, IEsdlDefMethod &mthde
                 xppToXmlString(*xpp, stag, logdata);
             else if (strnicmp(dataset, "LOG_", 4)==0)
                 xppToXmlString(*xpp, stag, logdata);
+            else if (strieq(dataset, "royaltyset"))
+                xppToXmlString(*xpp, stag, logdata);
             else
             {
                 WARNLOG("ESDL processing HPCC Result: Dataset ignored: %s", dataset);

+ 15 - 268
esp/logging/loggingagent/espserverloggingagent/loggingagent.cpp

@@ -31,6 +31,10 @@ static const char* const PropServerWaitingSeconds = "MaxServerWaitingSeconds";
 static const char* const PropMaxTransIDLength = "MaxTransIDLength";
 static const char* const PropMaxTransIDSequenceNumber = "MaxTransIDSequenceNumber";
 static const char* const PropMaxTransSeedTimeoutMinutes = "MaxTransSeedTimeoutMinutes";
+static const char* const PropTransactionSeedType = "TransactionSeedType";
+static const char* const PropAlternativeTransactionSeedType = "AlternativeTransactionSeedType";
+static const char* const DefaultTransactionSeedType =  "-";
+static const char* const DefaultAlternativeTransactionSeedType =  "-X"; //local
 static const char* const MaxTriesGTS = "MaxTriesGTS";
 static const char* const appESPServerLoggingAgent = "ESPServerLoggingAgent";
 
@@ -57,6 +61,11 @@ bool CESPServerLoggingAgent::init(const char * name, const char * type, IPropert
     maxServerWaitingSeconds = cfg->getPropInt(PropServerWaitingSeconds);
     maxGTSRetries = cfg->getPropInt(MaxTriesGTS, DefaultMaxTriesGTS);
 
+    transactionSeedType.set(cfg->hasProp(PropTransactionSeedType) ? cfg->queryProp(PropTransactionSeedType) :
+        DefaultTransactionSeedType);
+    alternativeTransactionSeedType.set(cfg->hasProp(PropAlternativeTransactionSeedType) ?
+        cfg->queryProp(PropAlternativeTransactionSeedType) : DefaultAlternativeTransactionSeedType);
+
     BoolHash uniqueGroupNames;
     StringBuffer sourceName, groupName, dbName, localTransactionSeed;
     Owned<IPropertyTreeIterator> iter = cfg->getElements("LogSourceMap/LogSource");
@@ -89,7 +98,7 @@ bool CESPServerLoggingAgent::init(const char * name, const char * type, IPropert
             getTransactionSeed(groupName.str(), transactionSeed, statusMessage);
             if (transactionSeed.length() > 0)
             {
-                Owned<CTransIDBuilder> entry = new CTransIDBuilder(transactionSeed.str(), false,
+                Owned<CTransIDBuilder> entry = new CTransIDBuilder(transactionSeed.str(), false, transactionSeedType.get(),
                     maxLength, maxSeq, seedExpiredSeconds);
                 transIDMap.setValue(groupName.str(), entry);
                 if (iter->query().getPropBool("@default", false))
@@ -99,85 +108,18 @@ bool CESPServerLoggingAgent::init(const char * name, const char * type, IPropert
                 PROGLOG("Failed to get TransactionSeed for <%s>", groupName.str());
         }
     }
+
     createLocalTransactionSeed(localTransactionSeed);
-    Owned<CTransIDBuilder> localTransactionEntry = new CTransIDBuilder(localTransactionSeed.str(), true,
+    Owned<CTransIDBuilder> localTransactionEntry = new CTransIDBuilder(localTransactionSeed.str(), true, alternativeTransactionSeedType.get(),
         cfg->getPropInt(PropMaxTransIDLength, 0), cfg->getPropInt(PropMaxTransIDSequenceNumber, 0),
         60 * cfg->getPropInt(PropMaxTransSeedTimeoutMinutes, 0));
     transIDMap.setValue(appESPServerLoggingAgent, localTransactionEntry);
 
-    readAllLogFilters(cfg);
+    logContentFilter.readAllLogFilters(cfg);
     return true;
 }
 
-void CESPServerLoggingAgent::readAllLogFilters(IPropertyTree* cfg)
-{
-    bool groupFilterRead = false;
-    VStringBuffer xpath("Filters/Filter[@type='%s']", espLogContentGroupNames[ESPLCGBackEndResp]);
-    IPropertyTree* filter = cfg->queryBranch(xpath.str());
-    if (filter && filter->hasProp("@value"))
-    {
-        logBackEndResp = filter->getPropBool("@value");
-        groupFilterRead = true;
-    }
-
-    for (unsigned i = 0; i < ESPLCGBackEndResp; i++)
-    {
-        if (readLogFilters(cfg, i))
-            groupFilterRead = true;
-    }
-
-    if (!groupFilterRead)
-    {
-        groupFilters.clear();
-        readLogFilters(cfg, ESPLCGAll);
-    }
-}
-
-bool CESPServerLoggingAgent::readLogFilters(IPropertyTree* cfg, unsigned groupID)
-{
-    Owned<CESPLogContentGroupFilters> espLogContentGroupFilters = new CESPLogContentGroupFilters((ESPLogContentGroup) groupID);
-    StringBuffer xpath;
-    if (groupID != ESPLCGAll)
-        xpath.appendf("Filters/Filter[@type='%s']", espLogContentGroupNames[groupID]);
-    else
-        xpath.append("Filters/Filter");
-    Owned<IPropertyTreeIterator> filters = cfg->getElements(xpath.str());
-    ForEach(*filters)
-    {
-        IPropertyTree &filter = filters->query();
-        StringBuffer value = filter.queryProp("@value");
-        if (!value.length())
-            continue;
-
-        //clean "//"
-        unsigned idx = value.length()-1;
-        while (idx)
-        {
-            if ((value.charAt(idx-1) == '/') && (value.charAt(idx) == '/'))
-                value.remove(idx, 1);
-            idx--;
-        }
-
-        //clean "/*" at the end
-        while ((value.length() > 1) && (value.charAt(value.length()-2) == '/') && (value.charAt(value.length()-1) == '*'))
-            value.setLength(value.length() - 2);
-
-        if (value.length() && !streq(value.str(), "*") && !streq(value.str(), "/") && !streq(value.str(), "*/"))
-        {
-            espLogContentGroupFilters->addFilter(value.str());
-        }
-        else
-        {
-            espLogContentGroupFilters->clearFilters();
-            break;
-        }
-    }
 
-    bool hasFilter = espLogContentGroupFilters->getFilterCount() > 0;
-    if (hasFilter)
-        groupFilters.append(*espLogContentGroupFilters.getClear());
-    return hasFilter;
-}
 
 void CESPServerLoggingAgent::createLocalTransactionSeed(StringBuffer& transactionSeed)
 {
@@ -282,7 +224,7 @@ void CESPServerLoggingAgent::resetTransSeed(CTransIDBuilder *builder, const char
         }
     }
 
-    builder->resetTransSeed(transactionSeed.str());
+    builder->resetTransSeed(transactionSeed.str(), builder->isLocalSeed() ? alternativeTransactionSeedType.get() : transactionSeedType.get());
 };
 
 void CESPServerLoggingAgent::getTransactionID(StringAttrMapping* transFields, StringBuffer& transactionID)
@@ -361,204 +303,9 @@ bool CESPServerLoggingAgent::updateLog(IEspUpdateLogRequestWrap& req, IEspUpdate
     return true;
 }
 
-void CESPServerLoggingAgent::addLogContentBranch(StringArray& branchNames, IPropertyTree* contentToLogBranch, IPropertyTree* updateLogRequestTree)
-{
-    IPropertyTree* pTree = updateLogRequestTree;
-    unsigned numOfBranchNames = branchNames.length();
-    if (numOfBranchNames > 0)
-    {
-        unsigned i = 0;
-        while (i < numOfBranchNames)
-        {
-            const char* branchName = branchNames.item(i);
-            if (branchName && *branchName)
-                pTree = ensurePTree(pTree, branchName);
-            i++;
-        }
-    }
-    pTree->addPropTree(contentToLogBranch->queryName(), LINK(contentToLogBranch));
-}
-
-void CESPServerLoggingAgent::filterAndAddLogContentBranch(StringArray& branchNamesInFilter, unsigned idx,
-    StringArray& branchNamesInLogContent, IPropertyTree* originalLogContentBranch, IPropertyTree* updateLogRequestTree, bool& logContentEmpty)
-{
-    Owned<IPropertyTreeIterator> contentItr = originalLogContentBranch->getElements(branchNamesInFilter.item(idx));
-    ForEach(*contentItr)
-    {
-        IPropertyTree& contentToLogBranch = contentItr->query();
-        if (idx == branchNamesInFilter.length() - 1)
-        {
-            addLogContentBranch(branchNamesInLogContent, &contentToLogBranch, updateLogRequestTree);
-            logContentEmpty = false;
-        }
-        else
-        {
-            branchNamesInLogContent.append(contentToLogBranch.queryName());
-            filterAndAddLogContentBranch(branchNamesInFilter, idx+1, branchNamesInLogContent, &contentToLogBranch,
-                updateLogRequestTree, logContentEmpty);
-            branchNamesInLogContent.remove(branchNamesInLogContent.length() - 1);
-        }
-    }
-}
-
-void CESPServerLoggingAgent::filterLogContentTree(StringArray& filters, IPropertyTree* originalContentTree, IPropertyTree* newLogContentTree, bool& logContentEmpty)
-{
-    ForEachItemIn(i, filters)
-    {
-        const char* logContentFilter = filters.item(i);
-        if(!logContentFilter || !*logContentFilter)
-            continue;
-
-        StringArray branchNamesInFilter, branchNamesInLogContent;
-        branchNamesInFilter.appendListUniq(logContentFilter, "/");
-        filterAndAddLogContentBranch(branchNamesInFilter, 0, branchNamesInLogContent, originalContentTree, newLogContentTree, logContentEmpty);
-    }
-}
-
 void CESPServerLoggingAgent::filterLogContent(IEspUpdateLogRequestWrap* req)
 {
-    const char* logContent = req->getUpdateLogRequest();
-    Owned<IPropertyTree> updateLogRequestTree = createPTree("UpdateLogRequest");
-
-    StringBuffer source;
-    if (groupFilters.length() < 1)
-    {//No filter
-        if (logContent && *logContent)
-        {
-            Owned<IPropertyTree> pTree = createPTreeFromXMLString(logContent);
-            source = pTree->queryProp("Source");
-            updateLogRequestTree->addPropTree(pTree->queryName(), LINK(pTree));
-        }
-        else
-        {
-            Owned<IPropertyTree> espContext = req->getESPContext();
-            Owned<IPropertyTree> userContext = req->getUserContext();
-            Owned<IPropertyTree> userRequest = req->getUserRequest();
-            const char* userResp = req->getUserResponse();
-            const char* logDatasets = req->getLogDatasets();
-            const char* backEndResp = req->getBackEndResponse();
-            if (!espContext && !userContext && !userRequest && (!userResp || !*userResp) && (!backEndResp || !*backEndResp))
-                throw MakeStringException(EspLoggingErrors::UpdateLogFailed, "Failed to read log content");
-            source = userContext->queryProp("Source");
-
-            StringBuffer espContextXML, userContextXML, userRequestXML;
-            IPropertyTree* logContentTree = ensurePTree(updateLogRequestTree, "LogContent");
-            if (espContext)
-            {
-                logContentTree->addPropTree(espContext->queryName(), LINK(espContext));
-            }
-            if (userContext)
-            {
-                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGUserContext]);
-                pTree->addPropTree(userContext->queryName(), LINK(userContext));
-            }
-            if (userRequest)
-            {
-                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGUserReq]);
-                pTree->addPropTree(userRequest->queryName(), LINK(userRequest));
-            }
-            if (userResp && *userResp)
-            {
-                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGUserResp]);
-                Owned<IPropertyTree> userRespTree = createPTreeFromXMLString(userResp);
-                pTree->addPropTree(userRespTree->queryName(), LINK(userRespTree));
-            }
-            if (logDatasets && *logDatasets)
-            {
-                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGLogDatasets]);
-                Owned<IPropertyTree> logDatasetTree = createPTreeFromXMLString(logDatasets);
-                pTree->addPropTree(logDatasetTree->queryName(), LINK(logDatasetTree));
-            }
-            if (backEndResp && *backEndResp)
-                logContentTree->addProp(espLogContentGroupNames[ESPLCGBackEndResp], backEndResp);
-        }
-    }
-    else
-    {
-        bool logContentEmpty = true;
-        IPropertyTree* logContentTree = ensurePTree(updateLogRequestTree, "LogContent");
-        if (logContent && *logContent)
-        {
-            Owned<IPropertyTree> originalContentTree = createPTreeFromXMLString(logContent);
-            source = originalContentTree->queryProp("Source");
-            filterLogContentTree(groupFilters.item(0).getFilters(), originalContentTree, logContentTree, logContentEmpty);
-        }
-        else
-        {
-            for (unsigned group = 0; group < ESPLCGBackEndResp; group++)
-            {
-                Owned<IPropertyTree> originalContentTree;
-                if (group == ESPLCGESPContext)
-                    originalContentTree.setown(req->getESPContext());
-                else if (group == ESPLCGUserContext)
-                {
-                    originalContentTree.setown(req->getUserContext());
-                    source = originalContentTree->queryProp("Source");
-                }
-                else if (group == ESPLCGUserReq)
-                    originalContentTree.setown(req->getUserRequest());
-                else if (group == ESPLCGLogDatasets)
-                {
-                    const char* logDatasets = req->getLogDatasets();
-                    if (logDatasets && *logDatasets)
-                        originalContentTree.setown(createPTreeFromXMLString(logDatasets));
-                }
-                else //group = ESPLCGUserResp
-                {
-                    const char* resp = req->getUserResponse();
-                    if (!resp || !*resp)
-                        continue;
-                    originalContentTree.setown(createPTreeFromXMLString(resp));
-                }
-                if (!originalContentTree)
-                    continue;
-
-                IPropertyTree* newContentTree = ensurePTree(logContentTree, espLogContentGroupNames[group]);
-                bool hasFilters = false;
-                ForEachItemIn(i, groupFilters)
-                {
-                    CESPLogContentGroupFilters& filtersGroup = groupFilters.item(i);
-                    if (filtersGroup.getGroup() == group)
-                    {
-                        if (group != ESPLCGESPContext)//For non ESPLCGESPContext, we want to keep the root of original tree.
-                            newContentTree = ensurePTree(newContentTree, originalContentTree->queryName());
-                        filterLogContentTree(filtersGroup.getFilters(), originalContentTree, newContentTree, logContentEmpty);
-                        hasFilters =  true;
-                        break;
-                    }
-                }
-
-                if (!hasFilters)
-                {
-                    newContentTree->addPropTree(originalContentTree->queryName(), LINK(originalContentTree));
-                    logContentEmpty = false;
-                }
-            }
-            if (logBackEndResp)
-            {
-                const char* resp = req->getBackEndResponse();
-                if (resp && *resp)
-                {
-                    logContentTree->addProp(espLogContentGroupNames[ESPLCGBackEndResp], resp);
-                    logContentEmpty = false;
-                }
-            }
-        }
-        if (logContentEmpty)
-            throw MakeStringException(EspLoggingErrors::UpdateLogFailed, "Failed to read log content");
-    }
-    if (!source.isEmpty())
-        updateLogRequestTree->addProp("LogContent/Source", source.str());
-
-    const char* option = req->getOption();
-    if (option && *option)
-        updateLogRequestTree->addProp("Option", option);
-
-    StringBuffer updateLogRequestXML;
-    toXML(updateLogRequestTree, updateLogRequestXML);
-    ESPLOG(LogMax, "filtered content and option: <%s>", updateLogRequestXML.str());
-    req->clearOriginalContent();
-    req->setUpdateLogRequest(updateLogRequestXML.str());
+    logContentFilter.filterLogContent(req);
 }
 
 bool CESPServerLoggingAgent::sendHTTPRequest(StringBuffer& req, StringBuffer &resp, StringBuffer &status)

+ 3 - 121
esp/logging/loggingagent/espserverloggingagent/loggingagent.hpp

@@ -27,136 +27,18 @@
     #define ESPSERVERLOGGINGAGENT_API DECL_IMPORT
 #endif
 
-enum ESPLogContentGroup
-{
-    ESPLCGESPContext = 0,
-    ESPLCGUserContext = 1,
-    ESPLCGUserReq = 2,
-    ESPLCGUserResp = 3,
-    ESPLCGLogDatasets = 4,
-    ESPLCGBackEndResp = 5,
-    ESPLCGAll = 6
-};
-
-static const char * const espLogContentGroupNames[] = { "ESPContext", "UserContext", "UserRequest", "UserResponse",
-    "LogDatasets", "BackEndResponse", "", NULL };
-
-class CTransIDBuilder : public CInterface, implements IInterface
-{
-    StringAttr seed;
-    bool localSeed;
-    unsigned __int64 seq = 0;
-
-    unsigned maxLength = 0;
-    unsigned maxSeq = 0;
-    unsigned seedExpiredSeconds = 0;
-    time_t createTime;
-
-    void add(StringAttrMapping* transIDFields, const char* key, StringBuffer& id)
-    {
-        StringAttr* value = transIDFields->getValue(key);
-        if (value)
-            id.append(value->get()).append('-');
-        else
-        {
-            const char* ptr = key;
-            if (strlen(key) > 11) //skip the "transaction" prefix of the key
-                ptr += 11;
-            id.append('?').append(ptr).append('-');
-        }
-    }
-
-public:
-    IMPLEMENT_IINTERFACE;
-    CTransIDBuilder(const char* _seed, bool _localSeed, unsigned _maxLength, unsigned _maxSeq, unsigned _seedExpiredSeconds)
-        : seed(_seed), localSeed(_localSeed), maxLength(_maxLength), maxSeq(_maxSeq), seedExpiredSeconds(_seedExpiredSeconds)
-    {
-        CDateTime now;
-        now.setNow();
-        createTime = now.getSimple();
-    };
-    virtual ~CTransIDBuilder() {};
-
-    bool checkMaxSequenceNumber() { return (maxSeq == 0) || (seq < maxSeq); };
-    bool checkMaxLength(unsigned length) { return (maxLength == 0) || (length <= maxLength); };
-    bool checkTimeout()
-    {
-        if (seedExpiredSeconds ==0)
-            return true;
-
-        CDateTime now;
-        now.setNow();
-        return now.getSimple() < createTime + seedExpiredSeconds;
-    };
-    bool isLocalSeed() { return localSeed; };
-    void resetTransSeed(const char* newSeed)
-    {
-        if (isEmptyString(newSeed))
-            throw MakeStringException(EspLoggingErrors::GetTransactionSeedFailed, "TransactionSeed cannot be empty.");
-        seed.set(newSeed);
-        seq = 0;
-
-        CDateTime now;
-        now.setNow();
-        createTime = now.getSimple();
-    };
-
-    virtual const char* getTransSeed() { return seed.get(); };
-    virtual void getTransID(StringAttrMapping* transIDFields, StringBuffer& id)
-    {
-        id.clear();
-
-        if (transIDFields)
-        {
-            add(transIDFields, sTransactionDateTime, id);
-            add(transIDFields, sTransactionMethod, id);
-            add(transIDFields, sTransactionIdentifier, id);
-        }
-        if (localSeed)
-            id.append(seed.get()).append("-X").append(++seq);
-        else
-            id.append(seed.get()).append('-').append(++seq);
-    };
-};
-
-class CESPLogContentGroupFilters : public CInterface, implements IInterface
-{
-    ESPLogContentGroup group;
-    StringArray filters;
-
-public:
-    IMPLEMENT_IINTERFACE;
-
-    CESPLogContentGroupFilters(ESPLogContentGroup _group) : group(_group) {};
-    ESPLogContentGroup getGroup() { return group; };
-    StringArray& getFilters() { return filters; };
-    void clearFilters() { filters.clear(); };
-    unsigned getFilterCount() { return filters.length(); };
-    void addFilter(const char* filter)
-    {
-        if (filter && *filter)
-            filters.append(filter);
-    };
-};
-
 class CESPServerLoggingAgent : public CInterface, implements IEspLogAgent
 {
     StringBuffer serviceName, loggingAgentName, defaultGroup;
     StringBuffer serverUrl, serverUserID, serverPassword;
     unsigned maxServerWaitingSeconds; //time out value for HTTP connection to logging server
     unsigned maxGTSRetries;
-    StringArray     logContentFilters;
-    CIArrayOf<CESPLogContentGroupFilters> groupFilters;
-    bool logBackEndResp;
+    CLogContentFilter logContentFilter;
     MapStringToMyClass<CTransIDBuilder> transIDMap;
     MapStringToMyClass<CLogSource> logSources;
+    StringAttr transactionSeedType;
+    StringAttr alternativeTransactionSeedType;
 
-    void readAllLogFilters(IPropertyTree* cfg);
-    bool readLogFilters(IPropertyTree* cfg, unsigned groupID);
-    void filterLogContentTree(StringArray& filters, IPropertyTree* originalContentTree, IPropertyTree* newLogContentTree, bool& logContentEmpty);
-    void filterAndAddLogContentBranch(StringArray& branchNamesInFilter, unsigned idx, StringArray& branchNamesInLogContent,
-        IPropertyTree* in, IPropertyTree* updateLogRequestTree, bool& logContentEmpty);
-    void addLogContentBranch(StringArray& branchNames, IPropertyTree* contentToLogBranch, IPropertyTree* updateLogRequestTree);
     bool sendHTTPRequest(StringBuffer& req, StringBuffer& resp, StringBuffer& status);
     int getTransactionSeed(const char* source, StringBuffer& transactionSeed, StringBuffer& statusMessage);
     bool getTransactionSeed(StringBuffer& soapreq, int& statusCode, StringBuffer& statusMessage, StringBuffer& seedID);

+ 276 - 0
esp/logging/logginglib/loggingagentbase.cpp

@@ -22,6 +22,282 @@ static const char* const defaultTransactionTable = "transactions";
 static const char* const defaultTransactionAppName = "accounting_log";
 static const char* const defaultLoggingTransactionAppName = "logging_transaction";
 
+void CLogContentFilter::readAllLogFilters(IPropertyTree* cfg)
+{
+    bool groupFilterRead = false;
+    VStringBuffer xpath("Filters/Filter[@type='%s']", espLogContentGroupNames[ESPLCGBackEndResp]);
+    IPropertyTree* filter = cfg->queryBranch(xpath.str());
+    if (filter && filter->hasProp("@value"))
+    {
+        logBackEndResp = filter->getPropBool("@value");
+        groupFilterRead = true;
+    }
+
+    for (unsigned i = 0; i < ESPLCGBackEndResp; i++)
+    {
+        if (readLogFilters(cfg, i))
+            groupFilterRead = true;
+    }
+
+    if (!groupFilterRead)
+    {
+        groupFilters.clear();
+        readLogFilters(cfg, ESPLCGAll);
+    }
+}
+
+bool CLogContentFilter::readLogFilters(IPropertyTree* cfg, unsigned groupID)
+{
+    Owned<CESPLogContentGroupFilters> espLogContentGroupFilters = new CESPLogContentGroupFilters((ESPLogContentGroup) groupID);
+    StringBuffer xpath;
+    if (groupID != ESPLCGAll)
+        xpath.appendf("Filters/Filter[@type='%s']", espLogContentGroupNames[groupID]);
+    else
+        xpath.append("Filters/Filter");
+    Owned<IPropertyTreeIterator> filters = cfg->getElements(xpath.str());
+    ForEach(*filters)
+    {
+        IPropertyTree &filter = filters->query();
+        StringBuffer value = filter.queryProp("@value");
+        if (!value.length())
+            continue;
+
+        //clean "//"
+        unsigned idx = value.length()-1;
+        while (idx)
+        {
+            if ((value.charAt(idx-1) == '/') && (value.charAt(idx) == '/'))
+                value.remove(idx, 1);
+            idx--;
+        }
+
+        //clean "/*" at the end
+        while ((value.length() > 1) && (value.charAt(value.length()-2) == '/') && (value.charAt(value.length()-1) == '*'))
+            value.setLength(value.length() - 2);
+
+        if (value.length() && !streq(value.str(), "*") && !streq(value.str(), "/") && !streq(value.str(), "*/"))
+        {
+            espLogContentGroupFilters->addFilter(value.str());
+        }
+        else
+        {
+            espLogContentGroupFilters->clearFilters();
+            break;
+        }
+    }
+
+    bool hasFilter = espLogContentGroupFilters->getFilterCount() > 0;
+    if (hasFilter)
+        groupFilters.append(*espLogContentGroupFilters.getClear());
+    return hasFilter;
+}
+
+void CLogContentFilter::addLogContentBranch(StringArray& branchNames, IPropertyTree* contentToLogBranch, IPropertyTree* updateLogRequestTree)
+{
+    IPropertyTree* pTree = updateLogRequestTree;
+    unsigned numOfBranchNames = branchNames.length();
+    unsigned i = 0;
+    while (i < numOfBranchNames)
+    {
+        const char* branchName = branchNames.item(i);
+        if (branchName && *branchName)
+            pTree = ensurePTree(pTree, branchName);
+        i++;
+    }
+    pTree->addPropTree(contentToLogBranch->queryName(), LINK(contentToLogBranch));
+}
+
+void CLogContentFilter::filterAndAddLogContentBranch(StringArray& branchNamesInFilter, unsigned idx,
+    StringArray& branchNamesInLogContent, IPropertyTree* originalLogContentBranch, IPropertyTree* updateLogRequestTree, bool& logContentEmpty)
+{
+    Owned<IPropertyTreeIterator> contentItr = originalLogContentBranch->getElements(branchNamesInFilter.item(idx));
+    ForEach(*contentItr)
+    {
+        IPropertyTree& contentToLogBranch = contentItr->query();
+        if (idx == branchNamesInFilter.length() - 1)
+        {
+            addLogContentBranch(branchNamesInLogContent, &contentToLogBranch, updateLogRequestTree);
+            logContentEmpty = false;
+        }
+        else
+        {
+            branchNamesInLogContent.append(contentToLogBranch.queryName());
+            filterAndAddLogContentBranch(branchNamesInFilter, idx+1, branchNamesInLogContent, &contentToLogBranch,
+                updateLogRequestTree, logContentEmpty);
+            branchNamesInLogContent.remove(branchNamesInLogContent.length() - 1);
+        }
+    }
+}
+
+void CLogContentFilter::filterLogContentTree(StringArray& filters, IPropertyTree* originalContentTree, IPropertyTree* newLogContentTree, bool& logContentEmpty)
+{
+    ForEachItemIn(i, filters)
+    {
+        const char* logContentFilter = filters.item(i);
+        if(!logContentFilter || !*logContentFilter)
+            continue;
+
+        StringArray branchNamesInFilter, branchNamesInLogContent;
+        branchNamesInFilter.appendListUniq(logContentFilter, "/");
+        filterAndAddLogContentBranch(branchNamesInFilter, 0, branchNamesInLogContent, originalContentTree, newLogContentTree, logContentEmpty);
+    }
+}
+
+void CLogContentFilter::filterLogContent(IEspUpdateLogRequestWrap* req)
+{
+    const char* logContent = req->getUpdateLogRequest();
+    Owned<IPropertyTree> logRequestTree = req->getLogRequestTree();
+    Owned<IPropertyTree> updateLogRequestTree = createPTree("UpdateLogRequest");
+
+    StringBuffer source;
+    if (groupFilters.length() < 1)
+    {//No filter
+        if (logRequestTree)
+        {
+            updateLogRequestTree->addPropTree(logRequestTree->queryName(), LINK(logRequestTree));
+        }
+        else if (logContent && *logContent)
+        {
+            Owned<IPropertyTree> pTree = createPTreeFromXMLString(logContent);
+            source = pTree->queryProp("Source");
+            updateLogRequestTree->addPropTree(pTree->queryName(), LINK(pTree));
+        }
+        else
+        {
+            Owned<IPropertyTree> espContext = req->getESPContext();
+            Owned<IPropertyTree> userContext = req->getUserContext();
+            Owned<IPropertyTree> userRequest = req->getUserRequest();
+            const char* userResp = req->getUserResponse();
+            const char* logDatasets = req->getLogDatasets();
+            const char* backEndResp = req->getBackEndResponse();
+            if (!espContext && !userContext && !userRequest && (!userResp || !*userResp) && (!backEndResp || !*backEndResp))
+                throw MakeStringException(EspLoggingErrors::UpdateLogFailed, "Failed to read log content");
+            source = userContext->queryProp("Source");
+
+            StringBuffer espContextXML, userContextXML, userRequestXML;
+            IPropertyTree* logContentTree = ensurePTree(updateLogRequestTree, "LogContent");
+            if (espContext)
+            {
+                logContentTree->addPropTree(espContext->queryName(), LINK(espContext));
+            }
+            if (userContext)
+            {
+                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGUserContext]);
+                pTree->addPropTree(userContext->queryName(), LINK(userContext));
+            }
+            if (userRequest)
+            {
+                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGUserReq]);
+                pTree->addPropTree(userRequest->queryName(), LINK(userRequest));
+            }
+            if (userResp && *userResp)
+            {
+                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGUserResp]);
+                Owned<IPropertyTree> userRespTree = createPTreeFromXMLString(userResp);
+                pTree->addPropTree(userRespTree->queryName(), LINK(userRespTree));
+            }
+            if (logDatasets && *logDatasets)
+            {
+                IPropertyTree* pTree = ensurePTree(logContentTree, espLogContentGroupNames[ESPLCGLogDatasets]);
+                Owned<IPropertyTree> logDatasetTree = createPTreeFromXMLString(logDatasets);
+                pTree->addPropTree(logDatasetTree->queryName(), LINK(logDatasetTree));
+            }
+            if (backEndResp && *backEndResp)
+                logContentTree->addProp(espLogContentGroupNames[ESPLCGBackEndResp], backEndResp);
+        }
+    }
+    else
+    {
+        bool logContentEmpty = true;
+        IPropertyTree* logContentTree = ensurePTree(updateLogRequestTree, "LogContent");
+        if (logRequestTree)
+        {
+            filterLogContentTree(groupFilters.item(0).getFilters(), logRequestTree, logContentTree, logContentEmpty);
+        }
+        else if (logContent && *logContent)
+        {
+            Owned<IPropertyTree> originalContentTree = createPTreeFromXMLString(logContent);
+            source = originalContentTree->queryProp("Source");
+            filterLogContentTree(groupFilters.item(0).getFilters(), originalContentTree, logContentTree, logContentEmpty);
+        }
+        else
+        {
+            for (unsigned group = 0; group < ESPLCGBackEndResp; group++)
+            {
+                Owned<IPropertyTree> originalContentTree;
+                if (group == ESPLCGESPContext)
+                    originalContentTree.setown(req->getESPContext());
+                else if (group == ESPLCGUserContext)
+                {
+                    originalContentTree.setown(req->getUserContext());
+                    source = originalContentTree->queryProp("Source");
+                }
+                else if (group == ESPLCGUserReq)
+                    originalContentTree.setown(req->getUserRequest());
+                else if (group == ESPLCGLogDatasets)
+                {
+                    const char* logDatasets = req->getLogDatasets();
+                    if (logDatasets && *logDatasets)
+                        originalContentTree.setown(createPTreeFromXMLString(logDatasets));
+                }
+                else //group = ESPLCGUserResp
+                {
+                    const char* resp = req->getUserResponse();
+                    if (!resp || !*resp)
+                        continue;
+                    originalContentTree.setown(createPTreeFromXMLString(resp));
+                }
+                if (!originalContentTree)
+                    continue;
+
+                IPropertyTree* newContentTree = ensurePTree(logContentTree, espLogContentGroupNames[group]);
+                bool hasFilters = false;
+                ForEachItemIn(i, groupFilters)
+                {
+                    CESPLogContentGroupFilters& filtersGroup = groupFilters.item(i);
+                    if (filtersGroup.getGroup() == group)
+                    {
+                        if (group != ESPLCGESPContext)//For non ESPLCGESPContext, we want to keep the root of original tree.
+                            newContentTree = ensurePTree(newContentTree, originalContentTree->queryName());
+                        filterLogContentTree(filtersGroup.getFilters(), originalContentTree, newContentTree, logContentEmpty);
+                        hasFilters =  true;
+                        break;
+                    }
+                }
+
+                if (!hasFilters)
+                {
+                    newContentTree->addPropTree(originalContentTree->queryName(), LINK(originalContentTree));
+                    logContentEmpty = false;
+                }
+            }
+            if (logBackEndResp)
+            {
+                const char* resp = req->getBackEndResponse();
+                if (resp && *resp)
+                {
+                    logContentTree->addProp(espLogContentGroupNames[ESPLCGBackEndResp], resp);
+                    logContentEmpty = false;
+                }
+            }
+        }
+        if (logContentEmpty)
+            throw MakeStringException(EspLoggingErrors::UpdateLogFailed, "Failed to read log content");
+    }
+    if (!source.isEmpty())
+        updateLogRequestTree->addProp("LogContent/Source", source.str());
+
+    const char* option = req->getOption();
+    if (option && *option)
+        updateLogRequestTree->addProp("Option", option);
+
+    StringBuffer updateLogRequestXML;
+    toXML(updateLogRequestTree, updateLogRequestXML);
+    ESPLOG(LogMax, "filtered content and option: <%s>", updateLogRequestXML.str());
+    req->clearOriginalContent();
+    req->setUpdateLogRequest(updateLogRequestXML.str());
+}
+
 void CDBLogAgentBase::readDBCfg(IPropertyTree* cfg, StringBuffer& server, StringBuffer& dbUser, StringBuffer& dbPassword)
 {
     ensureInputString(cfg->queryProp("@server"), true, server, -1, "Database server required");

+ 133 - 0
esp/logging/logginglib/loggingagentbase.hpp

@@ -25,6 +25,21 @@
 #include "datafieldmap.hpp"
 #include "ws_loggingservice_esp.ipp"
 #include "loggingcommon.hpp"
+#include "LoggingErrors.hpp"
+
+enum ESPLogContentGroup
+{
+    ESPLCGESPContext = 0,
+    ESPLCGUserContext = 1,
+    ESPLCGUserReq = 2,
+    ESPLCGUserResp = 3,
+    ESPLCGLogDatasets = 4,
+    ESPLCGBackEndResp = 5,
+    ESPLCGAll = 6
+};
+
+static const char * const espLogContentGroupNames[] = { "ESPContext", "UserContext", "UserRequest", "UserResponse",
+    "LogDatasets", "BackEndResponse", "", NULL };
 
 #define UPDATELOGTHREADWAITINGTIME 3000
 
@@ -34,6 +49,84 @@ static const char* sTransactionDateTime = "TransactionDateTime";
 static const char* sTransactionMethod = "TransactionMethod";
 static const char* sTransactionIdentifier = "TransactionIdentifier";
 
+class CTransIDBuilder : public CInterface, implements IInterface
+{
+    StringAttr seed, seedType;
+    bool localSeed;
+    unsigned __int64 seq = 0;
+
+    unsigned maxLength = 0;
+    unsigned maxSeq = 0;
+    unsigned seedExpiredSeconds = 0;
+    time_t createTime;
+
+    void add(StringAttrMapping* transIDFields, const char* key, StringBuffer& id)
+    {
+        StringAttr* value = transIDFields->getValue(key);
+        if (value)
+            id.append(value->get()).append('-');
+        else
+        {
+            const char* ptr = key;
+            if (strlen(key) > 11) //skip the "transaction" prefix of the key
+                ptr += 11;
+            id.append('?').append(ptr).append('-');
+        }
+    }
+
+public:
+    IMPLEMENT_IINTERFACE;
+    CTransIDBuilder(const char* _seed, bool _localSeed, const char* _seedType, unsigned _maxLength, unsigned _maxSeq, unsigned _seedExpiredSeconds)
+        : seed(_seed), localSeed(_localSeed), seedType(_seedType), maxLength(_maxLength), maxSeq(_maxSeq), seedExpiredSeconds(_seedExpiredSeconds)
+    {
+        CDateTime now;
+        now.setNow();
+        createTime = now.getSimple();
+    };
+    virtual ~CTransIDBuilder() {};
+
+    bool checkMaxSequenceNumber() { return (maxSeq == 0) || (seq < maxSeq); };
+    bool checkMaxLength(unsigned length) { return (maxLength == 0) || (length <= maxLength); };
+    bool checkTimeout()
+    {
+        if (seedExpiredSeconds ==0)
+            return true;
+
+        CDateTime now;
+        now.setNow();
+        return now.getSimple() < createTime + seedExpiredSeconds;
+    };
+    bool isLocalSeed() { return localSeed; };
+    void resetTransSeed(const char* newSeed, const char* newSeedType)
+    {
+        if (isEmptyString(newSeed))
+            throw MakeStringException(EspLoggingErrors::GetTransactionSeedFailed, "TransactionSeed cannot be empty.");
+        seed.set(newSeed);
+        seedType.set(newSeedType);
+        seq = 0;
+
+        CDateTime now;
+        now.setNow();
+        createTime = now.getSimple();
+    };
+
+    virtual const char* getTransSeed() { return seed.get(); };
+    virtual void getTransID(StringAttrMapping* transIDFields, StringBuffer& id)
+    {
+        id.clear();
+        if (transIDFields)
+        {
+            add(transIDFields, sTransactionDateTime, id);
+            add(transIDFields, sTransactionMethod, id);
+            add(transIDFields, sTransactionIdentifier, id);
+        }
+        id.append(seed.get());
+        if (seedType.length())
+            id.append(seedType.get());
+        id.append(++seq);
+    };
+};
+
 interface IEspUpdateLogRequestWrap : extends IInterface
 {
     virtual const char* getGUID()=0;
@@ -151,6 +244,46 @@ interface IEspLogAgent : extends IInterface
     virtual void filterLogContent(IEspUpdateLogRequestWrap* req) = 0;
 };
 
+class CESPLogContentGroupFilters : public CInterface, implements IInterface
+{
+    ESPLogContentGroup group;
+    StringArray filters;
+
+public:
+    IMPLEMENT_IINTERFACE;
+
+    CESPLogContentGroupFilters(ESPLogContentGroup _group) : group(_group) {};
+    ESPLogContentGroup getGroup() { return group; };
+    StringArray& getFilters() { return filters; };
+    void clearFilters() { filters.clear(); };
+    unsigned getFilterCount() { return filters.length(); };
+    void addFilter(const char* filter)
+    {
+        if (filter && *filter)
+            filters.append(filter);
+    };
+};
+
+class LOGGINGCOMMON_API CLogContentFilter : public CInterface
+{
+    bool            logBackEndResp;
+    StringArray     logContentFilters;
+    CIArrayOf<CESPLogContentGroupFilters> groupFilters;
+
+    bool readLogFilters(IPropertyTree* cfg, unsigned groupID);
+    void filterLogContentTree(StringArray& filters, IPropertyTree* originalContentTree, IPropertyTree* newLogContentTree, bool& logContentEmpty);
+    void filterAndAddLogContentBranch(StringArray& branchNamesInFilter, unsigned idx, StringArray& branchNamesInLogContent,
+        IPropertyTree* in, IPropertyTree* updateLogRequestTree, bool& logContentEmpty);
+    void addLogContentBranch(StringArray& branchNames, IPropertyTree* contentToLogBranch, IPropertyTree* updateLogRequestTree);
+public:
+    IMPLEMENT_IINTERFACE;
+
+    CLogContentFilter() {};
+
+    void readAllLogFilters(IPropertyTree* cfg);
+    void filterLogContent(IEspUpdateLogRequestWrap* req);
+};
+
 class LOGGINGCOMMON_API CDBLogAgentBase : public CInterface, implements IEspLogAgent
 {
 protected:

+ 2 - 0
esp/logging/loggingmanager/loggingmanager.cpp

@@ -330,6 +330,8 @@ bool CLoggingManager::getTransactionID(StringAttrMapping* transFields, StringBuf
 
             IEspLogAgent* loggingAgent = loggingThread->getLogAgent();
             loggingAgent->getTransactionID(transFields, transactionID);
+            if (!transactionID.isEmpty())
+                ESPLOG(LogMax, "Got TransactionID '%s'", transactionID.str());
             return true;
         }
     }

+ 2 - 0
initfiles/componentfiles/configxml/@temp/CMakeLists.txt

@@ -22,8 +22,10 @@ FOREACH ( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_WsSMC.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/roxiePlugins.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_DynamicESDL.xsl
+    ${CMAKE_CURRENT_SOURCE_DIR}/esp_logging_transid.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_wslogging.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/logging_agent.xsl
+    ${CMAKE_CURRENT_SOURCE_DIR}/wslogserviceespagent.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_wssql.xsl
 )
     Install ( FILES ${iFILES} DESTINATION componentfiles/configxml/@temp COMPONENT Runtime )

+ 35 - 0
initfiles/componentfiles/configxml/@temp/esp_logging_transid.xsl

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2018 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.
+################################################################################
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default"
+xmlns:set="http://exslt.org/sets" exclude-result-prefixes="set">
+
+  <xsl:template name="EspLoggingTransactionID">
+    <xsl:param name="agentNode"/>
+    <xsl:if test="string($agentNode/@MaxTransIDLength) != ''">
+      <MaxTransIDLength><xsl:value-of select="$agentNode/@MaxTransIDLength"/></MaxTransIDLength>
+    </xsl:if>
+    <xsl:if test="string($agentNode/@MaxTransIDSequenceNumber) != ''">
+      <MaxTransIDSequenceNumber><xsl:value-of select="$agentNode/@MaxTransIDSequenceNumber"/></MaxTransIDSequenceNumber>
+    </xsl:if>
+    <xsl:if test="string($agentNode/@MaxTransSeedTimeoutMinutes) != ''">
+      <MaxTransSeedTimeoutMinutes><xsl:value-of select="$agentNode/@MaxTransSeedTimeoutMinutes"/></MaxTransSeedTimeoutMinutes>
+    </xsl:if>
+  </xsl:template>
+</xsl:stylesheet>

+ 19 - 8
initfiles/componentfiles/configxml/@temp/esp_service_DynamicESDL.xsl

@@ -20,6 +20,7 @@
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default" xmlns:seisint="http://seisint.com"  xmlns:set="http://exslt.org/sets" exclude-result-prefixes="seisint set">
     <xsl:import href="esp_service.xsl"/>
     <xsl:import href="logging_agent.xsl"/>
+    <xsl:import href="wslogserviceespagent.xsl"/>
 
     <xsl:template match="EspService">
         <xsl:param name="bindingNode"/>
@@ -72,14 +73,24 @@
                 <LoggingManager name="{$managerNode/@name}">
                     <xsl:for-each select="$managerNode/ESPLoggingAgent">
                         <xsl:variable name="agentName" select="@ESPLoggingAgent"/>
-                        <xsl:variable name="agentNode" select="/Environment/Software/ESPLoggingAgent[@name=$agentName]"/>
-                        <xsl:if test="not($agentNode)">
-                            <xsl:message terminate="yes">An ESP Logging Agent <xsl:value-of select="$agentName"/>  for <xsl:value-of select="$managerNode/@name"/> is undefined!</xsl:message>
-                        </xsl:if>
-                        <xsl:call-template name="ESPLoggingAgent">
-                            <xsl:with-param name="agentName" select="$agentName"/>
-                            <xsl:with-param name="agentNode" select="$agentNode"/>
-                        </xsl:call-template>
+                        <xsl:variable name="espLoggingAgentNode" select="/Environment/Software/ESPLoggingAgent[@name=$agentName]"/>
+                        <xsl:variable name="wsLogServiceESPAgentNode" select="/Environment/Software/WsLogServiceESPAgent[@name=$agentName]"/>
+                        <xsl:choose>
+                            <xsl:when test="($espLoggingAgentNode)">
+                                <xsl:call-template name="ESPLoggingAgent">
+                                    <xsl:with-param name="agentName" select="$agentName"/>
+                                    <xsl:with-param name="agentNode" select="$espLoggingAgentNode"/>
+                                </xsl:call-template>
+                            </xsl:when>
+                            <xsl:when test="($wsLogServiceESPAgentNode)">
+                                <xsl:call-template name="WsLogServiceESPAgent">
+                                    <xsl:with-param name="managerNode" select="$managerNode"/>
+                                </xsl:call-template>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:message terminate="yes">ESP Logging Agent is undefined for <xsl:value-of select="$managerName"/> !</xsl:message>
+                            </xsl:otherwise>
+                        </xsl:choose>
                     </xsl:for-each>
                 </LoggingManager>
 

+ 7 - 9
initfiles/componentfiles/configxml/@temp/logging_agent.xsl

@@ -20,6 +20,8 @@
 
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default"
 xmlns:set="http://exslt.org/sets">
+    <xsl:import href="esp_logging_transid.xsl"/>
+
     <xsl:template name="LogSourceMap">
         <xsl:param name="agentNode"/>
         <LogSourceMap>
@@ -168,15 +170,11 @@ xmlns:set="http://exslt.org/sets">
             <xsl:if test="string($agentNode/@MaxServerWaitingSeconds) != ''">
                 <MaxServerWaitingSeconds><xsl:value-of select="$agentNode/@MaxServerWaitingSeconds"/></MaxServerWaitingSeconds>
             </xsl:if>
-            <xsl:if test="string($agentNode/@MaxTransIDLength) != ''">
-                <MaxTransIDLength><xsl:value-of select="$agentNode/@MaxTransIDLength"/></MaxTransIDLength>
-            </xsl:if>
-            <xsl:if test="string($agentNode/@MaxTransIDSequenceNumber) != ''">
-                <MaxTransIDSequenceNumber><xsl:value-of select="$agentNode/@MaxTransIDSequenceNumber"/></MaxTransIDSequenceNumber>
-            </xsl:if>
-            <xsl:if test="string($agentNode/@MaxTransSeedTimeoutMinutes) != ''">
-                <MaxTransSeedTimeoutMinutes><xsl:value-of select="$agentNode/@MaxTransSeedTimeoutMinutes"/></MaxTransSeedTimeoutMinutes>
-            </xsl:if>
+
+            <xsl:call-template name="EspLoggingTransactionID">
+                <xsl:with-param name="agentNode" select="$agentNode"/>
+            </xsl:call-template>
+
             <xsl:call-template name="LogBasic">
                 <xsl:with-param name="agentNode" select="$agentNode"/>
             </xsl:call-template>

+ 122 - 0
initfiles/componentfiles/configxml/@temp/wslogserviceespagent.xsl

@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2017 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.
+################################################################################
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default"
+xmlns:set="http://exslt.org/sets">
+    <xsl:import href="esp_logging_transid.xsl"/>
+
+    <xsl:template name="WsLogServiceESPAgent" type="DefaultLoggingAgent">
+        <xsl:param name="managerNode"/>
+        <xsl:for-each select="$managerNode/ESPLoggingAgent">
+            <xsl:variable name="agentName" select="@ESPLoggingAgent"/>
+            <xsl:variable name="agentNode" select="/Environment/Software/WsLogServiceESPAgent[@name=$agentName]"/>
+            <xsl:if test="not($agentNode)">
+                <xsl:message terminate="yes">An WsLogService ESP Logging Agent <xsl:value-of select="$agentName"/>  for <xsl:value-of select="$managerNode/@name"/> is undefined!</xsl:message>
+            </xsl:if>
+            <xsl:variable name="loggingServer" select="$agentNode/LoggingServer"/>
+            <xsl:if test="not($loggingServer)">
+                <xsl:message terminate="yes">ESP logging server is undefined for <xsl:value-of select="$agentName"/> </xsl:message>
+            </xsl:if>
+            <xsl:variable name="loggingServerUrl" select="$loggingServer/@Url"/>
+            <xsl:if test="string($loggingServerUrl) = ''">
+                <xsl:message terminate="yes">Logging Server URL is undefined!</xsl:message>
+            </xsl:if>
+            <xsl:variable name="logDataXPath" select="$agentNode/LogDataXPath"/>
+            <xsl:if test="not($logDataXPath)">
+                <xsl:message terminate="yes">Log Data XPath is undefined for <xsl:value-of select="$agentName"/> </xsl:message>
+            </xsl:if>
+
+            <LogAgent name="{$agentName}" type="LogAgent" services="GetTransactionSeed,UpdateLog,GetTransactionID" plugin="wslogserviceespagent">
+                <LoggingServer url="{$loggingServerUrl}" user="{$loggingServer/@User}" password="{$loggingServer/@Password}"/>
+                <xsl:if test="string($agentNode/@FailSafe) != ''">
+                    <FailSafe><xsl:value-of select="$agentNode/@FailSafe"/></FailSafe>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@FailSafeLogsDir) != ''">
+                    <FailSafeLogsDir><xsl:value-of select="$agentNode/@FailSafeLogsDir"/></FailSafeLogsDir>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@MaxLogQueueLength) != ''">
+                    <MaxLogQueueLength><xsl:value-of select="$agentNode/@MaxLogQueueLength"/></MaxLogQueueLength>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@MaxTriesGTS) != ''">
+                    <MaxTriesGTS><xsl:value-of select="$agentNode/@MaxTriesGTS"/></MaxTriesGTS>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@MaxTriesRS) != ''">
+                    <MaxTriesRS><xsl:value-of select="$agentNode/@MaxTriesRS"/></MaxTriesRS>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@QueueSizeSignal) != ''">
+                    <QueueSizeSignal><xsl:value-of select="$agentNode/@QueueSizeSignal"/></QueueSizeSignal>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@TransactionSeedType) != ''">
+                    <TransactionSeedType><xsl:value-of select="$agentNode/@TransactionSeedType"/></TransactionSeedType>
+                </xsl:if>
+                <xsl:if test="string($agentNode/@AlternativeTransactionSeedType) != ''">
+                    <AlternativeTransactionSeedType><xsl:value-of select="$agentNode/@AlternativeTransactionSeedType"/></AlternativeTransactionSeedType>
+                </xsl:if>
+
+                <xsl:call-template name="EspLoggingTransactionID">
+                    <xsl:with-param name="agentNode" select="$agentNode"/>
+                </xsl:call-template>
+                
+                <LogDataXPath>
+                    <xsl:if test="string($logDataXPath/@IP) != ''">
+                        <IP><xsl:value-of select="$logDataXPath/@IP"/></IP>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@UserName) != ''">
+                        <UserName><xsl:value-of select="$logDataXPath/@UserName"/></UserName>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@ServiceName) != ''">
+                        <ServiceName><xsl:value-of select="$logDataXPath/@ServiceName"/></ServiceName>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@RecordCount) != ''">
+                        <RecordCount><xsl:value-of select="$logDataXPath/@RecordCount"/></RecordCount>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@DomainName) != ''">
+                        <DomainName><xsl:value-of select="$logDataXPath/@DomainName"/></DomainName>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@GUID) != ''">
+                        <GUID><xsl:value-of select="$logDataXPath/@GUID"/></GUID>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@BlindLogging) != ''">
+                        <BlindLogging><xsl:value-of select="$logDataXPath/@BlindLogging"/></BlindLogging>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@EncryptedLogging) != ''">
+                        <EncryptedLogging><xsl:value-of select="$logDataXPath/@EncryptedLogging"/></EncryptedLogging>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@ForwardLog) != ''">
+                        <ForwardLog><xsl:value-of select="$logDataXPath/@ForwardLog"/></ForwardLog>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@RawLogInformation) != ''">
+                        <RawLogInformation><xsl:value-of select="$logDataXPath/@RawLogInformation"/></RawLogInformation>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@CompressBlobs) != ''">
+                        <CompressBlobs><xsl:value-of select="$logDataXPath/@CompressBlobs"/></CompressBlobs>
+                    </xsl:if>
+                    <xsl:if test="string($logDataXPath/@WorkunitID) != ''">
+                        <WorkunitID><xsl:value-of select="$logDataXPath/@WorkunitID"/></WorkunitID>
+                    </xsl:if>
+                    <xsl:for-each select="$agentNode/LogInfo">
+                        <LogInfo name="{current()/@name}" valueXPath="{current()/@valueXPath}" dataXPath="{current()/@dataXPath}" multipleValue="{current()/@multipleValue}" multipleData="{current()/@multipleData}" encodeValue="{current()/@encodeValue}" encodeData="{current()/@encodeData}"/>
+                    </xsl:for-each>
+                </LogDataXPath>
+            </LogAgent>
+        </xsl:for-each>
+    </xsl:template>
+
+</xsl:stylesheet>