You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rm...@apache.org on 2022/12/22 22:46:11 UTC

[logging-log4cxx] branch LOGCXX-559 created (now cb852afb)

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

rmiddleton pushed a change to branch LOGCXX-559
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


      at cb852afb LOCXX-559 Implement the LocationInfoFilter

This branch includes the following new commits:

     new cb852afb LOCXX-559 Implement the LocationInfoFilter

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[logging-log4cxx] 01/01: LOCXX-559 Implement the LocationInfoFilter

Posted by rm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit cb852afb9ba824eb87e5b5cbfbce25a48e1c831d
Author: Robert Middleton <ro...@rm5248.com>
AuthorDate: Thu Dec 22 17:45:57 2022 -0500

    LOCXX-559 Implement the LocationInfoFilter
---
 src/main/cpp/CMakeLists.txt                        |   1 +
 src/main/cpp/class.cpp                             |   2 +
 src/main/cpp/jsonlayout.cpp.orig                   | 429 +++++++++++++++++++++
 src/main/cpp/locationinfofilter.cpp                | 138 +++++++
 .../include/log4cxx/filter/locationinfofilter.h    |  53 ++-
 src/main/include/log4cxx/jsonlayout.h.orig         | 111 ++++++
 src/main/include/log4cxx/patternlayout.h           |   4 +-
 src/site/markdown/1-usage.md                       |   2 +-
 src/site/markdown/filters/LocationInfoFilter.md    |  74 ++++
 src/site/markdown/filters/filters.md               |  57 +++
 src/site/markdown/usage.md                         |  19 -
 src/test/cpp/filter/CMakeLists.txt                 |   1 +
 src/test/cpp/filter/locationinfofiltertest.cpp     | 135 +++++++
 13 files changed, 978 insertions(+), 48 deletions(-)

diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index d91d3aa2..01a5b991 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -125,6 +125,7 @@ target_sources(log4cxx
   loader.cpp
   locale.cpp
   locationinfo.cpp
+  locationinfofilter.cpp
   logger.cpp
   loggermatchfilter.cpp
   loggerpatternconverter.cpp
diff --git a/src/main/cpp/class.cpp b/src/main/cpp/class.cpp
index bbf02f52..3dcaecf1 100644
--- a/src/main/cpp/class.cpp
+++ b/src/main/cpp/class.cpp
@@ -53,6 +53,7 @@
 #include <log4cxx/filter/levelmatchfilter.h>
 #include <log4cxx/filter/levelrangefilter.h>
 #include <log4cxx/filter/stringmatchfilter.h>
+#include <log4cxx/filter/locationinfofilter.h>
 #include <log4cxx/rolling/filterbasedtriggeringpolicy.h>
 #include <log4cxx/rolling/fixedwindowrollingpolicy.h>
 #include <log4cxx/rolling/manualtriggeringpolicy.h>
@@ -178,6 +179,7 @@ void Class::registerClasses()
 	LevelMatchFilter::registerClass();
 	LevelRangeFilter::registerClass();
 	StringMatchFilter::registerClass();
+	LocationInfoFilter::registerClass();
 	log4cxx::rolling::RollingFileAppender::registerClass();
 	log4cxx::rolling::SizeBasedTriggeringPolicy::registerClass();
 	log4cxx::rolling::TimeBasedRollingPolicy::registerClass();
diff --git a/src/main/cpp/jsonlayout.cpp.orig b/src/main/cpp/jsonlayout.cpp.orig
new file mode 100644
index 00000000..730dd135
--- /dev/null
+++ b/src/main/cpp/jsonlayout.cpp.orig
@@ -0,0 +1,429 @@
+/*
+ * 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 <string.h>
+
+using namespace log4cxx;
+using namespace log4cxx::helpers;
+using namespace log4cxx::spi;
+
+IMPLEMENT_LOG4CXX_OBJECT(JSONLayout)
+
+struct JSONLayout::JSONLayoutPrivate{
+	JSONLayoutPrivate() :
+		locationInfo(false),
+		prettyPrint(false),
+		dateFormat(),
+		ppIndentL1(LOG4CXX_STR("  ")),
+		ppIndentL2(LOG4CXX_STR("    ")){}
+
+	// Print no location info by default
+	bool locationInfo; //= false
+	bool prettyPrint; //= false
+
+	helpers::ISO8601DateFormat dateFormat;
+
+	LogString ppIndentL1;
+	LogString ppIndentL2;
+};
+
+JSONLayout::JSONLayout() :
+	m_priv(std::make_unique<JSONLayoutPrivate>())
+{
+}
+
+void JSONLayout::setLocationInfo(bool locationInfoFlag)
+{
+	m_priv->locationInfo = locationInfoFlag;
+}
+
+bool JSONLayout::getLocationInfo() const
+{
+	return m_priv->locationInfo;
+}
+
+void JSONLayout::setPrettyPrint(bool prettyPrintFlag)
+{
+	m_priv->prettyPrint = prettyPrintFlag;
+}
+
+bool JSONLayout::getPrettyPrint() const
+{
+	return m_priv->prettyPrint;
+}
+
+LogString JSONLayout::getContentType() const
+{
+	return LOG4CXX_STR("application/json");
+}
+
+void JSONLayout::activateOptions(helpers::Pool& /* p */){
+
+}
+
+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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		output.append(m_priv->ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("timestamp"));
+	output.append(LOG4CXX_STR(": "));
+	LogString timestamp;
+	m_priv->dateFormat.format(timestamp, event->getTimeStamp(), p);
+	appendQuotedEscapedString(output, timestamp);
+	output.append(LOG4CXX_STR(","));
+	output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		output.append(m_priv->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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		output.append(m_priv->ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("logger"));
+	output.append(LOG4CXX_STR(": "));
+	appendQuotedEscapedString(output, event->getLoggerName());
+	output.append(LOG4CXX_STR(","));
+	output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		output.append(m_priv->ppIndentL1);
+	}
+
+	appendQuotedEscapedString(output, LOG4CXX_STR("message"));
+	output.append(LOG4CXX_STR(": "));
+	appendQuotedEscapedString(output, event->getMessage());
+
+	appendSerializedMDC(output, event);
+	appendSerializedNDC(output, event);
+
+	if (m_priv->locationInfo)
+	{
+		output.append(LOG4CXX_STR(","));
+		output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+		appendSerializedLocationInfo(output, event, p);
+	}
+
+	output.append(m_priv->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         */
+		0x00    /* terminating NULL for C-strings */
+	};
+
+	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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL1);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("context_map"));
+	buf.append(LOG4CXX_STR(": {"));
+	buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	for (LoggingEvent::KeySet::iterator it = keys.begin();
+		it != keys.end(); ++it)
+	{
+		if (m_priv->prettyPrint)
+		{
+			buf.append(m_priv->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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+		}
+		else
+		{
+			buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+		}
+	}
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL1);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("context_stack"));
+	buf.append(LOG4CXX_STR(": ["));
+	buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, ndcVal);
+	buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL1);
+	}
+
+	buf.append(LOG4CXX_STR("]"));
+}
+
+void JSONLayout::appendSerializedLocationInfo(LogString& buf,
+	const LoggingEventPtr& event, Pool& p) const
+{
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL1);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("location_info"));
+	buf.append(LOG4CXX_STR(": {"));
+	buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+	const LocationInfo& locInfo = event->getLocationInformation();
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->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(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL2);
+	}
+
+	appendQuotedEscapedString(buf, LOG4CXX_STR("method"));
+	buf.append(LOG4CXX_STR(": "));
+	LOG4CXX_DECODE_CHAR(methodName, locInfo.getMethodName());
+	appendQuotedEscapedString(buf, methodName);
+	buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
+
+	if (m_priv->prettyPrint)
+	{
+		buf.append(m_priv->ppIndentL1);
+	}
+
+	buf.append(LOG4CXX_STR("}"));
+}
+
diff --git a/src/main/cpp/locationinfofilter.cpp b/src/main/cpp/locationinfofilter.cpp
new file mode 100644
index 00000000..db8612fe
--- /dev/null
+++ b/src/main/cpp/locationinfofilter.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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/filter/locationinfofilter.h>
+#include <log4cxx/spi/loggingevent.h>
+#include <log4cxx/helpers/stringhelper.h>
+#include <log4cxx/helpers/optionconverter.h>
+#include <log4cxx/private/filter_priv.h>
+#include <log4cxx/helpers/loglog.h>
+
+using namespace log4cxx;
+using namespace log4cxx::filter;
+using namespace log4cxx::spi;
+using namespace log4cxx::helpers;
+
+#define priv static_cast<LocationInfoFilterPrivate*>(m_priv.get())
+
+struct LocationInfoFilter::LocationInfoFilterPrivate : public FilterPrivate
+{
+	LocationInfoFilterPrivate() : FilterPrivate(),
+		acceptOnMatch(true),
+		mustMatchAll(false),
+		lineNumber(-1)
+	{}
+
+	bool    acceptOnMatch;
+	bool    mustMatchAll; // true = AND; false = OR
+	int lineNumber;
+	LogString methodName;
+};
+
+IMPLEMENT_LOG4CXX_OBJECT(LocationInfoFilter)
+
+LocationInfoFilter::LocationInfoFilter() :
+	Filter(std::make_unique<LocationInfoFilterPrivate>())
+{
+}
+
+LocationInfoFilter::~LocationInfoFilter() {}
+
+void LocationInfoFilter::setOption(  const LogString& option,
+	const LogString& value)
+{
+	LogLog::warn(option + ":" + value);
+	if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("ACCEPTONMATCH"), LOG4CXX_STR("acceptonmatch")))
+	{
+		priv->acceptOnMatch = OptionConverter::toBoolean(value, priv->acceptOnMatch);
+	}
+	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("OPERATOR"), LOG4CXX_STR("operator")))
+	{
+		priv->mustMatchAll = StringHelper::equalsIgnoreCase(value, LOG4CXX_STR("AND"), LOG4CXX_STR("and")) ? true : false;
+	}
+	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("LINENUMBER"), LOG4CXX_STR("linenumber")))
+	{
+		priv->lineNumber = OptionConverter::toInt(value, -1);
+	}
+	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("METHOD"), LOG4CXX_STR("method")))
+	{
+		priv->methodName = value;
+	}
+}
+
+Filter::FilterDecision LocationInfoFilter::decide(
+	const log4cxx::spi::LoggingEventPtr& event) const
+{
+	if (priv->lineNumber == -1 &&
+			priv->methodName.empty())
+	{
+		return Filter::NEUTRAL;
+	}
+
+	if (event->getLocationInformation().getLineNumber() == -1 ||
+			event->getLocationInformation().getMethodName().compare(LocationInfo::NA_METHOD) == 0){
+		return Filter::NEUTRAL;
+	}
+
+	bool matched = false;
+	bool matchLineNumber = priv->lineNumber == event->getLocationInformation().getLineNumber();
+	bool matchMethodName = priv->methodName.compare(event->getLocationInformation().getMethodName()) == 0;
+
+	if(priv->mustMatchAll){
+		matched = matchLineNumber && matchMethodName;
+	}else{
+		matched = matchLineNumber || matchMethodName;
+	}
+
+	if (priv->acceptOnMatch)
+	{
+		return matched ? Filter::ACCEPT : Filter::NEUTRAL;
+	}
+	else
+	{
+		return matched ? Filter::DENY : Filter::NEUTRAL;
+	}
+}
+
+void LocationInfoFilter::setAcceptOnMatch(bool acceptOnMatch1)
+{
+	priv->acceptOnMatch = acceptOnMatch1;
+}
+
+bool LocationInfoFilter::getAcceptOnMatch() const
+{
+	return priv->acceptOnMatch;
+}
+
+bool LocationInfoFilter::getMustMatchAll() const
+{
+	return priv->mustMatchAll;
+}
+
+void LocationInfoFilter::setMustMatchAll(bool mustMatchAll1)
+{
+	priv->mustMatchAll = mustMatchAll1;
+}
+
+void LocationInfoFilter::setLineNumber(int lineNum){
+	priv->lineNumber = lineNum;
+}
+
+void LocationInfoFilter::setMethodName(const LogString& methodName){
+	priv->methodName = methodName;
+}
diff --git a/src/main/include/log4cxx/filter/locationinfofilter.h b/src/main/include/log4cxx/filter/locationinfofilter.h
index a6a574fe..cf4fcbb8 100644
--- a/src/main/include/log4cxx/filter/locationinfofilter.h
+++ b/src/main/include/log4cxx/filter/locationinfofilter.h
@@ -21,54 +21,50 @@
 
 namespace log4cxx
 {
-namespace rule
-{
-class ExpressionRule;
-class Rule;
-typedef helpers::ObjectPtrT < Rule > RulePtr;
-typedef helpers::ObjectPtrT < ExpressionRule > ExpressionRulePtr;
-}
 
 namespace filter
 {
+
 /**
- * Location information is usually specified at the appender level - all events associated
- * with an appender either create and parse stack traces or they do not.  This is
- * an expensive operation and in some cases not needed for all events associated with
- * an appender.
+ * When location information is available, individual log statements can be turned on or off
+ * depending on their source location.
  *
- * This filter creates event-level location information only if the provided expression evaluates to true.
- *
- * For information on expression syntax, see org.apache.log4j.rule.ExpressionRule
+ * This filter allows for filtering messages based off of either the line number of the
+ * message, or the name of the method that the log mesage is in.  The 'operator' parameter
+ * may be used to determine if both the method name and line number must match.
+ * If 'operator' is set to 'AND', then both the line number and method name must match,
+ * otherwise only one needs to match.  By default, 'operator' is set to 'OR'.
  *
+ * If location information is not available, this filter does nothing.
  *
  */
 class LOG4CXX_EXPORT LocationInfoFilter: public log4cxx::spi::Filter
 {
-		bool convertInFixToPostFix;
-		LOG4CXX_DECLARE_PRIVATE_MEMBER(LogString, expression)
-		LOG4CXX_DECLARE_PRIVATE_MEMBER(rule::RulePtr, expressionRule)
-		//HACK: Category is the last of the internal layers - pass this in as the class name
-		//in order for parsing to work correctly
-		LogString className;
-
+		struct LocationInfoFilterPrivate;
 	public:
 		DECLARE_LOG4CXX_OBJECT(LocationInfoFilter)
 		BEGIN_LOG4CXX_CAST_MAP()
-		LOG4CXX_CAST_ENTRY(log4cxx::spi::Filter)
+		LOG4CXX_CAST_ENTRY(LocationInfoFilter)
+		LOG4CXX_CAST_ENTRY_CHAIN(log4cxx::spi::Filter)
 		END_LOG4CXX_CAST_MAP()
 
 		LocationInfoFilter();
 
-		void activateOptions(helpers::Pool&) override;
+		~LocationInfoFilter();
 
-		void setExpression(const LogString& expression);
+		void setOption(const LogString& option, const LogString& value) override;
 
-		LogString getExpression() const;
+		void setLineNumber(int lineNum);
 
-		void setConvertInFixToPostFix(bool convertInFixToPostFix);
+		void setMethodName(const LogString& methodName);
 
-		bool getConvertInFixToPostFix() const;
+		void setAcceptOnMatch(bool acceptOnMatch1);
+
+		bool getAcceptOnMatch() const;
+
+		bool getMustMatchAll() const;
+
+		void setMustMatchAll(bool mustMatchAll1);
 
 		/**
 		 * If this event does not already contain location information,
@@ -82,6 +78,9 @@ class LOG4CXX_EXPORT LocationInfoFilter: public log4cxx::spi::Filter
 		FilterDecision decide(const spi::LoggingEventPtr& event) const override;
 
 };
+
+LOG4CXX_PTR_DEF(LocationInfoFilter);
+
 }
 }
 #endif
diff --git a/src/main/include/log4cxx/jsonlayout.h.orig b/src/main/include/log4cxx/jsonlayout.h.orig
new file mode 100644
index 00000000..d82ad66d
--- /dev/null
+++ b/src/main/include/log4cxx/jsonlayout.h.orig
@@ -0,0 +1,111 @@
+/*
+ * 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:
+		LOG4CXX_DECLARE_PRIVATE_MEMBER_PTR(JSONLayoutPrivate, m_priv)
+
+	protected:
+		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.
+		*/
+		void setLocationInfo(bool locationInfoFlag);
+
+
+		/**
+		Returns the current value of the <b>LocationInfo</b> option.
+		*/
+		bool getLocationInfo() const;
+
+		/**
+		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.
+		*/
+		void setPrettyPrint(bool prettyPrintFlag);
+
+		/**
+		Returns the current value of the <b>PrettyPrint</b> option.
+		*/
+		bool getPrettyPrint() const;
+
+
+		/**
+		Returns the content type output by this layout, i.e "application/json".
+		*/
+		LogString getContentType() const override;
+
+		void activateOptions(helpers::Pool& /* p */) override;
+
+		/**
+		Set options
+		*/
+		void setOption(const LogString& option, const LogString& value) override;
+
+		void format(LogString& output,
+			const spi::LoggingEventPtr& event, helpers::Pool& pool) const override;
+
+		/**
+		The JSON layout handles the throwable contained in logging
+		events. Hence, this method return <code>false</code>.  */
+		bool ignoresThrowable() const override
+		{
+			return false;
+		}
+
+}; // class JSONLayout
+LOG4CXX_PTR_DEF(JSONLayout);
+}  // namespace log4cxx
+
+#endif // _LOG4CXX_JSON_LAYOUT_H
diff --git a/src/main/include/log4cxx/patternlayout.h b/src/main/include/log4cxx/patternlayout.h
index 024f835d..41e09819 100644
--- a/src/main/include/log4cxx/patternlayout.h
+++ b/src/main/include/log4cxx/patternlayout.h
@@ -57,11 +57,13 @@ LOG4CXX_LIST_DEF(FormattingInfoList, log4cxx::pattern::FormattingInfoPtr);
  * <p>
  *  Let the conversion pattern be <strong>"%-5p [%t]: %m%n"</strong> and assume that the log4cxx
  *  environment was set to use a PatternLayout. Then the statements
- *  ~~~{.cpp}
+ *
+ * ~~~{.cpp}
  *      auto root = Logger::getRootLogger();
  *      root->debug("Message 1");
  *      root->warn("Message 2");
  * ~~~
+ *
  *  would yield the output
  *  <pre>
  *      DEBUG [main]: Message 1
diff --git a/src/site/markdown/1-usage.md b/src/site/markdown/1-usage.md
index 3d94cd89..8f3a370b 100644
--- a/src/site/markdown/1-usage.md
+++ b/src/site/markdown/1-usage.md
@@ -24,13 +24,13 @@ Usage {#usage-overview}
 See the following pages for usage information:
 
 * @subpage usage
+* @subpage filters
 * @subpage threading
 * @subpage extending-log4cxx
 * @subpage faq
 * @subpage configuration-samples
 * @subpage qt-support
 * @subpage performance
-* @subpage map-filter
 * @subpage multiprocess-logging
 * @subpage environment-variables
 * @subpage macros-influencing-log4cxx
diff --git a/src/site/markdown/filters/LocationInfoFilter.md b/src/site/markdown/filters/LocationInfoFilter.md
new file mode 100644
index 00000000..049cc130
--- /dev/null
+++ b/src/site/markdown/filters/LocationInfoFilter.md
@@ -0,0 +1,74 @@
+LocationInfoFilter {#location-info-filter}
+===
+<!--
+ Note: License header cannot be first, as doxygen does not generate
+ cleanly if it before the '==='
+-->
+<!--
+ 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.
+-->
+
+The LocationInfoFilter allows filtering against the location in the file that
+the log statement was made.  Location information must not be disabled in order
+for this filter to be effective.  Location information is disabled with the
+`LOG4CXX_DISABLE_LOCATION_INFO` macro.
+
+| **Parameter Name** | **Type**  | **Description**                                                                                                                                                                                                    |
+|:-------------------|:----------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Operator           | LogString | If the operator is `AND` then all the parts of the location(line number and method name) must match.  If set to `OR` then only one needs to match.  The default value is `OR`. |
+| AcceptOnMatch      | bool | If `true`, accept the message when it matches the parameters.  If `false`, deny the message when it matches the parameters. |
+| LineNumber         | int | The line number to match on.  The default line number is -1. |
+| Method             | LogString | The method to match on.  The method name may be compiler-specific.  On GCC, the method name will look like `Class::methodName` |
+
+Assume that our code looks something like the following:
+
+~~~{.cpp}
+	LOG4CXX_TRACE(logger, "About to do something!");
+	for( int x = 0; x < 100; x++ ){
+		LOG4CXX_TRACE(logger, "Do something number " << x);
+	}
+~~~
+
+For various reasons, we may want to know that we are about to do something, but
+we don't want to know each iteration of the loop.  In order to filter out this
+one message we can create a LocationInfoFilter in order to specifiy the line
+number that this message is on in order to filter it out:
+
+~~~{.xml}
+<?xml version="1.0" encoding="UTF-8"?>
+<log4j:configuration xmlns:log4j="http://logging.apache.org/">
+    <appender name="SIMPLE" class="ConsoleAppender">
+        <param name="Target" value="System.err"/>
+    
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p - %m%n"/>
+        </layout>
+
+        <filter class="LocationInfoFilter">
+            <param  name="LineNumber" value="182" />
+            <param  name="Operator" value="OR" />
+            <param  name="AcceptOnMatch" value="false" />
+        </filter>
+    </appender>
+    <root>
+        <priority       value="all"     />
+        <appender-ref   ref="SIMPLE"    />
+    </root>
+</log4j:configuration>
+~~~
+
+Doing this allows us to still see the "About to do something!" message, but
+ignore each iteration of the loop.
diff --git a/src/site/markdown/filters/filters.md b/src/site/markdown/filters/filters.md
new file mode 100644
index 00000000..5c2110cf
--- /dev/null
+++ b/src/site/markdown/filters/filters.md
@@ -0,0 +1,57 @@
+Filtering Log Messages {#filters}
+===
+<!--
+ Note: License header cannot be first, as doxygen does not generate
+ cleanly if it before the '==='
+-->
+<!--
+ 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.
+-->
+[TOC]
+
+# Filtering Messages {#filtering}
+
+When dealing with large amounts of logging information, it can be useful
+to filter on messages that we are interested in.  This filtering only
+takes places after determining that the level of the current logger would
+log the message in the first place.  When defining filters, note that
+they can only be defined on a per-appender basis, they do not globally
+affect anything.
+
+The filtering system is similar in concept to Linux iptables rules, in
+that there is a chain of filters that can accept a log message, deny the
+log message, or pass the message on to the next filter. Accepting a log
+message means that the message will be logged immediately without
+consulting other filters.  Denying has the opposite affect, immediately
+dropping the log message and not consulting any other filters.
+
+See the documentation for [Filter](@ref log4cxx.spi.Filter) for some more
+information, or view a [configuration sample](@ref configuration-samples).
+
+The following filters are available:
+* [AndFilter](@ref log4cxx.filter.AndFilter) - Takes in a list of filters that must all match
+* [DenyAllFilter](@ref log4cxx.filter.DenyAllFilter) - Drops all log messages that reach it
+* [LevelMatchFilter](@ref log4cxx.filter.LevelMatchFilter) - Filter log messages based off of their level
+* [LevelRangeFilter](@ref log4cxx.filter.LevelRangeFilter) - Filter log messages based off of their level in a given range
+* [LocationInfoFilter](@ref log4cxx.filter.LocationInfoFilter) - Filter log messages based off of their location(line number and/or method name)
+* [LoggerMatchFilter](@ref log4cxx.filter.LoggerMatchFilter) - Accept or deny depending on the logger that generated the message
+* [MapFilter](@ref log4cxx.filter.MapFilter) - Based off of the log messages MDC, accept or deny the message
+* [StringMatchFilter](@ref log4cxx.filter.StringMatchFilter) - If the given substring is found in the message, accept or deny
+
+The following pages have information on specific filters:
+
+* @subpage map-filter
+* @subpage location-info-filter
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
index 1c31c431..c376dcfb 100644
--- a/src/site/markdown/usage.md
+++ b/src/site/markdown/usage.md
@@ -683,25 +683,6 @@ the non-FMT macros.
 
 A full example can be seen in the \ref format-string.cpp file.
 
-# Filtering Messages {#filtering}
-
-When dealing with large amounts of logging information, it can be useful
-to filter on messages that we are interested in.  This filtering only
-takes places after determining that the level of the current logger would
-log the message in the first place.  When defining filters, note that
-they can only be defined on a per-appender basis, they do not globally
-affect anything.
-
-The filtering system is similar in concept to Linux iptables rules, in
-that there is a chain of filters that can accept a log message, deny the
-log message, or pass the message on to the next filter. Accepting a log
-message means that the message will be logged immediately without
-consulting other filters.  Denying has the opposite affect, immediately
-dropping the log message and not consulting any other filters.
-
-See the documentation for [Filter](@ref log4cxx.spi.Filter) for some more
-information, or view a [configuration sample](@ref configuration-samples).
-
 # Internal Debugging {#internal-debugging}
 
 Because Log4cxx is a logging library, we can't use it to output errors from
diff --git a/src/test/cpp/filter/CMakeLists.txt b/src/test/cpp/filter/CMakeLists.txt
index 018d0c88..43fec1ab 100644
--- a/src/test/cpp/filter/CMakeLists.txt
+++ b/src/test/cpp/filter/CMakeLists.txt
@@ -23,5 +23,6 @@ add_executable(filtertests
     loggermatchfiltertest.cpp
     mapfiltertest.cpp
     stringmatchfiltertest.cpp
+    locationinfofiltertest.cpp
 )
 set(ALL_LOG4CXX_TESTS ${ALL_LOG4CXX_TESTS} filtertests PARENT_SCOPE)
diff --git a/src/test/cpp/filter/locationinfofiltertest.cpp b/src/test/cpp/filter/locationinfofiltertest.cpp
new file mode 100644
index 00000000..4e0a12db
--- /dev/null
+++ b/src/test/cpp/filter/locationinfofiltertest.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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/filter/locationinfofilter.h>
+#include <log4cxx/logger.h>
+#include <log4cxx/spi/filter.h>
+#include <log4cxx/spi/loggingevent.h>
+#include "../logunit.h"
+
+using namespace log4cxx;
+using namespace log4cxx::filter;
+using namespace log4cxx::spi;
+using namespace log4cxx::helpers;
+
+/**
+ * Unit tests for LocationInfo.
+ */
+LOGUNIT_CLASS(LocationInfoFilterTest)
+{
+	LOGUNIT_TEST_SUITE(LocationInfoFilterTest);
+	LOGUNIT_TEST(test1);
+	LOGUNIT_TEST(test2);
+	LOGUNIT_TEST(test3);
+	LOGUNIT_TEST(test4);
+	LOGUNIT_TEST_SUITE_END();
+
+public:
+
+	/**
+	 * Check that LocationInfoFilter.decide() returns Filter.NEUTRAL
+	 *   when nothing is configured.
+	 */
+	void test1()
+	{
+		LocationInfo li("/path/to/foo.cpp",
+						"foo.cpp",
+						"exampleFun",
+						50);
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("LocationInfoFilter"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				li));
+		FilterPtr filter(new LocationInfoFilter());
+		Pool p;
+		filter->activateOptions(p);
+		LOGUNIT_ASSERT_EQUAL(Filter::NEUTRAL, filter->decide(event));
+	}
+
+	/**
+	 * Check that LocationInfoFilter.decide() returns Filter.NEUTRAL
+	 *   when line number does not match
+	 */
+	void test2()
+	{
+		LocationInfo li("/path/to/foo.cpp",
+						"foo.cpp",
+						"exampleFun",
+						50);
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("LocationInfoFilter"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				li));
+		LocationInfoFilterPtr filter(new LocationInfoFilter());
+		filter->setLineNumber(10);
+		Pool p;
+		filter->activateOptions(p);
+		LOGUNIT_ASSERT_EQUAL(Filter::NEUTRAL, filter->decide(event));
+	}
+
+	/**
+	 * Check that LocationInfoFilter.decide() returns Filter.ACCEPT
+	 * when the line number matches
+	 */
+	void test3()
+	{
+		LocationInfo li("/path/to/foo.cpp",
+						"foo.cpp",
+						"exampleFun",
+						50);
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("LocationInfoFilter"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				li));
+		LocationInfoFilterPtr filter(new LocationInfoFilter());
+		filter->setLineNumber(50);
+		filter->setAcceptOnMatch(true);
+		Pool p;
+		filter->activateOptions(p);
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));
+	}
+
+	/**
+	 * Check that LocationInfoFilter.decide() returns Filter.ACCEPT
+	 * when the line number and method name match
+	 */
+	void test4()
+	{
+		LocationInfo li("/path/to/foo.cpp",
+						"foo.cpp",
+						"exampleFun",
+						50);
+		LoggingEventPtr event(new LoggingEvent(
+				LOG4CXX_STR("LocationInfoFilter"),
+				Level::getInfo(),
+				LOG4CXX_STR("Hello, World"),
+				li));
+		LocationInfoFilterPtr filter(new LocationInfoFilter());
+		filter->setLineNumber(50);
+		filter->setMethodName("exampleFun");
+		filter->setAcceptOnMatch(true);
+		filter->setMustMatchAll(true);
+		Pool p;
+		filter->activateOptions(p);
+		LOGUNIT_ASSERT_EQUAL(Filter::ACCEPT, filter->decide(event));
+	}
+
+};
+
+LOGUNIT_TEST_SUITE_REGISTRATION(LocationInfoFilterTest);