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 08c83f5f Allow binary-to-text conversion to be moved to a background 
thread (#554)
08c83f5f is described below

commit 08c83f5f9bd8ae000d71ee9a5d6e75a7f7307682
Author: Stephen Webb <[email protected]>
AuthorDate: Tue Oct 14 09:58:54 2025 +1100

    Allow binary-to-text conversion to be moved to a background thread (#554)
    
    * Update example benchmark data
    
    * Allow all wide or all narrow strings in XXXXX_FMT_ASYNC macros
    
    * Add a unit test
    
    * Older fmt version fails so remove it from CI
    
    * Include XXXXX_ASYNC macro support in the Log4cxx configuration summary
---
 .github/workflows/log4cxx-ubuntu.yml           |   4 +-
 CMakeLists.txt                                 |   4 +
 src/examples/cpp/CMakeLists.txt                |  14 ++
 src/examples/cpp/format-async.cpp              |  69 ++++++++
 src/main/cpp/asyncbuffer.cpp                   | 110 ++++++++++++-
 src/main/cpp/basicconfigurator.cpp             |  61 +++++--
 src/main/include/CMakeLists.txt                |   1 +
 src/main/include/log4cxx/basicconfigurator.h   |  18 ++-
 src/main/include/log4cxx/helpers/asyncbuffer.h | 211 ++++++++++++++++++++++++-
 src/main/include/log4cxx/log4cxx.h.in          |   2 +
 src/site/doxy/Doxyfile.in                      |   3 +-
 src/site/markdown/change-report-gh.md          |   3 +-
 src/site/markdown/performance.md               |   8 +-
 src/test/cpp/CMakeLists.txt                    |  12 +-
 src/test/cpp/asyncappendertestcase.cpp         | 101 +++++++++++-
 src/test/cpp/benchmark/benchmark.cpp           |  10 +-
 16 files changed, 591 insertions(+), 40 deletions(-)

diff --git a/.github/workflows/log4cxx-ubuntu.yml 
b/.github/workflows/log4cxx-ubuntu.yml
index cd4b4863..6375fedf 100644
--- a/.github/workflows/log4cxx-ubuntu.yml
+++ b/.github/workflows/log4cxx-ubuntu.yml
@@ -42,7 +42,7 @@ jobs:
           - name: ubuntu22-clang
             os: ubuntu-22.04
             cxx: clang++
-            fmt: ON
+            fmt: OFF
             qt: OFF
             qt6: OFF
             odbc: ON
@@ -55,7 +55,7 @@ jobs:
           - name: ubuntu24-gcc
             os: ubuntu-24.04
             cxx: g++
-            fmt: OFF
+            fmt: ON
             qt: ON
             qt6: ON
             odbc: OFF
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d4bb3e1d..7db4e1eb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -105,8 +105,10 @@ endif(LOG4CXX_ENABLE_ESMTP)
 find_package(fmt 7.1 QUIET)
 if(${fmt_FOUND})
     option(ENABLE_FMT_LAYOUT "Enable the FMT layout(if libfmt found)" ON)
+    option(ENABLE_FMT_ASYNC "Add support for the LOG4CXX_XXXX_FMT_ASYNC macros 
(if libfmt found)" ON)
 else()
     set(ENABLE_FMT_LAYOUT "OFF")
+    set(ENABLE_FMT_ASYNC "OFF")
 endif()
 
 option(LOG4CXX_CONFIG_VAR_EXPANSION "Expand \${varname} instances in 
LOG4CXX_CONFIGURATION" ON)
@@ -299,6 +301,8 @@ message(STATUS "  character encoding .............. : 
${LOG4CXX_CHARSET}")
 message(STATUS "  Networking support .............. : 
${LOG4CXX_NETWORKING_SUPPORT}")
 message(STATUS "  DOMConfigurator support ......... : 
${LOG4CXX_DOMCONFIGURATOR_SUPPORT}")
 message(STATUS "  Qt support ...................... : ${LOG4CXX_QT_SUPPORT}")
+message(STATUS "  LOG4CXX_XXXX_ASYNC macros ....... : ON")
+message(STATUS "  LOG4CXX_XXXX_FMT_ASYNC macros ... : ${ENABLE_FMT_ASYNC}")
 message(STATUS "C++ version and Boost settings:")
 message(STATUS "  Prefer boost: ................... : ${PREFER_BOOST}")
 message(STATUS "  make_unique implementation :..... : ${STD_MAKE_UNIQUE_IMPL}")
diff --git a/src/examples/cpp/CMakeLists.txt b/src/examples/cpp/CMakeLists.txt
index 983bb748..87dc2ec6 100644
--- a/src/examples/cpp/CMakeLists.txt
+++ b/src/examples/cpp/CMakeLists.txt
@@ -74,3 +74,17 @@ if(${fmt_FOUND})
         )
     endif()
 endif(${fmt_FOUND})
+
+if(ENABLE_FMT_ASYNC)
+    add_executable( format-async format-async.cpp )
+    target_compile_definitions(format-async PRIVATE 
${LOG4CXX_COMPILE_DEFINITIONS} ${APR_COMPILE_DEFINITIONS} 
${APR_UTIL_COMPILE_DEFINITIONS} )
+    target_include_directories(format-async PRIVATE ${CMAKE_CURRENT_LIST_DIR} 
$<TARGET_PROPERTY:log4cxx,INCLUDE_DIRECTORIES>)
+    target_link_libraries(format-async PRIVATE log4cxx ${APR_UTIL_LIBRARIES} 
${EXPAT_LIBRARIES} ${APR_LIBRARIES} ${APR_SYSTEM_LIBS} fmt::fmt)
+    if( WIN32 )
+        set_target_properties( format-async PROPERTIES
+          VS_DEBUGGER_ENVIRONMENT "PATH=${ESCAPED_PATH}"
+          VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+          FOLDER Examples
+        )
+    endif()
+endif(ENABLE_FMT_ASYNC)
diff --git a/src/examples/cpp/format-async.cpp 
b/src/examples/cpp/format-async.cpp
new file mode 100644
index 00000000..fc46f44a
--- /dev/null
+++ b/src/examples/cpp/format-async.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 <stdlib.h>
+#include <log4cxx/logger.h>
+#include <log4cxx/logmanager.h>
+#include <log4cxx/basicconfigurator.h>
+#include <locale.h>
+#include <fmt/core.h>
+#include <fmt/args.h>
+#include <fmt/color.h>
+#include <fmt/ostream.h>
+#include <iomanip>
+
+using namespace log4cxx;
+using OutputStreamType = std::basic_ostream<logchar>;
+
+struct MyStruct {
+       int x;
+};
+       OutputStreamType& 
+operator<<(OutputStreamType& stream, const MyStruct& mystruct )
+{
+       stream << LOG4CXX_STR("[MyStruct x: ") << mystruct.x << 
LOG4CXX_STR("]");
+       return stream;
+}
+#if FMT_VERSION >= (9 * 10000)
+template <> struct fmt::formatter<MyStruct, logchar> : 
fmt::basic_ostream_formatter<logchar> {};
+#endif
+
+int main()
+{
+       setlocale(LC_ALL, "");
+
+       BasicConfigurator::configureAsync();
+       auto rootLogger = LogManager::getRootLogger();
+
+       LOG4CXX_INFO_FMT_ASYNC( rootLogger, "This is a {} message", "char" );
+       LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("This is a {} 
message"), LOG4CXX_STR("LogString") );
+       LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("We can also align text 
to the {:<10} or {:>10}"), LOG4CXX_STR("left"), LOG4CXX_STR("right") );
+
+       MyStruct mine{ 42 };
+       LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("This custom type {} 
can also be logged, since it implements operator<<"), mine );
+
+       LOG4CXX_INFO_ASYNC( rootLogger, LOG4CXX_STR("Numbers can be formatted 
with excessive operator<<: ")
+                                 << std::setprecision(3) << 22.456
+                                 << LOG4CXX_STR(" And as hex: ")
+                                 << std::setbase( 16 ) << 123 );
+       LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("Numbers can be 
formatted with a format string {:.1f} and as hex: {:x}"), 22.456, 123 );
+       // Uncomment the next line to verify that compile time type checking 
works
+       //LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("Numbers can be 
formatted with a format string {:.1f} and as hex: {:x}"), "wrong type", 123 );
+
+       LogManager::shutdown();
+       return 0;
+}
diff --git a/src/main/cpp/asyncbuffer.cpp b/src/main/cpp/asyncbuffer.cpp
index b7e5daf5..4a146bad 100644
--- a/src/main/cpp/asyncbuffer.cpp
+++ b/src/main/cpp/asyncbuffer.cpp
@@ -16,6 +16,7 @@
  */
 
 #include <log4cxx/helpers/asyncbuffer.h>
+#include <log4cxx/helpers/transcoder.h>
 
 namespace LOG4CXX_NS
 {
@@ -26,8 +27,59 @@ namespace helpers
 struct AsyncBuffer::Private
 {
        std::vector<MessageBufferAppender> data;
+
+       Private(const MessageBufferAppender& f)
+               : data{ f }
+       {}
+
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+       StringViewType fmt_string;
+       FmtArgStore    fmt_args;
+
+       Private(StringViewType&& format_string, FmtArgStore&& args)
+               : fmt_string{ std::move(format_string) }
+               , fmt_args{ std::move(args) }
+       {}
+
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+       WideStringViewType fmt_wstring;
+       WideFmtArgStore    fmt_wargs;
+
+       Private(WideStringViewType&& format_string, WideFmtArgStore&& args)
+               : fmt_wstring{ std::move(format_string) }
+               , fmt_wargs{ std::move(args) }
+       {}
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 };
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+void AsyncBuffer::initializeForFmt(StringViewType&& format_string, 
FmtArgStore&& args)
+{
+       if (!m_priv)
+               m_priv = std::make_unique<Private>(std::move(format_string), 
std::move(args));
+       else
+       {
+               m_priv->fmt_string = std::move(format_string);
+               m_priv->fmt_args = std::move(args);
+       }
+}
+
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+void AsyncBuffer::initializeForFmt(WideStringViewType&& format_string, 
WideFmtArgStore&& args)
+{
+       if (!m_priv)
+               m_priv = std::make_unique<Private>(std::move(format_string), 
std::move(args));
+       else
+       {
+               m_priv->fmt_wstring = std::move(format_string);
+               m_priv->fmt_wargs = std::move(args);
+       }
+}
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 /** An empty buffer.
 */
 AsyncBuffer::AsyncBuffer()
@@ -49,16 +101,57 @@ AsyncBuffer::~AsyncBuffer()
 /**
 * Has no item been added to this?
 */
-bool AsyncBuffer::empty() const { return !m_priv || m_priv->data.empty(); }
+bool AsyncBuffer::empty() const
+{
+       bool result{ true };
+       if (m_priv)
+       {
+               result = m_priv->data.empty();
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+               if (result)
+                       result = (0 == m_priv->fmt_string.size());
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+               if (result)
+                       result = (0 == m_priv->fmt_wstring.size());
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif
+       }
+       return result;
+}
 
 /**
 * Add text version of buffered values to \c msg
 */
-void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg)
+void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg) const
 {
        if (m_priv)
+       {
                for (auto& renderer : m_priv->data)
                        renderer(msg);
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+#if LOG4CXX_LOGCHAR_IS_UTF8
+               if (0 < m_priv->fmt_string.size())
+                       msg << fmt::vformat(m_priv->fmt_string, 
m_priv->fmt_args);
+#if LOG4CXX_WCHAR_T_API
+               if (0 < m_priv->fmt_wstring.size())
+               {
+                       LOG4CXX_DECODE_WCHAR(lsMsg, 
fmt::vformat(m_priv->fmt_wstring, m_priv->fmt_wargs));
+                       msg << lsMsg;
+               }
+#endif // LOG4CXX_WCHAR_T_API
+#endif // LOG4CXX_LOGCHAR_IS_UTF8
+
+#if LOG4CXX_LOGCHAR_IS_WCHAR
+               if (0 < m_priv->fmt_wstring.size())
+                       msg << fmt::vformat(m_priv->fmt_wstring, 
m_priv->fmt_wargs);
+               if (0 < m_priv->fmt_string.size())
+               {
+                       LOG4CXX_DECODE_CHAR(lsMsg, 
fmt::vformat(m_priv->fmt_string, m_priv->fmt_args));
+                       msg << lsMsg;
+               }
+#endif // LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+       }
 }
 
 /**
@@ -67,7 +160,15 @@ void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg)
 void AsyncBuffer::clear()
 {
        if (m_priv)
+       {
                m_priv->data.clear();
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+               m_priv->fmt_string = {};
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+               m_priv->fmt_wstring = {};
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+       }
 }
 
 /**
@@ -76,8 +177,9 @@ void AsyncBuffer::clear()
 void AsyncBuffer::append(const MessageBufferAppender& f)
 {
        if (!m_priv)
-               m_priv = std::make_unique<Private>();
-       m_priv->data.push_back(f);
+               m_priv = std::make_unique<Private>(f);
+       else
+               m_priv->data.push_back(f);
 }
 
 } // namespace helpers
diff --git a/src/main/cpp/basicconfigurator.cpp 
b/src/main/cpp/basicconfigurator.cpp
index e369f8ef..3895fa45 100644
--- a/src/main/cpp/basicconfigurator.cpp
+++ b/src/main/cpp/basicconfigurator.cpp
@@ -18,34 +18,63 @@
 #include <log4cxx/basicconfigurator.h>
 #include <log4cxx/patternlayout.h>
 #include <log4cxx/consoleappender.h>
+#include <log4cxx/asyncappender.h>
 #include <log4cxx/logmanager.h>
 #include <log4cxx/logger.h>
 #include <log4cxx/helpers/loglog.h>
 
 using namespace LOG4CXX_NS;
 
+namespace
+{
+
+LayoutPtr getDefaultLayout()
+{
+       auto pattern = LogString
+               { helpers::LogLog::isColorEnabled()
+               ? LOG4CXX_STR("%r [%t] %p %c %x - %Y%m%y%n")
+               : LOG4CXX_STR("%r [%t] %p %c %x - %m%n")
+               };
+       return std::make_shared<PatternLayout>(pattern);
+}
+
+} // namespace
+
+
 void BasicConfigurator::configure(const LayoutPtr& layoutArg)
 {
-       LogManager::getLoggerRepository()->setConfigured(true);
-       auto layout = layoutArg;
-       if (!layout)
-       {
-               auto pattern = LogString
-                       { helpers::LogLog::isColorEnabled()
-                       ? LOG4CXX_STR("%r [%t] %p %c %x - %Y%m%y%n")
-                       : LOG4CXX_STR("%r [%t] %p %c %x - %m%n")
-                       };
-               layout = std::make_shared<PatternLayout>(pattern);
-       }
-       auto appender = std::make_shared<ConsoleAppender>(layout);
-       Logger::getRootLogger()->addAppender(appender);
+       auto appender = std::make_shared<ConsoleAppender>(layoutArg ? layoutArg 
: getDefaultLayout());
+       appender->setName(LOG4CXX_STR("BasicConfigurator"));
+       auto r = LogManager::getLoggerRepository();
+       r->getRootLogger()->addAppender(appender);
+       r->setConfigured(true);
 }
 
 void BasicConfigurator::configure(const AppenderPtr& appender)
 {
-       LogManager::getLoggerRepository()->setConfigured(true);
-       LoggerPtr root = Logger::getRootLogger();
-       root->addAppender(appender);
+       auto r = LogManager::getLoggerRepository();
+       r->getRootLogger()->addAppender(appender);
+       r->setConfigured(true);
+}
+
+void BasicConfigurator::configureAsync(const LayoutPtr& layoutArg)
+{
+       auto ringBuffer = std::make_shared<AsyncAppender>();
+       ringBuffer->setName(LOG4CXX_STR("Default"));
+       ringBuffer->addAppender(std::make_shared<ConsoleAppender>(layoutArg ? 
layoutArg : getDefaultLayout()));
+       auto r = LogManager::getLoggerRepository();
+       r->getRootLogger()->addAppender(ringBuffer);
+       r->setConfigured(true);
+}
+
+void BasicConfigurator::configureAsync(const AppenderPtr& appender)
+{
+       auto ringBuffer = std::make_shared<AsyncAppender>();
+       ringBuffer->addAppender(appender);
+       ringBuffer->setName(LOG4CXX_STR("Default"));
+       auto r = LogManager::getLoggerRepository();
+       r->getRootLogger()->addAppender(ringBuffer);
+       r->setConfigured(true);
 }
 
 void BasicConfigurator::resetConfiguration()
diff --git a/src/main/include/CMakeLists.txt b/src/main/include/CMakeLists.txt
index 57195f51..c5d52f33 100644
--- a/src/main/include/CMakeLists.txt
+++ b/src/main/include/CMakeLists.txt
@@ -164,6 +164,7 @@ foreach(varName
   HAS_PTHREAD_SIGMASK
   HAS_PTHREAD_SETNAME
   HAS_PTHREAD_GETNAME
+  ENABLE_FMT_ASYNC
   )
   if(${varName} EQUAL 0)
     continue()
diff --git a/src/main/include/log4cxx/basicconfigurator.h 
b/src/main/include/log4cxx/basicconfigurator.h
index a2eb6fbd..b46a00e7 100644
--- a/src/main/include/log4cxx/basicconfigurator.h
+++ b/src/main/include/log4cxx/basicconfigurator.h
@@ -42,7 +42,7 @@ class LOG4CXX_EXPORT BasicConfigurator
 
        public:
                /**
-               Add a ConsoleAppender to the root logger that formats output 
using \c layout.
+               Add a ConsoleAppender that formats output using \c layout to 
the root logger.
 
                If \c layout is not provided,
                use a PatternLayout with the conversion pattern:
@@ -57,6 +57,22 @@ class LOG4CXX_EXPORT BasicConfigurator
                */
                static void configure(const AppenderPtr& appender);
 
+               /**
+               Add a ConsoleAppender that formats output using \c layout to an 
AsyncAppender attacted the root logger.
+
+               If \c layout is not provided,
+               use a PatternLayout with the conversion pattern:
+               - <code>%%r [%%t] %%p %%c %%x - %%Y%%m%%y%%n</code> if color is 
enabled (the default)
+               - <code>%%r [%%t] %%p %%c %%x - %%m%%n</code> if [color is 
disabled](disabling-color.html)
+               */
+               static void configureAsync(const LayoutPtr& layout = 
LayoutPtr());
+
+               /**
+               Add <code>appender</code> to an AsyncAppender attacted to the 
root logger.
+               @param appender The appender that asynchronously formats output.
+               */
+               static void configureAsync(const AppenderPtr& appender);
+
                /**
                Reset the default hierarchy to its defaut. It is equivalent to
                calling
diff --git a/src/main/include/log4cxx/helpers/asyncbuffer.h 
b/src/main/include/log4cxx/helpers/asyncbuffer.h
index ebc01a04..44110ebf 100644
--- a/src/main/include/log4cxx/helpers/asyncbuffer.h
+++ b/src/main/include/log4cxx/helpers/asyncbuffer.h
@@ -21,6 +21,12 @@
 #include <log4cxx/helpers/messagebuffer.h>
 #include <functional>
 #include <vector>
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+#include <fmt/args.h>
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#include <fmt/xchar.h>
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
 
 namespace LOG4CXX_NS
 {
@@ -35,7 +41,7 @@ namespace helpers
  */
 class LOG4CXX_EXPORT AsyncBuffer
 {
-public:
+public: // ...structors
        /** An empty buffer.
        */
        AsyncBuffer();
@@ -48,6 +54,7 @@ public:
        */
        ~AsyncBuffer();
 
+public: // Operators
        /** Append a function to this buffer that will convert \c value to text.
         *   @param value type must be copy-constructable
         *   @return this buffer.
@@ -77,6 +84,8 @@ public:
                return *this;
        }
 #endif
+
+public: // Accessors
        /**
        * Has no item been added to this?
        */
@@ -85,13 +94,38 @@ public:
        /**
        * Add text version of buffered values to \c msg
        */
-       void renderMessage(LogCharMessageBuffer& msg);
+       void renderMessage(LogCharMessageBuffer& msg) const;
 
+public: // Modifiers
        /**
        * Remove all message appenders
        */
        void clear();
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+       using StringViewType = fmt::basic_string_view<char>;
+       using FmtArgStore    = 
fmt::dynamic_format_arg_store<fmt::format_context>;
+       template <typename... Args>
+       void setMessage(fmt::format_string<Args...> fmt_str, Args&&... args)
+       {
+               auto store = FmtArgStore();
+               ( store.push_back(std::forward<Args>(args)), ...);
+               initializeForFmt(std::move(fmt_str), std::move(store));
+       }
+
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+       using WideStringViewType = fmt::basic_string_view<wchar_t>;
+       using WideFmtArgStore    = 
fmt::dynamic_format_arg_store<fmt::wformat_context>;
+       template <typename... Args>
+       void setMessage(fmt::wformat_string<Args...> fmt_str, Args&&... args)
+       {
+               auto store = WideFmtArgStore();
+               ( store.push_back(std::forward<Args>(args)), ...);
+               initializeForFmt(std::move(fmt_str), std::move(store));
+       }
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 private:
        AsyncBuffer(const AsyncBuffer&) = delete;
        AsyncBuffer& operator=(const AsyncBuffer&) = delete;
@@ -103,6 +137,14 @@ private:
         *   Append \c function to this buffer.
         */
        void append(const MessageBufferAppender& f);
+
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+       void initializeForFmt(StringViewType&& format_string, FmtArgStore&& 
args);
+
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+       void initializeForFmt(WideStringViewType&& format_string, 
WideFmtArgStore&& args);
+#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
 };
 
 } // namespace helpers
@@ -136,8 +178,36 @@ LOG4CXX_DEBUG_ASYNC(m_log, "AddMesh:"
                if 
(LOG4CXX_UNLIKELY(::LOG4CXX_NS::Logger::isDebugEnabledFor(logger))) {\
                        ::LOG4CXX_NS::helpers::AsyncBuffer buf; \
                        logger->addDebugEvent(std::move(buf << message), 
LOG4CXX_LOCATION); }} while (0)
+
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+Add a new logging event containing a message defined by \c fmt and 
<code>...</code> to attached appender(s) if \c logger is enabled for 
<code>DEBUG</code> events.
+
+\usage
+~~~{.cpp}
+LOG4CXX_DEBUG_FMT_ASYNC(m_log, "AddMesh: name {} type 0x{x} materialName {} 
visible? {d} at {} +/- {}"
+       , meshName
+       , traits.Type
+       , meshObject.GetMaterialName()
+       , traits.IsDefaultVisible
+       , obj->getBoundingBox().getCenter()
+       , obj->getBoundingBox().getHalfSize()
+       );
+~~~
+
+@param logger the logger to be used.
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+*/
+#define LOG4CXX_DEBUG_FMT_ASYNC(logger, fmt, ...) do { \
+               if 
(LOG4CXX_UNLIKELY(::LOG4CXX_NS::Logger::isDebugEnabledFor(logger))) {\
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addDebugEvent(std::move(buf), 
LOG4CXX_LOCATION); }} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
 #else
 #define LOG4CXX_DEBUG_ASYNC(logger, message)
+#define LOG4CXX_DEBUG_FMT_ASYNC(logger, message)
 #endif
 
 #if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 5000
@@ -156,8 +226,30 @@ Add a new logging event containing \c message to attached 
appender(s) if \c logg
                if 
(LOG4CXX_UNLIKELY(::LOG4CXX_NS::Logger::isTraceEnabledFor(logger))) {\
                        ::LOG4CXX_NS::helpers::AsyncBuffer buf; \
                        logger->addTraceEvent(std::move(buf << message), 
LOG4CXX_LOCATION); }} while (0)
+
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+Add a new logging event containing a message defined by \c fmt and 
<code>...</code> to attached appender(s) if \c logger is enabled for 
<code>TRACE</code> events.
+
+\usage
+~~~{.cpp}
+    LOG4CXX_TRACE_FMT_ASYNC(m_log, "AddVertex: at {} n {} {}", p, n, color);
+~~~
+
+@param logger the logger to be used.
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+*/
+#define LOG4CXX_TRACE_FMT_ASYNC(logger, fmt, ...) do { \
+               if 
(LOG4CXX_UNLIKELY(::LOG4CXX_NS::Logger::isTraceEnabledFor(logger))) {\
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addTraceEvent(std::move(buf), 
LOG4CXX_LOCATION); }} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 #else
 #define LOG4CXX_TRACE_ASYNC(logger, message)
+#define LOG4CXX_TRACE_FMT_ASYNC(logger, message)
 #endif
 
 #if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 20000
@@ -185,8 +277,33 @@ LOG4CXX_INFO_ASYNC(m_log, surface->GetName()
 
 #endif
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+Add a new logging event containing a message defined by \c fmt and 
<code>...</code> to attached appender(s) if \c logger is enabled for 
<code>INFO</code> events.
+
+\usage
+~~~{.cpp}
+LOG4CXX_INFO_FMT_ASYNC(m_log, "{} successfully planned {:.1f}% planned area 
{:.4f}m^2 unplanned area {:.4f}m^2 planned segments {:d} of {:d}"
+       , surface->GetName(), (plannedArea  / (plannedArea + unplannedArea)) * 
100.0
+       , plannedArea, unplannedArea
+       , surface->GetSegmentPlanCount(), surface->GetSegmentCount()
+       );
+~~~
+
+@param logger the logger to be used.
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+*/
+#define LOG4CXX_INFO_FMT_ASYNC(logger, fmt, ...) do { \
+               if (::LOG4CXX_NS::Logger::isInfoEnabledFor(logger)) {\
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addInfoEvent(std::move(buf), LOG4CXX_LOCATION); 
}} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 #else
 #define LOG4CXX_INFO_ASYNC(logger, message)
+#define LOG4CXX_INFO_FMT_ASYNC(logger, message)
 #endif
 
 #if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 30000
@@ -208,8 +325,33 @@ catch (const std::exception& ex)
                if (::LOG4CXX_NS::Logger::isWarnEnabledFor(logger)) {\
                        ::LOG4CXX_NS::helpers::AsyncBuffer buf; \
                        logger->addWarnEvent(std::move(buf << message), 
LOG4CXX_LOCATION); }} while (0)
+
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+Add a new logging event containing a message defined by \c fmt and 
<code>...</code> to attached appender(s) if \c logger is enabled for 
<code>WARN</code> events.
+
+\usage
+~~~{.cpp}
+catch (const std::exception& ex)
+{
+    LOG4CXX_WARN_FMT_ASYNC(m_log, "{}: in {}", ex.what(), 
m_task->GetParamFilePath());
+}
+~~~
+
+@param logger the logger to be used.
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+*/
+#define LOG4CXX_WARN_FMT_ASYNC(logger, fmt, ...) do { \
+               if (::LOG4CXX_NS::Logger::isWarnEnabledFor(logger)) {\
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addWarnEvent(std::move(buf), LOG4CXX_LOCATION); 
}} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 #else
 #define LOG4CXX_WARN_ASYNC(logger, message)
+#define LOG4CXX_WARN_FMT_ASYNC(logger, message)
 #endif
 
 #if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 40000
@@ -232,6 +374,29 @@ catch (std::exception& ex)
                        ::LOG4CXX_NS::helpers::AsyncBuffer buf; \
                        logger->addErrorEvent(std::move(buf << message), 
LOG4CXX_LOCATION); }} while (0)
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+Add a new logging event containing a message defined by \c fmt and 
<code>...</code> to attached appender(s) if \c logger is enabled for 
<code>ERROR</code> events.
+
+\usage
+~~~{.cpp}
+catch (std::exception& ex)
+{
+       LOG4CXX_ERROR_FMT_ASYNC(m_log, "{} in AddScanData", ex.what());
+}
+~~~
+
+@param logger the logger to be used.
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+*/
+#define LOG4CXX_ERROR_FMT_ASYNC(logger, fmt, ...) do { \
+               if (::LOG4CXX_NS::Logger::isErrorEnabledFor(logger)) {\
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addErrorEvent(std::move(buf), 
LOG4CXX_LOCATION); }} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 /**
 If \c condition is not true, add a new logging event containing \c message to 
attached appender(s) if \c logger is enabled for <code>ERROR</code> events.
 
@@ -245,9 +410,30 @@ If \c condition is not true, add a new logging event 
containing \c message to at
                        LOG4CXX_STACKTRACE \
                        logger->addErrorEvent(std::move(buf << message), 
LOG4CXX_LOCATION); }} while (0)
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+If \c condition is not true, add a new logging event containing
+a message defined by \c fmt and <code>...</code> to attached appender(s)
+if \c logger is enabled for <code>ERROR</code> events.
+
+@param logger the logger to be used.
+@param condition condition
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+*/
+#define LOG4CXX_ASSERT_FMT_ASYNC(logger, condition, fmt, ...) do { \
+               if (!(condition) && 
::LOG4CXX_NS::Logger::isErrorEnabledFor(logger)) {\
+                       LOG4CXX_STACKTRACE \
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addErrorEvent(std::move(buf), 
LOG4CXX_LOCATION); }} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 #else
 #define LOG4CXX_ERROR_ASYNC(logger, message)
+#define LOG4CXX_ERROR_FMT_ASYNC(logger, message)
 #define LOG4CXX_ASSERT_ASYNC(logger, condition, message)
+#define LOG4CXX_ASSERT_FMT_ASYNC(logger, condition, message)
 #endif
 
 #if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 50000
@@ -267,8 +453,29 @@ LOG4CXX_FATAL_ASYNC(m_log, m_renderSystem->getName() << " 
is not supported");
                        ::LOG4CXX_NS::helpers::AsyncBuffer buf; \
                        logger->addFatalEvent(std::move(buf << message), 
LOG4CXX_LOCATION); }} while (0)
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+/**
+Add a new logging event containing a message defined by \c fmt and 
<code>...</code> to attached appender(s) if \c logger is enabled for 
<code>FATAL</code> events.
+
+\usage
+~~~{.cpp}
+LOG4CXX_FATAL_FMT_ASYNC(m_log, "{} is not supported", 
m_renderSystem->getName());
+~~~
+@param logger the logger to be used.
+@param fmt the layout of the message.
+@param ... the variable parts of the message.
+
+*/
+#define LOG4CXX_FATAL_FMT_ASYNC(logger, fmt, ...) do { \
+               if (::LOG4CXX_NS::Logger::isFatalEnabledFor(logger)) {\
+                       ::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+                       buf.setMessage(fmt LOG4CXX_FMT_VA_ARG(__VA_ARGS__));\
+                       logger->addFatalEvent(std::move(buf), 
LOG4CXX_LOCATION); }} while (0)
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+
 #else
 #define LOG4CXX_FATAL_ASYNC(logger, message)
+#define LOG4CXX_FATAL_FMT_ASYNC(logger, message)
 #endif
 
 /**@} Logging macro group */
diff --git a/src/main/include/log4cxx/log4cxx.h.in 
b/src/main/include/log4cxx/log4cxx.h.in
index 0de00ecc..4e6c2931 100644
--- a/src/main/include/log4cxx/log4cxx.h.in
+++ b/src/main/include/log4cxx/log4cxx.h.in
@@ -131,8 +131,10 @@ namespace log4cxx = LOG4CXX_NS;
 #define LOG4CXX_USING_STD_FORMAT @LOG4CXX_USE_STANDARD_FORMAT@
 #if !defined(LOG4CXX_FORMAT_NS) && LOG4CXX_USING_STD_FORMAT
 #define LOG4CXX_FORMAT_NS std
+#define LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT 0
 #elif !defined(LOG4CXX_FORMAT_NS)
 #define LOG4CXX_FORMAT_NS fmt
+#define LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT @ENABLE_FMT_ASYNC@
 #endif
 
 #endif
diff --git a/src/site/doxy/Doxyfile.in b/src/site/doxy/Doxyfile.in
index 8de9efcb..009395ac 100644
--- a/src/site/doxy/Doxyfile.in
+++ b/src/site/doxy/Doxyfile.in
@@ -2350,7 +2350,8 @@ PREDEFINED             = LOG4CXX_NS=log4cxx \
                          LOG4CXX_UNICHAR_API \
                          LOG4CXX_CFSTRING_API \
                          LOG4CXX_ABI_VERSION=@log4cxx_ABI_VER@ \
-                         LOG4CXX_HAS_DOMCONFIGURATOR=1
+                         LOG4CXX_HAS_DOMCONFIGURATOR=1 \
+                                                
LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT=1
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The
diff --git a/src/site/markdown/change-report-gh.md 
b/src/site/markdown/change-report-gh.md
index 7e4e4cca..48eae411 100644
--- a/src/site/markdown/change-report-gh.md
+++ b/src/site/markdown/change-report-gh.md
@@ -62,7 +62,8 @@ and the LOG4CXX_CONFIGURATION environment variable (see 
log4cxx::spi::Configurat
 * Console output (Log4cxx internal logging and BasicConfigurator) use a color 
per message level by default
    \[[#529](https://github.com/apache/logging-log4cxx/pull/529)\]
 * New logging macros that defer binary-to-text conversion until used in 
AsyncAppender's background thread
-   \[[#548](https://github.com/apache/logging-log4cxx/pull/548)\]
+   * \[[#548](https://github.com/apache/logging-log4cxx/pull/548)\]
+   , \[[#554](https://github.com/apache/logging-log4cxx/pull/554)\]
 * A simplified way to attach an AsyncAppender to a logger using a 
configuration file
    \[[#550](https://github.com/apache/logging-log4cxx/pull/550)\]
 
diff --git a/src/site/markdown/performance.md b/src/site/markdown/performance.md
index 3f033719..e741d018 100644
--- a/src/site/markdown/performance.md
+++ b/src/site/markdown/performance.md
@@ -109,10 +109,10 @@ The "Iterations" column derivation is explained in 
[Google Benchmark documentati
 | Appending int+float using FMT, pattern: \%m\%n/threads:6 | 537 ns | 3036 ns 
| 212844 |
 | Appending int+10float using FMT, pattern: \%m\%n | 1671 ns | 1671 ns | 
417402 |
 | Appending int+10float using FMT, pattern: \%m\%n/threads:6 | 1275 ns | 7297 
ns | 96222 |
-| Async, Sending int+10float using FMT | 2190 ns | 2190 ns | 320109 |
-| Async, Sending int+10float using FMT/threads:6 | 1363 ns | 7862 ns | 84306 |
-| Async, Sending int+10float using AsyncBuffer, pattern: \%m\%n | 1226 ns | 
1226 ns | 571351 |
-| Async, Sending int+10float using AsyncBuffer, pattern: \%m\%n/threads:6 | 
1398 ns | 7902 ns | 89688 |
+| Async, Sending int+10float using FMT and AsyncBuffer | 784 ns | 783 ns | 
891953 |
+| Async, Sending int+10float using FMT and AsyncBuffer/threads:6 | 1375 ns | 
7692 ns | 88554 |
+| Async, Sending int+10float using operator<< and AsyncBuffer, pattern: \%m\%n 
| 1211 ns | 1211 ns | 578034 |
+| Async, Sending int+10float using operator<< and AsyncBuffer, pattern: 
\%m\%n/threads:6 | 1351 ns | 7670 ns | 90912 |
 | Logging int+float using MessageBuffer, pattern: \%d \%m\%n | 1073 ns | 1073 
ns | 656652 |
 | Logging int+float using MessageBuffer, pattern: \%d \%m\%n/threads:6 | 1083 
ns | 4895 ns | 142776 |
 | Logging int+float using MessageBuffer, JSON | 1394 ns | 1394 ns | 507493 |
diff --git a/src/test/cpp/CMakeLists.txt b/src/test/cpp/CMakeLists.txt
index 19dcabb1..4eb99f7d 100644
--- a/src/test/cpp/CMakeLists.txt
+++ b/src/test/cpp/CMakeLists.txt
@@ -18,12 +18,20 @@
 option(ENABLE_MULTITHREAD_TEST "Enable multithread test.  Note that this test 
is very unstable,
     it is mostly designed to ensure that we don't crash immediately if exit is 
called in a thread"  OFF)
 
+set(TESTING_DEPENDENCY_COMPILE_DEFINITIONS ${LOG4CXX_COMPILE_DEFINITIONS} 
${APR_COMPILE_DEFINITIONS} ${APR_UTIL_COMPILE_DEFINITIONS})
+
 # Components required by all tests
 add_library(testingFramework STATIC abts.cpp appenderskeletontestcase.cpp 
logunit.cpp vectorappender.cpp writerappendertestcase.cpp )
-target_compile_definitions(testingFramework PRIVATE 
${LOG4CXX_COMPILE_DEFINITIONS} ${APR_COMPILE_DEFINITIONS} 
${APR_UTIL_COMPILE_DEFINITIONS} )
+target_compile_definitions(testingFramework PRIVATE 
${TESTING_DEPENDENCY_COMPILE_DEFINITIONS} )
 target_include_directories(testingFramework PRIVATE ${CMAKE_CURRENT_LIST_DIR} 
$<TARGET_PROPERTY:log4cxx,INCLUDE_DIRECTORIES>)
 add_subdirectory(util)
 target_sources(testingUtilities PRIVATE xml/xlevel.cpp)
+if(ENABLE_FMT_ASYNC AND MSVC)
+    # This flag tells the MSVC compiler to read all source files 
+    # as encoded in UTF-8, which resolves the fmt static_assert.
+    target_compile_options(testingFramework PRIVATE /utf-8)
+    target_compile_options(testingUtilities PRIVATE /utf-8)
+endif()
 
 set( LOG4CXX_TEST_PROGRAM_PATH "" CACHE PATH "Extra path for test programs" )
 set( CMAKE_PROGRAM_PATH "${LOG4CXX_TEST_PROGRAM_PATH};${CMAKE_PROGRAM_PATH}" )
@@ -123,7 +131,7 @@ foreach(testName IN LISTS ALL_LOG4CXX_TESTS)
         target_link_libraries(${testName} PRIVATE MockCoreFoundation)
       endif()
     endif()
-    target_compile_definitions(${testName} PRIVATE ${TEST_COMPILE_DEFINITIONS} 
${LOG4CXX_COMPILE_DEFINITIONS} ${APR_COMPILE_DEFINITIONS} 
${APR_UTIL_COMPILE_DEFINITIONS} )
+    target_compile_definitions(${testName} PRIVATE ${TEST_COMPILE_DEFINITIONS} 
${TESTING_DEPENDENCY_COMPILE_DEFINITIONS} )
     target_include_directories(${testName} PRIVATE ${CMAKE_CURRENT_LIST_DIR} 
$<TARGET_PROPERTY:log4cxx,INCLUDE_DIRECTORIES>)
     target_link_libraries(${testName} PRIVATE testingFramework 
testingUtilities log4cxx ${APR_LIBRARIES} ${APR_SYSTEM_LIBS} Threads::Threads 
${ODBC_LIBRARIES} )
     if(HAS_LIBESMTP)
diff --git a/src/test/cpp/asyncappendertestcase.cpp 
b/src/test/cpp/asyncappendertestcase.cpp
index 1faa7c81..b9dd8bcc 100644
--- a/src/test/cpp/asyncappendertestcase.cpp
+++ b/src/test/cpp/asyncappendertestcase.cpp
@@ -26,6 +26,7 @@
 #include "appenderskeletontestcase.h"
 #include <log4cxx/helpers/loglog.h>
 #include <log4cxx/helpers/pool.h>
+#include <log4cxx/helpers/transcoder.h>
 #include <log4cxx/varia/fallbackerrorhandler.h>
 #include <apr_strings.h>
 #include "testchar.h"
@@ -34,8 +35,31 @@
 #include <log4cxx/xml/domconfigurator.h>
 #include <log4cxx/propertyconfigurator.h>
 #include <log4cxx/file.h>
+#include <ostream>
 #include <thread>
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+#include <fmt/core.h>
+#include <fmt/ostream.h>
+#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
+#include <fmt/xchar.h>
+#endif
+struct MyStruct
+{
+       int value;
+};
+using OutputStreamType = std::basic_ostream<log4cxx::logchar>;
+OutputStreamType& operator<<(OutputStreamType& stream, const MyStruct& data)
+{
+       stream << LOG4CXX_STR("[MyStruct value: ") << data.value << 
LOG4CXX_STR("]");
+       return stream;
+}
+
+#if FMT_VERSION >= (9 * 10000)
+template <> struct fmt::formatter<MyStruct, log4cxx::logchar> : 
fmt::basic_ostream_formatter<log4cxx::logchar> {};
+#endif
+#endif
+
 using namespace log4cxx;
 using namespace log4cxx::helpers;
 using namespace log4cxx::spi;
@@ -143,6 +167,9 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                LOGUNIT_TEST(testAsyncLoggerXML);
 #endif
                LOGUNIT_TEST(testAsyncLoggerProperties);
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+               LOGUNIT_TEST(testAsyncFmtLogging);
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
                LOGUNIT_TEST_SUITE_END();
 
 #ifdef _DEBUG
@@ -155,12 +182,12 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
 #endif
 
        public:
-               void setUp()
+               void setUp() override
                {
                        AppenderSkeletonTestCase::setUp();
                }
 
-               void tearDown()
+               void tearDown() override
                {
                        LogManager::shutdown();
                        AppenderSkeletonTestCase::tearDown();
@@ -626,6 +653,76 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                        LOGUNIT_ASSERT(vectorAppender->isClosed());
                }
 
+#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+               /**
+                * Tests asynchronous logging using FMT
+                */
+               void testAsyncFmtLogging()
+               {
+                       // Configure Log4cxx
+                       AsyncAppenderPtr async;
+                       auto r = LogManager::getLoggerRepository();
+                       r->ensureIsConfigured([r, &async]()
+                       {
+                               async = std::make_shared<AsyncAppender>();
+                               
async->setName(LOG4CXX_STR("withAsyncFmtLogging"));
+                               r->getRootLogger()->addAppender(async);
+                               r->setConfigured(true);
+                       });
+                       LOGUNIT_ASSERT(async);
+                       auto eventStore = std::make_shared<VectorAppender>();
+                       eventStore->setName(LOG4CXX_STR("VectorAppender"));
+                       async->addAppender(eventStore);
+
+                       // Log some messages
+                       auto rootLogger = r->getRootLogger();
+                       LOG4CXX_INFO_FMT_ASYNC(rootLogger, LOG4CXX_STR("Hello, 
{}"), LOG4CXX_STR("World"));
+                       for (MyStruct i = {0}; i.value < 10; ++i.value)
+                       {
+                               LOG4CXX_INFO_FMT_ASYNC(rootLogger, 
LOG4CXX_STR("Hello, {}"), i);
+                       }
+                       LOG4CXX_INFO_FMT_ASYNC(rootLogger, "Bye bye {}", 
"World");
+                       async->close();
+
+                       // Check all parts of all messages were received
+                       auto& events = eventStore->getVector();
+                       std::vector<int> messageCount;
+                       int eventCount[] = { 0, 0 };
+                       for (auto& e : events)
+                       {
+                               auto message = e->getRenderedMessage();
+                               //LogLog::debug(message);
+                               auto isNumberedMessage = (message.npos == 
message.find(LOG4CXX_STR("World")));
+                               ++eventCount[isNumberedMessage];
+                               if (isNumberedMessage)
+                               {
+                                        auto pos = message.rfind(' ');
+                                        if (message.npos != pos && pos + 1 < 
message.size())
+                                        {
+                                               try
+                                               {
+                                                       auto msgNumber = 
StringHelper::toInt(message.substr(pos));
+                                                       if (messageCount.size() 
<= msgNumber)
+                                                               
messageCount.resize(msgNumber + 1);
+                                                       
++messageCount[msgNumber];
+                                               }
+                                               catch (std::exception const& ex)
+                                               {
+                                                       LogString msg;
+                                                       
helpers::Transcoder::decode(ex.what(), msg);
+                                                       msg += LOG4CXX_STR(" 
processing\n");
+                                                       
helpers::Transcoder::decode(message, msg);
+                                                       
helpers::LogLog::warn(msg);
+                                               }
+                                        }
+                               }
+                       }
+                       LOGUNIT_ASSERT_EQUAL(int(messageCount.size()), 
eventCount[1]);
+                       for (auto& count : messageCount)
+                               LOGUNIT_ASSERT_EQUAL(count, 
messageCount.front());
+                       LOGUNIT_ASSERT_EQUAL(2, eventCount[0]);
+               }
+#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
 
 };
 
diff --git a/src/test/cpp/benchmark/benchmark.cpp 
b/src/test/cpp/benchmark/benchmark.cpp
index 85a348c2..61a6655c 100644
--- a/src/test/cpp/benchmark/benchmark.cpp
+++ b/src/test/cpp/benchmark/benchmark.cpp
@@ -495,7 +495,7 @@ BENCHMARK_DEFINE_F(benchmarker, 
asyncIntPlus10FloatValueFmtBuffer)(benchmark::St
                , static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
                , static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
                };
-               LOG4CXX_INFO_FMT(m_asyncLogger, "Hello: msg number {} 
pseudo-random float {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} 
{:.3f} {:.3f}"
+               LOG4CXX_INFO_FMT_ASYNC(m_asyncLogger, "Hello: msg number {} 
pseudo-random float {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} 
{:.3f} {:.3f}"
                        , ++x
                        , f[0]
                        , f[1]
@@ -510,8 +510,8 @@ BENCHMARK_DEFINE_F(benchmarker, 
asyncIntPlus10FloatValueFmtBuffer)(benchmark::St
                        );
        }
 }
-BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatValueFmtBuffer)->Name("Async, Sending int+10float using 
FMT");
-BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatValueFmtBuffer)->Name("Async, Sending int+10float using 
FMT")->Threads(benchmarker::threadCount());
+BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatValueFmtBuffer)->Name("Async, Sending int+10float using FMT 
and AsyncBuffer");
+BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatValueFmtBuffer)->Name("Async, Sending int+10float using FMT 
and AsyncBuffer")->Threads(benchmarker::threadCount());
 #endif
 
 BENCHMARK_DEFINE_F(benchmarker, 
asyncIntPlus10FloatAsyncBuffer)(benchmark::State& state)
@@ -546,8 +546,8 @@ BENCHMARK_DEFINE_F(benchmarker, 
asyncIntPlus10FloatAsyncBuffer)(benchmark::State
                        );
        }
 }
-BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatAsyncBuffer)->Name("Async, Sending int+10float using 
AsyncBuffer, pattern: %m%n");
-BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatAsyncBuffer)->Name("Async, Sending int+10float using 
AsyncBuffer, pattern: %m%n")->Threads(benchmarker::threadCount());
+BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatAsyncBuffer)->Name("Async, Sending int+10float using 
operator<< and AsyncBuffer, pattern: %m%n");
+BENCHMARK_REGISTER_F(benchmarker, 
asyncIntPlus10FloatAsyncBuffer)->Name("Async, Sending int+10float using 
operator<< and AsyncBuffer, pattern: 
%m%n")->Threads(benchmarker::threadCount());
 
 BENCHMARK_DEFINE_F(benchmarker, 
fileIntPlusFloatValueMessageBuffer)(benchmark::State& state)
 {


Reply via email to