Jelajahi Sumber

HPCC-24859 Define modular log agent framework and plugin

Add an ESP trace logging abstraction to provide granular control of trace
output. This is a substitute for ESPLOG that wraps jlog functions. Each trace
message specifies an audience, a class, and priority, with configurable control
which priorities should be included in trace output.

Add a generic log agent implementation to the core logginglib project. It
delegates functionality to helper modules that are defined either in the
same project or in a log agent plugin project. The generic code is separated
from any one plugin to support open and proprietary plugins.

Define a helper module to conditionally add UpdateLogRequest XML content to
trace log output. Provide configuration shortcuts that enable this behavior
with minimal configuration.

Define a helper module to conditionally append UpdateLogRequest XML content
and always append transformed content to a text file. Support for generic
data transformations is forthcoming.

Support configurations supplied as either XML or YAML. YAML support implies
no dependencies on empty elements and no reliance on element content.

Add a log agent plugin that exposes all base framework features.

Signed-off-by: Tim Klemm <tim.klemm@lexisnexisrisk.com>
Tim Klemm 4 tahun lalu
induk
melakukan
05079db757
26 mengubah file dengan 3727 tambahan dan 85 penghapusan
  1. 1 0
      esp/logging/loggingagent/CMakeLists.txt
  2. 71 0
      esp/logging/loggingagent/modularlogagent/CMakeLists.txt
  3. 383 0
      esp/logging/loggingagent/modularlogagent/README.md
  4. 35 0
      esp/logging/loggingagent/modularlogagent/modularlogagentplugin.cpp
  5. 29 0
      esp/logging/loggingagent/modularlogagent/modularlogagentplugin.hpp
  6. 1 0
      esp/logging/logginglib/CMakeLists.txt
  7. 6 3
      esp/logging/logginglib/LogFailSafe.cpp
  8. 178 0
      esp/logging/logginglib/logconfigptree.hpp
  9. 41 43
      esp/logging/logginglib/loggingagentbase.cpp
  10. 34 24
      esp/logging/logginglib/loggingagentbase.hpp
  11. 11 8
      esp/logging/logginglib/logthread.cpp
  12. 1515 0
      esp/logging/logginglib/modularlogagent.cpp
  13. 481 0
      esp/logging/logginglib/modularlogagent.hpp
  14. 400 0
      esp/logging/logginglib/modularlogagent.ipp
  15. 20 4
      esp/logging/loggingmanager/loggingmanager.cpp
  16. 164 0
      esp/platform/esptraceloggingcomponent.hpp
  17. 1 3
      esp/platform/tokenserialization.hpp
  18. 1 0
      initfiles/componentfiles/configschema/xsd/buildset.xml
  19. 1 0
      initfiles/componentfiles/configxml/@temp/CMakeLists.txt
  20. 9 0
      initfiles/componentfiles/configxml/@temp/esp_service_DynamicESDL.xsl
  21. 109 0
      initfiles/componentfiles/configxml/@temp/modularlogagent.xsl
  22. 1 0
      initfiles/componentfiles/configxml/CMakeLists.txt
  23. 6 0
      initfiles/componentfiles/configxml/buildsetCC.xml.in
  24. 1 0
      initfiles/componentfiles/configxml/cgencomplist_linux.xml.in
  25. 1 0
      initfiles/componentfiles/configxml/cgencomplist_win.xml.in
  26. 227 0
      initfiles/componentfiles/configxml/modularlogagent.xsd

+ 1 - 0
esp/logging/loggingagent/CMakeLists.txt

@@ -15,3 +15,4 @@
 ################################################################################
 HPCC_ADD_SUBDIRECTORY (espserverloggingagent)
 HPCC_ADD_SUBDIRECTORY (cassandraloggingagent "USE_CASSANDRA")
+HPCC_ADD_SUBDIRECTORY (modularlogagent)

+ 71 - 0
esp/logging/loggingagent/modularlogagent/CMakeLists.txt

@@ -0,0 +1,71 @@
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2020 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.
+################################################################################
+
+
+# Component: modularlogagent
+#####################################################
+# Description:
+# ------------
+#	 Cmake Input File for modularlogagent
+#####################################################
+
+project( modularlogagent )
+
+set ( SRCS
+    modularlogagentplugin.cpp
+)
+
+include_directories (
+    ${HPCC_SOURCE_DIR}/esp/platform
+    ${HPCC_SOURCE_DIR}/system/include
+    ${HPCC_SOURCE_DIR}/system/jlib
+    ${HPCC_SOURCE_DIR}/system/xmllib
+    ${HPCC_SOURCE_DIR}/system/security/shared
+    ${HPCC_SOURCE_DIR}/esp/bindings
+    ${HPCC_SOURCE_DIR}/esp/bindings/http/client
+    ${HPCC_SOURCE_DIR}/esp/bindings/SOAP/client
+    ${HPCC_SOURCE_DIR}/esp/bindings/SOAP/xpp
+    ${HPCC_SOURCE_DIR}/esp/bindings/SOAP/Platform
+    ${HPCC_SOURCE_DIR}/esp/bindings/SOAP/LoggingService
+    ${HPCC_SOURCE_DIR}/esp/clients
+    ${HPCC_SOURCE_DIR}/esp/clients/espcommonclient
+    ${HPCC_SOURCE_DIR}/esp/esdl
+    ${HPCC_SOURCE_DIR}/esp/esplib
+    ${HPCC_SOURCE_DIR}/esp/logging/logginglib
+    ${HPCC_SOURCE_DIR}/common/thorhelper
+    ./../..
+    ./..
+)
+
+ADD_DEFINITIONS( -D_USRDLL -DMODULARLOGAGENT_EXPORTS )
+
+if (RELEASE_BUILD EQUAL 1)
+    ADD_DEFINITIONS( -DISC_NO_MAIN)
+endif (RELEASE_BUILD EQUAL 1)
+
+HPCC_ADD_LIBRARY( modularlogagent SHARED ${SRCS} )
+
+add_dependencies( modularlogagent espscm )
+
+install ( TARGETS modularlogagent RUNTIME DESTINATION ${EXEC_DIR} LIBRARY DESTINATION ${LIB_DIR} )
+
+target_link_libraries ( modularlogagent
+    ${XALAN_LIBRARIES} ${XERCES_LIBRARIES}
+    jlib
+    xmllib
+    logginglib
+    esphttp
+)

File diff ditekan karena terlalu besar
+ 383 - 0
esp/logging/loggingagent/modularlogagent/README.md


+ 35 - 0
esp/logging/loggingagent/modularlogagent/modularlogagentplugin.cpp

@@ -0,0 +1,35 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#include "modularlogagentplugin.hpp"
+
+extern "C"
+{
+    /**
+     * The plugin passes a default instance of CModuleFactory to a new instance of
+     * CEspLogAgent. This makes all platform-native modules available for use.
+     *
+     * The README.md file must be updated if the default factory is updated to include more modules
+     * or if plugin-specific modules are defined and registered.
+     */
+    MODULARLOGAGENTPLUGIN_API IEspLogAgent* newLoggingAgent()
+    {
+        using namespace ModularLogAgent;
+        Owned<CModuleFactory> factory(new CModuleFactory());
+        return new CEspLogAgent(*factory.getClear());
+    }
+}

+ 29 - 0
esp/logging/loggingagent/modularlogagent/modularlogagentplugin.hpp

@@ -0,0 +1,29 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2020 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _MODULARLOGAGENTPLUGIN_HPP_
+#define _MODULARLOGAGENTPLUGIN_HPP_
+
+#include "modularlogagent.hpp"
+
+#ifdef MODULARLOGAGENT_EXPORTS
+    #define MODULARLOGAGENTPLUGIN_API DECL_EXPORT
+#else
+    #define MODULARLOGAGENTPLUGIN_API DECL_IMPORT
+#endif
+
+#endif // _MODULARLOGAGENTPLUGIN_HPP_

+ 1 - 0
esp/logging/logginglib/CMakeLists.txt

@@ -42,6 +42,7 @@ set ( SRCS
     ${HPCC_SOURCE_DIR}/esp/logging/logginglib/LogSerializer.cpp
     ${HPCC_SOURCE_DIR}/esp/logging/logginglib/LogFailSafe.cpp
     ${HPCC_SOURCE_DIR}/esp/logging/logginglib/logthread.cpp
+    ${HPCC_SOURCE_DIR}/esp/logging/logginglib/modularlogagent.cpp
 )
 
 HPCC_ADD_LIBRARY( logginglib SHARED ${SRCS} )

+ 6 - 3
esp/logging/logginglib/LogFailSafe.cpp

@@ -31,6 +31,9 @@
 #include "jmisc.hpp"
 #include "soapbind.hpp"
 
+#include "logconfigptree.hpp"
+using namespace LogConfigPTree;
+
 #define RECEIVING "_acked_"
 #define SENDING   "_sending_"
 
@@ -83,16 +86,16 @@ CLogFailSafe::~CLogFailSafe()
 
 void CLogFailSafe::readCfg(IPropertyTree* cfg)
 {
-    StringBuffer safeRolloverThreshold(cfg->queryProp(PropSafeRolloverThreshold));
+    StringBuffer safeRolloverThreshold(queryConfigValue(cfg, PropSafeRolloverThreshold));
     if (!safeRolloverThreshold.isEmpty())
         readSafeRolloverThresholdCfg(safeRolloverThreshold);
 
-    const char* logsDir = cfg->queryProp(PropFailSafeLogsDir);
+    const char* logsDir = queryConfigValue(cfg, PropFailSafeLogsDir);
     if (!isEmptyString(logsDir))
         m_logsdir.set(logsDir);
     else
         m_logsdir.set(DefaultFailSafeLogsDir);
-    decoupledLogging = cfg->getPropBool(PropDecoupledLogging, false);
+    decoupledLogging = getConfigValue<bool>(cfg, PropDecoupledLogging, false);
 }
 
 void CLogFailSafe::readSafeRolloverThresholdCfg(StringBuffer& safeRolloverThreshold)

+ 178 - 0
esp/logging/logginglib/logconfigptree.hpp

@@ -0,0 +1,178 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _LOGCONFIGPTREE_HPP_
+#define _LOGCONFIGPTREE_HPP_
+
+#include "jptree.hpp"
+#include "jstring.hpp"
+#include "tokenserialization.hpp"
+
+namespace LogConfigPTree
+{
+    /**
+     * When relying upon a default value, check and report if the default value is not compatibile
+     * with the requested value type. Most mismatches are errors. Some may be recorded as warnings
+     * if a reasonable use case, e.g., defaulting an unsigned value to -1, exists to justify it.
+     * In all cases, recorded findings should be addressed by either changing the default value or
+     * casting the intended value correctly.
+     */
+    template <typename value_t, typename default_t>
+    value_t applyDefault(const default_t& defaultValue, const char* xpath)
+    {
+        auto report = [&](bool isError)
+        {
+            StringBuffer msg;
+            msg << "unexpected default value '" << defaultValue << "'";
+            if (!isEmptyString(xpath))
+                msg << " for configuration XPath '" << xpath << "'";
+            if (isError)
+                IERRLOG("%s", msg.str());
+            else
+                IWARNLOG("%s", msg.str());
+        };
+
+        if (std::is_integral<value_t>() == std::is_integral<default_t>())
+        {
+            if (std::is_signed<value_t>() == std::is_signed<default_t>())
+            {
+                if (defaultValue < std::numeric_limits<value_t>::min())
+                    report(true);
+                else if (defaultValue > std::numeric_limits<value_t>::max())
+                    report(true);
+            }
+            else if (std::is_signed<value_t>())
+            {
+                if (defaultValue > std::numeric_limits<value_t>::max())
+                    report(true);
+            }
+            else
+            {
+                if (defaultValue < 0 || defaultValue > std::numeric_limits<value_t>::max())
+                    report(false);
+            }
+        }
+        else if (std::is_floating_point<value_t>() == std::is_floating_point<default_t>())
+        {
+            if (defaultValue < -std::numeric_limits<value_t>::max())
+                report(true);
+            else if (defaultValue > std::numeric_limits<value_t>::max())
+                report(true);
+        }
+        else
+        {
+            report(false);
+        }
+
+        return value_t(defaultValue);
+    }
+
+    /**
+     * Access a configuration property value, supporting legacy configurations that use element
+     * content and newer configurations relying on attributes.
+     *
+     * Preferring new configurations instead of legacy, consider a request for an attribute to be
+     * just that and a request for element content to be a request for either an attribute or an
+     * element. A request for "A/@B" will be viewed as only a request for attribute B in element A,
+     * while a request for "A/B" will be viewed first as a request for "A/@B" and as a request for
+     * "A/B" only if "A/@B" is not found.
+     */
+    inline const char* queryConfigValue(const IPTree& node, const char* xpath)
+    {
+        const char* value = nullptr;
+        if (!isEmptyString(xpath))
+        {
+            const char* delim = strrchr(xpath, '/');
+            size_t attrIndex = (delim ? delim - xpath + 1 : 0);
+            if (xpath[attrIndex] != '@')
+            {
+                StringBuffer altXPath(xpath);
+                altXPath.insert(attrIndex, '@');
+                value = node.queryProp(altXPath);
+                if (value)
+                    return value;
+            }
+            value = node.queryProp(xpath);
+        }
+        return value;
+    }
+
+    inline const char* queryConfigValue(const IPTree* node, const char* xpath)
+    {
+        if (node)
+            return queryConfigValue(*node, xpath);
+        return nullptr;
+    }
+
+    inline bool getConfigValue(const IPTree& node, const char* xpath, StringBuffer& value)
+    {
+        const char* raw = queryConfigValue(node, value);
+        if (raw)
+        {
+            value.set(raw);
+            return true;
+        }
+        return false;
+    }
+
+    inline bool getConfigValue(const IPTree* node, const char* xpath, StringBuffer& value)
+    {
+        if (node)
+            return getConfigValue(*node, xpath, value);
+        return false;
+    }
+
+    template <typename value_t, typename default_t>
+    value_t getConfigValue(const IPTree& node, const char* xpath, const default_t& defaultValue)
+    {
+        const char* raw = queryConfigValue(node, xpath);
+        if (raw)
+        {
+            static TokenDeserializer deserializer;
+            value_t value;
+            if (deserializer(raw, value) == Deserialization_SUCCESS)
+                return value;
+        }
+        return applyDefault<value_t>(defaultValue, xpath);
+    }
+
+    template <typename value_t>
+    value_t getConfigValue(const IPTree& node, const char* xpath)
+    {
+        value_t defaultValue = value_t(0);
+        return getConfigValue<value_t>(node, xpath, defaultValue);
+    }
+
+    template <typename value_t, typename default_t>
+    value_t getConfigValue(const IPTree* node, const char* xpath, const default_t& defaultValue)
+    {
+        if (node)
+            return getConfigValue<value_t>(*node, xpath, defaultValue);
+        return applyDefault<value_t>(defaultValue, xpath);
+    }
+
+    template <typename value_t>
+    value_t getConfigValue(const IPTree* node, const char* xpath)
+    {
+        if (node)
+            return getConfigValue<value_t>(*node, xpath);
+        return value_t(0);
+    }
+
+} // namespace LogConfigPTree
+
+#endif // _LOGCONFIGPTREE_HPP_

+ 41 - 43
esp/logging/logginglib/loggingagentbase.cpp

@@ -345,52 +345,14 @@ IEspUpdateLogRequestWrap* CLogContentFilter::filterLogContent(IEspUpdateLogReque
     return newReq.getClear();
 }
 
-CLogAgentBase::CVariantIterator::CVariantIterator(const CLogAgentBase& agent)
-    : m_agent(&agent)
-    , m_variantIt(m_agent->agentVariants.end())
-{
-}
-
-CLogAgentBase::CVariantIterator::~CVariantIterator()
-{
-}
-
-bool CLogAgentBase::CVariantIterator::first()
-{
-    m_variantIt = m_agent->agentVariants.begin();
-    return isValid();
-}
-
-bool CLogAgentBase::CVariantIterator::next()
-{
-    if (m_variantIt != m_agent->agentVariants.end())
-        ++m_variantIt;
-    return isValid();
-}
-
-bool CLogAgentBase::CVariantIterator::isValid()
-{
-    return (m_variantIt != m_agent->agentVariants.end());
-}
-
-const IEspLogAgentVariant& CLogAgentBase::CVariantIterator::query()
-{
-    if (!isValid())
-        throw MakeStringException(0, "CVariantIterator::query called in invalid state");
-    IEspLogAgentVariant* entry = m_variantIt->get();
-    if (nullptr == entry)
-        throw MakeStringException(0, "CVariantIterator::query encountered a NULL variant");
-    return *entry;
-}
-
-CLogAgentBase::CVariant::CVariant(const char* name, const char* type, const char* group)
+CEspLogAgentVariant::CEspLogAgentVariant(const char* name, const char* type, const char* group)
 {
     m_name.setown(normalize(name));
     m_type.setown(normalize(type));
     m_group.setown(normalize(group));
 }
 
-String* CLogAgentBase::CVariant::normalize(const char* token)
+String* CEspLogAgentVariant::normalize(const char* token)
 {
     struct PoolComparator
     {
@@ -422,6 +384,42 @@ String* CLogAgentBase::CVariant::normalize(const char* token)
     return tmp.getClear();
 }
 
+CEspLogAgentVariantIterator::CEspLogAgentVariantIterator()
+{
+}
+
+CEspLogAgentVariantIterator::~CEspLogAgentVariantIterator()
+{
+}
+
+bool CEspLogAgentVariantIterator::first()
+{
+    m_variantIt = variants().begin();
+    return isValid();
+}
+
+bool CEspLogAgentVariantIterator::next()
+{
+    if (m_variantIt != variants().end())
+        ++m_variantIt;
+    return isValid();
+}
+
+bool CEspLogAgentVariantIterator::isValid()
+{
+    return (m_variantIt != variants().end());
+}
+
+const IEspLogAgentVariant& CEspLogAgentVariantIterator::query()
+{
+    if (!isValid())
+        throw makeStringException(0, "CEspLogAgentVariantIterator::query called in invalid state");
+    IEspLogAgentVariant* entry = m_variantIt->get();
+    if (nullptr == entry)
+        throw makeStringException(0, "CEspLogAgentVariantIterator::query encountered a NULL variant");
+    return *entry;
+}
+
 bool CLogAgentBase::initVariants(IPropertyTree* cfg)
 {
     Owned<IPropertyTreeIterator> variantNodes(cfg->getElements("//Variant"));
@@ -430,15 +428,15 @@ bool CLogAgentBase::initVariants(IPropertyTree* cfg)
         IPTree& variant = variantNodes->query();
         const char* type = variant.queryProp("@type");
         const char* group = variant.queryProp("@group");
-        Owned<CVariant> instance = new CVariant(agentName.get(), type, group);
-        Variants::iterator it = agentVariants.find(instance);
+        Owned<CEspLogAgentVariant> instance = new CEspLogAgentVariant(agentName.get(), type, group);
+        CEspLogAgentVariants::iterator it = agentVariants.find(instance);
 
         if (agentVariants.end() == it)
             agentVariants.insert(instance.getClear());
     }
 
     if (agentVariants.empty())
-        agentVariants.insert(new CVariant(agentName.get(), nullptr, nullptr));
+        agentVariants.insert(new CEspLogAgentVariant(agentName.get(), nullptr, nullptr));
     return true;
 }
 

+ 34 - 24
esp/logging/logginglib/loggingagentbase.hpp

@@ -361,6 +361,35 @@ public:
     IEspUpdateLogRequestWrap* filterLogContent(IEspUpdateLogRequestWrap* req);
 };
 
+class LOGGINGCOMMON_API CEspLogAgentVariant : implements CInterfaceOf<IEspLogAgentVariant>
+{
+public:
+    CEspLogAgentVariant(const char* name, const char* type, const char* group);
+    virtual const char* getName() const override { return m_name->str(); }
+    virtual const char* getType() const override { return m_type->str(); }
+    virtual const char* getGroup() const override { return m_group->str(); }
+private:
+    String* normalize(const char* token);
+    Owned<String> m_name;
+    Owned<String> m_type;
+    Owned<String> m_group;
+};
+using CEspLogAgentVariants = std::set<Owned<CEspLogAgentVariant>, IEspLogAgentVariantComparator>;
+class LOGGINGCOMMON_API CEspLogAgentVariantIterator : implements CInterfaceOf<const IEspLogAgentVariantIterator>
+{
+public:
+    CEspLogAgentVariantIterator();
+    ~CEspLogAgentVariantIterator();
+    virtual bool first() override;
+    virtual bool next() override;
+    virtual bool isValid() override;
+    virtual const IEspLogAgentVariant& query() override;
+protected:
+    virtual const CEspLogAgentVariants& variants() const = 0;
+protected:
+    CEspLogAgentVariants::const_iterator m_variantIt;
+};
+
 class LOGGINGCOMMON_API CLogAgentBase : public CInterface, implements IEspLogAgent
 {
 protected:
@@ -406,33 +435,14 @@ public:
     bool initVariants(IPropertyTree* cfg) override;
     IEspLogAgentVariantIterator* getVariants() const override;
 protected:
-    class CVariant : implements CInterfaceOf<IEspLogAgentVariant>
+    CEspLogAgentVariants agentVariants;
+    class CVariantIterator : extends CEspLogAgentVariantIterator
     {
-    public:
-        CVariant(const char* name, const char* type, const char* group);
-        const char* getName() const override { return m_name->str(); }
-        const char* getType() const override { return m_type->str(); }
-        const char* getGroup() const override { return m_group->str(); }
-    private:
-        String* normalize(const char* token);
-        Owned<String> m_name;
-        Owned<String> m_type;
-        Owned<String> m_group;
-    };
-    using Variants = std::set<Owned<CVariant>, IEspLogAgentVariantComparator>;
-    Variants agentVariants;
-    class CVariantIterator : implements CInterfaceOf<const IEspLogAgentVariantIterator>
-    {
-    public:
-        CVariantIterator(const CLogAgentBase& agent);
-        ~CVariantIterator();
-        bool first() override;
-        bool next() override;
-        bool isValid() override;
-        const IEspLogAgentVariant& query() override;
     protected:
+        const CEspLogAgentVariants& variants() const override { return m_agent->agentVariants; }
         Linked<const CLogAgentBase> m_agent;
-        Variants::const_iterator    m_variantIt;
+    public:
+        CVariantIterator(const CLogAgentBase& agent) { m_agent.set(&agent); m_variantIt = variants().end(); }
     };
 };
 

+ 11 - 8
esp/logging/logginglib/logthread.cpp

@@ -23,6 +23,9 @@
 #include "logthread.hpp"
 #include "compressutil.hpp"
 
+#include "logconfigptree.hpp"
+using namespace LogConfigPTree;
+
 const char* const PropMaxLogQueueLength = "MaxLogQueueLength";
 const char* const PropQueueSizeSignal = "QueueSizeSignal";
 const char* const PropMaxTriesRS = "MaxTriesRS";
@@ -71,12 +74,12 @@ CLogThread::CLogThread(IPropertyTree* _cfg , const char* _service, const char* _
 
     logAgent.setown(_logAgent);
 
-    maxLogQueueLength = _cfg->getPropInt(PropMaxLogQueueLength, MaxLogQueueLength);
-    signalGrowingQueueAt = _cfg->getPropInt(PropQueueSizeSignal, QueueSizeSignal);
-    maxLogRetries = _cfg->getPropInt(PropMaxTriesRS, DefaultMaxTriesRS);
+    maxLogQueueLength = getConfigValue<int>(_cfg, PropMaxLogQueueLength, MaxLogQueueLength);
+    signalGrowingQueueAt = getConfigValue<int>(_cfg, PropQueueSizeSignal, QueueSizeSignal);
+    maxLogRetries = getConfigValue<int>(_cfg, PropMaxTriesRS, DefaultMaxTriesRS);
     //For decoupled logging, the fail safe is not needed because the logging agent always
     //picks up the logging requests from tank file.
-    ensureFailSafe = _cfg->getPropBool(PropFailSafe) && !_cfg->getPropBool(PropDisableFailSafe, false);
+    ensureFailSafe = getConfigValue<bool>(_cfg, PropFailSafe) && !getConfigValue<bool>(_cfg, PropDisableFailSafe, false);
     if(ensureFailSafe)
     {
         logFailSafe.setown(createFailSafeLogger(_cfg, _service, _agentName));
@@ -92,16 +95,16 @@ CLogThread::CLogThread(IPropertyTree* _cfg , const char* _service, const char* _
         Owned<CLogRequestReaderSettings> settings = new CLogRequestReaderSettings();
         settings->tankFileDir.set(tankFileDir.get());
 
-        const char* ackedFiles = _cfg->queryProp(PropAckedFiles);
+        const char* ackedFiles = queryConfigValue(_cfg, PropAckedFiles);
         settings->ackedFileList.set(isEmptyString(ackedFiles) ? PropDefaultAckedFiles : ackedFiles);
-        const char* ackedLogRequestFile = _cfg->queryProp(PropAckedLogRequests);
+        const char* ackedLogRequestFile = queryConfigValue(_cfg, PropAckedLogRequests);
         settings->ackedLogRequestFile.set(isEmptyString(ackedLogRequestFile) ? PropDefaultAckedLogRequests : ackedLogRequestFile);
-        int pendingLogBufferSize = _cfg->getPropInt(PropPendingLogBufferSize, DEFAULTPENDINGLOGBUFFERSIZE);
+        int pendingLogBufferSize = getConfigValue<int>(_cfg, PropPendingLogBufferSize, DEFAULTPENDINGLOGBUFFERSIZE);
         if (pendingLogBufferSize <= 0)
             throw MakeStringException(-1, "The %s (%d) should be greater than 0.", PropPendingLogBufferSize, pendingLogBufferSize);
 
         settings->pendingLogBufferSize = pendingLogBufferSize;
-        int waitSeconds = _cfg->getPropInt(PropReadRequestWaitingSeconds, DEFAULTREADLOGREQUESTWAITSECOND);
+        int waitSeconds = getConfigValue<int>(_cfg, PropReadRequestWaitingSeconds, DEFAULTREADLOGREQUESTWAITSECOND);
         if (waitSeconds <= 0)
             throw MakeStringException(-1, "The %s (%d) should be greater than 0.", PropReadRequestWaitingSeconds, waitSeconds);
 

File diff ditekan karena terlalu besar
+ 1515 - 0
esp/logging/logginglib/modularlogagent.cpp


+ 481 - 0
esp/logging/logginglib/modularlogagent.hpp

@@ -0,0 +1,481 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _MODULARLOGAGENT_HPP_
+#define _MODULARLOGAGENT_HPP_
+
+/**
+ * The modular log agent framework defines an implementation of IEspLogAgent that delegates agent
+ * requests to helper classes. This delegation enables a single agent plugin to support multiple
+ * logging requirements by configuring which helpers are to be used by any single agent created
+ * by the plugin.
+ *
+ * The framework includes at least one implementation of each type of helper. Plugins are not
+ * required to use these implementations, nor are they required to reference them with "standard"
+ * configuration keys. Consider that a plugin implementing a user's business logic may want its
+ * default behavior to use the business logic in place of a default assigned by the platform.
+ * Each plugin is encouraged to provide documentation of its exposed functionality.
+ *
+ * The "modularlogagent" platform plugin includes README.md, which describes the configuration of
+ * multiple elements used by the agent. The properties of each element may vary from plugin to
+ * plugin, but the elements are defined by the framework.
+ *
+ * Refer to interface and implementation declarations for additional details regarding available
+ * modules. Refer to each plugin to know which modules are registered and how they may be accessed.
+ */
+
+#include "loggingagentbase.hpp"
+#include "esptraceloggingcomponent.hpp"
+#include "jlog.hpp"
+#include "jstring.hpp"
+#include <map>
+
+interface IEsdlScriptContext;
+interface IXpathContext;
+
+/**
+ * The framework interfaces and classes are defined within a namespace.
+ *
+ * Plugins, especially those built as standoalone entities, are suscenptible to name collisions.
+ * All framework names are encapsulated in a namespace to reduce the risk of collisions.
+ */
+namespace ModularLogAgent
+{
+    interface IAgent;
+    class CModuleFactory;
+
+    ///
+    /// Root element name of an agent configuration. The value is imposed by the logging
+    /// framework and applies to all IEspLogAgent implementations.
+    ///
+    constexpr static const char* moduleAgent            = "LogAgent";
+
+    /**
+     * Implementation of IEspLogAgent interface that integrates modular log agent service (as
+     * identified by the LOGServiceType enumeration) implementations into general logging framework.
+     * Log content filters and variant identification are handled directly. All service
+     * functionality is delegated to user-configured module implementations.
+     *
+     * Log content filters are always applied, but are not required. The application of filters,
+     * even when none are configured, produces a side effect of combining update request fragments
+     * into single XML document. The agent requires the combined document and assumes, given an
+     * interface which accepts fragments, that the data given might not be in the required form.
+     *
+     * Agent variant identifiers are defined as they are for other agent implementations. The use of
+     * consistent terminology for multiple agent implementations does perpetuate the use of
+     * terminology that is inconsistent with custom ESDL transform terminology for the same concepts
+     * (specifically, group versus profile and type versus option).
+     *
+     * Service delegation requires an implementation of the IAgent interface. This
+     * implementation is obtained from a plugin-supplied CModuleFactory instance. Each
+     * plugin is responsible for specifying which modules are available to the plugin, the labels
+     * used to choose them, and which (if any) are chosen by default.
+     */
+    class LOGGINGCOMMON_API CEspLogAgent : implements IEspLogAgent, public CEspTraceLoggingComponent
+    {
+    public:
+        constexpr static const char* propConfiguration = "@configuration";
+    private:
+        class CVariantIterator : public CEspLogAgentVariantIterator
+        {
+        protected:
+            virtual const CEspLogAgentVariants& variants() const override { return m_agent->m_variants; }
+            Linked<const CEspLogAgent> m_agent;
+        public:
+            CVariantIterator(const CEspLogAgent& agent) { m_agent.set(&agent); m_variantIt = variants().end(); }
+        };
+    public:
+        IMPLEMENT_IINTERFACE;
+        virtual const char* getName() override;
+        virtual bool init(const char* name, const char* type, IPTree* configuration, const char* process) override;
+        virtual bool initVariants(IPTree* configuration) override;
+        virtual bool getTransactionSeed(IEspGetTransactionSeedRequest& request, IEspGetTransactionSeedResponse& response) override;
+        virtual void getTransactionID(StringAttrMapping* fields, StringBuffer& id) override;
+        virtual bool updateLog(IEspUpdateLogRequestWrap& request, IEspUpdateLogResponse& response) override;
+        virtual bool hasService(LOGServiceType type) override;
+        virtual IEspUpdateLogRequestWrap* filterLogContent(IEspUpdateLogRequestWrap* unfilteredRequest) override;
+        virtual IEspLogAgentVariantIterator* getVariants() const override;
+    private:
+        Owned<const CModuleFactory> m_factory;
+        StringBuffer m_name;
+        Owned<ModularLogAgent::IAgent> m_agent;
+        CEspLogAgentVariants m_variants;
+        CLogContentFilter m_filter;
+    public:
+        CEspLogAgent(const CModuleFactory& m_factory);
+    };
+
+    /**
+     * Common interface inherited by framework modules.
+     *
+     * Regardless of the logical purpose of a module, every module is expected to support a core
+     * interface. The common interface addresses issues of module initialization, availability, and
+     * trace log content generation.
+     *
+     * - All modules should use a common constructor interface. Technically not part of the module
+     *   interface, the module factory assumes construction with a single argument - the module or
+     *   log agent that contains it.
+     * - All modules are initialized with a common interface.
+     * - All modules are trace log sources, with independent control of message priority limits.
+     * - All modules must provide an active or inactive state. This means that a module which may be
+     *   inactive must be able to identify itself as inactive; it does not mean that all modules
+     *   must support an inactive state.
+     * - All modules must be able to produce a text representation of themselves as a debugging aid.
+     */
+    interface IModule : extends ITraceLoggingComponent
+    {
+        ///
+        /// XPath of configuration property that informs the module factory which module
+        /// implementation to instantiate.
+        ///
+        constexpr static const char* propFactoryKey         = "@module";
+
+        /**
+         * Initialize an instance from the configuration.
+         *
+         * Each instance initializes from a single property tree node. This is the node that
+         * identified the specific implementation being initialized. An IAgent module will be
+         * initialized using the moduleAgent element, while an IUpdateLog module will be
+         * initialized using the moduleUpdateLog element.
+         *
+         * Modules that fail to initialize should not be relied upon, but it is the container's
+         * decision as to how this is handled.
+         *
+         * @param configuration Required reference to a property tree node. Expectations for node
+         *                      content are set by each implementation.
+         * @param factory       Required reference to the log agent's module factory, enabling
+         *                      modules to create nested modules.
+         * @return True if an instance is successfully initialized, otherwise false.
+         */
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory) = 0;
+
+        /**
+         * Return the enabled state of the module.
+         *
+         * @return True if enabled, otherwise false.
+         */
+        virtual bool isEnabled() const = 0;
+
+        /**
+         * Add a text summary of the instance to the given text buffer.
+         *
+         * @param str The target buffer for the summary text.
+         * @return Reference to the target buffer.
+         */
+        virtual StringBuffer& toString(StringBuffer& str) const = 0;
+    };
+
+    /**
+     * Abstract representation of the LogAgent configuration element.
+     *
+     * The module accepts requests for each of LOGServiceType identified log agent service requests.
+     * Every implementation must respond to all service requests but may choose which are supported
+     * and which are no more than required stubs.
+     */
+    interface IAgent : extends IModule
+    {
+        /**
+         * Provide access to the LOGServiceType::LGSTGetTransactionSeed log agent service.
+         *
+         * Service support is optional. A caller should not rely on the outcome unless
+         * hasService(LGSTGetTransactionSeed) returns true.
+         *
+         * @param request  The seed request data as given to the log agent by the log manager.
+         * @param response The outcome of the request.
+         * @return True if a seed was obtained, otherwise false.
+         */
+        virtual bool getTransactionSeed(IEspGetTransactionSeedRequest& request, IEspGetTransactionSeedResponse& response) = 0;
+
+        /**
+         * Provide access to the LOGServiceType::LGSTGetTransactionID log agent service.
+         *
+         * Service support is optional. A caller should not rely on the outcome unless
+         * hasService(LGSTGetTransactionID) returns true.
+         *
+         * @param transactionFields  Optional parameters used to construct an ID.
+         * @param transactionId     Storage for a generated ID.
+         */
+        virtual void getTransactionID(StringAttrMapping* transactionFields, StringBuffer& transactionId) = 0;
+
+        /**
+         * Provide access to the LOGServiceType::LGSTUpdateLOG log agent service.
+         *
+         * Service support is optional. A caller should not rely on the outcome unless
+         * hasService(LGSTUpdateLOG) returns true.
+         *
+         * @param request  The update request data as given to the log agent by the log manager.
+         * @param response The outcome of the request.
+         */
+        virtual void updateLog(IEspUpdateLogRequestWrap& request, IEspUpdateLogResponse& response) = 0;
+
+        /**
+         * Inform the caller whether a requested log agent service is supported.
+         *
+         * Support is a combination of two factors. First, a subclass must include an implementation
+         * of the service. Second, the implementation must not be disabled.
+         *
+         * @param type Log agent service identifier.
+         * @return True if the instance can respond to a service request, otherwise false.
+         */
+        virtual bool hasService(LOGServiceType type) const = 0;
+    };
+
+    /**
+     * Internal extension of the IAgent interface for use by implementations that delegate service
+     * requests to separate modules.
+     *
+     * Each supported service is delegated to a unique service module interface. An implementation
+     * will decide the number of service modules needed. One implementation might use one module per
+     * service interface, while another might use one module for multiple services. This is an
+     * implementation detail allowing developers to provide optimal solutions for their situatios.
+     *
+     * The LGSTUpdateLOG service is delegated to an IUpdateLog service module.
+     */
+    interface IDelegatingAgent : extends IAgent
+    {
+        virtual interface IUpdateLog* queryUpdateLog() const = 0;
+    };
+
+    /**
+     * Placeholder interface to be extended by all modules used by IDelegatingAgent as delegates. As
+     * the interfaces for these modules develop, any common interfaces may be declared here.
+     */
+    interface IServiceDelegate : extends IModule
+    {
+    };
+
+    /**
+     * Abstract representation of the UpdateLog configuration element, processing LGSTUpdateLOG
+     * requests on behalf of an IDelegatingAgent. Implementations must accept a text representation
+     * of an UpdateLogRequest XML document and report the outcome of the update.
+     */
+    interface IUpdateLog : extends IServiceDelegate
+    {
+        /**
+         * Process an update log request.
+         *
+         * @param updateLogRequest Original request content, in text form. It is the caller's
+         *                         responsibility to convert the log agent input into a string.
+         * @param response         Output parameter updated by the implementation with the
+         *                         outcome of the update. A status code of 0 indicates success.
+         *                         A status code other than 0 indicates failure, and makes the
+         *                         transaction eligible for a retry subject to the externally
+         *                         configured retry rules.
+         */
+        virtual void updateLog(const char* updateLogRequest, IEspUpdateLogResponse& response) = 0;
+    };
+
+    /**
+     * Internal extension of the IUpdateLog interface for use by implementations that delegate
+     * aspects of update processing to separate modules.
+     *
+     * Similar in concept to IDelegatingAgent, it enables delegate modules to request information
+     * from the delegating service. Unlike IDelegatingAgent, there is no immediately obvious
+     * upgrade path requiring one delegate to communicate with another. It is, and will remain, an
+     * empty placeholder until such communication is necessary.
+     */
+    interface IDelegatingUpdateLog : extends IUpdateLog
+    {
+    };
+
+    /**
+     * Abstract representation of the Target configuration element.
+     *
+     * An implementation is responsible for preserving log update content for future reference.
+     * Each implementation defines its own method for content preservation. Examples might include
+     * insertion of content into trace log output, writing to a file, inserting into a database, or
+     * submitting SOAP requests to a remote web service.
+     */
+    interface IContentTarget : extends IModule
+    {
+        ///
+        /// ESDL script context section containing the original log agent content.
+        ///
+        constexpr static const char* sectionOriginal = "original";
+        ///
+        /// ESDL script context section containing any results of custom ESDL transformations
+        /// applied by the caller.
+        ///
+        constexpr static const char* sectionIntermediate = "intermediate";
+
+        /**
+         * Request data for one transaction to be preserved.
+         *
+         * Content intended for preservation is given in up to three different forms:
+         *   -# The original content supplied to the log agent.
+         *   -# The result of applying custom transforms to the original content.
+         *   -# The result of applying an XSLT to either of the other content forms.
+         *
+         * Each subclass decides for itself which form or forms it will use. Where some may only
+         * operate on the third form, others may act solely on the first. The configuration author
+         * must ensure that each configured instance receives the required content form(s) from
+         * the containing IDelegatingUpdateLog instance.
+         *
+         * The request outcome is reported using the response output parameter.
+         *
+         * @param scriptContext       Required ESDL script context enabling reuse between a
+         *                            delegating module and its delegate. The first content form
+         *                            can be accessed from sectionOriginal and the second form,
+         *                            can be eccessed from sectionIntermediate.
+         * @param originalContent     Required first content form.
+         * @param intermediateContent Optional second content form. NULL suggests that no
+         *                            custom transformations were applied.
+         * @param finalContent        Optional third content form. NULL suggests no XSLT was
+         *                            applied, while empty suggests there is no content to be
+         *                            preserved.
+         * @param response            Output parameter updated by the implementation with the
+         *                            outcome of the update. A status code of 0 indicates success.
+         *                            A status code other than 0 indicates failure, and makes the
+         *                            transaction eligible for a retry subject to the externally
+         *                            configured retry rules.
+         */
+        virtual void updateTarget(IEsdlScriptContext& scriptContext, IXpathContext& originalContent, IXpathContext* intermediateContent, const char* finalContent, IEspUpdateLogResponse& response) const = 0;
+    };
+
+    /**
+     * An external helper for CEspLogAgent. The agent requires an instance of the factory to be
+     * given to it upon construction.
+     *
+     * The factory maintains registrations for each known type of module. A registration associates
+     * a text label with a creation function that creates a concrete implementation of an abstract
+     * module interface. When the configuration specifies a module label, the factory will create an
+     * instance of the concrete type associated with the label. When no module label is given, the
+     * label keyDefault is assumed..
+     *
+     * The factory initializes itself with all platform-defined modules. A plugin using only the built-
+     * in modules may pass a default factory instance directly to the log agent. A plugin that wants
+     * different default behavior, or that requires custom helpers, must update the registrations
+     * as needed before creating the log agent.
+     */
+    class LOGGINGCOMMON_API CModuleFactory : public CInterface
+    {
+    public:
+        enum Outcome
+        {
+            Added,
+            Replaced,
+            NotFound,
+            Removed,
+        };
+
+        constexpr static const char* keyDefault = "default";
+        constexpr static const char* keyFile = "file";
+
+        /**
+         * An association of text label to module creation function for a single module type. Template
+         * parameters are the module interface implemented by a created interface and the object type
+         * that contains it. The creation function takes a container instance as a parameter and
+         * returns an implementation of the interface.
+         */
+        template <typename base_t, typename container_t>
+        class TRegistrants
+        {
+        public:
+            using Creator = std::function<base_t*(const container_t&)>;
+            using Collection = std::map<std::string, Creator>;
+
+            /**
+             * Registers an association of the given text label with an auto-generated creation function
+             * that creates instances of the method's template parameter type.
+             */
+            template <typename sub_t>
+            Outcome add(const char* uid)
+            {
+                Outcome outcome = Added;
+                if (isEmptyString(uid))
+                    uid = keyDefault;
+                WriteLockBlock block(m_rwLock);
+                Creator& creator = m_registrants[uid];
+                if (creator)
+                    outcome = Replaced;
+                creator = [](const container_t& container) { return new sub_t(container); };
+                return outcome;
+            }
+
+            Outcome remove(const char* uid)
+            {
+                Outcome outcome = NotFound;
+                if (isEmptyString(uid))
+                    uid = keyDefault;
+                WriteLockBlock block(m_rwLock);
+                typename Collection::iterator it = m_registrants.find(uid);
+                if (it != m_registrants.end())
+                {
+                    m_registrants.erase(it);
+                    outcome = Removed;
+                }
+                return outcome;
+            }
+
+            bool create(Owned<base_t>& module, const IPTree* configuration, const container_t& container) const
+            {
+                bool result = true;
+                if (configuration)
+                {
+                    using namespace TraceLoggingPriority;
+                    const char* uid = configuration->queryProp(IModule::propFactoryKey);
+                    if (isEmptyString(uid))
+                        uid = keyDefault;
+                    {
+                        ReadLockBlock block(m_rwLock);
+                        typename Collection::const_iterator it = m_registrants.find(uid);
+                        if (m_registrants.end() == it)
+                        {
+                            container.uerrlog(Major, "unknown key (%s.%s)'", configuration->queryName(), uid);
+                            result = false;
+                        }
+                        else if (!it->second)
+                        {
+                            container.ierrlog(Major, "invalid creator (%s.%s)", configuration->queryName(), uid);
+                            result = false;
+                        }
+                        else
+                        {
+                            module.setown((it->second)(container));
+                            if (!module)
+                            {
+                                container.ierrlog(Major, "creation failed (%s.%s)", configuration->queryName(), uid);
+                                result = false;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    module.clear();
+                }
+                return result;
+            }
+
+        private:
+            mutable ReadWriteLock m_rwLock;
+            Collection            m_registrants;
+        };
+
+        using Agents = TRegistrants<ModularLogAgent::IAgent, CEspLogAgent>;
+        using UpdateLogs = TRegistrants<ModularLogAgent::IUpdateLog, ModularLogAgent::IDelegatingAgent>;
+        using ContentTargets = TRegistrants<ModularLogAgent::IContentTarget, ModularLogAgent::IDelegatingUpdateLog>;
+        Agents              m_agents;
+        UpdateLogs          m_updateLogs;
+        ContentTargets      m_contentTargets;
+
+        CModuleFactory();
+    };
+
+} // namespace ModularLogAgent
+
+#endif // _MODULARLOGAGENT_HPP_

+ 400 - 0
esp/logging/logginglib/modularlogagent.ipp

@@ -0,0 +1,400 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _MODULARLOGAGENT_IPP_
+#define _MODULARLOGAGENT_IPP_
+
+#include "modularlogagent.hpp"
+#include "tokenserialization.hpp"
+#include "xpathprocessor.hpp"
+
+namespace ModularLogAgent
+{
+    class LOGGINGCOMMON_API CModule
+    {
+    public:
+        constexpr static const char* propDisabled           = "@disabled";
+        constexpr static const char* propName               = "@name";
+        constexpr static const char* propTracePriorityLimit = "@trace-priority-limit";
+    public:
+        virtual LogMsgDetail tracePriorityLimit(const LogMsgCategory& category) const;
+        virtual const char* traceId() const;
+        virtual void traceOutput(const LogMsgCategory& category, const char* format, va_list& arguments) const;
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory);
+        virtual bool isEnabled() const;
+        virtual StringBuffer& toString(StringBuffer& str) const;
+    protected:
+        mutable ReadWriteLock m_rwLock;
+    private:
+        template <typename container_t> friend class TModule;
+        const ITraceLoggingComponent* m_self = nullptr;
+        StringBuffer m_traceId;
+        bool         m_disabled = false;
+        bool         m_inheritTracePriorityLimit = true;
+        LogMsgDetail m_tracePriorityLimit = TraceLoggingPriority::Major;
+    protected:
+        virtual bool appendProperties(StringBuffer& str) const;
+        template <typename value_t>
+        bool extract(const char* xpath, const IPTree& configuration, value_t& value, bool optional = true)
+        {
+            const char* property = configuration.queryProp(xpath);
+            if (isEmptyString(property) && !optional)
+            {
+                if (m_self)
+                    m_self->uerrlog(TraceLoggingPriority::Major, "missing required configuration property '%s'", xpath);
+                return false;
+            }
+            DeserializationResult dr = TokenDeserializer().deserialize(property, value);
+            switch (dr)
+            {
+            case Deserialization_SUCCESS:
+                return true;
+            default:
+                return false;
+            }
+        }
+    };
+
+    template <typename container_t>
+    class TModule : extends CModule
+    {
+    public:
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory) override
+        {
+            const char* containerPrefix = m_container.traceId();
+            if (!isEmptyString(containerPrefix))
+                m_traceId.setf("%s.", containerPrefix);
+            return CModule::configure(configuration, factory);
+        }
+        virtual LogMsgDetail tracePriorityLimit(const LogMsgCategory& category) const override
+        {
+            return (m_inheritTracePriorityLimit ? m_container.tracePriorityLimit(category) : CModule::tracePriorityLimit(category));
+        }
+    protected:
+        using Container = container_t;
+        const Container& m_container;
+    public:
+        TModule(const Container& container) : m_container(container) {}
+    protected:
+        const Container& queryContainer() const { return m_container; }
+
+        /**
+         * Provides for the creation and initialization of a delegate module. Delegates are not, by
+         * default, required to be configured. An optional delegate omitted from the configuration
+         * is acceptable, but the existence of a delegate configuration requires the configuration
+         * to be valid even when the delegate is optional.
+         */
+        template <typename child_t, typename registrants_t, typename parent_t>
+        bool createAndConfigure(const IPTree& configuration, const char* xpath, const CModuleFactory& factory, const registrants_t& registrants, Owned<child_t>& child, const parent_t& parent,  bool optional = true) const
+        {
+            const IPTree* node = configuration.queryBranch(xpath);
+            if (!node)
+            {
+                if (optional)
+                {
+                    return true;
+                }
+                else
+                {
+                    if (m_self)
+                        m_self->uerrlog(TraceLoggingPriority::Major, "required configuration node '%s' not found", xpath);
+                }
+            }
+            else
+            {
+                return createAndConfigure(*node, factory, registrants, child, parent);
+            }
+            return false;
+        }
+        template <typename child_t, typename registrants_t, typename parent_t>
+        bool createAndConfigure(const IPTree& node, const CModuleFactory& factory, const registrants_t& registrants, Owned<child_t>& child, const parent_t& parent) const
+        {
+            if (!registrants.create(child, &node, parent))
+            {
+                // create() has already reported the failure
+            }
+            else if (!child)
+            {
+                // should never happen, but just in case...
+                if (m_self)
+                    m_self->ierrlog(TraceLoggingPriority::Major, "factory creation of '%s' reported success but created no instance", node.queryName());
+            }
+            else if (!child->configure(node, factory))
+            {
+                // configure() has already reported the failure
+                child.clear();
+            }
+            else
+            {
+                return true;
+            }
+            return false;
+        }
+    };
+
+    class LOGGINGCOMMON_API CDelegatingAgent : extends CSimpleInterfaceOf<IDelegatingAgent>, extends TModule<CEspLogAgent>
+    {
+    public:
+        constexpr static const char* moduleUpdateLog = "UpdateLog";
+        constexpr static const char* propUpdateLog   = "@UpdateLog";
+    public:
+        using Base = TModule<CEspLogAgent>;
+        IMPLEMENT_ITRACELOGGINGCOMPONENT_WITH(Base);
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory) override;
+        virtual bool isEnabled() const override { return true; }
+        virtual StringBuffer& toString(StringBuffer& str) const override { return Base::toString(str); }
+        virtual bool getTransactionSeed(IEspGetTransactionSeedRequest& request, IEspGetTransactionSeedResponse& response) override;
+        virtual void getTransactionID(StringAttrMapping* fields, StringBuffer& id) override;
+        virtual void updateLog(IEspUpdateLogRequestWrap& request, IEspUpdateLogResponse& response) override;
+        virtual bool hasService(LOGServiceType type) const override;
+        virtual IUpdateLog* queryUpdateLog() const override { return m_updateLog; }
+    protected:
+        virtual bool configureUpdateLog(const IPTree& configuration, const CModuleFactory& factory);
+        virtual bool appendProperties(StringBuffer& str) const override;
+    protected:
+        friend class CService;
+        Owned<IUpdateLog>          m_updateLog;
+    public:
+        using Base::Base;
+    };
+
+    using CServiceDelegate = TModule<IDelegatingAgent>;
+
+    class LOGGINGCOMMON_API CDelegatingUpdateLog : extends CSimpleInterfaceOf<IDelegatingUpdateLog>, extends CServiceDelegate
+    {
+    public:
+        constexpr static const char* moduleTarget = "Target";
+    public:
+        using Base = CServiceDelegate;
+        IMPLEMENT_ITRACELOGGINGCOMPONENT_WITH(Base);
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory) override;
+        virtual bool isEnabled() const override { return Base::isEnabled(); }
+        virtual StringBuffer& toString(StringBuffer& str) const override { return Base::toString(str); }
+        virtual void updateLog(const char* updateLogRequest, IEspUpdateLogResponse& response) override;
+    protected:
+        virtual bool appendProperties(StringBuffer& str) const override;
+    protected:
+        Owned<IContentTarget> m_target;
+    public:
+        using Base::Base;
+    };
+
+    using CUpdateLogDelegate = TModule<IDelegatingUpdateLog>;
+
+    class LOGGINGCOMMON_API CContentTarget : extends CSimpleInterfaceOf<IContentTarget>, extends CUpdateLogDelegate
+    {
+    public:
+        using Base = CUpdateLogDelegate;
+        IMPLEMENT_ITRACELOGGINGCOMPONENT_WITH(Base);
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory) override { return Base::configure(configuration, factory); }
+        virtual bool isEnabled() const override { return Base::isEnabled(); }
+        virtual StringBuffer& toString(StringBuffer& str) const override { return Base::toString(str); }
+        virtual void updateTarget(IEsdlScriptContext& scriptContext, IXpathContext& originalContent, IXpathContext* intermediateContent, const char* finalContent, IEspUpdateLogResponse& response) const override;
+    public:
+        using Base::Base;
+    };
+
+    /**
+     * Concrete implementation of IContentTarget used to write text content to a file.
+     *
+     * By default, the third content form is written when not empty. The first content form is used
+     * in this case to identify variable values that may be used to to construct a target filepath.
+     * Enabling the optional debug mode writes all provided content forms to the file.
+     *
+     * The majority of file management code focuses on variable substitution. With XPath evaulation
+     * readily available, it's natural to question why it isn't being used.
+     *
+     * Variable substitution in this module is a two step process. All variables except the creation
+     * timestamp are substituted in step one. This yields a key pattern used to identify an already
+     * open file that can accept the transaction data. If a new file is needed, the creation time-
+     * stamp is substituted in the key pattern to yield a new filepath. XPath evaluation expects to
+     * replace all values at once.
+     *
+     * Most variables are simple name-value substitutions, which XPath evaluation handles well. The
+     * creation timestamp, however, is more complex. And it is more complex to satisfy internal
+     * business requirements.
+     */
+    class LOGGINGCOMMON_API CFileTarget : public CContentTarget
+    {
+    public:
+        using Variables = std::map<std::string, std::string>;
+
+        class Pattern : public CInterface
+        {
+        private:
+            interface IFragment : extends IInterface
+            {
+                virtual bool matches(const char* name, const char* option) const = 0;
+                virtual bool resolvedBy(const Variables& variables) const = 0;
+                virtual StringBuffer& toString(StringBuffer& pattern, const Variables* variables) const = 0;
+                virtual IFragment* clone(const Pattern& pattern) const = 0;
+            };
+
+            struct Fragment : extends CSimpleInterfaceOf<IFragment>
+            {
+            protected:
+                const Pattern& m_pattern;
+
+                Fragment(const Pattern& pattern) : m_pattern(pattern) {};
+            };
+
+            struct TextFragment : extends Fragment
+            {
+                size32_t startOffset = 0;
+                size32_t endOffset = 0;
+
+                bool matches(const char* name, const char* option) const override { return false; }
+                bool resolvedBy(const Variables& variables) const override { return startOffset < endOffset; }
+                StringBuffer& toString(StringBuffer& pattern, const Variables* variables) const override { return pattern.append(endOffset - startOffset, m_pattern.m_textCache.str() + startOffset); }
+                TextFragment* clone(const Pattern& pattern) const override { return nullptr; }
+
+                TextFragment(const Pattern& pattern) : Fragment(pattern) {};
+            };
+
+            struct VariableFragment : extends Fragment
+            {
+                std::string m_name;
+                std::string m_option;
+                bool        m_withOption = false;
+
+                bool matches(const char* name, const char* option) const override;
+                bool resolvedBy(const Variables& variables) const override;
+                StringBuffer& toString(StringBuffer& pattern, const Variables* variables) const override;
+                VariableFragment* clone(const Pattern& pattern) const override;
+
+                VariableFragment(const Pattern& pattern) : Fragment(pattern) {}
+            };
+
+            using Fragments = std::vector<Owned<IFragment> >;
+
+            const CFileTarget& m_target;
+            StringBuffer       m_textCache;
+            Fragments          m_fragments;
+
+        public:
+            Pattern(const CFileTarget& target) : m_target(target) {}
+
+            bool contains(const char* name, const char* option) const;
+            void appendText(const char* begin, const char* end);
+            bool setPattern(const char* pattern);
+            Pattern* resolve(const Variables& variables) const;
+            StringBuffer& toString(StringBuffer& output, const Variables* variables) const;
+            inline StringBuffer& toString(StringBuffer& output) const { return toString(output, nullptr); }
+        };
+
+    protected:
+        enum RolloverInterval
+        {
+            NoRollover,
+            DailyRollover,
+            UnknownRollover,
+        };
+
+        /**
+         * Definition of an output file's data.
+         */
+        struct File : public CInterface
+        {
+            CDateTime m_lastWrite;   // time of last write to file
+            StringBuffer m_filePath; // full path to file
+            Owned<IFileIO> m_io;     // open file IO instance, ovtained from an untracked IFile
+        };
+
+        constexpr static const char* xpathHeader                     = "@header-text";
+        constexpr static const char* xpathCreationDateFormat         = "@format-creation-date";
+        constexpr static const char* defaultCreationDateFormat       = "%Y_%m_%d";
+        constexpr static const char* xpathCreationTimeFormat         = "@format-creation-time";
+        constexpr static const char* defaultCreationTimeFormat       = "%H_%M_%S";
+        constexpr static const char* xpathCreationDateTimeFormat     = "@format-creation-datetime";
+        constexpr static const char* defaultCreationDateTimeFormat   = "%Y_%m_%d_%H_%M_%S";
+        constexpr static const char* xpathRolloverInterval           = "@rollover-interval";
+        constexpr static const char* rolloverInterval_None           = "none";
+        constexpr static const char* rolloverInterval_Daily          = "daily";
+        constexpr static const char* defaultRolloverInterval         = rolloverInterval_Daily;
+        constexpr static const char* xpathRolloverSize               = "@rollover-size";
+        constexpr static const char* rolloverSize_None               = "0";
+        constexpr static const char* defaultRolloverSize             = rolloverSize_None;
+        constexpr static const char* xpathConcurrentFiles            = "@concurrent-files";
+        constexpr static const char* concurrentFiles_None            = "1";
+        constexpr static const char* defaultConcurrentFiles          = concurrentFiles_None;
+        constexpr static const char* xpathFilePathPattern            = "@filepath";
+        constexpr static const char* xpathDebug                      = "@debug";
+
+        /**
+         * File creation timestamp tokens. The variable name is defined by creationVarName. The format
+         * string is defined, either directly or indirectly, by options:
+         *   - creationVarDateOption: use m_creationDateFormat
+         *   - creationVarTimeOption: use m_creationTimeFormat
+         *   - creationVarDateTimeOption: use m_creationDateTimeFormat
+         *   - creationVarCustomOption: use the variable value as a format string
+         *   - any other value: use the option text as a format string
+         */
+        constexpr static const char* creationVarName                 = "creation";
+        constexpr static const char* creationVarDateOption           = "date";
+        constexpr static const char* creationVarTimeOption           = "time";
+        constexpr static const char* creationVarDateTimeOption       = "datetime";
+        constexpr static const char* creationVarCustomOption         = "custom";
+        constexpr static const char* creationVarDefaultOption        = creationVarDateTimeOption;
+
+        constexpr static const char* bindingVarName = "binding";
+        constexpr static const char* processVarName = "process";
+        constexpr static const char* portVarName = "port";
+        constexpr static const char* esdlServiceVarName = "esdl-service";
+        constexpr static const char* esdlMethodVarName = "esdl-method";
+        constexpr static const char* serviceVarName = "service";
+
+    public:
+        virtual bool configure(const IPTree& configuration, const CModuleFactory& factory) override;
+        virtual void updateTarget(IEsdlScriptContext& scriptContext, IXpathContext& originalContent, IXpathContext* intermediateContent, const char* finalContent, IEspUpdateLogResponse& response) const override;
+    protected:
+        virtual bool appendProperties(StringBuffer& str) const override;
+
+    protected:
+        using TargetMap = std::map<std::string, Owned<File> >;
+        Owned<Pattern>    m_pattern;
+        StringBuffer      m_header;
+        RolloverInterval  m_rolloverInterval = DailyRollover;
+        offset_t          m_rolloverSize = 0;
+        StringBuffer      m_creationDateFormat;
+        StringBuffer      m_creationTimeFormat;
+        StringBuffer      m_creationDateTimeFormat;
+        uint8_t           m_concurrentFiles = 1;
+        bool              m_debugMode = false;
+        mutable TargetMap m_targets;
+
+    public:
+        using CContentTarget::CContentTarget;
+    protected:
+        virtual bool configureHeader(const IPTree& configuration);
+        virtual bool configureCreationFormat(const IPTree& configuration, const char* xpath, const char* defaultValue, bool checkDate, bool checkTime, StringBuffer& format);
+        virtual bool configureFileHandling(const IPTree& configuration);
+        virtual bool configurePattern(const IPTree& configuration);
+        virtual bool configureDebugMode(const IPTree& configuration);
+        virtual void updateFile(const char* content, const Variables& variables, IEspUpdateLogResponse& response) const;
+        virtual void readPatternVariables(IEsdlScriptContext& scriptContext, IXpathContext& originalContent, IXpathContext* intermediateContent, Variables& variables) const;
+        virtual bool validateVariable(const char* name, const char* option) const;
+        virtual void resolveVariable(const char* name, const char* option, const char* value, StringBuffer& output) const;
+        virtual File* getFile(const char* key) const;
+        virtual bool needNewFile(size_t contentLength, const File& file, const Pattern& pattern, Variables& variables) const;
+        virtual bool haveFile(const File& file) const;
+        virtual bool createNewFile(File& file, const Pattern& pattern, Variables& variables) const;
+        virtual bool writeChunk(File& file, offset_t pos, size32_t len, const char* data) const;
+    };
+
+} // namespace ModularLogAgent
+
+#endif // _MODULARLOGAGENT_IPP_

+ 20 - 4
esp/logging/loggingmanager/loggingmanager.cpp

@@ -20,6 +20,9 @@
 #include "loggingmanager.hpp"
 #include "compressutil.hpp"
 
+#include "logconfigptree.hpp"
+using namespace LogConfigPTree;
+
 CLoggingManager::~CLoggingManager(void)
 {
     for (unsigned int x = 0; x < loggingAgentThreads.size(); x++)
@@ -41,13 +44,13 @@ bool CLoggingManager::init(IPropertyTree* cfg, const char* service)
     }
 
     StringAttr failSafeLogsDir;
-    decoupledLogging = cfg->getPropBool(PropDecoupledLogging, false);
-    oneTankFile = cfg->getPropBool("FailSafe", true);
+    decoupledLogging = getConfigValue<bool>(cfg, PropDecoupledLogging, false);
+    oneTankFile = getConfigValue<bool>(cfg, "FailSafe", true);
     if (decoupledLogging)
     {   //Only set the failSafeLogsDir for decoupledLogging.
         //The failSafeLogsDir tells a logging agent to work as a decoupledLogging agent,
         //as well as where to read the tank file.
-        const char* logsDir = cfg->queryProp(PropFailSafeLogsDir);
+        const char* logsDir = queryConfigValue(cfg, PropFailSafeLogsDir);
         if (!isEmptyString(logsDir))
             failSafeLogsDir.set(logsDir);
         else
@@ -191,6 +194,10 @@ bool CLoggingManager::updateLog(IEspContext* espContext, const char* option, IPr
         Owned<IPropertyTree> espContextTree;
         if (espContext)
         {
+            double responseTime = (msTick() - espContext->queryCreationTime()) / 1000.0;
+            CDateTime when;
+            when.setNow();
+
             espContextTree.setown(createPTree("ESPContext"));
 
             short port;
@@ -208,7 +215,16 @@ bool CLoggingManager::updateLog(IEspContext* espContext, const char* option, IPr
             if (userId && *userId)
                 espContextTree->addProp("UserName", userId);
 
-            espContextTree->addProp("ResponseTime", VStringBuffer("%.4f", (msTick()-espContext->queryCreationTime())/1000.0));
+            espContextTree->addProp("ResponseTime", VStringBuffer("%.4f", responseTime));
+            StringBuffer whenStr;
+            when.getString(whenStr);
+            espContextTree->addProp("TransactionEnd", whenStr);
+            if (responseTime > 1.0)
+            {
+                when.adjustTimeSecs(-int(responseTime));
+                when.getString(whenStr.clear());
+            }
+            espContextTree->addProp("TransactionStart", whenStr);
         }
         Owned<IEspUpdateLogRequestWrap> req =  new CUpdateLogRequestWrap(nullptr, option, espContextTree.getClear(), LINK(userContext), LINK(userRequest),
             backEndReq, backEndResp, userResp, logDatasets);

+ 164 - 0
esp/platform/esptraceloggingcomponent.hpp

@@ -0,0 +1,164 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2021 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _EspTraceLoggingComponent_HPP_
+#define _EspTraceLoggingComponent_HPP_
+
+#include "espcontext.hpp"
+#include "jlog.hpp"
+
+/// Common log message detail priority values.
+namespace TraceLoggingPriority
+{
+    static const LogMsgDetail Critical = 1;
+    static const LogMsgDetail Major = 10;
+    static const LogMsgDetail Highest = 20;
+    static const LogMsgDetail High = 30;
+    static const LogMsgDetail Medium = 40;
+    static const LogMsgDetail Normal = 50;
+    static const LogMsgDetail Low = 60;
+    static const LogMsgDetail Lowest = 70;
+    static const LogMsgDetail Trivial = 80;
+    static const LogMsgDetail DataDump = 90;
+    static const LogMsgDetail CallStack = 100;
+
+} // namespace TraceLoggingPriority
+
+/**
+ * ITraceLoggingComponent reimagines multiple platform approahes to trace log output. Specifically,
+ * it is a hybrid of the jlog LOG-related functions and the ESP's ESPLOG. Like jlog, trace content
+ * is associated with an audience, a classification, and a priority. Like ESPLOG, the priority is
+ * assigned by the caller instead of the function. Also like ESPLOG, generated content is filtered
+ * by comparing the assigned priority to a user configured limit.
+ *
+ * The logging methods in the interface are not virtual. The encapsulation of them within an
+ * interface allows the use of virtual helper functions that enable transparent support for
+ * additional functionality, including:
+ *   - define its own priority limit;
+ *   - adjust prepared content before output;
+ *   - direct prepared content to any combination of output targets.
+ *
+ * Why multiple priority limits? Especially during development and testing, it is often the case
+ * that a desire for verbose output is limited to the area(s) under review. Raising A single limit,
+ * such as is done with ESPLOG, can increase the volume of log content to the point that finding
+ * content of interest is unnecessarily difficult. This enables, without requiring, a more granular
+ * configuration of limits.
+ *
+ * Why message content adjustments? Knowing that a class instance failed is good, but knowning which
+ * instance failed is better. Obviously the content passed to each log call can be composed to
+ * include identifying information, but this enables identifying information to be automatically
+ * attached to each message.
+ *
+ * Why multiple output targets? A proposal has been made to allow transactional trace log entries
+ * to be returned in some ESP responses. The proposal is primarily concerned with returning entries
+ * generated by DESDL custom transforms to the users developing and testing said transforms. This
+ * provides a hook where accumulation of relevant entries can occur.
+ */
+interface ITraceLoggingComponent : extends IInterface
+{
+    /// Returns the highest accepted message priority for the given message category.
+    virtual LogMsgDetail tracePriorityLimit(const LogMsgCategory& category) const = 0;
+
+    /// Directs accepted content to implemtation-defined targets.
+    virtual void         traceOutput(const LogMsgCategory& category, const char* format, va_list& arguments) const = 0;
+
+    /// Defines optional instance identification for content adjustment.
+    virtual const char*  traceId() const = 0;
+
+    /**
+     * LOG_METHOD_TEMPLATE defines a non-virtual method using its first parameter. The method
+     * defines a shared instance of a LogMsgCategory using the remaining parameters. If the
+     * requested message priority is neither zero nor greater than the message priority limit, the
+     * message will be logged.
+     */
+#define LOG_METHOD_TEMPLATE(function, audience, classification) \
+    void function(LogMsgDetail priorityRequested, const char* format, ...) const __attribute__((format(printf, 3, 4))) \
+    { \
+        static const LogMsgCategory LMC##function(audience, classification, 1); \
+        if (priorityRequested && priorityRequested <= tracePriorityLimit(LMC##function)) \
+        { \
+            va_list arguments; \
+            va_start(arguments, format); \
+            traceOutput(LMC##function, format, arguments); \
+            va_end(arguments); \
+        } \
+    }
+
+    LOG_METHOD_TEMPLATE(uerrlog, MSGAUD_user, MSGCLS_error);
+    LOG_METHOD_TEMPLATE(uwarnlog, MSGAUD_user, MSGCLS_warning);
+    LOG_METHOD_TEMPLATE(uproglog, MSGAUD_user, MSGCLS_progress);
+    LOG_METHOD_TEMPLATE(uinfolog, MSGAUD_user, MSGCLS_information);
+    LOG_METHOD_TEMPLATE(ierrlog, MSGAUD_programmer, MSGCLS_error);
+    LOG_METHOD_TEMPLATE(iwarnlog, MSGAUD_programmer, MSGCLS_warning);
+    LOG_METHOD_TEMPLATE(iproglog, MSGAUD_programmer, MSGCLS_progress);
+    LOG_METHOD_TEMPLATE(iinfolog, MSGAUD_programmer, MSGCLS_information);
+    LOG_METHOD_TEMPLATE(oerrlog, MSGAUD_operator, MSGCLS_error);
+    LOG_METHOD_TEMPLATE(owarnlog, MSGAUD_operator, MSGCLS_warning);
+    LOG_METHOD_TEMPLATE(oproglog, MSGAUD_operator, MSGCLS_progress);
+    LOG_METHOD_TEMPLATE(oinfolog, MSGAUD_operator, MSGCLS_information);
+
+#undef LOG_METHOD_TEMPLATE
+};
+
+/**
+ * Standard interface implementation, inheriting behavior from base class 'C'.
+ */
+#define IMPLEMENT_ITRACELOGGINGCOMPONENT_WITH(C) \
+    inline  LogMsgDetail tracePriorityLimit(LogMsgAudience audience, LogMsgClass classification) const { return tracePriorityLimit(LogMsgCategory(audience, classification, 1)); } \
+    virtual LogMsgDetail tracePriorityLimit(const LogMsgCategory& category) const override { return C::tracePriorityLimit(category); } \
+    virtual void         traceOutput(const LogMsgCategory& category, const char* format, va_list& arguments) const override { return C::traceOutput(category, format, arguments); } \
+    virtual const char*  traceId() const override { return C::traceId(); }
+
+/**
+ * Utility class for logging entry to and exit from a block of code.
+ */
+class CTraceBlock
+{
+private:
+    const ITraceLoggingComponent& m_component;
+    StringBuffer                  m_caption;
+public:
+    CTraceBlock(const ITraceLoggingComponent& component, const char* caption)
+        : m_component(component)
+        , m_caption(caption)
+    {
+        m_component.iproglog(TraceLoggingPriority::CallStack, "Enter '%s'", m_caption.str());
+    }
+    ~CTraceBlock()
+    {
+        m_component.iproglog(TraceLoggingPriority::CallStack, "Exit '%s'", m_caption.str());
+    }
+};
+
+#define TRACE_BLOCK_WITH(component, caption) CTraceBlock traceBlock(component, caption)
+#define TRACE_BLOCK(caption) TRACE_BLOCK_WITH(*this, caption)
+
+/**
+ * Basic implementation of the ITraceLoggingComponent interface for use as a mixin base, exposing
+ * the logging methods with default behavior.
+ */
+class CEspTraceLoggingComponent : implements ITraceLoggingComponent, extends CInterface
+{
+public:
+    IMPLEMENT_IINTERFACE;
+    virtual LogMsgDetail tracePriorityLimit(const LogMsgCategory& category) const override { return (getEspLogLevel() * 10); }
+    virtual void         traceOutput(const LogMsgCategory& category, const char* format, va_list& arguments) const override { VALOG(category, format, arguments); }
+    virtual const char*  traceId() const override { return nullptr; }
+    inline  LogMsgDetail tracePriorityLimit(LogMsgAudience audience, LogMsgClass classification) const { return tracePriorityLimit(LogMsgCategory(audience, classification, 1)); } \
+};
+
+#endif // _EspTraceLoggingComponent_HPP_

+ 1 - 3
esp/platform/tokenserialization.hpp

@@ -424,9 +424,7 @@ private:
         default:                            resultStr = "unexpected"; break;
         }
 
-        if (success)
-            DBGLOG("Result of deserializing '%s' to type '%s': %s", buffer, typeStr, resultStr);
-        else
+        if (!success)
             OERRLOG("Result of deserializing '%s' to type '%s': %s", buffer, typeStr, resultStr);
     }
     bool isEmptyOrWhitespace(const char* buffer) const

+ 1 - 0
initfiles/componentfiles/configschema/xsd/buildset.xml

@@ -128,6 +128,7 @@
       </BuildSet>
       <BuildSet deployable="no" installSet="deploy_map.xml" name="cassandraloggingagent" path="componentfiles/cassandraloggingagent" processName="CassandraLoggingAgent" schema="cassandraloggingagent.xsd"/>
       <BuildSet deployable="no" installSet="deploy_map.xml" name="esploggingagent" path="componentfiles/esploggingagent" processName="ESPLoggingAgent" schema="esploggingagent.xsd"/>
+      <BuildSet deployable="no" installSet="deploy_map.xml" name="modularlogagent" path="componentfiles/modularlogagent" processName="ModularLogAgent" schema="modularlogagent.xsd"/>
       <BuildSet deployable="no" installSet="deploy_map.xml" name="loggingmanager" path="componentfiles/loggingmanager" processName="LoggingManager" schema="loggingmanager.xsd"/>
       <BuildSet deployable="no" installSet="deploy_map.xml" name="wslogging" path="componentfiles/wslogging" processName="EspService" schema="wslogging.xsd">
         <Properties bindingType="loggingservice_binding" defaultPort="8146" defaultResourcesBasedn="ou=EspServices,ou=ecl" defaultSecurePort="18146" plugin="wslogging" type="wslogging">

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

@@ -27,6 +27,7 @@ FOREACH ( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_wslogging.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/decoupled_logging.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/logging_agent.xsl
+    ${CMAKE_CURRENT_SOURCE_DIR}/modularlogagent.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/wslogserviceespagent.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_wssql.xsl
     ${CMAKE_CURRENT_SOURCE_DIR}/esp_service_wsstore.xsl

+ 9 - 0
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="modularlogagent.xsl"/>
     <xsl:import href="wslogserviceespagent.xsl"/>
 
     <xsl:template match="EspService">
@@ -93,6 +94,7 @@
                     <xsl:for-each select="$managerNode/ESPLoggingAgent">
                         <xsl:variable name="agentName" select="@ESPLoggingAgent"/>
                         <xsl:variable name="espLoggingAgentNode" select="/Environment/Software/ESPLoggingAgent[@name=$agentName]"/>
+                        <xsl:variable name="modularlogagentNode" select="/Environment/Software/ModularLogAgent[@name=$agentName]"/>
                         <xsl:variable name="wsLogServiceESPAgentNode" select="/Environment/Software/WsLogServiceESPAgent[@name=$agentName]"/>
                         <xsl:variable name="disableFailSafe" select="$managerNode/@DecoupledLogging"/>
                         <xsl:choose>
@@ -102,6 +104,13 @@
                                     <xsl:with-param name="agentNode" select="$espLoggingAgentNode"/>
                                 </xsl:call-template>
                             </xsl:when>
+                            <xsl:when test="($modularlogagentNode)">
+                                <xsl:call-template name="ModularLogAgent">
+                                    <xsl:with-param name="agentName" select="$agentName"/>
+                                    <xsl:with-param name="agentNode" select="$modularlogagentNode"/>
+                                    <xsl:with-param name="disableFailSafe" select="$disableFailSafe"/>
+                                </xsl:call-template>
+                            </xsl:when>
                             <xsl:when test="($wsLogServiceESPAgentNode)">
                                 <xsl:call-template name="WsLogServiceESPAgent">
                                     <xsl:with-param name="agentName" select="$agentName"/>

+ 109 - 0
initfiles/componentfiles/configxml/@temp/modularlogagent.xsl

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2021 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:seisint="http://seisint.com"  xmlns:set="http://exslt.org/sets" exclude-result-prefixes="seisint set">
+
+    <xsl:template name="ModularLogAgent" type="DefaultLoggingAgent">
+        <xsl:param name="agentName"/>
+        <xsl:param name="agentNode"/>
+        <xsl:param name="disableFailSafe"/>
+
+        <xsl:if test="not($agentNode)">
+            <xsl:message terminate="yes">ModularLogAgent '<xsl:value-of select="$agentName"/>' is undefined!</xsl:message>
+        </xsl:if>
+        <xsl:variable name="plugin">
+            <xsl:choose>
+                <xsl:when test="string($agentNode/@plugin) != ''"><xsl:value-of select="$agentNode/@plugin"/></xsl:when>
+                <xsl:otherwise>modularlogagent</xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+        <LogAgent name="{$agentName}" type="LogAgent" plugin="{$plugin}">
+            <!-- Setup the FailSafe override flag based on template input. -->
+            <xsl:if test="string($disableFailSafe) != ''">
+                <xsl:attribute name="DisableFailSafe"><xsl:value-of select="$disableFailSafe"/></xsl:attribute>
+            </xsl:if>
+
+            <!-- Set attributes equivalent to the elements defined by DecoupledLogging (decoupled_logging.xsl).-->
+            <xsl:if test="string($agentNode/@AckedFiles) != ''">
+                <xsl:attribute name="AckedFiles"><xsl:value-of select="$agentNode/@AckedFiles"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@AckedLogRequests) != ''">
+                <xsl:attribute name="AckedLogRequests"><xsl:value-of select="$agentNode/@AckedLogRequests"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@PendingLogBufferSize) != ''">
+                <xsl:attribute name="PendingLogBufferSize"><xsl:value-of select="$agentNode/@PendingLogBufferSize"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@ReadRequestWaitingSeconds) != ''">
+                <xsl:attribute name="ReadRequestWaitingSeconds"><xsl:value-of select="$agentNode/@ReadRequestWaitingSeconds"/></xsl:attribute>
+            </xsl:if>
+
+            <!-- Set attributes equivalent to the elements defined by EspLoggingAgentBasic (esp_logging_agent_basic.xsl).-->
+            <xsl:if test="string($agentNode/@FailSafe) != ''">
+                <xsl:attribute name="FailSafe"><xsl:value-of select="$agentNode/@FailSafe"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@FailSafeLogsDir) != ''">
+                <xsl:attribute name="FailSafeLogsDir"><xsl:value-of select="$agentNode/@FailSafeLogsDir"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@MaxLogQueueLength) != ''">
+                <xsl:attribute name="MaxLogQueueLength"><xsl:value-of select="$agentNode/@MaxLogQueueLength"/></xsl:attribute>
+            </xsl:if>
+            <!-- Presented for completeness; value applies to GetTransactionSeed and is read by individual agents.
+            <xsl:if test="string($agentNode/@MaxTriesGTS) != ''">
+                <xsl:attribute name="MaxTriesGTS"><xsl:value-of select="$agentNode/@MaxTriesGTS"/></xsl:attribute>
+            </xsl:if>
+            -->
+            <xsl:if test="string($agentNode/@MaxTriesRS) != ''">
+                <xsl:attribute name="MaxTriesRS"><xsl:value-of select="$agentNode/@MaxTriesRS"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@QueueSizeSignal) != ''">
+                <xsl:attribute name="QueueSizeSignal"><xsl:value-of select="$agentNode/@QueueSizeSignal"/></xsl:attribute>
+            </xsl:if>
+            <xsl:if test="string($agentNode/@SafeRolloverThreshold) != ''">
+                <xsl:attribute name="SafeRolloverThreshold"><xsl:value-of select="$agentNode/@SafeRolloverThreshold"/></xsl:attribute>
+            </xsl:if>
+
+            <!-- Define either split or inline configuration. -->
+            <xsl:choose>
+                <xsl:when test="string($agentNode/@configuration) != ''">
+                    <xsl:attribute name="configuration"><xsl:value-of select="$agentNode/@configuration"/></xsl:attribute>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:attribute name="trace-priority-limit"><xsl:value-of select="$agentNode/@trace-priority-limit"/></xsl:attribute>
+                    <!-- All LogAgent attributes must be added before adding a child. -->
+                    <UpdateLog/>
+                </xsl:otherwise>
+            </xsl:choose>
+
+            <!-- Copy agent variant identification. -->
+            <xsl:for-each select="$agentNode/Variant">
+                <Variant type="{current()/@type}" group="{current()/@group}"/>
+            </xsl:for-each>
+
+            <!-- Copy content filters. -->
+            <xsl:if test="count($agentNode/Filter) > 0">
+                <Filters>
+                <xsl:for-each select="$agentNode/Filter">
+                    <Filter type="{current()/@type}" value="{current()/@filter}"/>
+                </xsl:for-each>
+                </Filters>
+            </xsl:if>
+        </LogAgent>
+    </xsl:template>
+
+</xsl:stylesheet>

+ 1 - 0
initfiles/componentfiles/configxml/CMakeLists.txt

@@ -100,6 +100,7 @@ FOREACH( iFILES
     ${CMAKE_CURRENT_SOURCE_DIR}/esploggingagent.xsd
     ${CMAKE_CURRENT_SOURCE_DIR}/wslogging.xsd
     ${CMAKE_CURRENT_SOURCE_DIR}/cassandraloggingagent.xsd
+    ${CMAKE_CURRENT_SOURCE_DIR}/modularlogagent.xsd
     ${CMAKE_CURRENT_SOURCE_DIR}/daliplugin.xsd
     ${CMAKE_CURRENT_SOURCE_DIR}/backupnode.xsd
     ${CMAKE_CURRENT_SOURCE_DIR}/backupnode_vars.xsl

+ 6 - 0
initfiles/componentfiles/configxml/buildsetCC.xml.in

@@ -406,6 +406,12 @@
                 schema="esploggingagent.xsd"/>
             <BuildSet deployable="no"
                 installSet="deploy_map.xml"
+                name="modularlogagent"
+                path="componentfiles/modularlogagent"
+                processName="ModularLogAgent"
+                schema="modularlogagent.xsd"/>
+            <BuildSet deployable="no"
+                installSet="deploy_map.xml"
                 name="loggingmanager"
                 path="componentfiles/loggingmanager"
                 processName="LoggingManager"

+ 1 - 0
initfiles/componentfiles/configxml/cgencomplist_linux.xml.in

@@ -102,6 +102,7 @@
   </Component>
   <Component name="CassandraLoggingAgent" processName='CassandraLoggingAgent' schema='cassandraloggingagent.xsd' deployable='no'/>
   <Component name="ESPLoggingAgent" processName='ESPLoggingAgent' schema='esploggingagent.xsd' deployable='no'/>
+  <Component name="ModularLogAgent" processName='ModularLogAgent' schema='modularlogagent.xsd' deployable='no'/>
   <Component name="LoggingManager" processName='LoggingManager' schema='loggingmanager.xsd' deployable='no'/>
   <Component name="WsDecoupledLog" processName='EspService' schema='esp_service_decoupledlogging.xsd' deployable='no'>
     <File name="@temp/esp_service_decoupledlogging.xsl" method="esp_service_module"/>

+ 1 - 0
initfiles/componentfiles/configxml/cgencomplist_win.xml.in

@@ -111,6 +111,7 @@
   </Component>
   <Component name="cassandraloggingagent" processName='CassandraLoggingAgent' schema='cassandraloggingagent.xsd' deployable='no'/>
   <Component name="ESPLoggingAgent" processName='ESPLoggingAgent' schema='esploggingagent.xsd' deployable='no'/>
+  <Component name="modularlogagent" processName='ModularLogAgent' schema='modularlogagent.xsd' deployable='no'/>
   <Component name="LoggingManager" processName='LoggingManager' schema='loggingmanager.xsd' deployable='no'/>
   <Component name="WsDecoupledLog" processName='EspService' schema='esp_service_decoupledlogging.xsd' deployable='no'>
     <File name="@temp/esp_service_decoupledlogging.xsl" method="esp_service_module"/>

+ 227 - 0
initfiles/componentfiles/configxml/modularlogagent.xsd

@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+################################################################################
+#    HPCC SYSTEMS software Copyright (C) 2021 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.
+################################################################################
+-->
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+  <xs:include schemaLocation="environment.xsd"/>
+  <xs:element name="ModularLogAgent">
+    <xs:complexType>
+      <xs:annotation>
+        <xs:appinfo>
+            <title>Modular Log Agent</title>
+        </xs:appinfo>
+      </xs:annotation>
+      <xs:sequence>
+        <xs:element name="Variant" minOccurs="0" maxOccurs="unbounded">
+          <xs:annotation>
+            <xs:appinfo>
+              <title>Identity Variant</title>
+            </xs:appinfo>
+          </xs:annotation>
+          <xs:complexType>
+            <xs:attribute name="type" type="xs:string" use="optional" default="">
+              <xs:annotation>
+                <xs:appinfo>
+                  <colIndex>1</colIndex>
+                  <tooltip>Specifies the nature of the logged content (e.g., billing info). Needed only when agent filtering by type will be used.</tooltip>
+                </xs:appinfo>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="group" type="xs:string" use="optional" default="">
+              <xs:annotation>
+                <xs:appinfo>
+                  <colIndex>2</colIndex>
+                  <tooltip>Identifies a set of agents used to log a transaction. Needed only when agent filtering by transaction kind will be used.</tooltip>
+                </xs:appinfo>
+              </xs:annotation>
+            </xs:attribute>
+          </xs:complexType>
+        </xs:element>
+        <xs:element name="Filter" minOccurs="0" maxOccurs="unbounded">
+          <xs:annotation>
+            <xs:appinfo>
+              <title>Filters</title>
+            </xs:appinfo>
+          </xs:annotation>
+          <xs:complexType>
+            <xs:attribute name="filter" type="relativePath" use="required">
+              <xs:annotation>
+                <xs:appinfo>
+                  <colIndex>1</colIndex>
+                </xs:appinfo>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="type" use="optional">
+              <xs:annotation>
+                <xs:appinfo>
+                  <colIndex>2</colIndex>
+                </xs:appinfo>
+              </xs:annotation>
+              <xs:simpleType>
+                <xs:restriction base="xs:string">
+                  <xs:enumeration value="ESPContext"/>
+                  <xs:enumeration value="UserContext"/>
+                  <xs:enumeration value="UserRequest"/>
+                  <xs:enumeration value="UserResponse"/>
+                  <xs:enumeration value="BackEndResponse"/>
+                 </xs:restriction>
+               </xs:simpleType>
+            </xs:attribute>
+          </xs:complexType>
+        </xs:element>
+      </xs:sequence>
+      <!-- All Log Agent Plugins must define the type="DefaultLoggingAgent" attribute -->
+      <xs:attribute name="type" type="DefaultLoggingAgent" use="required" default="DefaultLoggingAgent">
+        <xs:annotation>
+          <xs:appinfo>
+            <viewType>hidden</viewType>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="build" type="buildType" use="required">
+        <xs:annotation>
+          <xs:appinfo>
+            <viewType>hidden</viewType>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="buildSet" type="buildSetType" use="required">
+        <xs:annotation>
+          <xs:appinfo>
+            <viewType>hidden</viewType>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="name" type="xs:string" use="required">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Name of this log agent</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="description" type="xs:string" use="optional" default="Bare-metal Modular Log Agent">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Description for this log agent</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="plugin" type="xs:string" use="optional" default="modularlogagent">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Name of shared library to load</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="configuration" type="xs:string" use="optional">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Path to file containing all other configuration data for the log agent</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="trace-priority-limit" type="xs:integer" use="optional">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Highest trace log priority to be included in trace log output; omit or -1 to use ESP log level; ignored unless @configuration is empty</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attributeGroup ref="QueueControl"/>
+      <xs:attributeGroup ref="FailSafe"/>
+      <xs:attributeGroup ref="DecoupledLogging"/>
+    </xs:complexType>
+  </xs:element>
+  <xs:attributeGroup name="QueueControl">
+      <xs:attribute name="MaxLogQueueLength" type="xs:nonNegativeInteger" use="optional" default="500000">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>maximum queue length for log update requests.</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="QueueSizeSignal" type="xs:nonNegativeInteger" use="optional" default="10000">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Add warning to esp log when the queue length of log update requests reaches this value.</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="MaxTriesRS" type="xs:nonNegativeInteger" use="optional" default="3">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Maximum retries of sending LogUpdate requests when failed to get a response.</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:attributeGroup name="FailSafe">
+      <xs:attribute name="FailSafe" type="xs:boolean" use="optional" default="false">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Enable FailSafe functionality.</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="FailSafeLogsDir" type="xs:string" use="optional" default="">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>Log directory where Failsafe files are stored.</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="SafeRolloverThreshold" type="xs:string" use="required">
+        <xs:annotation>
+          <xs:appinfo>
+            <tooltip>The threshold at which a new tank file will be started: n for number of requests, nK, nM, nG, or nT for file size.</tooltip>
+          </xs:appinfo>
+        </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:attributeGroup name="DecoupledLogging">
+    <xs:attribute name="AckedFiles" type="xs:string" use="optional">
+      <xs:annotation>
+        <xs:appinfo>
+          <tooltip>A file listing the tank file names which have been acked in a Logging Agent.</tooltip>
+        </xs:appinfo>
+      </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="AckedLogRequests" type="xs:string" use="optional">
+      <xs:annotation>
+        <xs:appinfo>
+          <tooltip>A file listing the Log Request GUIDs which have been acked in a Logging Agent.</tooltip>
+        </xs:appinfo>
+      </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="ReadRequestWaitingSeconds" type="xs:nonNegativeInteger" use="optional">
+      <xs:annotation>
+        <xs:appinfo>
+          <tooltip>Define how often a Logging thread checks logging requests from tank files.</tooltip>
+        </xs:appinfo>
+      </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="PendingLogBufferSize" type="xs:nonNegativeInteger" use="optional">
+      <xs:annotation>
+        <xs:appinfo>
+          <tooltip>Buffer size of Pending Logging requests. The buffer will be flushed when reaching the size.</tooltip>
+        </xs:appinfo>
+      </xs:annotation>
+    </xs:attribute>
+  </xs:attributeGroup>
+</xs:schema>