Merge branch 'master' of https://gitbox.apache.org/repos/asf/logging-log4cxx
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 92d8b47..2113a81 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -106,6 +106,7 @@
 			<action issue="LOGCXX-262" type="fix">socketappendertestcase and xmlsocketappendertestcase not run</action>
 			<action issue="LOGCXX-249" type="fix">Console appender crashes if layout is not set</action>
 
+			<action type="add">JSONLayout (https://github.com/apache/logging-log4cxx/pull/13)</action>
 			<action type="update">Behavior of StringHelper::startsWith and endsWith synced</action>
 			<action type="update">Documented C (class) and M (method) log format keywords.</action>
 			<action type="add">Locationinfo for Borland C++ Builder and successors improved</action>
diff --git a/src/main/cpp/Makefile.am b/src/main/cpp/Makefile.am
index 6da89cb..236a9a6 100644
--- a/src/main/cpp/Makefile.am
+++ b/src/main/cpp/Makefile.am
@@ -72,6 +72,7 @@
         inputstreamreader.cpp \
         integer.cpp \
         integerpatternconverter.cpp \
+        jsonlayout.cpp \
         layout.cpp\
         level.cpp \
         levelmatchfilter.cpp \
diff --git a/src/main/cpp/jsonlayout.cpp b/src/main/cpp/jsonlayout.cpp
new file mode 100644
index 0000000..a2e72c3
--- /dev/null
+++ b/src/main/cpp/jsonlayout.cpp
@@ -0,0 +1,389 @@
+/*
+ * 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/logstring.h>
+#include <log4cxx/jsonlayout.h>
+#include <log4cxx/spi/loggingevent.h>
+#include <log4cxx/level.h>
+#include <log4cxx/helpers/optionconverter.h>
+#include <log4cxx/helpers/iso8601dateformat.h>
+#include <log4cxx/helpers/stringhelper.h>
+#include <log4cxx/helpers/transcoder.h>
+
+#include <apr_time.h>
+#include <apr_strings.h>
+#include <string.h>
+
+using namespace log4cxx;
+using namespace log4cxx::helpers;
+using namespace log4cxx::spi;
+
+IMPLEMENT_LOG4CXX_OBJECT(JSONLayout)
+
+
+JSONLayout::JSONLayout() :
+	locationInfo(false),
+	prettyPrint(false),
+	dateFormat(),
+	ppIndentL1(LOG4CXX_STR("  ")),
+	ppIndentL2(LOG4CXX_STR("    "))
+{
+}
+
+
+void JSONLayout::setOption(const LogString& option, const LogString& value)
+{
+	if (StringHelper::equalsIgnoreCase(option,
+			LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
+	{
+		setLocationInfo(OptionConverter::toBoolean(value, false));
+	}
+
+	if (StringHelper::equalsIgnoreCase(option,
+			LOG4CXX_STR("PRETTYPRINT"), LOG4CXX_STR("prettyprint")))
+	{
+		setPrettyPrint(OptionConverter::toBoolean(value, false));
+	}
+}
+void JSONLayout::format(LogString& output,
+	const spi::LoggingEventPtr& event,
+	Pool& p) const
+{
+	output.append(LOG4CXX_STR("{"));
+	output.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		output.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("timestamp"));
+	output.append(LOG4CXX_STR(": "));
+	LogString timestamp;
+	dateFormat.format(timestamp, event->getTimeStamp(), p);
+	appendQuotedEscapedString(output, timestamp);
+	output.append(LOG4CXX_STR(","));
+	output.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		output.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("level"));
+	output.append(LOG4CXX_STR(": "));
+	LogString level;
+	event->getLevel()->toString(level);
+	appendQuotedEscapedString(output, level);
+	output.append(LOG4CXX_STR(","));
+	output.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		output.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("logger"));
+	output.append(LOG4CXX_STR(": "));
+	appendQuotedEscapedString(output, event->getLoggerName());
+	output.append(LOG4CXX_STR(","));
+	output.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		output.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("message"));
+	output.append(LOG4CXX_STR(": "));
+	appendQuotedEscapedString(output, event->getMessage());
+
+	appendSerializedMDC(output, event);
+	appendSerializedNDC(output, event);
+
+	if (locationInfo)
+	{
+		output.append(LOG4CXX_STR(","));
+		output.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+		appendSerializedLocationInfo(output, event, p);
+	}
+
+	output.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+	output.append(LOG4CXX_STR("}"));
+	output.append(LOG4CXX_EOL);
+}
+
+void JSONLayout::appendQuotedEscapedString(LogString& buf,
+	const LogString& input) const
+{
+	/* add leading quote */
+	buf.push_back(0x22);
+
+	logchar specialChars[] =
+	{
+		0x08,   /* \b backspace         */
+		0x09,   /* \t tab               */
+		0x0a,   /* \n newline           */
+		0x0c,   /* \f form feed         */
+		0x0d,   /* \r carriage return   */
+		0x22,   /* \" double quote      */
+		0x5c    /* \\ backslash         */
+	};
+
+	size_t start = 0;
+	size_t found = input.find_first_of(specialChars, start);
+
+	while (found != LogString::npos)
+	{
+		if (found > start)
+		{
+			buf.append(input, start, found - start);
+		}
+
+		switch (input[found])
+		{
+			case 0x08:
+				/* \b backspace */
+				buf.push_back(0x5c);
+				buf.push_back('b');
+				break;
+
+			case 0x09:
+				/* \t tab */
+				buf.push_back(0x5c);
+				buf.push_back('t');
+				break;
+
+			case 0x0a:
+				/* \n newline */
+				buf.push_back(0x5c);
+				buf.push_back('n');
+				break;
+
+			case 0x0c:
+				/* \f form feed */
+				buf.push_back(0x5c);
+				buf.push_back('f');
+				break;
+
+			case 0x0d:
+				/* \r carriage return */
+				buf.push_back(0x5c);
+				buf.push_back('r');
+				break;
+
+			case 0x22:
+				/* \" double quote */
+				buf.push_back(0x5c);
+				buf.push_back(0x22);
+				break;
+
+			case 0x5c:
+				/* \\ backslash */
+				buf.push_back(0x5c);
+				buf.push_back(0x5c);
+				break;
+
+			default:
+				buf.push_back(input[found]);
+				break;
+		}
+
+		start = found + 1;
+
+		if (found < input.size())
+		{
+			found = input.find_first_of(specialChars, start);
+		}
+		else
+		{
+			found = LogString::npos;
+		}
+	}
+
+	if (start < input.size())
+	{
+		buf.append(input, start, input.size() - start);
+	}
+
+	/* add trailing quote */
+	buf.push_back(0x22);
+}
+
+void JSONLayout::appendSerializedMDC(LogString& buf,
+	const LoggingEventPtr& event) const
+{
+	LoggingEvent::KeySet keys = event->getMDCKeySet();
+
+	if (keys.empty())
+	{
+		return;
+	}
+
+	buf.append(LOG4CXX_STR(","));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("context_map"));
+	buf.append(LOG4CXX_STR(": {"));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	for (LoggingEvent::KeySet::iterator it = keys.begin();
+		it != keys.end(); ++it)
+	{
+		if (prettyPrint)
+		{
+			buf.append(ppIndentL2);
+		}
+
+		appendQuotedEscapedString(buf, *it);
+		buf.append(LOG4CXX_STR(": "));
+		LogString value;
+		event->getMDC(*it, value);
+		appendQuotedEscapedString(buf, value);
+
+		/* if this isn't the last k:v pair, we need a comma */
+		if (it + 1 != keys.end())
+		{
+			buf.append(LOG4CXX_STR(","));
+			buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+		}
+		else
+		{
+			buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+		}
+	}
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL1);
+	}
+
+	buf.append(LOG4CXX_STR("}"));
+}
+
+void JSONLayout::appendSerializedNDC(LogString& buf,
+	const LoggingEventPtr& event) const
+{
+	LogString ndcVal;
+
+	if (!event->getNDC(ndcVal))
+	{
+		return;
+	}
+
+	buf.append(LOG4CXX_STR(","));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("context_stack"));
+	buf.append(LOG4CXX_STR(": ["));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, ndcVal);
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL1);
+	}
+
+	buf.append(LOG4CXX_STR("]"));
+}
+
+void JSONLayout::appendSerializedLocationInfo(LogString& buf,
+	const LoggingEventPtr& event, Pool& p) const
+{
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL1);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("location_info"));
+	buf.append(LOG4CXX_STR(": {"));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+	const LocationInfo& locInfo = event->getLocationInformation();
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("file"));
+	buf.append(LOG4CXX_STR(": "));
+	LOG4CXX_DECODE_CHAR(fileName, locInfo.getFileName());
+	appendQuotedEscapedString(buf, fileName);
+	buf.append(LOG4CXX_STR(","));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("line"));
+	buf.append(LOG4CXX_STR(": "));
+	LogString lineNumber;
+	StringHelper::toString(locInfo.getLineNumber(), p, lineNumber);
+	appendQuotedEscapedString(buf, lineNumber);
+	buf.append(LOG4CXX_STR(","));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("class"));
+	buf.append(LOG4CXX_STR(": "));
+	LOG4CXX_DECODE_CHAR(className, locInfo.getClassName());
+	appendQuotedEscapedString(buf, className);
+	buf.append(LOG4CXX_STR(","));
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("method"));
+	buf.append(LOG4CXX_STR(": "));
+	LOG4CXX_DECODE_CHAR(methodName, locInfo.getMethodName());
+	appendQuotedEscapedString(buf, methodName);
+	buf.append(prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (prettyPrint)
+	{
+		buf.append(ppIndentL1);
+	}
+
+	buf.append(LOG4CXX_STR("}"));
+}
+
diff --git a/src/main/include/log4cxx/Makefile.am b/src/main/include/log4cxx/Makefile.am
index 75ef01c..f629b45 100644
--- a/src/main/include/log4cxx/Makefile.am
+++ b/src/main/include/log4cxx/Makefile.am
@@ -35,6 +35,7 @@
     $(top_srcdir)/src/main/include/log4cxx/file.h \
     $(top_srcdir)/src/main/include/log4cxx/hierarchy.h \
     $(top_srcdir)/src/main/include/log4cxx/htmllayout.h \
+    $(top_srcdir)/src/main/include/log4cxx/jsonlayout.h \
     $(top_srcdir)/src/main/include/log4cxx/layout.h \
     $(top_srcdir)/src/main/include/log4cxx/level.h \
     $(top_srcdir)/src/main/include/log4cxx/logger.h \
diff --git a/src/main/include/log4cxx/jsonlayout.h b/src/main/include/log4cxx/jsonlayout.h
new file mode 100644
index 0000000..dd997d1
--- /dev/null
+++ b/src/main/include/log4cxx/jsonlayout.h
@@ -0,0 +1,139 @@
+/*
+ * 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_JSON_LAYOUT_H
+#define _LOG4CXX_JSON_LAYOUT_H
+
+#include <log4cxx/layout.h>
+#include <log4cxx/helpers/iso8601dateformat.h>
+#include <log4cxx/spi/loggingevent.h>
+
+
+
+namespace log4cxx
+{
+/**
+This layout outputs events in a JSON dictionary.
+*/
+class LOG4CXX_EXPORT JSONLayout : public Layout
+{
+	private:
+		// Print no location info by default
+		bool locationInfo; //= false
+		bool prettyPrint; //= false
+
+		helpers::ISO8601DateFormat dateFormat;
+
+	protected:
+
+		LogString ppIndentL1;
+		LogString ppIndentL2;
+
+		void appendQuotedEscapedString(LogString& buf, const LogString& input) const;
+		void appendSerializedMDC(LogString& buf,
+			const spi::LoggingEventPtr& event) const;
+		void appendSerializedNDC(LogString& buf,
+			const spi::LoggingEventPtr& event) const;
+		void appendSerializedLocationInfo(LogString& buf,
+			const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& p) const;
+
+	public:
+		DECLARE_LOG4CXX_OBJECT(JSONLayout)
+		BEGIN_LOG4CXX_CAST_MAP()
+		LOG4CXX_CAST_ENTRY(JSONLayout)
+		LOG4CXX_CAST_ENTRY_CHAIN(Layout)
+		END_LOG4CXX_CAST_MAP()
+
+		JSONLayout();
+
+		/**
+		The <b>LocationInfo</b> option takes a boolean value. By
+		default, it is set to false which means there will be no location
+		information output by this layout. If the the option is set to
+		true, then the file name and line number of the statement
+		at the origin of the log statement will be output.
+		*/
+		inline void setLocationInfo(bool locationInfoFlag)
+		{
+			this->locationInfo = locationInfoFlag;
+		}
+
+
+		/**
+		Returns the current value of the <b>LocationInfo</b> option.
+		*/
+		inline bool getLocationInfo() const
+		{
+			return locationInfo;
+		}
+
+		/**
+		The <b>PrettyPrint</b> option takes a boolean value. By
+		default, it is set to false which means output by this layout will
+		be one line per log event.  If the option is set to true, then
+		then each log event will produce multiple lines, each indented
+		for readability.
+		*/
+		inline void setPrettyPrint(bool prettyPrintFlag)
+		{
+			this->prettyPrint = prettyPrintFlag;
+		}
+
+		/**
+		Returns the current value of the <b>PrettyPrint</b> option.
+		*/
+		inline bool getPrettyPrint() const
+		{
+			return prettyPrint;
+		}
+
+
+		/**
+		Returns the content type output by this layout, i.e "application/json".
+		*/
+		virtual LogString getContentType() const
+		{
+			return LOG4CXX_STR("application/json");
+		}
+
+		/**
+		No options to activate.
+		*/
+		virtual void activateOptions(log4cxx::helpers::Pool& /* p */) {}
+
+		/**
+		Set options
+		*/
+		virtual void setOption(const LogString& option, const LogString& value);
+
+		virtual void format(LogString& output,
+			const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& pool) const;
+
+		/**
+		The JSON layout handles the throwable contained in logging
+		events. Hence, this method return <code>false</code>.  */
+		virtual bool ignoresThrowable() const
+		{
+			return false;
+		}
+
+}; // class JSONLayout
+LOG4CXX_PTR_DEF(JSONLayout);
+}  // namespace log4cxx
+
+
+#endif // _LOG4CXX_JSON_LAYOUT_H
diff --git a/src/test/cpp/Makefile.am b/src/test/cpp/Makefile.am
index 13b82ac..8b6c95d 100644
--- a/src/test/cpp/Makefile.am
+++ b/src/test/cpp/Makefile.am
@@ -168,6 +168,7 @@
     filetestcase.cpp \
     hierarchytest.cpp \
     hierarchythresholdtestcase.cpp \
+    jsonlayouttest.cpp \
     l7dtestcase.cpp \
     leveltestcase.cpp \
     logunit.cpp \
diff --git a/src/test/cpp/jsonlayouttest.cpp b/src/test/cpp/jsonlayouttest.cpp
new file mode 100644
index 0000000..de1d074
--- /dev/null
+++ b/src/test/cpp/jsonlayouttest.cpp
@@ -0,0 +1,481 @@
+/*
+ * 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 "logunit.h"
+#include <log4cxx/logger.h>
+#include <log4cxx/jsonlayout.h>
+#include <log4cxx/ndc.h>
+#include <log4cxx/mdc.h>
+
+#include <iostream>
+#include <log4cxx/helpers/stringhelper.h>
+
+
+using namespace log4cxx;
+using namespace log4cxx::helpers;
+using namespace log4cxx::spi;
+
+#if defined(__LOG4CXX_FUNC__)
+	#undef __LOG4CXX_FUNC__
+	#define __LOG4CXX_FUNC__ "X::X()"
+#else
+	#error __LOG4CXX_FUNC__ expected to be defined
+#endif
+/**
+ * Test for JSONLayout.
+ *
+ */
+LOGUNIT_CLASS(JSONLayoutTest), public JSONLayout
+{
+	LOGUNIT_TEST_SUITE(JSONLayoutTest);
+	LOGUNIT_TEST(testGetContentType);
+	LOGUNIT_TEST(testIgnoresThrowable);
+	LOGUNIT_TEST(testAppendQuotedEscapedStringWithPrintableChars);
+	LOGUNIT_TEST(testAppendQuotedEscapedStringWithControlChars);
+	LOGUNIT_TEST(testAppendSerializedMDC);
+	LOGUNIT_TEST(testAppendSerializedMDCWithPrettyPrint);
+	LOGUNIT_TEST(testAppendSerializedNDC);
+	LOGUNIT_TEST(testAppendSerializedNDCWithPrettyPrint);
+	LOGUNIT_TEST(testAppendSerializedLocationInfo);
+	LOGUNIT_TEST(testAppendSerializedLocationInfoWithPrettyPrint);
+	LOGUNIT_TEST(testFormat);
+	LOGUNIT_TEST(testFormatWithPrettyPrint);
+	LOGUNIT_TEST(testGetSetLocationInfo);
+	LOGUNIT_TEST_SUITE_END();
+
+
+public:
+	/**
+	 * Clear MDC and NDC before test.
+	 */
+	void setUp()
+	{
+		NDC::clear();
+		MDC::clear();
+	}
+
+	/**
+	 * Clear MDC and NDC after test.
+	 */
+	void tearDown()
+	{
+		setUp();
+	}
+
+
+public:
+	/**
+	 * Tests getContentType.
+	 */
+	void testGetContentType()
+	{
+		LogString expected(LOG4CXX_STR("application/json"));
+		LogString actual(JSONLayout().getContentType());
+		LOGUNIT_ASSERT(expected == actual);
+	}
+
+	/**
+	 * Tests ignoresThrowable.
+	 */
+	void testIgnoresThrowable()
+	{
+		LOGUNIT_ASSERT_EQUAL(false, JSONLayout().ignoresThrowable());
+	}
+
+	/**
+	 * Tests appendQuotedEscapedString with printable characters.
+	 */
+	void testAppendQuotedEscapedStringWithPrintableChars()
+	{
+		LogString s1(LOG4CXX_STR("foo"));	/*  foo */
+		LogString s2;
+		appendQuotedEscapedString(s2, s1);	/*  "foo"  */
+		LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("\"foo\""), s2);
+
+		LogString s3;
+		appendQuotedEscapedString(s3, s2);	/*  "\"foo\""  */
+		LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("\"\\\"foo\\\"\""), s3);
+
+		LogString t1(LOG4CXX_STR("bar\"baz"));	/*  bar"baz */
+		LogString t2;
+		appendQuotedEscapedString(t2, t1);		/*  "bar\"baz"  */
+		LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("\"bar\\\"baz\""), t2);
+
+		LogString t3;
+		appendQuotedEscapedString(t3, t2);		/*  "\"bar\\\"baz\""    */
+		LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("\"\\\"bar\\\\\\\"baz\\\"\""), t3);
+	}
+
+	/**
+	 * Tests appendQuotedEscapedString with control characters.
+	 */
+	void testAppendQuotedEscapedStringWithControlChars()
+	{
+		logchar bs[] = {0x08, 0x00};
+		logchar bs_expected[] = {0x22, 0x5c, 'b', 0x22, 0x00};      /* "\b" */
+		LogString bs_escaped;
+
+		appendQuotedEscapedString(bs_escaped, bs);
+		LOGUNIT_ASSERT_EQUAL(bs_expected, bs_escaped);
+
+		logchar tab[] = {0x09, 0x00};
+		logchar tab_expected[] = {0x22, 0x5c, 't', 0x22, 0x00};     /* "\t" */
+		LogString tab_escaped;
+
+		appendQuotedEscapedString(tab_escaped, tab);
+		LOGUNIT_ASSERT_EQUAL(tab_expected, tab_escaped);
+
+		logchar newline[] = {0x0a, 0x00};
+		logchar newline_expected[] = {0x22, 0x5c, 'n', 0x22, 0x00}; /* "\n" */
+		LogString newline_escaped;
+
+		appendQuotedEscapedString(newline_escaped, newline);
+		LOGUNIT_ASSERT_EQUAL(newline_expected, newline_escaped);
+
+		logchar ff[] = {0x0c, 0x00};
+		logchar ff_expected[] = {0x22, 0x5c, 'f', 0x22, 0x00};      /* "\f" */
+		LogString ff_escaped;
+
+		appendQuotedEscapedString(ff_escaped, ff);
+		LOGUNIT_ASSERT_EQUAL(ff_expected, ff_escaped);
+
+		logchar cr[] = {0x0d, 0x00};
+		logchar cr_expected[] = {0x22, 0x5c, 'r', 0x22, 0x00};      /* "\r" */
+		LogString cr_escaped;
+
+		appendQuotedEscapedString(cr_escaped, cr);
+		LOGUNIT_ASSERT_EQUAL(cr_expected, cr_escaped);
+	}
+
+	/**
+	 * Tests appendSerializedMDC.
+	 */
+	void testAppendSerializedMDC()
+	{
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			LOG4CXX_LOCATION);
+
+		MDC::put("key1", "value1");
+		MDC::put("key2", "value2");
+
+		LogString output1;
+		LogString expected1 = LOG4CXX_STR(", \"context_map\": { "
+			"\"key1\": \"value1\", \"key2\": \"value2\" }");
+
+		appendSerializedMDC(output1, event1);
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests appendSerializedMDC with prettyPrint set to true.
+	 */
+	void testAppendSerializedMDCWithPrettyPrint()
+	{
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			LOG4CXX_LOCATION);
+
+		MDC::put("key1", "value1");
+		MDC::put("key2", "value2");
+
+		LogString output1;
+		LogString expected1;
+
+		expected1
+			.append(LOG4CXX_STR(","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"context_map\": {"))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"key1\": \"value1\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"key2\": \"value2\""))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("}"));
+
+		setPrettyPrint(true);
+		appendSerializedMDC(output1, event1);
+
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests appendSerializedNDC.
+	 */
+	void testAppendSerializedNDC()
+	{
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			LOG4CXX_LOCATION);
+
+		NDC::push("one");
+		NDC::push("two");
+		NDC::push("three");
+
+		LogString output1;
+		LogString expected1 = LOG4CXX_STR(", \"context_stack\": [ \"one two three\" ]");
+
+		appendSerializedNDC(output1, event1);
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests appendSerializedNDC with prettyPrint set to true.
+	 */
+	void testAppendSerializedNDCWithPrettyPrint()
+	{
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			LOG4CXX_LOCATION);
+
+		NDC::push("one");
+		NDC::push("two");
+		NDC::push("three");
+
+		LogString output1;
+		LogString expected1;
+
+		expected1
+			.append(LOG4CXX_STR(","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"context_stack\": ["))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"one two three\""))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("]"));
+
+		setPrettyPrint(true);
+		appendSerializedNDC(output1, event1);
+
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests appendSerializedLocationInfo.
+	 */
+	void testAppendSerializedLocationInfo()
+	{
+		Pool p;
+
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			spi::LocationInfo("FooFile", "BarFunc", 42));
+
+		LogString output1;
+		LogString expected1;
+
+		expected1
+			.append(LOG4CXX_STR("\"location_info\": { "))
+			.append(LOG4CXX_STR("\"file\": \"FooFile\", "))
+			.append(LOG4CXX_STR("\"line\": \"42\", "))
+			.append(LOG4CXX_STR("\"class\": \"\", "))
+			.append(LOG4CXX_STR("\"method\": \"BarFunc\" }"));
+
+		appendSerializedLocationInfo(output1, event1, p);
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests appendSerializedLocationInfo with prettyPrint set to true.
+	 */
+	void testAppendSerializedLocationInfoWithPrettyPrint()
+	{
+		Pool p;
+
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			spi::LocationInfo("FooFile", "BarFunc", 42));
+
+		LogString output1;
+		LogString expected1;
+
+		expected1
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"location_info\": {"))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"file\": \"FooFile\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"line\": \"42\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"class\": \"\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL2)
+			.append(LOG4CXX_STR("\"method\": \"BarFunc\""))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("}"));
+
+		setPrettyPrint(true);
+		appendSerializedLocationInfo(output1, event1, p);
+
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests format.
+	 */
+	void testFormat()
+	{
+		Pool p;
+
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			spi::LocationInfo("FooFile", "BarFunc", 42));
+
+		LogString timestamp;
+		helpers::ISO8601DateFormat dateFormat;
+		dateFormat.format(timestamp, event1->getTimeStamp(), p);
+
+		NDC::push("one");
+		NDC::push("two");
+		NDC::push("three");
+
+		MDC::put("key1", "value1");
+		MDC::put("key2", "value2");
+
+		LogString output1;
+		LogString expected1;
+
+		expected1
+			.append(LOG4CXX_STR("{ \"timestamp\": \""))
+			.append(timestamp)
+			.append(LOG4CXX_STR("\", "))
+			.append(LOG4CXX_STR("\"level\": \"INFO\", "))
+			.append(LOG4CXX_STR("\"logger\": \"Logger\", "))
+			.append(LOG4CXX_STR("\"message\": \"A message goes here.\""));
+
+		setLocationInfo(true);
+
+		appendSerializedMDC(expected1, event1);
+		appendSerializedNDC(expected1, event1);
+		expected1.append(LOG4CXX_STR(", "));
+		appendSerializedLocationInfo(expected1, event1, p);
+
+		expected1.append(LOG4CXX_STR(" }"));
+		expected1.append(LOG4CXX_EOL);
+		format(output1, event1, p);
+
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests format with PrettyPrint set to true.
+	 */
+	void testFormatWithPrettyPrint()
+	{
+		Pool p;
+
+		LoggingEventPtr event1 = new LoggingEvent(LOG4CXX_STR("Logger"),
+			Level::getInfo(),
+			LOG4CXX_STR("A message goes here."),
+			spi::LocationInfo("FooFile", "BarFunc", 42));
+
+		LogString timestamp;
+		helpers::ISO8601DateFormat dateFormat;
+		dateFormat.format(timestamp, event1->getTimeStamp(), p);
+
+		NDC::push("one");
+		NDC::push("two");
+		NDC::push("three");
+
+		MDC::put("key1", "value1");
+		MDC::put("key2", "value2");
+
+		LogString output1;
+		LogString expected1;
+
+		expected1
+			.append(LOG4CXX_STR("{"))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"timestamp\": \""))
+			.append(timestamp)
+			.append(LOG4CXX_STR("\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"level\": \"INFO\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"logger\": \"Logger\","))
+			.append(LOG4CXX_EOL)
+			.append(ppIndentL1)
+			.append(LOG4CXX_STR("\"message\": \"A message goes here.\""));
+
+		setPrettyPrint(true);
+		setLocationInfo(true);
+
+		appendSerializedMDC(expected1, event1);
+		appendSerializedNDC(expected1, event1);
+		expected1.append(LOG4CXX_STR(","));
+		expected1.append(LOG4CXX_EOL);
+		appendSerializedLocationInfo(expected1, event1, p);
+
+		expected1.append(LOG4CXX_EOL);
+		expected1.append(LOG4CXX_STR("}"));
+		expected1.append(LOG4CXX_EOL);
+		format(output1, event1, p);
+
+		LOGUNIT_ASSERT_EQUAL(expected1, output1);
+	}
+
+	/**
+	 * Tests getLocationInfo and setLocationInfo.
+	 */
+	void testGetSetLocationInfo()
+	{
+		JSONLayout layout;
+		LOGUNIT_ASSERT_EQUAL(false, layout.getLocationInfo());
+		layout.setLocationInfo(true);
+		LOGUNIT_ASSERT_EQUAL(true, layout.getLocationInfo());
+		layout.setLocationInfo(false);
+		LOGUNIT_ASSERT_EQUAL(false, layout.getLocationInfo());
+	}
+
+	/**
+	 * Tests getPrettyPrint and setPrettyPrint.
+	 */
+	void testGetSetPrettyPrint()
+	{
+		JSONLayout layout;
+		LOGUNIT_ASSERT_EQUAL(false, layout.getPrettyPrint());
+		layout.setPrettyPrint(true);
+		LOGUNIT_ASSERT_EQUAL(true, layout.getPrettyPrint());
+		layout.setPrettyPrint(false);
+		LOGUNIT_ASSERT_EQUAL(false, layout.getPrettyPrint());
+	}
+};
+
+
+LOGUNIT_TEST_SUITE_REGISTRATION(JSONLayoutTest);
+