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">