This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new 5ba7c712 ODBCAppender support for logging MDC values (#207)
5ba7c712 is described below

commit 5ba7c712ae7791687e334314a3269f1406689274
Author: Stephen Webb <[email protected]>
AuthorDate: Wed Apr 26 16:40:35 2023 +1000

    ODBCAppender support for logging MDC values (#207)
    
    * ODBCAppender support for logging a single value or all values (as JASON) 
of an event's mapped diagnostic context
    
    * Correct erroneous documentation about the %X PatternLayout converter
    
    * Add JASON formatted MDC output to PatternLayout capabilities
    
    * Duplicate single quotes in JASON formatted MDC output (using %J{'})
    
    Increase allowed xml attribute string length to allow long SQL statements
---
 src/main/cpp/CMakeLists.txt                        |  1 +
 src/main/cpp/domconfigurator.cpp                   |  2 +-
 src/main/cpp/jsonlayout.cpp                        |  5 ++
 src/main/cpp/mdcpatternconverter.cpp               | 83 ++++++++++++++++++++++
 src/main/cpp/odbcappender.cpp                      | 19 ++++-
 src/main/cpp/patternlayout.cpp                     |  2 +
 src/main/include/log4cxx/db/odbcappender.h         | 39 ++++++----
 src/main/include/log4cxx/jsonlayout.h              |  1 +
 .../include/log4cxx/pattern/mdcpatternconverter.h  | 63 ++++++++++++++++
 src/main/include/log4cxx/patternlayout.h           | 19 +++--
 .../input/xml/odbcAppenderDSN-Log4cxxTest.xml      |  7 +-
 11 files changed, 214 insertions(+), 27 deletions(-)

diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index fcf25b88..55c10381 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -143,6 +143,7 @@ target_sources(log4cxx
   nameabbreviator.cpp
   namepatternconverter.cpp
   ndc.cpp
+  mdcpatternconverter.cpp
   ndcpatternconverter.cpp
   nteventlogappender.cpp
   odbcappender.cpp
diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp
index 861b98f4..4d3b9e03 100644
--- a/src/main/cpp/domconfigurator.cpp
+++ b/src/main/cpp/domconfigurator.cpp
@@ -56,7 +56,7 @@ using namespace log4cxx::spi;
 using namespace log4cxx::config;
 using namespace log4cxx::rolling;
 
-#define MAX_ATTRIBUTE_NAME_LEN 200
+#define MAX_ATTRIBUTE_NAME_LEN 2000
 
 struct DOMConfigurator::DOMConfiguratorPrivate
 {
diff --git a/src/main/cpp/jsonlayout.cpp b/src/main/cpp/jsonlayout.cpp
index aca87f51..fa133a89 100644
--- a/src/main/cpp/jsonlayout.cpp
+++ b/src/main/cpp/jsonlayout.cpp
@@ -172,6 +172,11 @@ void JSONLayout::format(LogString& output,
 
 void JSONLayout::appendQuotedEscapedString(LogString& buf,
        const LogString& input) const
+{
+       appendItem(input, buf);
+}
+
+void JSONLayout::appendItem(const LogString& input, LogString& buf)
 {
        /* add leading quote */
        buf.push_back(0x22);
diff --git a/src/main/cpp/mdcpatternconverter.cpp 
b/src/main/cpp/mdcpatternconverter.cpp
new file mode 100644
index 00000000..5dd68b95
--- /dev/null
+++ b/src/main/cpp/mdcpatternconverter.cpp
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 <log4cxx/pattern/mdcpatternconverter.h>
+#include <log4cxx/private/patternconverter_priv.h>
+#include <log4cxx/spi/loggingevent.h>
+#include <log4cxx/jsonlayout.h>
+
+using namespace log4cxx;
+using namespace log4cxx::pattern;
+
+IMPLEMENT_LOG4CXX_OBJECT(MDCPatternConverter)
+
+MDCPatternConverter::MDCPatternConverter
+       ( const LogString&              name
+       , const LogString&              style
+       , const std::vector<LogString>& options
+       )
+       : 
LoggingEventPatternConverter(std::make_unique<PatternConverter::PatternConverterPrivate>(name,
 style))
+{
+}
+
+PatternConverterPtr MDCPatternConverter::newInstance(
+       const std::vector<LogString>& options)
+{
+       if (options.empty())
+       {
+               static PatternConverterPtr def = 
std::make_shared<MDCPatternConverter>();
+               return def;
+       }
+       return std::make_shared<MDCPatternConverter>(LogString(), 
options.front());
+}
+
+void MDCPatternConverter::format
+       ( const spi::LoggingEventPtr& event
+       , LogString&                  toAppendTo
+       , helpers::Pool&           /* p */
+       ) const
+{
+       size_t startIndex = toAppendTo.size();
+       if (m_priv->name.empty()) // Full MDC required?
+       {
+               bool first = true;
+               for (auto key : event->getMDCKeySet())
+               {
+                       toAppendTo.append(first ? LOG4CXX_STR("{") : 
LOG4CXX_STR(","));
+                       JSONLayout::appendItem(key, toAppendTo);
+                       toAppendTo.append(LOG4CXX_STR(":"));
+                       LogString value;
+                       event->getMDC(key, value);
+                       JSONLayout::appendItem(value, toAppendTo);
+                       first = false;
+               }
+               if (!first)
+                       toAppendTo.append(LOG4CXX_STR("}"));
+       }
+       else
+               event->getMDC(m_priv->name, toAppendTo);
+       if (!m_priv->style.empty()) // In a quoted context?
+       {
+               auto quote = m_priv->style.front();
+               size_t endIndex;
+               while ((endIndex = toAppendTo.find(quote, startIndex)) != 
toAppendTo.npos)
+               {
+                       toAppendTo.insert(endIndex + 1, 1, quote);
+                       startIndex = endIndex + 2;
+               }
+       }
+}
diff --git a/src/main/cpp/odbcappender.cpp b/src/main/cpp/odbcappender.cpp
index 7a2ed5ab..79f647f9 100644
--- a/src/main/cpp/odbcappender.cpp
+++ b/src/main/cpp/odbcappender.cpp
@@ -20,6 +20,7 @@
 #include <log4cxx/helpers/stringhelper.h>
 #include <log4cxx/helpers/transcoder.h>
 #include <log4cxx/patternlayout.h>
+#include <log4cxx/pattern/mdcpatternconverter.h>
 #include <apr_strings.h>
 #include <apr_time.h>
 #include <cmath> // std::pow
@@ -147,6 +148,7 @@ static PatternMap getFormatSpecifiers()
                RULES_PUT("level", LevelPatternConverter);
                RULES_PUT("thread", ThreadPatternConverter);
                RULES_PUT("threadname", ThreadUsernamePatternConverter);
+               RULES_PUT("mdc", MDCPatternConverter);
                RULES_PUT("ndc", NDCPatternConverter);
        }
        return specs;
@@ -201,9 +203,22 @@ void ODBCAppender::activateOptions(log4cxx::helpers::Pool&)
        auto specs = getFormatSpecifiers();
        for (auto& name : _priv->mappedName)
        {
-               auto pItem = specs.find(StringHelper::toLowerCase(name));
+               auto lowerName = StringHelper::toLowerCase(name);
+               auto pItem = specs.find(lowerName);
                if (specs.end() == pItem)
-                       LogLog::error(name + LOG4CXX_STR(" is not a supported 
ColumnMapping value"));
+               {
+                       if (lowerName.size() < 5
+                        || lowerName.substr(0, 4) != LOG4CXX_STR("mdc{"))
+                               LogLog::error(name + LOG4CXX_STR(" is not a 
supported ColumnMapping value"));
+                       else // A single MDC entry
+                       {
+                               auto index = lowerName.find(0x7D /* '}' */, 4);
+                               auto len = (lowerName.npos == index ? 
lowerName.size() : index) - 4;
+                               ODBCAppenderPriv::DataBinding paramData{ 0, 0, 
0, 0, 0 };
+                               paramData.converter = 
std::make_shared<MDCPatternConverter>(lowerName.substr(4, len));
+                               _priv->parameterValue.push_back(paramData);
+                       }
+               }
                else
                {
                        ODBCAppenderPriv::DataBinding paramData{ 0, 0, 0, 0, 0 
};
diff --git a/src/main/cpp/patternlayout.cpp b/src/main/cpp/patternlayout.cpp
index 08260190..b7e985e5 100644
--- a/src/main/cpp/patternlayout.cpp
+++ b/src/main/cpp/patternlayout.cpp
@@ -43,6 +43,7 @@
 #include <log4cxx/pattern/levelpatternconverter.h>
 #include <log4cxx/pattern/relativetimepatternconverter.h>
 #include <log4cxx/pattern/threadpatternconverter.h>
+#include <log4cxx/pattern/mdcpatternconverter.h>
 #include <log4cxx/pattern/ndcpatternconverter.h>
 #include <log4cxx/pattern/propertiespatternconverter.h>
 #include <log4cxx/pattern/throwableinformationpatternconverter.h>
@@ -253,6 +254,7 @@ log4cxx::pattern::PatternMap 
PatternLayout::getFormatSpecifiers()
        RULES_PUT("ndc", NDCPatternConverter);
 
        RULES_PUT("X", PropertiesPatternConverter);
+       RULES_PUT("J", MDCPatternConverter);
        RULES_PUT("properties", PropertiesPatternConverter);
 
        RULES_PUT("throwable", ThrowableInformationPatternConverter);
diff --git a/src/main/include/log4cxx/db/odbcappender.h 
b/src/main/include/log4cxx/db/odbcappender.h
index 60db2ccb..36135b16 100644
--- a/src/main/include/log4cxx/db/odbcappender.h
+++ b/src/main/include/log4cxx/db/odbcappender.h
@@ -81,18 +81,22 @@ The following <b>param</b> elements are optional:
   One element for each "?" in the <b>sql</b> statement
   in a sequence corresponding to the columns in the insert statement.
   The following values are supported:
-  - logger
-  - level
-  - thread
-  - threadname
-  - time
-  - shortfilename
-  - fullfilename
-  - line
-  - class
-  - method
-  - message
-  - ndc
+  - <b>logger</b> - the name of the logger that generated the logging event
+  - <b>level</b> - the level of the logging event
+  - <b>thread</b> - the thread number as a hex string that generated the 
logging event
+  - <b>threadname</b> - the name assigned to the thread that generated the 
logging event
+  - <b>time</b> - a datetime or datetime2 SQL field type at which the event 
was generated
+  - <b>shortfilename</b> - the basename of the file containing the logging 
statement
+  - <b>fullfilename</b> - the path of the file containing the logging statement
+  - <b>line</b> - the position in the file at which the logging event was 
generated
+  - <b>class</b> - the class from which the logging event was generated (\ref 
usingMacros "1")
+  - <b>method</b> - the function in which the logging event was generated 
(\ref usingMacros "1")
+  - <b>message</b> - the data sent by the logging statement
+  - <b>mdc</b> - A JSON format string of all entries in the logging thread's 
mapped diagnostic context
+  - <b>mdc{key}</b> - the value associated with the <b>key</b> entry in the 
logging thread's mapped diagnostic context 
+  - <b>ndc</b> - the last entry the logging thread's nested diagnostic context 
+
+\anchor usingMacros 1. Only available when the LOG4CXX_* macros are used to 
issue the logging request.
 
 <p>For use as a base class:
 
@@ -113,9 +117,9 @@ pool it came from.
 An example configuration that writes to the data source named "LoggingDSN" is:
 ~~~{.xml}
 <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/";>
-<appender name="SqlAppender" class="ODBCAppender">
+<appender name="PreparedAppender" class="ODBCAppender">
  <param name="DSN" value="LoggingDSN"/>
- <param name="sql" value="INSERT INTO 
[SomeDatabaseName].[SomeUserName].[SomeTableName] 
([Thread],[LogName],[LogTime],[LogLevel],[FileName],[FileLine],[Message]) 
VALUES (?,?,?,?,?,?,?)" />
+ <param name="sql" value="INSERT INTO 
[SomeDatabaseName].[SomeUserName].[SomeTableName] 
([Thread],[LogName],[LogTime],[LogLevel],[FileName],[FileLine],[Message],[MappedContext])
 VALUES (?,?,?,?,?,?,?,?)" />
  <param name="ColumnMapping" value="thread"/>
  <param name="ColumnMapping" value="logger"/>
  <param name="ColumnMapping" value="time"/>
@@ -123,16 +127,21 @@ An example configuration that writes to the data source 
named "LoggingDSN" is:
  <param name="ColumnMapping" value="shortfilename"/>
  <param name="ColumnMapping" value="line"/>
  <param name="ColumnMapping" value="message"/>
+ <param name="ColumnMapping" value="mdc"/>
 </appender>
 <appender name="ASYNC" class="AsyncAppender">
   <param name="BufferSize" value="1000"/>
   <param name="Blocking" value="false"/>
-  <appender-ref ref="SqlAppender"/>
+  <appender-ref ref="PreparedAppender"/>
 </appender>
 <root>
   <priority value ="INFO" />
   <appender-ref ref="ASYNC" />
 </root>
+<appender name="PatternAppender" class="ODBCAppender">
+ <param name="DSN" value="LoggingDSN"/>
+ <param name="sql" value="INSERT INTO [ApplicationLogs].[dbo].[UnitTestLog] 
([Thread],[LogName],[LogTime],[LogLevel],[FileName],[FileLine],[Message],[MappedContext])
 VALUES ('%t', '%c','%d{yyyy-MM-dd 
HH:mm:ss.SSSSSS}','%p','%f','%L','%m{'}','%J{'}')" />
+</appender>
 </log4j:configuration>
 ~~~
 */
diff --git a/src/main/include/log4cxx/jsonlayout.h 
b/src/main/include/log4cxx/jsonlayout.h
index 0dca1d01..b53009b4 100644
--- a/src/main/include/log4cxx/jsonlayout.h
+++ b/src/main/include/log4cxx/jsonlayout.h
@@ -43,6 +43,7 @@ class LOG4CXX_EXPORT JSONLayout : public Layout
                        const spi::LoggingEventPtr& event, 
log4cxx::helpers::Pool& p) const;
 
        public:
+               static void appendItem(const LogString& item, LogString& 
toAppendTo);
                DECLARE_LOG4CXX_OBJECT(JSONLayout)
                BEGIN_LOG4CXX_CAST_MAP()
                LOG4CXX_CAST_ENTRY(JSONLayout)
diff --git a/src/main/include/log4cxx/pattern/mdcpatternconverter.h 
b/src/main/include/log4cxx/pattern/mdcpatternconverter.h
new file mode 100644
index 00000000..9b0feb3e
--- /dev/null
+++ b/src/main/include/log4cxx/pattern/mdcpatternconverter.h
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 _LOG4CXX_PATTERN_MDC_PATTERN_CONVERTER
+#define _LOG4CXX_PATTERN_MDC_PATTERN_CONVERTER
+
+#include <log4cxx/pattern/loggingeventpatternconverter.h>
+
+namespace log4cxx
+{
+namespace pattern
+{
+
+
+/**
+ * Provides all key value pairs in JSON or a single value from an event's 
mapped diagnostic context
+ */
+class LOG4CXX_EXPORT MDCPatternConverter : public LoggingEventPatternConverter
+{
+       public:
+               DECLARE_LOG4CXX_PATTERN(MDCPatternConverter)
+               BEGIN_LOG4CXX_CAST_MAP()
+               LOG4CXX_CAST_ENTRY(MDCPatternConverter)
+               LOG4CXX_CAST_ENTRY_CHAIN(LoggingEventPatternConverter)
+               END_LOG4CXX_CAST_MAP()
+
+               MDCPatternConverter
+                       ( const LogString&              name = LogString()
+                       , const LogString&              style = LogString()
+                       , const std::vector<LogString>& options = 
std::vector<LogString>()
+                       );
+
+               /**
+                * An instance of MDCPatternConverter.
+                * @param options if not empty, options[0][0] is the character 
to duplicate
+                */
+               static PatternConverterPtr newInstance(const 
std::vector<LogString>& options);
+
+               using LoggingEventPatternConverter::format;
+
+               void format
+                       ( const spi::LoggingEventPtr& event
+                       , LogString&                  toAppendTo
+                       , helpers::Pool&              p
+                       ) const override;
+};
+}
+}
+#endif
diff --git a/src/main/include/log4cxx/patternlayout.h 
b/src/main/include/log4cxx/patternlayout.h
index 3ae3805b..8c9d4d04 100644
--- a/src/main/include/log4cxx/patternlayout.h
+++ b/src/main/include/log4cxx/patternlayout.h
@@ -88,7 +88,7 @@ LOG4CXX_LIST_DEF(FormattingInfoList, 
log4cxx::pattern::FormattingInfoPtr);
  *  <tr>
  *      <td align="center"><strong>c</strong></td>
  *      <td>
- *          Used to output the logger of the logging event. The logger 
conversion specifier
+ *          Used to output the name of the logger generating the logging 
event. The <strong>c</strong> conversion specifier
  *          can be optionally followed by <em>precision specifier</em>, that 
is a decimal
  *          constant in brackets.
  *          <p>
@@ -224,14 +224,25 @@ LOG4CXX_LIST_DEF(FormattingInfoList, 
log4cxx::pattern::FormattingInfoPtr);
  *      <td align="center"><strong>X</strong></td>
  *      <td>
  *          Used to output the MDC (mapped diagnostic context) associated with 
the thread that
- *          generated the logging event. The <strong>X</strong> conversion 
character <em>must</em> be
- *          followed by the key for the map placed between braces, as in 
<strong>%X{clientNumber}</strong>
- *          where <code>clientNumber</code> is the key. The value in the MDC 
corresponding to
+ *          generated the logging event. All key/value pairs are output, each 
inside <strong>{}</strong> unless
+ *          the <strong>X</strong> is followed by a key placed between braces, 
as in <strong>%X{clientNumber}</strong>
+ *          where <code>clientNumber</code> is the key. In this case the value 
in the MDC corresponding to
  *          the key will be output.
  *          <p>See MDC class for more details.</p>
  *      </td>
  *  </tr>
  *  <tr>
+ *      <td align="center"><strong>J</strong></td>
+ *      <td>
+ *          Used to output JSON key/value pairs of all MDC (mapped diagnostic 
context)
+ *          entries associated with the thread that generated the logging 
event.
+ *          To output in a quoted context, add set of braces containing the 
quote character.
+ *          Any quote character in the message is augmented with a second 
quote character.
+ *          For example, use %J{'} in an SQL insert statement.
+ *          <p>See MDC class for more details.</p>
+ *      </td>
+ *  </tr>
+ *  <tr>
  *      <td align="center"><strong>y</strong></td>
  *      <td>
  *          Used to wrap log with color. The <strong>y</strong> is the end of 
a color block.<br>
diff --git a/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml 
b/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml
index 8873ab40..781bd84e 100644
--- a/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml
+++ b/src/test/resources/input/xml/odbcAppenderDSN-Log4cxxTest.xml
@@ -25,11 +25,9 @@
     <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
   </layout>
 </appender>
-<appender name="DirectAppender" class="ODBCAppender">
+<appender name="PatternAppender" class="ODBCAppender">
  <param name="DSN" value="Log4cxxTest"/>
- <param name="sql" value="INSERT INTO [ApplicationLogs].[dbo].[UnitTestLog] 
([Thread],[LogName],[LogTime],[LogLevel],[FileName],[FileLine],[Message]) 
VALUES ('%t', '%c','%d{dd MMM yyyy HH:mm:ss.SSS}','%p','%f','%L','%m{'}')" />
- <!--param name="User" value="dbo"/-->
- <!--param name="Password" value="myLog4cxxTestPassword"/-->
+ <param name="sql" value="INSERT INTO [ApplicationLogs].[dbo].[UnitTestLog] 
([Thread],[LogName],[LogTime],[LogLevel],[FileName],[FileLine],[Message]) 
VALUES ('%t','%c','%d{yyyy-MM-dd HH:mm:ss.SSSSSS}','%p','%f','%L','%m{'}')" />
 </appender>
 <appender name="PreparedAppender" class="ODBCAppender">
  <param name="DSN" value="Log4cxxTest"/>
@@ -42,7 +40,6 @@
  <param name="ColumnMapping" value="line"/>
  <param name="ColumnMapping" value="message"/>
  <!--param name="User" value="dbo"/-->
- <!--param name="User" value="dbo"/-->
  <!--param name="Password" value="myLog4cxxTestPassword"/-->
 </appender>
 <appender name="ASYNC" class="AsyncAppender">

Reply via email to