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

rgoers pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/master by this push:
     new 73fa864e91 LOG4J2-3495, LOG4J2-3481, LOG4J2-3482 Add 
MutableThreadContextMapFilter. Pass Auth provider when creating a connection.
73fa864e91 is described below

commit 73fa864e914e03b54035bd66dc2ffb65ad4e6726
Author: Ralph Goers <[email protected]>
AuthorDate: Mon May 2 14:52:15 2022 +0200

    LOG4J2-3495, LOG4J2-3481, LOG4J2-3482 Add MutableThreadContextMapFilter. 
Pass Auth provider when creating a connection.
---
 .../src/main/resources/Log4j-config.xsd            |  38 +-
 .../filter/HttpThreadContextMapFilterTest.java     | 198 ++++++++++
 .../filter/MutableThreadContextMapFilterTest.java  |  98 +++++
 .../log4j/core/net/UrlConnectionFactoryTest.java   |  22 +-
 .../src/test/resources/emptyConfig.json            |   4 +
 .../src/test/resources/filterConfig.json           |   6 +
 .../src/test/resources/log4j2-mutableFilter.xml    |  36 ++
 .../logging/log4j/core/config/HttpWatcher.java     |  94 ++---
 .../core/filter/MutableThreadContextMapFilter.java | 411 +++++++++++++++++++++
 .../core/filter/mutable/KeyValuePairConfig.java    |  46 +++
 .../log4j/core/net/UrlConnectionFactory.java       |  75 ++--
 .../core/util/internal/HttpInputStreamUtil.java    | 135 +++++++
 .../core/util/internal/LastModifiedSource.java     |  51 +++
 .../logging/log4j/core/util/internal/Status.java   |  25 ++
 log4j-core/src/main/resources/Log4j-config.xsd     |  38 +-
 .../boot/Log4j2CloudConfigLoggingSystem.java       |  19 +-
 src/changes/changes.xml                            |  12 +-
 src/site/asciidoc/manual/filters.adoc              |  71 ++++
 18 files changed, 1263 insertions(+), 116 deletions(-)

diff --git a/log4j-core-test/src/main/resources/Log4j-config.xsd 
b/log4j-core-test/src/main/resources/Log4j-config.xsd
index 79eaac7848..8c173e2483 100644
--- a/log4j-core-test/src/main/resources/Log4j-config.xsd
+++ b/log4j-core-test/src/main/resources/Log4j-config.xsd
@@ -29,6 +29,13 @@
    <element name="Configuration">
       <complexType>
          <sequence>
+            <choice minOccurs="0">
+               <element name="AsyncWaitStrategyFactory">
+                  <complexType>
+                     <attribute name="class" type="string" use="required"/>
+                  </complexType>
+               </element>
+            </choice>
             <choice minOccurs="0">
                <element name="CustomLevels" type="tns:CustomLevelsType">
                   <unique name="UniqueCustomLevelName">
@@ -158,7 +165,6 @@
       </complexType>
    </element>
 
-
    <!--
     
================================================================================================================================
     <Configuration><CustomLevels> type and related types
@@ -194,6 +200,7 @@
          <element name="DynamicThresholdFilter" 
type="tns:DynamicThresholdFilterType" minOccurs="0" />
          <element name="MapFilter" type="tns:MapFilterType" minOccurs="0" />
          <element name="MarkerFilter" type="tns:MarkerFilterType" 
minOccurs="0" />
+         <element name="MutableThreadContextMapFilter" 
type="tns:MutableThreadContextMapFilterType" minOccurs="0" />
          <element name="NoMarkerFilter" type="tns:NoMarkerFilterType" 
minOccurs="0" />
          <element name="RegexFilter" type="tns:RegexFilterType" minOccurs="0" 
/>
          <element name="ScriptFilter" type="tns:ScriptFilterType" 
minOccurs="0" />
@@ -305,6 +312,35 @@
       </complexContent>
    </complexType>
 
+   <complexType name="MutableThreadContextMapFilterType">
+      <complexContent>
+         <extension base="tns:AbstractFilterType">
+            <attribute name="configLocation" type="string">
+               <annotation>
+                  <documentation>
+                     The location of the configuration for the filter.
+                  </documentation>
+               </annotation>
+            </attribute>
+            <attribute name="pollInterval" type="int">
+               <annotation>
+                  <documentation>
+                     The frequency in seconds that the configuration file 
should be checked for modifications.
+                  </documentation>
+               </annotation>
+            </attribute>
+            <attribute name="operator" type="string">
+               <annotation>
+                  <documentation>
+                     If the operator is "or" then a match by any one of the 
key/value pairs will be considered to be a match, otherwise all the
+                     key/value pairs must match.
+                  </documentation>
+               </annotation>
+            </attribute>
+         </extension>
+      </complexContent>
+   </complexType>
+
    <complexType name="MapFilterType">
       <complexContent>
          <extension base="tns:AbstractFilterTypeWithKeyValuePairs">
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java
new file mode 100644
index 0000000000..712fe0a4ea
--- /dev/null
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Base64;
+import java.util.Enumeration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.Assert;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Unit test for simple App.
+ */
+public class HttpThreadContextMapFilterTest implements 
MutableThreadContextMapFilter.FilterConfigUpdateListener {
+
+    private static final String BASIC = "Basic ";
+    private static final String expectedCreds = "log4j:log4j";
+    private static Server server;
+    private static final Base64.Decoder decoder = Base64.getDecoder();
+    private static int port;
+    static final String CONFIG = "log4j2-mutableFilter.xml";
+    static LoggerContext loggerContext = null;
+    static final File targetFile = new 
File("target/test-classes/testConfig.json");
+    static final Path target = targetFile.toPath();
+    CountDownLatch updated = new CountDownLatch(1);
+
+
+    @BeforeAll
+    public static void startServer() throws Exception {
+        try {
+            server = new Server(0);
+            ServletContextHandler context = new ServletContextHandler();
+            ServletHolder defaultServ = new ServletHolder("default", 
TestServlet.class);
+            defaultServ.setInitParameter("resourceBase", 
System.getProperty("user.dir"));
+            defaultServ.setInitParameter("dirAllowed", "true");
+            context.addServlet(defaultServ, "/");
+            server.setHandler(context);
+
+            // Start Server
+            server.start();
+            port = ((ServerConnector) 
server.getConnectors()[0]).getLocalPort();
+            try {
+                Files.deleteIfExists(target);
+            } catch (IOException ioe) {
+                // Ignore this.
+            }
+        } catch (Throwable ex) {
+            ex.printStackTrace();
+            throw ex;
+        }
+    }
+
+    @AfterAll
+    public static void stopServer() throws Exception {
+        server.stop();
+    }
+    @AfterEach
+    public void after() {
+        try {
+            Files.deleteIfExists(target);
+        } catch (IOException ioe) {
+            // Ignore this.
+        }
+        loggerContext.stop();
+        loggerContext = null;
+    }
+
+    @Test
+    public void filterTest() throws Exception {
+        System.setProperty("log4j2.Configuration.allowedProtocols", "http");
+        System.setProperty("logging.auth.username", "log4j");
+        System.setProperty("logging.auth.password", "log4j");
+        System.setProperty("configLocation", "http://localhost:"; + port + 
"/testConfig.json");
+        ThreadContext.put("loginId", "rgoers");
+        Path source = new 
File("target/test-classes/emptyConfig.json").toPath();
+        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+        long fileTime = targetFile.lastModified() - 2000;
+        assertTrue(targetFile.setLastModified(fileTime));
+        loggerContext = Configurator.initialize(null, CONFIG);
+        assertNotNull(loggerContext);
+        Appender app = loggerContext.getConfiguration().getAppender("List");
+        assertNotNull(app);
+        assertTrue(app instanceof ListAppender);
+        MutableThreadContextMapFilter filter = (MutableThreadContextMapFilter) 
loggerContext.getConfiguration().getFilter();
+        assertNotNull(filter);
+        filter.registerListener(this);
+        Logger logger = loggerContext.getLogger("Test");
+        logger.debug("This is a test");
+        Assertions.assertEquals(0, ((ListAppender) app).getEvents().size());
+        source = new File("target/test-classes/filterConfig.json").toPath();
+        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+        assertNotEquals(fileTime, targetFile.lastModified());
+        if (!updated.await(5, TimeUnit.SECONDS)) {
+            fail("File update was not detected");
+        }
+        updated = new CountDownLatch(1);
+        logger.debug("This is a test");
+        Assertions.assertEquals(1, ((ListAppender) app).getEvents().size());
+        Assertions.assertTrue(Files.deleteIfExists(target));
+        if (!updated.await(5, TimeUnit.SECONDS)) {
+            fail("File update for delete was not detected");
+        }
+    }
+
+    public static class TestServlet extends DefaultServlet {
+
+        private static final long serialVersionUID = -2885158530511450659L;
+
+        @Override
+        protected void doGet(HttpServletRequest request,
+                HttpServletResponse response) throws ServletException, 
IOException {
+            Enumeration<String> headers = 
request.getHeaders(HttpHeader.AUTHORIZATION.toString());
+            if (headers == null) {
+                response.sendError(401, "No Auth header");
+                return;
+            }
+            while (headers.hasMoreElements()) {
+                String authData = headers.nextElement();
+                Assert.assertTrue("Not a Basic auth header", 
authData.startsWith(BASIC));
+                String credentials = new 
String(decoder.decode(authData.substring(BASIC.length())));
+                if (!expectedCreds.equals(credentials)) {
+                    response.sendError(401, "Invalid credentials");
+                    return;
+                }
+            }
+            if (request.getServletPath().equals("/testConfig.json")) {
+                File file = new File("target/test-classes/testConfig.json");
+                if (!file.exists()) {
+                    response.sendError(404, "File not found");
+                    return;
+                }
+                long modifiedSince = 
request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString());
+                long lastModified = (file.lastModified() / 1000) * 1000;
+                if (modifiedSince > 0 && lastModified <= modifiedSince) {
+                    response.setStatus(304);
+                    return;
+                }
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), 
lastModified);
+                response.setContentLengthLong(file.length());
+                Files.copy(file.toPath(), response.getOutputStream());
+                response.getOutputStream().flush();
+                response.setStatus(200);
+            } else {
+                response.sendError(400, "Unsupported request");
+            }
+        }
+    }
+
+    @Override
+    public void onEvent() {
+        updated.countDown();
+    }
+}
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
new file mode 100644
index 0000000000..ae34b1d45d
--- /dev/null
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Unit test for simple App.
+ */
+public class MutableThreadContextMapFilterTest implements 
MutableThreadContextMapFilter.FilterConfigUpdateListener {
+
+    static final String CONFIG = "log4j2-mutableFilter.xml";
+    static LoggerContext loggerContext = null;
+    static File targetFile = new File("target/test-classes/testConfig.json");
+    static Path target = targetFile.toPath();
+    CountDownLatch updated = new CountDownLatch(1);
+
+    @AfterEach
+    public void after() {
+        try {
+            Files.deleteIfExists(target);
+        } catch (IOException ioe) {
+            // Ignore this.
+        }
+        ThreadContext.clearMap();
+        loggerContext.stop();
+        loggerContext = null;
+    }
+
+    @Test
+    public void filterTest() throws Exception {
+        System.setProperty("configLocation", 
"target/test-classes/testConfig.json");
+        ThreadContext.put("loginId", "rgoers");
+        Path source = new 
File("target/test-classes/emptyConfig.json").toPath();
+        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+        long fileTime = targetFile.lastModified() - 1000;
+        assertTrue(targetFile.setLastModified(fileTime));
+        loggerContext = Configurator.initialize(null, CONFIG);
+        assertNotNull(loggerContext);
+        Appender app = loggerContext.getConfiguration().getAppender("List");
+        assertNotNull(app);
+        assertTrue(app instanceof ListAppender);
+        MutableThreadContextMapFilter filter = (MutableThreadContextMapFilter) 
loggerContext.getConfiguration().getFilter();
+        assertNotNull(filter);
+        filter.registerListener(this);
+        Logger logger = loggerContext.getLogger("Test");
+        logger.debug("This is a test");
+        Assertions.assertEquals(0, ((ListAppender) app).getEvents().size());
+        source = new File("target/test-classes/filterConfig.json").toPath();
+        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+        assertNotEquals(fileTime, targetFile.lastModified());
+        if (!updated.await(5, TimeUnit.SECONDS)) {
+            fail("File update was not detected");
+        }
+        logger.debug("This is a test");
+        Assertions.assertEquals(1, ((ListAppender) app).getEvents().size());
+    }
+
+    @Override
+    public void onEvent() {
+        updated.countDown();
+    }
+}
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
index 16f948efb5..e84e156bf0 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
@@ -68,7 +68,7 @@ public class UrlConnectionFactoryTest {
     private static final String BASIC = "Basic ";
     private static final String expectedCreds = "testuser:password";
     private static Server server;
-    private static Base64.Decoder decoder = Base64.getDecoder();
+    private static final Base64.Decoder decoder = Base64.getDecoder();
     private static int port;
     private static final int BUF_SIZE = 1024;
 
@@ -76,8 +76,8 @@ public class UrlConnectionFactoryTest {
     public static void startServer() throws Exception {
         try {
             server = new Server(0);
-            ServletContextHandler context = new ServletContextHandler();
-            ServletHolder defaultServ = new ServletHolder("default", 
TestServlet.class);
+            final ServletContextHandler context = new ServletContextHandler();
+            final ServletHolder defaultServ = new ServletHolder("default", 
TestServlet.class);
             defaultServ.setInitParameter("resourceBase", 
System.getProperty("user.dir"));
             defaultServ.setInitParameter("dirAllowed", "true");
             context.addServlet(defaultServ, "/");
@@ -102,8 +102,8 @@ public class UrlConnectionFactoryTest {
         System.setProperty("log4j2.Configuration.username", "foo");
         System.setProperty("log4j2.Configuration.password", "bar");
         System.setProperty("log4j2.Configuration.allowedProtocols", "http");
-        URI uri = new URI("http://localhost:"; + port + "/log4j2-config.xml");
-        ConfigurationSource source = ConfigurationSource.fromUri(uri);
+        final URI uri = new URI("http://localhost:"; + port + 
"/log4j2-config.xml");
+        final ConfigurationSource source = ConfigurationSource.fromUri(uri);
         assertNull(source, "A ConfigurationSource should not have been 
returned");
     }
 
@@ -112,16 +112,16 @@ public class UrlConnectionFactoryTest {
         System.setProperty("log4j2.Configuration.username", "testuser");
         System.setProperty("log4j2.Configuration.password", "password");
         System.setProperty("log4j2.Configuration.allowedProtocols", "http");
-        URI uri = new URI("http://localhost:"; + port + "/log4j2-config.xml");
-        ConfigurationSource source = ConfigurationSource.fromUri(uri);
+        final URI uri = new URI("http://localhost:"; + port + 
"/log4j2-config.xml");
+        final ConfigurationSource source = ConfigurationSource.fromUri(uri);
         assertNotNull(source, "No ConfigurationSource returned");
         InputStream is = source.getInputStream();
         assertNotNull(is, "No data returned");
         is.close();
-        long lastModified = source.getLastModified();
+        final long lastModified = source.getLastModified();
         int result = verifyNotModified(uri, lastModified);
         assertEquals(SC_NOT_MODIFIED, result,"File was modified");
-        File file = new File("target/test-classes/log4j2-config.xml");
+        final File file = new File("target/test-classes/log4j2-config.xml");
         if (!file.setLastModified(System.currentTimeMillis())) {
             fail("Unable to set last modified time");
         }
@@ -129,9 +129,9 @@ public class UrlConnectionFactoryTest {
         assertEquals(SC_OK, result,"File was not modified");
     }
 
-    private int verifyNotModified(URI uri, long lastModifiedMillis) throws 
Exception {
+    private int verifyNotModified(final URI uri, final long 
lastModifiedMillis) throws Exception {
         final HttpURLConnection urlConnection = 
UrlConnectionFactory.createConnection(uri.toURL(),
-                lastModifiedMillis, null);
+                lastModifiedMillis, null, null);
         urlConnection.connect();
 
         try {
diff --git a/log4j-core-test/src/test/resources/emptyConfig.json 
b/log4j-core-test/src/test/resources/emptyConfig.json
new file mode 100644
index 0000000000..c3a465a6ab
--- /dev/null
+++ b/log4j-core-test/src/test/resources/emptyConfig.json
@@ -0,0 +1,4 @@
+{
+  "keyValuePairs": {
+  }
+}
\ No newline at end of file
diff --git a/log4j-core-test/src/test/resources/filterConfig.json 
b/log4j-core-test/src/test/resources/filterConfig.json
new file mode 100644
index 0000000000..8857185068
--- /dev/null
+++ b/log4j-core-test/src/test/resources/filterConfig.json
@@ -0,0 +1,6 @@
+{
+  "debugIds": {
+    "loginId": ["rgoers", "adam"],
+    "corpAcctNumber": ["30510263"]
+  }
+}
\ No newline at end of file
diff --git a/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml 
b/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml
new file mode 100644
index 0000000000..46e7e29079
--- /dev/null
+++ b/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<Configuration name="ConfigTest" status="ERROR">
+  <MutableThreadContextMapFilter configLocation="${sys:configLocation}" 
pollInterval="1" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+  <Appenders>
+    <Console name="STDOUT">
+      <PatternLayout pattern="%m%n"/>
+    </Console>
+    <List name="List">
+    </List>
+  </Appenders>
+  <Loggers>
+    <Logger name="Test" level="error">
+      <AppenderRef ref="List"/>
+    </Logger>
+    <Root level="error">
+      <AppenderRef ref="STDOUT"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
index f6eb7e2fe4..221a34ab0a 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
@@ -21,10 +21,14 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.List;
 
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.util.AuthorizationProvider;
+import org.apache.logging.log4j.core.util.internal.HttpInputStreamUtil;
+import org.apache.logging.log4j.core.util.internal.LastModifiedSource;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginAliases;
 import org.apache.logging.log4j.core.net.UrlConnectionFactory;
@@ -34,6 +38,7 @@ import org.apache.logging.log4j.core.util.AbstractWatcher;
 import org.apache.logging.log4j.core.util.Source;
 import org.apache.logging.log4j.core.util.Watcher;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.PropertiesUtil;
 
 /**
  *
@@ -45,16 +50,14 @@ public class HttpWatcher extends AbstractWatcher {
     private final Logger LOGGER = StatusLogger.getLogger();
 
     private final SslConfiguration sslConfiguration;
+    private AuthorizationProvider authorizationProvider;
     private URL url;
     private volatile long lastModifiedMillis;
-    private static final int NOT_MODIFIED = 304;
-    private static final int OK = 200;
-    private static final int BUF_SIZE = 1024;
     private static final String HTTP = "http";
     private static final String HTTPS = "https";
 
     public HttpWatcher(final Configuration configuration, final Reconfigurable 
reconfigurable,
-                       final List<ConfigurationListener> 
configurationListeners, final long lastModifiedMillis) {
+            final List<ConfigurationListener> configurationListeners, final 
long lastModifiedMillis) {
         super(configuration, reconfigurable, configurationListeners);
         sslConfiguration = SslConfigurationFactory.getSslConfiguration();
         this.lastModifiedMillis = lastModifiedMillis;
@@ -74,10 +77,11 @@ public class HttpWatcher extends AbstractWatcher {
     public void watching(final Source source) {
         if (!source.getURI().getScheme().equals(HTTP) && 
!source.getURI().getScheme().equals(HTTPS)) {
             throw new IllegalArgumentException(
-                "HttpWatcher requires a url using the HTTP or HTTPS protocol, 
not " + source.getURI().getScheme());
+                    "HttpWatcher requires a url using the HTTP or HTTPS 
protocol, not " + source.getURI().getScheme());
         }
         try {
             url = source.getURI().toURL();
+            authorizationProvider = 
ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
         } catch (final MalformedURLException ex) {
             throw new IllegalArgumentException("Invalid URL for HttpWatcher " 
+ source.getURI(), ex);
         }
@@ -86,7 +90,7 @@ public class HttpWatcher extends AbstractWatcher {
 
     @Override
     public Watcher newWatcher(final Reconfigurable reconfigurable, final 
List<ConfigurationListener> listeners,
-           final long lastModifiedMillis) {
+            final long lastModifiedMillis) {
         final HttpWatcher watcher = new HttpWatcher(getConfiguration(), 
reconfigurable, listeners, lastModifiedMillis);
         if (getSource() != null) {
             watcher.watching(getSource());
@@ -96,61 +100,37 @@ public class HttpWatcher extends AbstractWatcher {
 
     private boolean refreshConfiguration() {
         try {
-            final HttpURLConnection urlConnection = 
UrlConnectionFactory.createConnection(url, lastModifiedMillis,
-                sslConfiguration);
-            urlConnection.connect();
-
-            try {
-                final int code = urlConnection.getResponseCode();
-                switch (code) {
-                    case NOT_MODIFIED: {
-                        LOGGER.debug("Configuration Not Modified");
-                        return false;
-                    }
-                    case OK: {
-                        try (final InputStream is = 
urlConnection.getInputStream()) {
-                            final ConfigurationSource configSource = 
getConfiguration().getConfigurationSource();
-                            configSource.setData(readStream(is));
-                            lastModifiedMillis = 
urlConnection.getLastModified();
-                            configSource.setModifiedMillis(lastModifiedMillis);
-                            LOGGER.debug("Content was modified for {}", 
url.toString());
-                            return true;
-                        } catch (final IOException e) {
-                            try (final InputStream es = 
urlConnection.getErrorStream()) {
-                                LOGGER.info("Error accessing configuration at 
{}: {}", url, readStream(es));
-                            } catch (final IOException ioe) {
-                                LOGGER.error("Error accessing configuration at 
{}: {}", url, e.getMessage());
-                            }
-                            return false;
-                        }
-                    }
-                    default: {
-                        if (code < 0) {
-                            LOGGER.info("Invalid response code returned");
-                        } else {
-                            LOGGER.info("Unexpected response code returned 
{}", code);
-                        }
+            final LastModifiedSource source = new 
LastModifiedSource(url.toURI(), lastModifiedMillis);
+            final HttpInputStreamUtil.Result result = 
HttpInputStreamUtil.getInputStream(source, authorizationProvider);
+            switch (result.getStatus()) {
+                case NOT_MODIFIED: {
+                    LOGGER.debug("Configuration Not Modified");
+                    return false;
+                }
+                case SUCCESS: {
+                    final ConfigurationSource configSource = 
getConfiguration().getConfigurationSource();
+                    try {
+                        
configSource.setData(HttpInputStreamUtil.readStream(result.getInputStream()));
+                        
configSource.setModifiedMillis(source.getLastModified());
+                        LOGGER.debug("Content was modified for {}", 
url.toString());
+                        return true;
+                    } catch (final IOException e) {
+                        LOGGER.error("Error accessing configuration at {}: 
{}", url, e.getMessage());
                         return false;
                     }
                 }
-            } catch (final IOException ioe) {
-                LOGGER.error("Error accessing configuration at {}: {}", url, 
ioe.getMessage());
-            } finally {
-                urlConnection.disconnect();
+                case NOT_FOUND: {
+                    LOGGER.info("Unable to locate configuration at {}", 
url.toString());
+                    return false;
+                }
+                default: {
+                    LOGGER.warn("Unexpected error accessing configuration at 
{}", url.toString());
+                    return false;
+                }
             }
-        } catch (final IOException ioe) {
-            LOGGER.error("Error connecting to configuration at {}: {}", url, 
ioe.getMessage());
-        }
-        return false;
-    }
-
-    private byte[] readStream(final InputStream is) throws IOException {
-        final ByteArrayOutputStream result = new ByteArrayOutputStream();
-        final byte[] buffer = new byte[BUF_SIZE];
-        int length;
-        while ((length = is.read(buffer)) != -1) {
-            result.write(buffer, 0, length);
+        } catch(final URISyntaxException ex) {
+            LOGGER.error("Bad configuration URL: {}, {}", url.toString(), 
ex.getMessage());
+            return false;
         }
-        return result.toByteArray();
     }
 }
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
new file mode 100644
index 0000000000..6fa8682304
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
@@ -0,0 +1,411 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationException;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationScheduler;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.filter.mutable.KeyValuePairConfig;
+import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
+import org.apache.logging.log4j.core.util.AuthorizationProvider;
+import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.core.util.internal.HttpInputStreamUtil;
+import org.apache.logging.log4j.core.util.internal.LastModifiedSource;
+import org.apache.logging.log4j.core.util.internal.Status;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Filter based on a value in the Thread Context Map (MDC).
+ */
+@Plugin(name = "MutableThreadContextMapFilter", category = Node.CATEGORY, 
elementType = Filter.ELEMENT_TYPE, printObject = true)
+@PluginAliases("MutableContextMapFilter")
+@PerformanceSensitive("allocation")
+public class MutableThreadContextMapFilter extends AbstractFilter {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private volatile Filter filter;
+    private final long pollInterval;
+    private final ConfigurationScheduler scheduler;
+    private final LastModifiedSource source;
+    private final AuthorizationProvider authorizationProvider;
+    private final List<FilterConfigUpdateListener> listeners = new 
ArrayList<>();
+    private ScheduledFuture<?> future = null;
+
+    private MutableThreadContextMapFilter(final Filter filter, final 
LastModifiedSource source,
+            final long pollInterval, final AuthorizationProvider 
authorizationProvider,
+            final Result onMatch, final Result onMismatch, final Configuration 
configuration) {
+        super(onMatch, onMismatch);
+        this.filter = filter;
+        this.pollInterval = pollInterval;
+        this.source = source;
+        this.scheduler = configuration.getScheduler();
+        this.authorizationProvider = authorizationProvider;
+    }
+
+    @Override
+    public void start() {
+
+        if (pollInterval > 0) {
+            future = scheduler.scheduleWithFixedDelay(new FileMonitor(), 0, 
pollInterval, TimeUnit.SECONDS);
+            LOGGER.debug("Watching {} with poll interval {}", 
source.toString(), pollInterval);
+        }
+        super.start();
+    }
+
+    @Override
+    public boolean stop(long timeout, TimeUnit timeUnit) {
+        future.cancel(true);
+        return super.stop(timeout, timeUnit);
+    }
+
+    public void registerListener(FilterConfigUpdateListener listener) {
+        listeners.add(listener);
+    }
+
+    @PluginFactory
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    @Override
+    public Result filter(LogEvent event) {
+        return filter.filter(event);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, Message 
msg, Throwable t) {
+        return filter.filter(logger, level, marker, msg, t);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, Object 
msg, Throwable t) {
+        return filter.filter(logger, level, marker, msg, t);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object... params) {
+        return filter.filter(logger, level, marker, msg, params);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0) {
+        return filter.filter(logger, level, marker, msg, p0);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1) {
+        return filter.filter(logger, level, marker, msg, p0, p1);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3, Object p4) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3, Object p4, Object p5) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, 
p5);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3, Object p4, Object p5, Object p6) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, 
p5, p6);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, 
p5, p6, p7);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, 
Object p8) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, 
p5, p6, p7, p8);
+    }
+
+    @Override
+    public Result filter(Logger logger, Level level, Marker marker, String 
msg, Object p0,
+        Object p1,
+        Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, 
Object p8, Object p9) {
+        return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, 
p5, p6, p7, p8, p9);
+    }
+
+    public static class Builder extends AbstractFilterBuilder<Builder>
+            implements 
org.apache.logging.log4j.core.util.Builder<MutableThreadContextMapFilter> {
+        @PluginAttribute
+        private String configLocation;
+
+        @PluginAttribute
+        private long pollInterval;
+
+        @PluginConfiguration
+        private Configuration configuration;
+
+        /**
+         * Sets the Configuration.
+         * @param configuration The Configuration.
+         * @return this.
+         */
+        public Builder setConfiguration(final Configuration configuration) {
+            this.configuration = configuration;
+            return this;
+        }
+
+        /**
+         * Set the frequency in seconds that changes to the list a 
ThreadContext valudes should be
+         * checked.
+         * @param pollInterval interval in seconds to check the file for 
changes.
+         * @return this.
+         */
+        public Builder setPollInterval(final long pollInterval) {
+            this.pollInterval = pollInterval;
+            return this;
+        }
+
+        /**
+         * Sets the configuration to use.
+         * @param configLocation the location of the configuration.
+         * @return this
+         */
+        public Builder setConfigLocation(final String configLocation) {
+            this.configLocation = configLocation;
+            return this;
+        }
+
+        @Override
+        public MutableThreadContextMapFilter build() {
+            final LastModifiedSource source = getSource(configLocation);
+            if (source == null) {
+               return new MutableThreadContextMapFilter(new NoOpFilter(), 
null, 0,
+                       null, getOnMatch(), getOnMismatch(), configuration);
+            }
+            final AuthorizationProvider authorizationProvider =
+                    
ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
+            final ConfigResult result = getConfig(source, 
authorizationProvider);
+            Filter filter;
+            if (result.status == Status.SUCCESS) {
+                if (result.pairs.length > 0) {
+                    filter = ThreadContextMapFilter.newBuilder()
+                            .setPairs(result.pairs)
+                            .setOperator("or")
+                            .setOnMatch(getOnMatch())
+                            .setOnMismatch(getOnMismatch())
+                            
.setContextDataInjector(ContextDataInjectorFactory.createInjector())
+                            .get();
+                } else {
+                    filter = new NoOpFilter();
+                }
+            } else if (result.status == Status.NOT_FOUND || result.status == 
Status.EMPTY) {
+                filter = new NoOpFilter();
+            } else {
+                LOGGER.warn("Unexpected response returned on initial call: 
{}", result.status);
+                filter = new NoOpFilter();
+            }
+
+            if (pollInterval > 0) {
+                configuration.getScheduler().incrementScheduledItems();
+            }
+            return new MutableThreadContextMapFilter(filter, source, 
pollInterval, authorizationProvider,
+                    getOnMatch(), getOnMismatch(), configuration);
+        }
+    }
+
+    private class FileMonitor implements Runnable {
+
+        @Override
+        public void run() {
+            final ConfigResult result = getConfig(source, 
authorizationProvider);
+            if (result.status == Status.SUCCESS) {
+                filter = ThreadContextMapFilter.newBuilder()
+                        .setPairs(result.pairs)
+                        .setOperator("or")
+                        .setOnMatch(getOnMatch())
+                        .setOnMismatch(getOnMismatch())
+                        
.setContextDataInjector(ContextDataInjectorFactory.createInjector())
+                        .get();
+                LOGGER.info("Filter configuration was updated: {}", 
filter.toString());
+                for (FilterConfigUpdateListener listener : listeners) {
+                    listener.onEvent();
+                }
+            } else if (result.status == Status.NOT_FOUND) {
+                if (!(filter instanceof NoOpFilter)) {
+                    LOGGER.info("Filter configuration was removed");
+                    filter = new NoOpFilter();
+                    for (FilterConfigUpdateListener listener : listeners) {
+                        listener.onEvent();
+                    }
+                }
+            } else if (result.status == Status.EMPTY) {
+                LOGGER.debug("Filter configuration is empty");
+                filter = new NoOpFilter();
+            }
+        }
+    }
+
+    private static LastModifiedSource getSource(final String configLocation) {
+        LastModifiedSource source = null;
+        try {
+            final URI uri = new URI(configLocation);
+            if (uri.getScheme() != null) {
+                source = new LastModifiedSource(new URI(configLocation));
+            } else {
+                source = new LastModifiedSource(new File(configLocation));
+            }
+
+        } catch (Exception ex) {
+            source = new LastModifiedSource(new File(configLocation));
+        }
+        return source;
+    }
+
+    private static ConfigResult getConfig(final LastModifiedSource source,
+            final AuthorizationProvider authorizationProvider) {
+        final File inputFile = source.getFile();
+        InputStream inputStream = null;
+        HttpInputStreamUtil.Result result = null;
+        final long lastModified = source.getLastModified();
+        if (inputFile != null && inputFile.exists()) {
+            try {
+                final long modified = inputFile.lastModified();
+                if (modified > lastModified) {
+                    source.setLastModified(modified);
+                    inputStream = new FileInputStream(inputFile);
+                    result = new HttpInputStreamUtil.Result(Status.SUCCESS);
+                } else {
+                    result = new 
HttpInputStreamUtil.Result(Status.NOT_MODIFIED);
+                }
+            } catch (Exception ex) {
+                result = new HttpInputStreamUtil.Result(Status.ERROR);
+            }
+        } else if (source.getURI() != null) {
+            try {
+                result = HttpInputStreamUtil.getInputStream(source, 
authorizationProvider);
+                inputStream = result.getInputStream();
+            } catch (ConfigurationException ex) {
+                result = new HttpInputStreamUtil.Result(Status.ERROR);
+            }
+        } else {
+            result = new HttpInputStreamUtil.Result(Status.NOT_FOUND);
+        }
+        final ConfigResult configResult = new ConfigResult();
+        if (result.getStatus() == Status.SUCCESS) {
+            LOGGER.debug("Processing Debug key/value pairs from: {}", 
source.toString());
+            try {
+                final KeyValuePairConfig config = 
MAPPER.readValue(inputStream, KeyValuePairConfig.class);
+                if (config != null && config.getdebugIds() != null) {
+                    if (config.getdebugIds().size() > 0) {
+                        final List<KeyValuePair> pairs = new ArrayList<>();
+                        for (Map.Entry<String, String[]> entry : 
config.getdebugIds().entrySet()) {
+                            final String key = entry.getKey();
+                            for (final String value : entry.getValue()) {
+                                if (value != null) {
+                                    pairs.add(new KeyValuePair(key, value));
+                                } else {
+                                    LOGGER.warn("Ignoring null value for {}", 
key);
+                                }
+                            }
+                        }
+                        if (pairs.size() > 0) {
+                            configResult.pairs = pairs.toArray(new 
KeyValuePair[0]);
+                            configResult.status = Status.SUCCESS;
+                        } else {
+                            configResult.status = Status.EMPTY;
+                        }
+                    } else {
+                        configResult.status = Status.EMPTY;
+                    }
+                } else {
+                    LOGGER.warn("No debugIds element in ThreadContextMapFilter 
configuration");
+                    configResult.status = Status.ERROR;
+                }
+            } catch (Exception ex) {
+                LOGGER.warn("Invalid key/value pair configuration, input 
ignored: {}", ex.getMessage());
+                configResult.status = Status.ERROR;
+            }
+        } else {
+            configResult.status = result.getStatus();
+        }
+        return configResult;
+    }
+
+    private static class NoOpFilter extends AbstractFilter {
+
+        public NoOpFilter() {
+            super(Result.NEUTRAL, Result.NEUTRAL);
+        }
+    }
+
+    public interface FilterConfigUpdateListener {
+        void onEvent();
+    }
+
+    private static class ConfigResult extends HttpInputStreamUtil.Result {
+        public KeyValuePair[] pairs;
+        public Status status;
+    }
+}
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/mutable/KeyValuePairConfig.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/mutable/KeyValuePairConfig.java
new file mode 100644
index 0000000000..e0372e6146
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/mutable/KeyValuePairConfig.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.filter.mutable;
+
+import java.util.Map;
+
+/**
+ * Class representing the configuration of KeyValue pairs.
+ */
+public class KeyValuePairConfig {
+
+    /**
+     * Map of keys and values for the MutableThreadContextMapFilter. Example 
file:
+     * <pre>
+     *   {
+     *     "debugIds": {
+     *       "loginId": ["rgoers", "adam"],
+     *       "accountNumber": ["30510263"]
+     *   }
+     * }
+     * </pre>
+     */
+    private Map<String, String[]> debugIds;
+
+    public Map<String, String[]> getdebugIds() {
+        return debugIds;
+    }
+
+    public void setKeyValuePairs(Map<String, String[]> debugIds) {
+        this.debugIds = debugIds;
+    }
+}
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
index fc7214175d..cd9cb99e54 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
@@ -25,7 +25,6 @@ import java.net.URLConnection;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-
 import javax.net.ssl.HttpsURLConnection;
 
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
@@ -50,14 +49,18 @@ public class UrlConnectionFactory {
     private static final String TEXT = "text/plain";
     private static final String HTTP = "http";
     private static final String HTTPS = "https";
+    private static final String JAR = "jar";
+    private static final String DEFAULT_ALLOWED_PROTOCOLS = "https, file, jar";
     private static final String NO_PROTOCOLS = "_none";
     public static final String ALLOWED_PROTOCOLS = 
"log4j2.Configuration.allowedProtocols";
 
-    public static HttpURLConnection createConnection(final URL url, final long 
lastModifiedMillis, final SslConfiguration sslConfiguration)
+    @SuppressWarnings("unchecked")
+    public static <T extends URLConnection> T createConnection(final URL url, 
final long lastModifiedMillis,
+            final SslConfiguration sslConfiguration, final 
AuthorizationProvider authorizationProvider)
         throws IOException {
         final PropertiesUtil props = PropertiesUtil.getProperties();
         final List<String> allowed = Arrays.asList(Strings.splitList(props
-                .getStringProperty(ALLOWED_PROTOCOLS, 
HTTPS).toLowerCase(Locale.ROOT)));
+                .getStringProperty(ALLOWED_PROTOCOLS, 
DEFAULT_ALLOWED_PROTOCOLS).toLowerCase(Locale.ROOT)));
         if (allowed.size() == 1 && NO_PROTOCOLS.equals(allowed.get(0))) {
             throw new ProtocolException("No external protocols have been 
enabled");
         }
@@ -68,42 +71,50 @@ public class UrlConnectionFactory {
         if (!allowed.contains(protocol)) {
             throw new ProtocolException("Protocol " + protocol + " has not 
been enabled as an allowed protocol");
         }
-        final HttpURLConnection urlConnection = (HttpURLConnection) 
url.openConnection();
-
-        final AuthorizationProvider provider = 
ConfigurationFactory.authorizationProvider(props);
-        if (provider != null) {
-            provider.addAuthorization(urlConnection);
-        }
-        urlConnection.setAllowUserInteraction(false);
-        urlConnection.setDoOutput(true);
-        urlConnection.setDoInput(true);
-        urlConnection.setRequestMethod("GET");
-        if (connectTimeoutMillis > 0) {
-            urlConnection.setConnectTimeout(connectTimeoutMillis);
-        }
-        if (readTimeoutMillis > 0) {
-            urlConnection.setReadTimeout(readTimeoutMillis);
-        }
-        final String[] fileParts = url.getFile().split("\\.");
-        final String type = fileParts[fileParts.length - 1].trim();
-        final String contentType = isXml(type) ? XML : isJson(type) ? JSON : 
isProperties(type) ? PROPERTIES : TEXT;
-        urlConnection.setRequestProperty("Content-Type", contentType);
-        if (lastModifiedMillis > 0) {
-            urlConnection.setIfModifiedSince(lastModifiedMillis);
-        }
-        if (url.getProtocol().equals(HTTPS) && sslConfiguration != null) {
-            ((HttpsURLConnection) 
urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory());
-            if (!sslConfiguration.isVerifyHostName()) {
-                ((HttpsURLConnection) 
urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE);
+        URLConnection urlConnection;
+        if (url.getProtocol().equals(HTTP) || url.getProtocol().equals(HTTPS)) 
{
+            final HttpURLConnection httpURLConnection = (HttpURLConnection) 
url.openConnection();
+            if (authorizationProvider != null) {
+                authorizationProvider.addAuthorization(httpURLConnection);
+            }
+            httpURLConnection.setAllowUserInteraction(false);
+            httpURLConnection.setDoOutput(true);
+            httpURLConnection.setDoInput(true);
+            httpURLConnection.setRequestMethod("GET");
+            if (connectTimeoutMillis > 0) {
+                httpURLConnection.setConnectTimeout(connectTimeoutMillis);
             }
+            if (readTimeoutMillis > 0) {
+                httpURLConnection.setReadTimeout(readTimeoutMillis);
+            }
+            final String[] fileParts = url.getFile().split("\\.");
+            final String type = fileParts[fileParts.length - 1].trim();
+            final String contentType = isXml(type) ? XML : isJson(type) ? JSON 
: isProperties(type) ? PROPERTIES : TEXT;
+            httpURLConnection.setRequestProperty("Content-Type", contentType);
+            if (lastModifiedMillis > 0) {
+                httpURLConnection.setIfModifiedSince(lastModifiedMillis);
+            }
+            if (url.getProtocol().equals(HTTPS) && sslConfiguration != null) {
+                ((HttpsURLConnection) 
httpURLConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory());
+                if (!sslConfiguration.isVerifyHostName()) {
+                    ((HttpsURLConnection) 
httpURLConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE);
+                }
+            }
+            urlConnection = httpURLConnection;
+        } else if (url.getProtocol().equals(JAR)) {
+            urlConnection = url.openConnection();
+            urlConnection.setUseCaches(false);
+        } else {
+            urlConnection = url.openConnection();
         }
-        return urlConnection;
+        return (T) urlConnection;
     }
 
     public static URLConnection createConnection(final URL url) throws 
IOException {
         URLConnection urlConnection = null;
         if (url.getProtocol().equals(HTTPS) || url.getProtocol().equals(HTTP)) 
{
-            urlConnection = createConnection(url, 0, 
SslConfigurationFactory.getSslConfiguration());
+            final AuthorizationProvider provider = 
ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
+            urlConnection = createConnection(url, 0, 
SslConfigurationFactory.getSslConfiguration(), provider);
         } else {
             urlConnection = url.openConnection();
             if (urlConnection instanceof JarURLConnection) {
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
new file mode 100644
index 0000000000..a5458ea5ea
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
@@ -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.
+ */
+package org.apache.logging.log4j.core.util.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.ConfigurationException;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
+import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
+import org.apache.logging.log4j.core.util.AuthorizationProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Utility method for reading data from an HTTP InputStream.
+ */
+public final class HttpInputStreamUtil {
+
+    private static final Logger LOGGER = StatusLogger.getLogger();
+    private static final int NOT_MODIFIED = 304;
+    private static final int NOT_AUTHORIZED = 401;
+    private static final int NOT_FOUND = 404;
+    private static final int OK = 200;
+    private static final int BUF_SIZE = 1024;
+
+    public static Result getInputStream(final LastModifiedSource source,
+            final AuthorizationProvider authorizationProvider) {
+        final Result result = new Result();
+        try {
+            long lastModified = source.getLastModified();
+            HttpURLConnection connection = 
UrlConnectionFactory.createConnection(source.getURI().toURL(),
+                    lastModified, 
SslConfigurationFactory.getSslConfiguration(), authorizationProvider);
+            connection.connect();
+            try {
+                int code = connection.getResponseCode();
+                switch (code) {
+                    case NOT_MODIFIED: {
+                        LOGGER.debug("Configuration not modified");
+                        result.status = Status.NOT_MODIFIED;
+                        return result;
+                    }
+                    case NOT_FOUND: {
+                        LOGGER.debug("Unable to access {}: Not Found", 
source.toString());
+                        result.status = Status.NOT_FOUND;
+                        return result;
+                    }
+                    case OK: {
+                        try (InputStream is = connection.getInputStream()) {
+                            
source.setLastModified(connection.getLastModified());
+                            LOGGER.debug("Content was modified for {}. 
previous lastModified: {}, new lastModified: {}",
+                                    source.toString(), lastModified, 
connection.getLastModified());
+                            result.status = Status.SUCCESS;
+                            result.inputStream = new 
ByteArrayInputStream(readStream(is));
+                            return result;
+                        } catch (final IOException e) {
+                            try (InputStream es = connection.getErrorStream()) 
{
+                                LOGGER.info("Error accessing configuration at 
{}: {}", source.toString(),
+                                        readStream(es));
+                            } catch (final IOException ioe) {
+                                LOGGER.error("Error accessing configuration at 
{}: {}", source.toString(),
+                                        e.getMessage());
+                            }
+                            throw new ConfigurationException("Unable to access 
" + source.toString(), e);
+                        }
+                    }
+                    case NOT_AUTHORIZED: {
+                        throw new ConfigurationException("Authorization 
failed");
+                    }
+                    default: {
+                        if (code < 0) {
+                            LOGGER.info("Invalid response code returned");
+                        } else {
+                            LOGGER.info("Unexpected response code returned 
{}", code);
+                        }
+                        throw new ConfigurationException("Unable to access " + 
source.toString());
+                    }
+                }
+            } finally {
+                connection.disconnect();
+            }
+        } catch (IOException e) {
+            LOGGER.warn("Error accessing {}: {}", source.toString(), 
e.getMessage());
+            throw new ConfigurationException("Unable to access " + 
source.toString(), e);
+        }
+    }
+
+    public static byte[] readStream(final InputStream is) throws IOException {
+        final ByteArrayOutputStream result = new ByteArrayOutputStream();
+        final byte[] buffer = new byte[BUF_SIZE];
+        int length;
+        while ((length = is.read(buffer)) != -1) {
+            result.write(buffer, 0, length);
+        }
+        return result.toByteArray();
+    }
+
+    public static class Result {
+
+        private InputStream inputStream;
+        private Status status;
+
+        public Result() {
+        }
+
+        public Result(Status status) {
+            this.status = status;
+        }
+
+        public InputStream getInputStream() {
+            return inputStream;
+        }
+
+        public Status getStatus() {
+            return status;
+        }
+    }
+}
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/LastModifiedSource.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/LastModifiedSource.java
new file mode 100644
index 0000000000..9b2817bc11
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/LastModifiedSource.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.util.internal;
+
+import java.io.File;
+import java.net.URI;
+
+import org.apache.logging.log4j.core.util.Source;
+
+/**
+ * A Source that includes the last modified time.
+ */
+public class LastModifiedSource extends Source {
+    private volatile long lastModified;
+
+    public LastModifiedSource(final File file) {
+        super(file);
+        lastModified = 0;
+    }
+
+    public LastModifiedSource(final URI uri) {
+        this(uri, 0);
+    }
+
+    public LastModifiedSource(final URI uri, long lastModifiedMillis) {
+        super(uri);
+        lastModified = lastModifiedMillis;
+    }
+
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    public void setLastModified(long lastModified) {
+        this.lastModified = lastModified;
+    }
+}
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/Status.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/Status.java
new file mode 100644
index 0000000000..baf12c2c92
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/Status.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+package org.apache.logging.log4j.core.util.internal;
+
+/**
+ * Status from reading the InputStream.
+ * This class should be considered internal to the Log4j implementation.
+ */
+public enum Status {
+    SUCCESS, NOT_MODIFIED, NOT_FOUND, ERROR, EMPTY;
+}
diff --git a/log4j-core/src/main/resources/Log4j-config.xsd 
b/log4j-core/src/main/resources/Log4j-config.xsd
index 79eaac7848..8c173e2483 100644
--- a/log4j-core/src/main/resources/Log4j-config.xsd
+++ b/log4j-core/src/main/resources/Log4j-config.xsd
@@ -29,6 +29,13 @@
    <element name="Configuration">
       <complexType>
          <sequence>
+            <choice minOccurs="0">
+               <element name="AsyncWaitStrategyFactory">
+                  <complexType>
+                     <attribute name="class" type="string" use="required"/>
+                  </complexType>
+               </element>
+            </choice>
             <choice minOccurs="0">
                <element name="CustomLevels" type="tns:CustomLevelsType">
                   <unique name="UniqueCustomLevelName">
@@ -158,7 +165,6 @@
       </complexType>
    </element>
 
-
    <!--
     
================================================================================================================================
     <Configuration><CustomLevels> type and related types
@@ -194,6 +200,7 @@
          <element name="DynamicThresholdFilter" 
type="tns:DynamicThresholdFilterType" minOccurs="0" />
          <element name="MapFilter" type="tns:MapFilterType" minOccurs="0" />
          <element name="MarkerFilter" type="tns:MarkerFilterType" 
minOccurs="0" />
+         <element name="MutableThreadContextMapFilter" 
type="tns:MutableThreadContextMapFilterType" minOccurs="0" />
          <element name="NoMarkerFilter" type="tns:NoMarkerFilterType" 
minOccurs="0" />
          <element name="RegexFilter" type="tns:RegexFilterType" minOccurs="0" 
/>
          <element name="ScriptFilter" type="tns:ScriptFilterType" 
minOccurs="0" />
@@ -305,6 +312,35 @@
       </complexContent>
    </complexType>
 
+   <complexType name="MutableThreadContextMapFilterType">
+      <complexContent>
+         <extension base="tns:AbstractFilterType">
+            <attribute name="configLocation" type="string">
+               <annotation>
+                  <documentation>
+                     The location of the configuration for the filter.
+                  </documentation>
+               </annotation>
+            </attribute>
+            <attribute name="pollInterval" type="int">
+               <annotation>
+                  <documentation>
+                     The frequency in seconds that the configuration file 
should be checked for modifications.
+                  </documentation>
+               </annotation>
+            </attribute>
+            <attribute name="operator" type="string">
+               <annotation>
+                  <documentation>
+                     If the operator is "or" then a match by any one of the 
key/value pairs will be considered to be a match, otherwise all the
+                     key/value pairs must match.
+                  </documentation>
+               </annotation>
+            </attribute>
+         </extension>
+      </complexContent>
+   </complexType>
+
    <complexType name="MapFilterType">
       <complexContent>
          <extension base="tns:AbstractFilterTypeWithKeyValuePairs">
diff --git 
a/log4j-spring-boot/src/main/java/org/apache/logging/log4j/spring/boot/Log4j2CloudConfigLoggingSystem.java
 
b/log4j-spring-boot/src/main/java/org/apache/logging/log4j/spring/boot/Log4j2CloudConfigLoggingSystem.java
index 8705742f80..65901b14be 100644
--- 
a/log4j-spring-boot/src/main/java/org/apache/logging/log4j/spring/boot/Log4j2CloudConfigLoggingSystem.java
+++ 
b/log4j-spring-boot/src/main/java/org/apache/logging/log4j/spring/boot/Log4j2CloudConfigLoggingSystem.java
@@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
 import org.apache.logging.log4j.core.config.ConfigurationSource;
 import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
 import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier;
 import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
 import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
@@ -190,20 +191,12 @@ public class Log4j2CloudConfigLoggingSystem extends 
Log4J2LoggingSystem {
     }
 
     private ConfigurationSource getConfigurationSource(URL url) throws 
IOException, URISyntaxException {
-        URLConnection urlConnection = url.openConnection();
-        // A "jar:" URL file remains open after the stream is closed, so do 
not cache it.
-        urlConnection.setUseCaches(false);
         AuthorizationProvider provider = 
ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
-        provider.addAuthorization(urlConnection);
-        if (url.getProtocol().equals(HTTPS)) {
-            SslConfiguration sslConfiguration = 
SslConfigurationFactory.getSslConfiguration();
-            if (sslConfiguration != null) {
-                ((HttpsURLConnection) 
urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory());
-                if (!sslConfiguration.isVerifyHostName()) {
-                    ((HttpsURLConnection) 
urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE);
-                }
-            }
-        }
+        SslConfiguration sslConfiguration = url.getProtocol().equals(HTTPS)
+                ? SslConfigurationFactory.getSslConfiguration() : null;
+        URLConnection urlConnection = 
UrlConnectionFactory.createConnection(url, 0, sslConfiguration,
+                provider);
+
         File file = FileUtils.fileFromUri(url.toURI());
         try {
             if (file != null) {
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index c8017e5093..f940f7addd 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -191,8 +191,14 @@
                Fixes incorrect constructor call in LocalizedMessageFactory.
       </action>
     </release>
-    <release version="2.17.3" date="20YY-MM-DD" description="GA Release 
2.17.3">
+    <release version="2.18.0" date="20YY-MM-DD" description="GA Release 
2.17.3">
       <!-- FIXES -->
+      <action issue="LOG4J2-3481" dev="rgoers" type="fix">
+        HttpWatcher did not pass credentials when polling.
+      </action>
+      <action issue="LOG4J2-3482" dev="rgoers" type="fix">
+        UrlConnectionFactory.createConnection now accepts an 
AuthorizationProvider as a parameter.
+      </action>
       <action issue="LOG4J2-3477" dev="vy" type="fix" due-to="filipc">
         Add the missing context stack to JsonLayout template.
       </action>
@@ -217,6 +223,10 @@
       <action issue="LOG4J2-3475" dev="jjlin" type="fix" due-to="Jeremy Lin">
         Add missing message parameterization in RegexFilter.
       </action>
+      <!-- Additions -->
+      <action issue="LOG4J2-3495" dev="rgoers" type="fix">
+        Add MutableThreadContextMapFilter.
+      </action>
     </release>
     <release version="2.17.2" date="2022-02-23" description="GA Release 
2.17.2">
       <!-- FIXES -->
diff --git a/src/site/asciidoc/manual/filters.adoc 
b/src/site/asciidoc/manual/filters.adoc
index 7a1ade77c0..26046b44ef 100644
--- a/src/site/asciidoc/manual/filters.adoc
+++ b/src/site/asciidoc/manual/filters.adoc
@@ -397,6 +397,77 @@ appender if the Marker matches:
 </Configuration>
 ----
 
+[#MutableThreadContextMapFilter]
+== MutableThreadContextMapFilter
+
+The MutableThreadContextMapFilter or MutableContextMapFilter allows filtering 
against data elements that are in the current context. By default this is the 
ThreadContext Map. The values to compare are defined externally and can be 
periodically polled for changes.
+
+.Mutable Context Map Filter Parameters
+[cols="1m,1,4"]
+|===
+|Parameter Name |Type |Description
+
+|configLocation
+|String
+|A file path or URI that points to the configuration. See below for a sample 
configuration.
+
+|operator
+|String
+|If the operator is "or" then a match by any one of
+the key/value pairs will be considered to be a match, otherwise all the
+key/value pairs must match.
+
+|pollInterval
+|int
+|The number of seconds to wait before checking to see if the configuration has 
been modified. When using HTTP or HTTPS the server must support the 
If-Modified-Since header and return a Last-Modified header containing the date 
and time the file was last modified. Note that by default only the https, file, 
and jar protocols are allowed. Support for other protocols can be enabled by 
specifying them in the log4j2.Configuration.allowedProtocols system property
+
+|onMatch
+|String
+|Action to take when the filter matches. May be ACCEPT,
+DENY or NEUTRAL. The default value is NEUTRAL.
+
+|onMismatch
+|String
+|Action to take when the filter does not match. May
+be ACCEPT, DENY or NEUTRAL. The default value is DENY.
+|===
+
+A sample configuration that only allows the event to be written by the
+appender if the Marker matches:
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="warn" name="MyApp" packages="">
+  <MutableContextMapFilter onMatch="ACCEPT" onMismatch="NEUTRAL" operator="or"
+    configLocation="http://localhost:8080/threadContextFilter.json"; 
pollInterval="300"/>
+  <Appenders>
+    <RollingFile name="RollingFile" fileName="logs/app.log"
+                 filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
+      <BurstFilter level="INFO" rate="16" maxBurst="100"/>
+      <PatternLayout>
+        <pattern>%d %p %c{1.} [%t] %m%n</pattern>
+      </PatternLayout>
+      <TimeBasedTriggeringPolicy />
+    </RollingFile>
+  </Appenders>
+  <Loggers>
+    <Root level="error">
+      <AppenderRef ref="RollingFile"/>
+    </Root>
+  </Loggers>
+----
+The configuration file supplied to the filter should look similar to:
+[source,json]
+----
+{
+  "debugIds": {
+    "loginId": ["[email protected]", "[email protected]"],
+    "accountNumber": ["30510263"]
+  }
+}
+----
+
 [#NoMarkerFilter]
 == NoMarkerFilter
 

Reply via email to