This is an automated email from the ASF dual-hosted git repository.
gus pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_10x by this push:
new a8c8f6270f5 SOLR-18042 Manage MDC Logging Context setup/teardown in a
servlet filter. (#3985) (#3996)
a8c8f6270f5 is described below
commit a8c8f6270f53ae1454624814c48c8e5533d21be5
Author: Gus Heck <[email protected]>
AuthorDate: Fri Jan 2 08:11:40 2026 -0500
SOLR-18042 Manage MDC Logging Context setup/teardown in a servlet filter.
(#3985) (#3996)
---
changelog/unreleased/SOLR-18042.yml | 8 +++
.../solr/servlet/CoreContainerAwareHttpFilter.java | 59 ++++++++++++++++++++++
.../apache/solr/servlet/CoreContainerProvider.java | 20 ++++----
.../org/apache/solr/servlet/MdcLoggingFilter.java | 57 +++++++++++++++++++++
.../apache/solr/servlet/SolrDispatchFilter.java | 58 +++++++--------------
.../org/apache/solr/embedded/JettySolrRunner.java | 7 +++
solr/webapp/web/WEB-INF/web.xml | 10 ++++
7 files changed, 167 insertions(+), 52 deletions(-)
diff --git a/changelog/unreleased/SOLR-18042.yml
b/changelog/unreleased/SOLR-18042.yml
new file mode 100644
index 00000000000..73ea60967df
--- /dev/null
+++ b/changelog/unreleased/SOLR-18042.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: SOLR 18042 - MDC Logging context lifecycle is now managed by a servlet
filter
+type: other # added, changed, fixed, deprecated, removed, dependency_update,
security, other
+authors:
+ - name: Gus Heck
+links:
+ - name: SOLR-18042
+ url: https://issues.apache.org/jira/browse/SOLR-18042
diff --git
a/solr/core/src/java/org/apache/solr/servlet/CoreContainerAwareHttpFilter.java
b/solr/core/src/java/org/apache/solr/servlet/CoreContainerAwareHttpFilter.java
new file mode 100644
index 00000000000..bfddf0357b7
--- /dev/null
+++
b/solr/core/src/java/org/apache/solr/servlet/CoreContainerAwareHttpFilter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.solr.servlet;
+
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.UnavailableException;
+import jakarta.servlet.http.HttpFilter;
+import java.lang.invoke.MethodHandles;
+import org.apache.solr.core.CoreContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** A superclass for filters that will need to interact with the
CoreContainer. */
+public abstract class CoreContainerAwareHttpFilter extends HttpFilter {
+ private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private CoreContainerProvider containerProvider;
+
+ @Override
+ public void init(FilterConfig config) throws ServletException {
+ super.init(config);
+ containerProvider =
CoreContainerProvider.serviceForContext(config.getServletContext());
+ if (log.isTraceEnabled()) {
+ log.trace("{}.init(): {}", this.getClass().getName(),
this.getClass().getClassLoader());
+ }
+ }
+
+ /**
+ * The CoreContainer. It's guaranteed to be constructed before this filter
is initialized, but
+ * could have been shut down. Never null.
+ */
+ public CoreContainer getCores() throws UnavailableException {
+ return containerProvider.getCoreContainer();
+ }
+
+ // TODO: not fond of having these here, but RateLimiter initialization can
be sorted out later
+ RateLimitManager getRateLimitManager() {
+ return containerProvider.getRateLimitManager();
+ }
+
+ void replaceRateLimitManager(RateLimitManager rlm) {
+ containerProvider.setRateLimitManager(rlm);
+ }
+}
diff --git
a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
index 1dcf7fd4ae3..d7e6adad942 100644
--- a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
+++ b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
@@ -54,7 +54,6 @@ import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NodeConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrXmlConfig;
-import org.apache.solr.metrics.SolrMetricProducer;
import org.apache.solr.servlet.RateLimitManager.Builder;
import org.apache.solr.util.StartupLoggingUtils;
import org.slf4j.Logger;
@@ -67,9 +66,8 @@ import org.slf4j.LoggerFactory;
*/
public class CoreContainerProvider implements ServletContextListener {
private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private final String metricTag = SolrMetricProducer.getUniqueMetricTag(this,
null);
private CoreContainer cores;
- private Properties extraProperties;
+ // TODO: this probably should not live here...
private RateLimitManager rateLimitManager;
/**
@@ -151,7 +149,7 @@ public class CoreContainerProvider implements
ServletContextListener {
// "extra" properties must be initialized first, so we know things like
"do we have a zkHost"
// wrap as defaults (if set) so we can modify w/o polluting the
Properties provided by our
// caller
- this.extraProperties =
+ Properties extraProperties =
SolrXmlConfig.wrapAndSetZkHostFromSysPropIfNeeded(
(Properties) servletContext.getAttribute(PROPERTIES_ATTRIBUTE));
@@ -160,7 +158,7 @@ public class CoreContainerProvider implements
ServletContextListener {
log.info("Using logger factory {}",
StartupLoggingUtils.getLoggerImplStr());
}
- logWelcomeBanner();
+ logWelcomeBanner(extraProperties);
String muteConsole = System.getProperty(SOLR_LOG_MUTECONSOLE);
if (muteConsole != null
@@ -219,7 +217,7 @@ public class CoreContainerProvider implements
ServletContextListener {
}
}
- private void logWelcomeBanner() {
+ private void logWelcomeBanner(Properties props) {
// _Really_ sorry about how clumsy this is as a result of the logging call
checker, but this is
// the only one that's so ugly so far.
if (log.isInfoEnabled()) {
@@ -228,7 +226,7 @@ public class CoreContainerProvider implements
ServletContextListener {
if (log.isInfoEnabled()) {
log.info(
"/ __| ___| |_ _ Starting in {} mode on port {}",
- isCloudMode() ? "cloud" : "standalone",
+ isCloudMode(props) ? "cloud" : "standalone",
getSolrPort());
}
if (log.isInfoEnabled()) {
@@ -290,12 +288,12 @@ public class CoreContainerProvider implements
ServletContextListener {
* is non-empty
*
* @see SolrXmlConfig#wrapAndSetZkHostFromSysPropIfNeeded
- * @see #extraProperties
* @see #init
+ * @param props the "extra properties" which will indicate cloud mode before
startup.
*/
- private boolean isCloudMode() {
- assert null != extraProperties; // we should never be called w/o this
being initialized
- return (null != extraProperties.getProperty(SolrXmlConfig.ZK_HOST))
+ private boolean isCloudMode(Properties props) {
+ assert null != props; // we should never be called w/o this being
initialized
+ return (null != props.getProperty(SolrXmlConfig.ZK_HOST))
|| EnvUtils.getPropertyAsBool("solr.zookeeper.server.enabled", false);
}
diff --git a/solr/core/src/java/org/apache/solr/servlet/MdcLoggingFilter.java
b/solr/core/src/java/org/apache/solr/servlet/MdcLoggingFilter.java
new file mode 100644
index 00000000000..30dda327295
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/servlet/MdcLoggingFilter.java
@@ -0,0 +1,57 @@
+/*
+ * 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.solr.servlet;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.logging.MDCSnapshot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Servlet Filter to set up and tear down MDC Logging context for each
reqeust. */
+public class MdcLoggingFilter extends CoreContainerAwareHttpFilter {
+
+ private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Override
+ @SuppressForbidden(
+ reason =
+ "Set the thread contextClassLoader for all 3rd party dependencies
that we cannot control")
+ protected void doFilter(HttpServletRequest req, HttpServletResponse res,
FilterChain chain)
+ throws IOException, ServletException {
+ ClassLoader contextClassLoader =
Thread.currentThread().getContextClassLoader();
+ // this autocloseable is here to invoke MDCSnapshot.close() which restores
captured state
+ try (var mdcSnapshot = MDCSnapshot.create()) {
+ log.trace("MDC snapshot recorded {}", mdcSnapshot); // avoid both
compiler and ide warning.
+ MDCLoggingContext.reset();
+ MDCLoggingContext.setNode(getCores());
+ // This doesn't belong here, but for the moment it is here to preserve
it's relative
+ // timing of execution for now. Probably I will break this out in a
subsequent change
+
Thread.currentThread().setContextClassLoader(getCores().getResourceLoader().getClassLoader());
+ chain.doFilter(req, res);
+ } finally {
+ MDCLoggingContext.reset();
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index aad2d353052..6d80be11c70 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -20,31 +20,23 @@ import static
org.apache.solr.servlet.ServletUtils.closeShield;
import static org.apache.solr.util.tracing.TraceUtils.getSpan;
import static org.apache.solr.util.tracing.TraceUtils.setTracer;
-import com.google.common.annotations.VisibleForTesting;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.UnavailableException;
-import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Pattern;
import org.apache.solr.api.V2HttpCall;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.ExecutorUtil;
-import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NodeRoles;
import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.logging.MDCSnapshot;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.security.AuditEvent;
import org.apache.solr.security.AuditEvent.EventType;
@@ -65,19 +57,13 @@ import org.slf4j.LoggerFactory;
// servlets that are more focused in scope. This should become possible now
that we have a
// ServletContextListener for startup/shutdown of CoreContainer that sets up a
service from which
// things like CoreContainer can be requested. (or better yet injected)
-public class SolrDispatchFilter extends HttpFilter {
+public class SolrDispatchFilter extends CoreContainerAwareHttpFilter {
private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- private CoreContainerProvider containerProvider;
-
- protected final CountDownLatch init = new CountDownLatch(1);
-
protected String abortErrorMessage = null;
private HttpSolrCallFactory solrCallFactory;
- private List<Pattern> excludePatterns;
-
public final boolean isV2Enabled = V2ApiUtils.isEnabled();
/**
@@ -115,9 +101,8 @@ public class SolrDispatchFilter extends HttpFilter {
@Override
public void init(FilterConfig config) throws ServletException {
- super.init(config);
try {
- containerProvider =
CoreContainerProvider.serviceForContext(config.getServletContext());
+ super.init(config);
boolean isCoordinator =
NodeRoles.MODE_ON.equals(getCores().nodeRoles.getRoleMode(NodeRoles.Role.COORDINATOR));
solrCallFactory =
@@ -134,37 +119,33 @@ public class SolrDispatchFilter extends HttpFilter {
}
} finally {
log.trace("SolrDispatchFilter.init() done");
- init.countDown();
}
}
- /** The CoreContainer. It's ready for use, albeit could shut down whenever.
Never null. */
- public CoreContainer getCores() throws UnavailableException {
- return containerProvider.getCoreContainer();
- }
-
@Override
- @SuppressForbidden(
- reason =
- "Set the thread contextClassLoader for all 3rd party dependencies
that we cannot control")
public void doFilter(HttpServletRequest request, HttpServletResponse
response, FilterChain chain)
throws IOException, ServletException {
+ // internal version of doFilter that tracks if we are in a retry
+ doFilterRetry(closeShield(request), closeShield(response), chain, false);
+ }
- try (var mdcSnapshot = MDCSnapshot.create()) {
- assert null != mdcSnapshot; // prevent compiler warning
- MDCLoggingContext.reset();
- MDCLoggingContext.setNode(getCores());
-
Thread.currentThread().setContextClassLoader(getCores().getResourceLoader().getClassLoader());
+ /*
+ Wait? Where did X go??? (I hear you ask).
- doFilterRetry(closeShield(request), closeShield(response), chain, false);
- }
- }
+ For over a decade this class did anything and everything
+ In late 2021 SOLR-15590 moved container startup to CoreContainerProvider
+ In late 2025 SOLR-18040 moved request wrappers to independent ServletFilters
+ such as PathExclusionFilter see web.xml for a full, up-to-date list
+
+ This class is moving toward only handling dispatch, please think twice
+ before adding anything else to it.
+ */
- protected void doFilterRetry(
+ private void doFilterRetry(
HttpServletRequest request, HttpServletResponse response, FilterChain
chain, boolean retry)
throws IOException, ServletException {
setTracer(request, getCores().getTracer());
- RateLimitManager rateLimitManager =
containerProvider.getRateLimitManager();
+ RateLimitManager rateLimitManager = getRateLimitManager();
try {
ServletUtils.rateLimitRequest(
rateLimitManager,
@@ -369,11 +350,6 @@ public class SolrDispatchFilter extends HttpFilter {
&& cores.getAuditLoggerPlugin().shouldLog(eventType);
}
- @VisibleForTesting
- void replaceRateLimitManager(RateLimitManager rateLimitManager) {
- containerProvider.setRateLimitManager(rateLimitManager);
- }
-
/** internal API */
public interface HttpSolrCallFactory {
default HttpSolrCall createInstance(
diff --git
a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
index 982fc98c135..2825b1f4a72 100644
--- a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
+++ b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
@@ -61,6 +61,7 @@ import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.servlet.CoreContainerProvider;
+import org.apache.solr.servlet.MdcLoggingFilter;
import org.apache.solr.servlet.PathExclusionFilter;
import org.apache.solr.servlet.SolrDispatchFilter;
import org.apache.solr.util.SocketProxy;
@@ -112,6 +113,7 @@ public class JettySolrRunner {
volatile FilterHolder debugFilter;
volatile FilterHolder pathExcludeFilter;
+ volatile FilterHolder mdcLoggingFilter;
volatile FilterHolder dispatchFilter;
private int jettyPort = -1;
@@ -409,12 +411,17 @@ public class JettySolrRunner {
pathExcludeFilter.setHeldClass(PathExclusionFilter.class);
pathExcludeFilter.setInitParameter("excludePatterns", excludePatterns);
+ // logging context setup
+ mdcLoggingFilter =
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
+ mdcLoggingFilter.setHeldClass(MdcLoggingFilter.class);
+
// This is our main workhorse
dispatchFilter =
root.getServletHandler().newFilterHolder(Source.EMBEDDED);
dispatchFilter.setHeldClass(SolrDispatchFilter.class);
// Map dispatchFilter in same path as in web.xml
root.addFilter(pathExcludeFilter, "/*",
EnumSet.of(DispatcherType.REQUEST));
+ root.addFilter(mdcLoggingFilter, "/*",
EnumSet.of(DispatcherType.REQUEST));
root.addFilter(dispatchFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
// Default servlet as a fall-through
diff --git a/solr/webapp/web/WEB-INF/web.xml b/solr/webapp/web/WEB-INF/web.xml
index aa3a192862d..a7df6a32113 100644
--- a/solr/webapp/web/WEB-INF/web.xml
+++ b/solr/webapp/web/WEB-INF/web.xml
@@ -44,6 +44,16 @@
<url-pattern>/*</url-pattern>
</filter-mapping>
+ <filter>
+ <filter-name>MDCLoggingFilter</filter-name>
+ <filter-class>org.apache.solr.servlet.MdcLoggingFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>MDCLoggingFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
<filter>
<filter-name>SolrRequestFilter</filter-name>
<filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class>