Allow binary-to-text conversion to be moved to a background thread (#548)

diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index 465a51d..8a2526f 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -78,6 +78,7 @@
   appenderskeleton.cpp
   aprinitializer.cpp
   asyncappender.cpp
+  asyncbuffer.cpp
   basicconfigurator.cpp
   bufferedwriter.cpp
   bytearrayinputstream.cpp
diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index 2427cb6..d199e39 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -498,7 +498,7 @@
 	LogString msg(LOG4CXX_STR("Discarded "));
 	StringHelper::toString(count, p, msg);
 	msg.append(LOG4CXX_STR(" messages due to a full event buffer including: "));
-	msg.append(maxEvent->getMessage());
+	msg.append(maxEvent->getRenderedMessage());
 	return std::make_shared<LoggingEvent>(
 				maxEvent->getLoggerName(),
 				maxEvent->getLevel(),
diff --git a/src/main/cpp/asyncbuffer.cpp b/src/main/cpp/asyncbuffer.cpp
new file mode 100644
index 0000000..b7e5daf
--- /dev/null
+++ b/src/main/cpp/asyncbuffer.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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/helpers/asyncbuffer.h>
+
+namespace LOG4CXX_NS
+{
+
+namespace helpers
+{
+
+struct AsyncBuffer::Private
+{
+	std::vector<MessageBufferAppender> data;
+};
+
+/** An empty buffer.
+*/
+AsyncBuffer::AsyncBuffer()
+{}
+
+/** A new buffer with the content of \c other
+*/
+AsyncBuffer::AsyncBuffer(AsyncBuffer&& other)
+	: m_priv(std::move(other.m_priv))
+{
+}
+
+/** Release resources.
+*/
+AsyncBuffer::~AsyncBuffer()
+{
+}
+
+/**
+* Has no item been added to this?
+*/
+bool AsyncBuffer::empty() const { return !m_priv || m_priv->data.empty(); }
+
+/**
+* Add text version of buffered values to \c msg
+*/
+void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg)
+{
+	if (m_priv)
+		for (auto& renderer : m_priv->data)
+			renderer(msg);
+}
+
+/**
+* Remove all message appenders
+*/
+void AsyncBuffer::clear()
+{
+	if (m_priv)
+		m_priv->data.clear();
+}
+
+/**
+ *   Append \c function to this buffer.
+ */
+void AsyncBuffer::append(const MessageBufferAppender& f)
+{
+	if (!m_priv)
+		m_priv = std::make_unique<Private>();
+	m_priv->data.push_back(f);
+}
+
+} // namespace helpers
+} // namespace LOG4CXX_NS
+
diff --git a/src/main/cpp/fmtlayout.cpp b/src/main/cpp/fmtlayout.cpp
index faa0d0c..e6c5bf7 100644
--- a/src/main/cpp/fmtlayout.cpp
+++ b/src/main/cpp/fmtlayout.cpp
@@ -89,7 +89,8 @@
 	const spi::LoggingEventPtr& event,
 	LOG4CXX_NS::helpers::Pool&) const
 {
-	output.reserve(m_priv->expectedPatternLength + event->getMessage().size());
+	auto& lsMsg = event->getRenderedMessage();
+	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
 	auto locationFull = fmt::format("{}({})",
 										 event->getLocationInformation().getFileName(),
 										 event->getLocationInformation().getLineNumber());
@@ -100,7 +101,7 @@
 	LOG4CXX_ENCODE_CHAR(sPattern, m_priv->conversionPattern);
 	LOG4CXX_ENCODE_CHAR(sLogger, event->getLoggerName());
 	LOG4CXX_ENCODE_CHAR(sLevel, event->getLevel()->toString());
-	LOG4CXX_ENCODE_CHAR(sMsg, event->getMessage());
+	LOG4CXX_ENCODE_CHAR(sMsg, lsMsg);
 	LOG4CXX_ENCODE_CHAR(sThread, event->getThreadName());
 	LOG4CXX_ENCODE_CHAR(endOfLine, LOG4CXX_EOL);
 #else
@@ -108,7 +109,7 @@
 	auto& sPattern = m_priv->conversionPattern;
 	auto& sLogger = event->getLoggerName();
 	auto sLevel = event->getLevel()->toString();
-	auto& sMsg = event->getMessage();
+	auto& sMsg = lsMsg;
 	auto& sThread = event->getThreadName();
 	auto endOfLine = LOG4CXX_EOL;
 #endif
diff --git a/src/main/cpp/htmllayout.cpp b/src/main/cpp/htmllayout.cpp
index f1b2747..bcfce2f 100644
--- a/src/main/cpp/htmllayout.cpp
+++ b/src/main/cpp/htmllayout.cpp
@@ -84,7 +84,8 @@
 	const spi::LoggingEventPtr& event,
 	Pool& p) const
 {
-	output.reserve(m_priv->expectedPatternLength + event->getMessage().size());
+	auto& lsMsg = event->getRenderedMessage();
+	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
 	output.append(LOG4CXX_EOL);
 	output.append(LOG4CXX_STR("<tr>"));
 	output.append(LOG4CXX_EOL);
@@ -152,7 +153,7 @@
 	}
 
 	output.append(LOG4CXX_STR("<td title=\"Message\">"));
-	Transform::appendEscapingTags(output, event->getRenderedMessage());
+	Transform::appendEscapingTags(output, lsMsg);
 	output.append(LOG4CXX_STR("</td>"));
 	output.append(LOG4CXX_EOL);
 	output.append(LOG4CXX_STR("</tr>"));
diff --git a/src/main/cpp/jsonlayout.cpp b/src/main/cpp/jsonlayout.cpp
index 9b286af..5471235 100644
--- a/src/main/cpp/jsonlayout.cpp
+++ b/src/main/cpp/jsonlayout.cpp
@@ -132,7 +132,8 @@
 	const spi::LoggingEventPtr& event,
 	Pool& p) const
 {
-	output.reserve(m_priv->expectedPatternLength + event->getMessage().size());
+	auto& lsMsg = event->getRenderedMessage();
+	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
 	output.append(LOG4CXX_STR("{"));
 	output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
 
@@ -187,7 +188,7 @@
 	}
 
 	output.append(LOG4CXX_STR("\"message\": "));
-	appendQuotedEscapedString(output, event->getMessage());
+	appendQuotedEscapedString(output, lsMsg);
 
 	appendSerializedMDC(output, event);
 	appendSerializedNDC(output, event);
diff --git a/src/main/cpp/logger.cpp b/src/main/cpp/logger.cpp
index e814620..9574a14 100644
--- a/src/main/cpp/logger.cpp
+++ b/src/main/cpp/logger.cpp
@@ -173,6 +173,15 @@
 	}
 }
 
+void Logger::addEvent(const LevelPtr& level, helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	if (!getHierarchy()) // Has removeHierarchy() been called?
+		return;
+	auto event = std::make_shared<LoggingEvent>(m_priv->name, level, location, std::move(messageAppender));
+	Pool p;
+	callAppenders(event, p);
+}
+
 void Logger::addEvent(const LevelPtr& level, std::string&& message, const LocationInfo& location) const
 {
 	if (!getHierarchy()) // Has removeHierarchy() been called?
@@ -192,31 +201,61 @@
 	addEvent(m_priv->levelData->Fatal, std::move(message), location);
 }
 
+void Logger::addFatalEvent(helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	addEvent(m_priv->levelData->Fatal, std::move(messageAppender), location);
+}
+
 void Logger::addErrorEvent(std::string&& message, const LocationInfo& location) const
 {
 	addEvent(m_priv->levelData->Error, std::move(message), location);
 }
 
+void Logger::addErrorEvent(helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	addEvent(m_priv->levelData->Error, std::move(messageAppender), location);
+}
+
 void Logger::addWarnEvent(std::string&& message, const LocationInfo& location) const
 {
 	addEvent(m_priv->levelData->Warn, std::move(message), location);
 }
 
+void Logger::addWarnEvent(helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	addEvent(m_priv->levelData->Warn, std::move(messageAppender), location);
+}
+
 void Logger::addInfoEvent(std::string&& message, const LocationInfo& location) const
 {
 	addEvent(m_priv->levelData->Info, std::move(message), location);
 }
 
+void Logger::addInfoEvent(helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	addEvent(m_priv->levelData->Info, std::move(messageAppender), location);
+}
+
 void Logger::addDebugEvent(std::string&& message, const LocationInfo& location) const
 {
 	addEvent(m_priv->levelData->Debug, std::move(message), location);
 }
 
+void Logger::addDebugEvent(helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	addEvent(m_priv->levelData->Debug, std::move(messageAppender), location);
+}
+
 void Logger::addTraceEvent(std::string&& message, const LocationInfo& location) const
 {
 	addEvent(m_priv->levelData->Trace, std::move(message), location);
 }
 
+void Logger::addTraceEvent(helpers::AsyncBuffer&& messageAppender, const LocationInfo& location) const
+{
+	addEvent(m_priv->levelData->Trace, std::move(messageAppender), location);
+}
+
 void Logger::forcedLog(const LevelPtr& level, const std::string& message,
 	const LocationInfo& location) const
 {
diff --git a/src/main/cpp/loggingevent.cpp b/src/main/cpp/loggingevent.cpp
index e1fc11f..ceae144 100644
--- a/src/main/cpp/loggingevent.cpp
+++ b/src/main/cpp/loggingevent.cpp
@@ -29,6 +29,7 @@
 #include <log4cxx/helpers/aprinitializer.h>
 #include <log4cxx/helpers/threadspecificdata.h>
 #include <log4cxx/helpers/bytebuffer.h>
+#include <log4cxx/helpers/messagebuffer.h>
 #include <log4cxx/helpers/date.h>
 #include <log4cxx/helpers/optional.h>
 
@@ -61,6 +62,23 @@
 	{
 	}
 
+	LoggingEventPrivate
+		( const LogString& logger1
+		, const LevelPtr& level1
+		, const LocationInfo& locationInfo1
+		, helpers::AsyncBuffer&& messageAppenderArg
+		, const ThreadSpecificData::NamePairPtr p = ThreadSpecificData::getNames()
+		)
+		: logger(logger1)
+		, level(level1)
+		, timeStamp(Date::currentTime())
+		, locationInfo(locationInfo1)
+		, chronoTimeStamp(std::chrono::microseconds(timeStamp))
+		, pNames(p)
+		, messageAppender(std::move(messageAppenderArg))
+	{
+	}
+
 	LoggingEventPrivate(
 		const LogString& logger1, const LevelPtr& level1,
 		const LogString& message1, const LocationInfo& locationInfo1,
@@ -124,6 +142,21 @@
 	 *  of this LoggingEvent exceeds the duration of the logging request.
 	 */
 	mutable std::unique_ptr<DiagnosticContext> dc;
+
+	/** Application supplied message builders.
+	 */
+	helpers::AsyncBuffer messageAppender;
+
+	void renderMessage()
+	{
+		if (!this->messageAppender.empty())
+		{
+			helpers::LogCharMessageBuffer buf;
+			this->messageAppender.renderMessage(buf);
+			this->message = buf.extract_str(buf);
+			this->messageAppender.clear();
+		}
+	}
 };
 
 IMPLEMENT_LOG4CXX_OBJECT(LoggingEvent)
@@ -152,6 +185,16 @@
 {
 }
 
+LoggingEvent::LoggingEvent
+	( const LogString&    logger
+	, const LevelPtr&     level
+	, const LocationInfo& location
+	, helpers::AsyncBuffer&& messageAppender
+	)
+	: m_priv(std::make_unique<LoggingEventPrivate>(logger, level, location, std::move(messageAppender)))
+{
+}
+
 LoggingEvent::LoggingEvent(
 	const LogString& logger1, const LevelPtr& level1,
 	const LogString& message1, const LocationInfo& locationInfo1) :
@@ -273,6 +316,11 @@
 	return set;
 }
 
+void LoggingEvent::renderMessage()
+{
+	m_priv->renderMessage();
+}
+
 void LoggingEvent::setProperty(const LogString& key, const LogString& value)
 {
 	if (m_priv->properties == 0)
@@ -300,6 +348,7 @@
 
 const LogString& LoggingEvent::getRenderedMessage() const
 {
+	m_priv->renderMessage();
 	return m_priv->message;
 }
 
diff --git a/src/main/cpp/messagebuffer.cpp b/src/main/cpp/messagebuffer.cpp
index 6c7f1a4..df908bf 100644
--- a/src/main/cpp/messagebuffer.cpp
+++ b/src/main/cpp/messagebuffer.cpp
@@ -161,7 +161,7 @@
 
 std::basic_string<char> CharMessageBuffer::extract_str(CharMessageBuffer&)
 {
-	return std::move(m_priv->buf);
+	return std::move(m_priv->BufFromStream());
 }
 
 const std::basic_string<char>& CharMessageBuffer::str(std::basic_ostream<char>&)
@@ -171,7 +171,7 @@
 
 const std::basic_string<char>& CharMessageBuffer::str(CharMessageBuffer&)
 {
-	return m_priv->buf;
+	return m_priv->BufFromStream();
 }
 
 bool CharMessageBuffer::hasStream() const
@@ -306,7 +306,7 @@
 
 std::basic_string<wchar_t> WideMessageBuffer::extract_str(WideMessageBuffer&)
 {
-	return std::move(m_priv->buf);
+	return std::move(m_priv->BufFromStream());
 }
 
 const std::basic_string<wchar_t>& WideMessageBuffer::str(std::basic_ostream<wchar_t>&)
@@ -316,7 +316,7 @@
 
 const std::basic_string<wchar_t>& WideMessageBuffer::str(WideMessageBuffer&)
 {
-	return m_priv->buf;
+	return m_priv->BufFromStream();
 }
 
 bool WideMessageBuffer::hasStream() const
@@ -672,7 +672,7 @@
 
 std::basic_string<LOG4CXX_NS::UniChar> UniCharMessageBuffer::extract_str(UniCharMessageBuffer&)
 {
-	return std::move(m_priv->buf);
+	return std::move(m_priv->BufFromStream());
 }
 
 const std::basic_string<LOG4CXX_NS::UniChar>& UniCharMessageBuffer::str(UniCharMessageBuffer::uostream&)
@@ -682,7 +682,7 @@
 
 const std::basic_string<LOG4CXX_NS::UniChar>& UniCharMessageBuffer::str(UniCharMessageBuffer&)
 {
-	return m_priv->buf;
+	return m_priv->BufFromStream();
 }
 
 bool UniCharMessageBuffer::hasStream() const
diff --git a/src/main/cpp/patternlayout.cpp b/src/main/cpp/patternlayout.cpp
index 371e47d..68d43c5 100644
--- a/src/main/cpp/patternlayout.cpp
+++ b/src/main/cpp/patternlayout.cpp
@@ -122,7 +122,8 @@
 	const spi::LoggingEventPtr& event,
 	Pool& pool) const
 {
-	output.reserve(m_priv->expectedPatternLength + event->getMessage().size());
+	auto& lsMsg = event->getRenderedMessage();
+	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
 	std::vector<FormattingInfoPtr>::const_iterator formatterIter =
 		m_priv->patternFields.begin();
 
diff --git a/src/main/cpp/telnetappender.cpp b/src/main/cpp/telnetappender.cpp
index fcacc40..cc3b143 100644
--- a/src/main/cpp/telnetappender.cpp
+++ b/src/main/cpp/telnetappender.cpp
@@ -230,7 +230,7 @@
 		if (_priv->layout)
 			_priv->layout->format(msg, event, p);
 		else
-			msg = event->getMessage();
+			msg = event->getRenderedMessage();
 		msg.append(LOG4CXX_STR("\r\n"));
 		size_t bytesSize = msg.size() * 2;
 		char* bytes = p.pstralloc(bytesSize);
diff --git a/src/main/cpp/xmllayout.cpp b/src/main/cpp/xmllayout.cpp
index 0b7fe07..e26be6c 100644
--- a/src/main/cpp/xmllayout.cpp
+++ b/src/main/cpp/xmllayout.cpp
@@ -78,7 +78,8 @@
 	const spi::LoggingEventPtr& event,
 	Pool& p) const
 {
-	output.reserve(m_priv->expectedPatternLength + event->getMessage().size());
+	auto& lsMsg = event->getRenderedMessage();
+	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
 	output.append(LOG4CXX_STR("<log4j:event logger=\""));
 	Transform::appendEscapingTags(output, event->getLoggerName());
 	output.append(LOG4CXX_STR("\" timestamp=\""));
@@ -93,7 +94,7 @@
 	output.append(LOG4CXX_STR("<log4j:message><![CDATA["));
 	// Append the rendered message. Also make sure to escape any
 	// existing CDATA sections.
-	Transform::appendEscapingCDATA(output, event->getRenderedMessage());
+	Transform::appendEscapingCDATA(output, lsMsg);
 	output.append(LOG4CXX_STR("]]></log4j:message>"));
 	output.append(LOG4CXX_EOL);
 
diff --git a/src/main/include/log4cxx/helpers/asyncbuffer.h b/src/main/include/log4cxx/helpers/asyncbuffer.h
new file mode 100644
index 0000000..ebc01a0
--- /dev/null
+++ b/src/main/include/log4cxx/helpers/asyncbuffer.h
@@ -0,0 +1,274 @@
+/*
+ * 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_ASYNC_BUFFER_H
+#define LOG4CXX_ASYNC_BUFFER_H
+
+#include <log4cxx/helpers/messagebuffer.h>
+#include <functional>
+#include <vector>
+
+namespace LOG4CXX_NS
+{
+
+namespace helpers
+{
+
+/**
+ *   This class is used by the LOG4CXX_INFO_ASYNC and similar
+ *   macros to support insertion operators.
+ *   The class is not intended for use outside of that context.
+ */
+class LOG4CXX_EXPORT AsyncBuffer
+{
+public:
+	/** An empty buffer.
+	*/
+	AsyncBuffer();
+
+	/** A new buffer with the content of \c other
+	*/
+	AsyncBuffer(AsyncBuffer&& other);
+
+	/** Release resources.
+	*/
+	~AsyncBuffer();
+
+	/** Append a function to this buffer that will convert \c value to text.
+	 *   @param value type must be copy-constructable
+	 *   @return this buffer.
+	 */
+	template<typename T>
+	AsyncBuffer& operator<<(const T& value)
+	{
+		append([value](LogCharMessageBuffer& msgBuf)
+			{
+				msgBuf << value;
+			});
+		return *this;
+	}
+
+#ifdef __cpp_init_captures // C++ >= 14
+	/** Append a function to this buffer that will convert \c value to text.
+	 *   @param value type must be move-constructable
+	 *   @return this buffer.
+	 */
+	template<typename T>
+	AsyncBuffer& operator<<(const T&& rvalue)
+	{
+		append([value = std::move(rvalue)](LogCharMessageBuffer& msgBuf)
+			{
+				msgBuf << value;
+			});
+		return *this;
+	}
+#endif
+	/**
+	* Has no item been added to this?
+	*/
+	bool empty() const;
+
+	/**
+	* Add text version of buffered values to \c msg
+	*/
+	void renderMessage(LogCharMessageBuffer& msg);
+
+	/**
+	* Remove all message appenders
+	*/
+	void clear();
+
+private:
+	AsyncBuffer(const AsyncBuffer&) = delete;
+	AsyncBuffer& operator=(const AsyncBuffer&) = delete;
+
+	LOG4CXX_DECLARE_PRIVATE_MEMBER_PTR(Private, m_priv)
+	using MessageBufferAppender = std::function<void(LogCharMessageBuffer&)>;
+
+	/**
+	 *   Append \c function to this buffer.
+	 */
+	void append(const MessageBufferAppender& f);
+};
+
+} // namespace helpers
+} // namespace LOG4CXX_NS
+
+/** @addtogroup LoggingMacros Logging macros
+@{
+*/
+
+#if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 10000
+/**
+Add a new logging event containing \c message to attached appender(s) if \c logger is enabled for <code>DEBUG</code> events.
+
+\usage
+~~~{.cpp}
+LOG4CXX_DEBUG_ASYNC(m_log, "AddMesh:"
+	<< " name " << meshName
+	<< " type 0x" << std:: hex << traits.Type
+	<< " materialName " << meshObject.GetMaterialName()
+	<< " visible? " << traits.IsDefaultVisible
+	<< " at " << obj->getBoundingBox().getCenter()
+	<< " +/- " << obj->getBoundingBox().getHalfSize()
+	);
+~~~
+
+@param logger the logger that has the enabled status.
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+
+*/
+#define LOG4CXX_DEBUG_ASYNC(logger, message) do { \
+		if (LOG4CXX_UNLIKELY(::LOG4CXX_NS::Logger::isDebugEnabledFor(logger))) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf; \
+			logger->addDebugEvent(std::move(buf << message), LOG4CXX_LOCATION); }} while (0)
+#else
+#define LOG4CXX_DEBUG_ASYNC(logger, message)
+#endif
+
+#if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 5000
+/**
+Add a new logging event containing \c message to attached appender(s) if \c logger is enabled for <code>TRACE</code> events.
+
+\usage
+~~~{.cpp}
+    LOG4CXX_TRACE_ASYNC(m_log, "AddVertex:" << " at " << p << " n " << n << ' ' << color);
+~~~
+
+@param logger the logger that has the enabled status.
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOG4CXX_TRACE_ASYNC(logger, message) do { \
+		if (LOG4CXX_UNLIKELY(::LOG4CXX_NS::Logger::isTraceEnabledFor(logger))) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf; \
+			logger->addTraceEvent(std::move(buf << message), LOG4CXX_LOCATION); }} while (0)
+#else
+#define LOG4CXX_TRACE_ASYNC(logger, message)
+#endif
+
+#if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 20000
+/**
+Add a new logging event containing \c message to attached appender(s) if \c logger is enabled for <code>INFO</code> events.
+
+\usage
+~~~{.cpp}
+LOG4CXX_INFO_ASYNC(m_log, surface->GetName()
+	<< " successfully planned " << std::fixed << std::setprecision(1) << ((plannedArea  / (plannedArea + unplannedArea)) * 100.0) << "%"
+	<< " planned area " << std::fixed << std::setprecision(4) << plannedArea << "m^2"
+	<< " unplanned area " << unplannedArea << "m^2"
+	<< " planned segments " << surface->GetSegmentPlanCount() << " of " << surface->GetSegmentCount()
+	);
+~~~
+
+@param logger the logger that has the enabled status.
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOG4CXX_INFO_ASYNC(logger, message) do { \
+		if (::LOG4CXX_NS::Logger::isInfoEnabledFor(logger)) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf;\
+			logger->addInfoEvent(std::move(buf << message), LOG4CXX_LOCATION);\
+		}} while (0)
+
+#endif
+
+#else
+#define LOG4CXX_INFO_ASYNC(logger, message)
+#endif
+
+#if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 30000
+/**
+Add a new logging event containing \c message to attached appender(s) if \c logger is enabled for <code>WARN</code> events.
+
+\usage
+~~~{.cpp}
+catch (const std::exception& ex)
+{
+    LOG4CXX_WARN_ASYNC(m_log, ex.what() << ": in " << m_task->GetParamFilePath());
+}
+~~~
+
+@param logger the logger to be used.
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOG4CXX_WARN_ASYNC(logger, message) do { \
+		if (::LOG4CXX_NS::Logger::isWarnEnabledFor(logger)) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf; \
+			logger->addWarnEvent(std::move(buf << message), LOG4CXX_LOCATION); }} while (0)
+#else
+#define LOG4CXX_WARN_ASYNC(logger, message)
+#endif
+
+#if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 40000
+/**
+Add a new logging event containing \c message to attached appender(s) if \c logger is enabled for <code>ERROR</code> events.
+
+\usage
+~~~{.cpp}
+catch (std::exception& ex)
+{
+	LOG4CXX_ERROR_ASYNC(m_log, ex.what() << " in AddScanData");
+}
+~~~
+
+@param logger the logger to be used.
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOG4CXX_ERROR_ASYNC(logger, message) do { \
+		if (::LOG4CXX_NS::Logger::isErrorEnabledFor(logger)) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf; \
+			logger->addErrorEvent(std::move(buf << message), LOG4CXX_LOCATION); }} while (0)
+
+/**
+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.
+
+@param logger the logger to be used.
+@param condition condition
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOG4CXX_ASSERT_ASYNC(logger, condition, message) do { \
+		if (!(condition) && ::LOG4CXX_NS::Logger::isErrorEnabledFor(logger)) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf; \
+			LOG4CXX_STACKTRACE \
+			logger->addErrorEvent(std::move(buf << message), LOG4CXX_LOCATION); }} while (0)
+
+#else
+#define LOG4CXX_ERROR_ASYNC(logger, message)
+#define LOG4CXX_ASSERT_ASYNC(logger, condition, message)
+#endif
+
+#if !defined(LOG4CXX_THRESHOLD) || LOG4CXX_THRESHOLD <= 50000
+/**
+Add a new logging event containing \c message to attached appender(s) if \c logger is enabled for <code>FATAL</code> events.
+
+\usage
+~~~{.cpp}
+LOG4CXX_FATAL_ASYNC(m_log, m_renderSystem->getName() << " is not supported");
+~~~
+
+@param logger the logger to be used.
+@param message a valid r-value expression of an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOG4CXX_FATAL_ASYNC(logger, message) do { \
+		if (::LOG4CXX_NS::Logger::isFatalEnabledFor(logger)) {\
+			::LOG4CXX_NS::helpers::AsyncBuffer buf; \
+			logger->addFatalEvent(std::move(buf << message), LOG4CXX_LOCATION); }} while (0)
+
+#else
+#define LOG4CXX_FATAL_ASYNC(logger, message)
+#endif
+
+/**@} Logging macro group */
diff --git a/src/main/include/log4cxx/logger.h b/src/main/include/log4cxx/logger.h
index 9118150..1241d0c 100644
--- a/src/main/include/log4cxx/logger.h
+++ b/src/main/include/log4cxx/logger.h
@@ -23,6 +23,7 @@
 #include <log4cxx/helpers/pool.h>
 #include <log4cxx/spi/location/locationinfo.h>
 #include <log4cxx/helpers/resourcebundle.h>
+#include <log4cxx/helpers/asyncbuffer.h>
 #include <log4cxx/helpers/messagebuffer.h>
 
 namespace LOG4CXX_NS
@@ -507,6 +508,16 @@
 			, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new \c level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param level The logging event level.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addEvent(const LevelPtr& level, helpers::AsyncBuffer&& messageAppender
+			, const spi::LocationInfo& sourceLocation = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new fatal level logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param message The text to add to the logging event.
@@ -515,6 +526,14 @@
 		void addFatalEvent(std::string&& message, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new FATAL level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addFatalEvent(helpers::AsyncBuffer&& messageAppender, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new error level logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param message The text to add to the logging event.
@@ -523,6 +542,14 @@
 		void addErrorEvent(std::string&& message, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new ERROR level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addErrorEvent(helpers::AsyncBuffer&& messageAppender, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new warning level logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param message The text to add to the logging event.
@@ -531,6 +558,14 @@
 		void addWarnEvent(std::string&& message, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new WARN level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addWarnEvent(helpers::AsyncBuffer&& messageAppender, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new info level logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param message The text to add to the logging event.
@@ -539,6 +574,14 @@
 		void addInfoEvent(std::string&& message, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new INFO level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addInfoEvent(helpers::AsyncBuffer&& messageAppender, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new debug level logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param message The text to add to the logging event.
@@ -547,6 +590,14 @@
 		void addDebugEvent(std::string&& message, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new DEBUG level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addDebugEvent(helpers::AsyncBuffer&& messageAppender, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new trace level logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param message The text to add to the logging event.
@@ -555,6 +606,14 @@
 		void addTraceEvent(std::string&& message, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
 
 		/**
+		Add to attached appender(s) a new TRACE level LoggingEvent which was requested at \c sourceLocation where the message is built asynchronously by \c messageAppender
+		without further checks.
+		@param message The text to add to the logging event.
+		@param location The source code location of the logging request.
+		*/
+		void addTraceEvent(helpers::AsyncBuffer&& messageAppender, const spi::LocationInfo& location = spi::LocationInfo::getLocationUnavailable()) const;
+
+		/**
 		Add a new logging event containing \c message and \c location to attached appender(s)
 		without further checks.
 		@param level The logging event level.
diff --git a/src/main/include/log4cxx/spi/loggingevent.h b/src/main/include/log4cxx/spi/loggingevent.h
index cd0b81c..e40c8a5 100644
--- a/src/main/include/log4cxx/spi/loggingevent.h
+++ b/src/main/include/log4cxx/spi/loggingevent.h
@@ -87,6 +87,21 @@
 			, const LocationInfo& location
 			);
 
+		/**
+		An event composed using the supplied parameters.
+
+		@param logger The logger used to make the logging request.
+		@param level The severity of this event.
+		@param location The source code location of the logging request.
+		@param messageAppender  Builds the message text to add to this event.
+		*/
+		LoggingEvent
+			( const LogString& logger
+			, const LevelPtr& level
+			, const LocationInfo& location
+			, helpers::AsyncBuffer&& messageAppender
+			);
+
 		~LoggingEvent();
 
 		/** The severity level of the logging request that generated this event. */
@@ -195,6 +210,11 @@
 		*/
 		void setProperty(const LogString& key, const LogString& value);
 
+		/**
+		* Use the renderers to construct the message
+		*/
+		void renderMessage();
+
 	private:
 		LOG4CXX_DECLARE_PRIVATE_MEMBER_PTR(LoggingEventPrivate, m_priv)
 
diff --git a/src/site/markdown/change-report-gh.md b/src/site/markdown/change-report-gh.md
index 9d9d93c..4b8b535 100644
--- a/src/site/markdown/change-report-gh.md
+++ b/src/site/markdown/change-report-gh.md
@@ -61,6 +61,8 @@
    \[[#520](https://github.com/apache/logging-log4cxx/pull/520)\]
 * 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)\]
 
 The following issues have been addressed:
 
diff --git a/src/site/markdown/performance.md b/src/site/markdown/performance.md
index 5672350..608e5cc 100644
--- a/src/site/markdown/performance.md
+++ b/src/site/markdown/performance.md
@@ -98,8 +98,10 @@
 | 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 | 1663 ns | 1663 ns | 421796 |
-| Async, Sending int+10float using FMT/threads:6 | 1286 ns | 7368 ns | 88308 |
+| 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 |
 | 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 |
@@ -137,5 +139,6 @@
 from the calling thread to the background thread.
 
 When logging floating point values from a high priority thread,
+and you cannot use a background thread to format and write the log data,
 the LOG4CXX_[level]_FMT series of macros impose the least overhead.
 
diff --git a/src/test/cpp/asyncappendertestcase.cpp b/src/test/cpp/asyncappendertestcase.cpp
index 28b2c9e..42a2eaa 100644
--- a/src/test/cpp/asyncappendertestcase.cpp
+++ b/src/test/cpp/asyncappendertestcase.cpp
@@ -104,13 +104,14 @@
 	LoggerInstancePtr logger{ "LoggingVectorAppender" };
 	void append(const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& p) override
 	{
+		auto lsMsg = event->getRenderedMessage();
 		VectorAppender::append(event, p);
-		if (event->getMessage() == LOG4CXX_STR("Hello, World"))
+		if (LogString::npos != lsMsg.find(LOG4CXX_STR("World")))
 		{
-			LOG4CXX_LOGLS(logger, Level::getError(), LOG4CXX_STR("Some example error"));
-			LOG4CXX_LOGLS(logger, Level::getWarn(), LOG4CXX_STR("Some example warning"));
-			LOG4CXX_LOGLS(logger, Level::getInfo(), LOG4CXX_STR("Some information"));
-			LOG4CXX_LOGLS(logger, Level::getDebug(), LOG4CXX_STR("Some detailed data"));
+			LOG4CXX_LOGLS(logger, Level::getError(), LOG4CXX_STR("Some appender error"));
+			LOG4CXX_LOGLS(logger, Level::getWarn(), LOG4CXX_STR("Some appender warning"));
+			LOG4CXX_LOGLS(logger, Level::getInfo(), LOG4CXX_STR("Some appender information"));
+			LOG4CXX_LOGLS(logger, Level::getDebug(), LOG4CXX_STR("Some appender detailed data"));
 		}
 	}
 };
@@ -129,6 +130,7 @@
 
 		LOGUNIT_TEST(closeTest);
 		LOGUNIT_TEST(test2);
+		LOGUNIT_TEST(testAutoMessageBufferSelection);
 		LOGUNIT_TEST(testEventFlush);
 		LOGUNIT_TEST(testMultiThread);
 		LOGUNIT_TEST(testBadAppender);
@@ -205,6 +207,37 @@
 			LOGUNIT_ASSERT(vectorAppender->isClosed());
 		}
 
+		// Test behaviour when logging with a char type that is not logchar
+		void testAutoMessageBufferSelection()
+		{
+			VectorAppenderPtr vectorAppender;
+			auto r = LogManager::getLoggerRepository();
+			r->ensureIsConfigured([r, &vectorAppender]()
+			{
+				vectorAppender = std::make_shared<VectorAppender>();
+				r->getRootLogger()->addAppender(vectorAppender);
+			});
+			auto root = r->getRootLogger();
+
+			int expectedMessageCount = 1;
+#ifdef LOG4CXX_XXXX_ASYNC_MACROS_WORK_WITH_ANY_CHAR_TYPE
+			++expectedMessageCount
+#if LOG4CXX_LOGCHAR_IS_UTF8
+			LOG4CXX_INFO_ASYNC(root, L"Some wide string " << 42);
+#else
+			LOG4CXX_INFO_ASYNC(root, "Some narrow string " << 42);
+#endif
+#endif // LOG4CXX_XXXX_ASYNC_MACROS_WORK_WITH_ANY_CHAR_TYPE
+
+#if LOG4CXX_LOGCHAR_IS_UTF8
+			LOG4CXX_INFO(root, L"Some wide string " << 42);
+#else
+			LOG4CXX_INFO(root, "Some narrow string " << 42);
+#endif
+			auto& v = vectorAppender->getVector();
+			LOGUNIT_ASSERT_EQUAL(expectedMessageCount, int(v.size()));
+		}
+
 		// this test checks all messages are delivered when an AsyncAppender is closed
 		void testEventFlush()
 		{
@@ -219,7 +252,7 @@
 
 			for (size_t i = 0; i < LEN; i++)
 			{
-				LOG4CXX_DEBUG(root, "message" << i);
+				LOG4CXX_DEBUG_ASYNC(root, "message" << i);
 			}
 
 			asyncAppender->close();
@@ -232,7 +265,7 @@
 			{
 				LogString m(LOG4CXX_STR("message"));
 				StringHelper::toString(i, p, m);
-				LOGUNIT_ASSERT(v[i]->getMessage() == m);
+				LOGUNIT_ASSERT(v[i]->getRenderedMessage() == m);
 			}
 			LOGUNIT_ASSERT_EQUAL(true, vectorAppender->isClosed());
 		}
@@ -257,7 +290,7 @@
 				{
 					for (int i = 0; i < LEN; i++)
 					{
-						LOG4CXX_DEBUG(root, "message" << i);
+						LOG4CXX_DEBUG_ASYNC(root, "message" << i);
 					}
 				});
 			}
@@ -277,7 +310,7 @@
 			std::vector<int> msgCount(LEN, 0);
 			for (auto m : v)
 			{
-				auto i = StringHelper::toInt(m->getMessage().substr(7));
+				auto i = StringHelper::toInt(m->getRenderedMessage().substr(7));
 				LOGUNIT_ASSERT(0 <= i);
 				LOGUNIT_ASSERT(i < LEN);
 				++msgCount[i];
@@ -340,17 +373,17 @@
 			async->activateOptions(p);
 			auto rootLogger = Logger::getRootLogger();
 			rootLogger->addAppender(async);
-			LOG4CXX_INFO(rootLogger, "Hello, World"); // This causes the dispatch thread creation
+			LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World"); // This causes the dispatch thread creation
 			std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); // Wait for the dispatch thread  to be ready
 			{
 				std::lock_guard<std::mutex> sync(blockableAppender->getBlocker());
 
 				for (int i = 0; i < 140; i++)
 				{
-					LOG4CXX_INFO(rootLogger, "Hello, World");
+					LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World " << i);
 				}
 
-				LOG4CXX_ERROR(rootLogger, "That's all folks.");
+				LOG4CXX_ERROR_ASYNC(rootLogger, "That's all folks.");
 			}
 			async->close();
 
@@ -359,14 +392,14 @@
 			LOGUNIT_ASSERT(!events.empty());
 			LOGUNIT_ASSERT(events.size() <= 142);
 			LoggingEventPtr initialEvent = events.front();
-			LOGUNIT_ASSERT(initialEvent->getMessage() == LOG4CXX_STR("Hello, World"));
+			LOGUNIT_ASSERT(initialEvent->getRenderedMessage() == LOG4CXX_STR("Hello, World"));
 			std::map<LevelPtr, int> levelCount;
 			int discardMessageCount{ 0 };
 			LoggingEventPtr discardEvent;
 			for (auto& e : events)
 			{
 				++levelCount[e->getLevel()];
-				if (e->getMessage().substr(0, 10) == LOG4CXX_STR("Discarded "))
+				if (e->getRenderedMessage().substr(0, 10) == LOG4CXX_STR("Discarded "))
 				{
 					++discardMessageCount;
 					discardEvent = e;
@@ -414,45 +447,46 @@
 			async->activateOptions(p);
 			auto rootLogger = Logger::getRootLogger();
 			rootLogger->addAppender(async);
-			LOG4CXX_INFO(rootLogger, "Hello, World"); // This causes the dispatch thread creation
+			LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World"); // This causes the dispatch thread creation
 			std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); // Wait for the dispatch thread  to be ready
 			for (int i = 0; i < 10; i++)
 			{
-				LOG4CXX_INFO(rootLogger, "Hello, World");
+				LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World " << i);
 			}
-			LOG4CXX_INFO(rootLogger, "Bye bye World");
+			LOG4CXX_INFO_ASYNC(rootLogger, "Bye bye World");
 			std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); // Wait for the dispatch thread take the above events
 			async->close();
 			auto& events = loggingAppender->getVector();
 			std::map<LevelPtr, int> levelCount;
 			int discardMessageCount{ 0 };
+			int eventCount[] = { 0, 0 };
 			for (auto& e : events)
 			{
 				++levelCount[e->getLevel()];
-				auto message = e->getMessage();
+				auto message = e->getRenderedMessage();
+				LogLog::debug(message);
+				auto isAppenderMessage = (message.npos == message.find(LOG4CXX_STR("World")));
+				++eventCount[isAppenderMessage];
 				if (message.substr(0, 10) == LOG4CXX_STR("Discarded "))
 				{
 					++discardMessageCount;
-					auto isAppenderMessage = (message.npos == message.find(LOG4CXX_STR("World")));
 					LOGUNIT_ASSERT(isAppenderMessage);
 				}
 			}
 			if (helpers::LogLog::isDebugEnabled())
 			{
 				LogString msg{ LOG4CXX_STR("messageCounts:") };
-				for (auto& item : levelCount)
-				{
-					msg += LOG4CXX_STR(" ");
-					msg += item.first->toString();
-					msg += LOG4CXX_STR(" ");
-					StringHelper::toString(item.second, p, msg);
-				}
-				msg += LOG4CXX_STR(" ");
-				msg += LOG4CXX_STR("Discarded ");
+				msg += LOG4CXX_STR(" nonAppender ");
+				StringHelper::toString(eventCount[0], p, msg);
+				msg += LOG4CXX_STR(" appender ");
+				StringHelper::toString(eventCount[1], p, msg);
+				msg += LOG4CXX_STR(" discard ");
 				StringHelper::toString(discardMessageCount, p, msg);
-				helpers::LogLog::debug(msg);
+				LogLog::debug(msg);
 			}
 			LOGUNIT_ASSERT(12 < events.size());
+			// A race condition in AsyncAppender can result in a lost message when the dispatch thread is logging events
+			LOGUNIT_ASSERT(10 <= eventCount[0]);
 		}
 
 #if LOG4CXX_HAS_DOMCONFIGURATOR
@@ -479,7 +513,7 @@
 
 			for (size_t i = 0; i < LEN; i++)
 			{
-				LOG4CXX_DEBUG(root, "message" << i);
+				LOG4CXX_DEBUG_ASYNC(root, "message" << i);
 			}
 
 			asyncAppender->close();
diff --git a/src/test/cpp/benchmark/benchmark.cpp b/src/test/cpp/benchmark/benchmark.cpp
index ac79283..3de48d5 100644
--- a/src/test/cpp/benchmark/benchmark.cpp
+++ b/src/test/cpp/benchmark/benchmark.cpp
@@ -132,7 +132,7 @@
 class benchmarker : public ::benchmark::Fixture
 {
 public: // Attributes
-	LoggerPtr m_logger = getLogger();
+	LoggerPtr m_appender = getNullWriter();
 	LoggerPtr m_asyncLogger = getAsyncLogger();
 	LoggerPtr m_fileLogger = getFileLogger();
 	LoggerPtr m_JSONLogger = getJSONFileLogger();
@@ -176,7 +176,7 @@
 			});
 	}
 
-	static LoggerPtr getLogger(const LogString& pattern = LogString())
+	static LoggerPtr getNullWriter(const LogString& pattern = LogString())
 	{
 		static struct initializer
 		{
@@ -303,10 +303,10 @@
 
 BENCHMARK_DEFINE_F(benchmarker, logDisabledTrace)(benchmark::State& state)
 {
-	m_logger->setLevel(Level::getDebug());
+	m_appender->setLevel(Level::getDebug());
 	for (auto _ : state)
 	{
-		LOG4CXX_TRACE( m_logger, LOG4CXX_STR("Hello: static string message"));
+		LOG4CXX_TRACE( m_appender, LOG4CXX_STR("Hello: static string message"));
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logDisabledTrace)->Name("Testing disabled logging request")->MinWarmUpTime(benchmarker::warmUpSeconds());
@@ -314,10 +314,10 @@
 
 BENCHMARK_DEFINE_F(benchmarker, logShortString)(benchmark::State& state)
 {
-	m_logger->setLevel(Level::getInfo());
+	m_appender->setLevel(Level::getInfo());
 	for (auto _ : state)
 	{
-		LOG4CXX_INFO(m_logger, LOG4CXX_STR("Hello"));
+		LOG4CXX_INFO(m_appender, LOG4CXX_STR("Hello"));
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logShortString)->Name("Appending 5 char string using MessageBuffer, pattern: %m%n");
@@ -325,10 +325,10 @@
 
 BENCHMARK_DEFINE_F(benchmarker, logLongString)(benchmark::State& state)
 {
-	m_logger->setLevel(Level::getInfo());
+	m_appender->setLevel(Level::getInfo());
 	for (auto _ : state)
 	{
-		LOG4CXX_INFO( m_logger, LOG4CXX_STR("Hello: this is a long static string message"));
+		LOG4CXX_INFO( m_appender, LOG4CXX_STR("Hello: this is a long static string message"));
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logLongString)->Name("Appending 49 char string using MessageBuffer, pattern: %m%n");
@@ -339,7 +339,7 @@
 	int x = 0;
 	for (auto _ : state)
 	{
-		LOG4CXX_INFO( m_logger, "Hello: message number " << ++x);
+		LOG4CXX_INFO( m_appender, "Hello: message number " << ++x);
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logIntValueMessageBuffer)->Name("Appending int value using MessageBuffer, pattern: %m%n");
@@ -351,7 +351,7 @@
 	for (auto _ : state)
 	{
 		auto f = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
-		LOG4CXX_INFO( m_logger, "Hello: message number " << ++x
+		LOG4CXX_INFO( m_appender, "Hello: message number " << ++x
 			<< " pseudo-random float " << std::setprecision(3) << std::fixed << f);
 	}
 }
@@ -398,7 +398,7 @@
 {
     auto args_tuple = std::make_tuple(std::move(args)...);
 	LogString conversionPattern = std::get<0>(args_tuple);
-	auto logger = benchmarker::getLogger(conversionPattern);
+	auto logger = benchmarker::getNullWriter(conversionPattern);
 	int x = 0;
 	for (auto _ : state)
 	{
@@ -413,7 +413,7 @@
 {
 	for (auto _ : state)
 	{
-		LOG4CXX_INFO_FMT(m_logger, "Hello: this is a long static string message", 0);
+		LOG4CXX_INFO_FMT(m_appender, "Hello: this is a long static string message", 0);
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logLongStringFMT)->Name("Appending 49 char string using FMT, pattern: %m%n");
@@ -424,7 +424,7 @@
 	int x = 0;
 	for (auto _ : state)
 	{
-		LOG4CXX_INFO_FMT(m_logger, "Hello: msg number {}", ++x);
+		LOG4CXX_INFO_FMT(m_appender, "Hello: msg number {}", ++x);
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logIntValueFMT)->Name("Appending int value using FMT, pattern: %m%n");
@@ -436,7 +436,7 @@
 	for (auto _ : state)
 	{
 		auto f = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
-		LOG4CXX_INFO_FMT(m_logger, "Hello: msg number {} pseudo-random float {:.3f}", ++x, f);
+		LOG4CXX_INFO_FMT(m_appender, "Hello: msg number {} pseudo-random float {:.3f}", ++x, f);
 	}
 }
 BENCHMARK_REGISTER_F(benchmarker, logIntPlusFloatValueFMT)->Name("Appending int+float using FMT, pattern: %m%n");
@@ -459,7 +459,7 @@
 		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
 		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
 		};
-		LOG4CXX_INFO_FMT(m_logger, "Hello: msg number {} pseudo-random float {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}"
+		LOG4CXX_INFO_FMT(m_appender, "Hello: msg number {} pseudo-random float {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}"
 			, ++x
 			, f[0]
 			, f[1]
@@ -476,7 +476,6 @@
 }
 BENCHMARK_REGISTER_F(benchmarker, logIntPlus10FloatValueFMT)->Name("Appending int+10float using FMT, pattern: %m%n");
 BENCHMARK_REGISTER_F(benchmarker, logIntPlus10FloatValueFMT)->Name("Appending int+10float using FMT, pattern: %m%n")->Threads(benchmarker::threadCount());
-#endif
 
 BENCHMARK_DEFINE_F(benchmarker, asyncIntPlus10FloatValueFmtBuffer)(benchmark::State& state)
 {
@@ -495,7 +494,7 @@
 		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
 		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
 		};
-		LOG4CXX_INFO_FMT(m_logger, "Hello: msg number {} pseudo-random float {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}"
+		LOG4CXX_INFO_FMT(m_asyncLogger, "Hello: msg number {} pseudo-random float {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}"
 			, ++x
 			, f[0]
 			, f[1]
@@ -512,6 +511,42 @@
 }
 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());
+#endif
+
+BENCHMARK_DEFINE_F(benchmarker, asyncIntPlus10FloatAsyncBuffer)(benchmark::State& state)
+{
+	int x = 0;
+	for (auto _ : state)
+	{
+		float f[] =
+		{ static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		, static_cast<float>(rand()) / static_cast<float>(RAND_MAX)
+		};
+		LOG4CXX_INFO_ASYNC(m_asyncLogger, "Hello: message number " << ++x
+			<< " pseudo-random float" << std::setprecision(3) << std::fixed
+			<< ' ' << f[0]
+			<< ' ' << f[1]
+			<< ' ' << f[2]
+			<< ' ' << f[3]
+			<< ' ' << f[4]
+			<< ' ' << f[5]
+			<< ' ' << f[6]
+			<< ' ' << f[7]
+			<< ' ' << f[8]
+			<< ' ' << f[9]
+			);
+	}
+}
+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_DEFINE_F(benchmarker, fileIntPlusFloatValueMessageBuffer)(benchmark::State& state)
 {