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

hossman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new aea698b  SOLR-15790: SearchHandler now includes the  value in the 
Logging MDC for the duration of the request, allowing custom logging 
configurations to include it.
aea698b is described below

commit aea698b79fc4b90a0f73b99af644c18ebdeb2ef3
Author: Chris Hostetter <[email protected]>
AuthorDate: Fri Nov 12 11:23:40 2021 -0700

    SOLR-15790: SearchHandler now includes the  value in the Logging MDC for 
the duration of the request, allowing custom logging configurations to include 
it.
---
 solr/CHANGES.txt                                   |   3 +
 .../solr/handler/component/SearchHandler.java      |  16 +++-
 .../org/apache/solr/handler/TestRequestId.java     | 103 +++++++++++++++++++++
 .../org/apache/solr/util/Log4jListAppender.java    |  79 ++++++++++++++++
 .../org/apache/solr/util/TestErrorLogMuter.java    |  53 +----------
 5 files changed, 204 insertions(+), 50 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 931a6a7..f3a7fc3 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -167,6 +167,9 @@ when told to. The admin UI now tells it to. (Nazerke 
Seidan, David Smiley)
 * SOLR-15705: A delete-by-id command is forwarded to all shards when using the 
CompositeId router with a router field
   and the route is missing from the command. (Michael Kosten via Christine 
Poerschke, David Smiley, Eric Pugh)
 
+* SOLR-15790: SearchHandler now includes the `rid` value in the Logging MDC 
for the duration of the request, allowing custom logging
+  configurations to include it. (hossman)
+
 Build
 ---------------------
 
diff --git 
a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java 
b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
index 0cd3db0..d3a448c 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SearchHandler.java
@@ -53,6 +53,7 @@ import org.apache.solr.util.plugin.PluginInfoInitialized;
 import org.apache.solr.util.plugin.SolrCoreAware;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -290,6 +291,8 @@ public class SearchHandler extends RequestHandlerBase 
implements SolrCoreAware,
     if (rb.requestInfo != null) {
       rb.requestInfo.setResponseBuilder(rb);
     }
+    
+    tagRequestWithRequestId(rb);
 
     boolean dbg = req.getParams().getBool(CommonParams.DEBUG_QUERY, false);
     rb.setDebug(dbg);
@@ -324,8 +327,6 @@ public class SearchHandler extends RequestHandlerBase 
implements SolrCoreAware,
 
     final ShardHandler shardHandler1 = getAndPrepShardHandler(req, rb); // 
creates a ShardHandler object only if it's needed
 
-    tagRequestWithRequestId(rb);
-
     if (timer == null) {
       // non-debugging prepare phase
       for( SearchComponent c : components ) {
@@ -549,6 +550,17 @@ public class SearchHandler extends RequestHandlerBase 
implements SolrCoreAware,
     final boolean ridTaggingDisabled = 
rb.req.getParams().getBool(CommonParams.DISABLE_REQUEST_ID, false);
     if (! ridTaggingDisabled) {
       String rid = getOrGenerateRequestId(rb.req);
+
+      // NOTE: SearchHandler explicitly never clears/removes this MDC value...
+      // We want it to live for the entire request, beyond the scope of 
SearchHandler's processing, and trust
+      // SolrDispatchFilter to clean it up at the end of the request.
+      //
+      // Examples:
+      // - ERROR logging of Exceptions propogated up to our base class
+      // - SolrCore.RequestLog
+      // - ERRORs that may be logged during response writing
+      MDC.put(CommonParams.REQUEST_ID, rid);
+      
       if 
(StringUtils.isBlank(rb.req.getParams().get(CommonParams.REQUEST_ID))) {
         ModifiableSolrParams params = new 
ModifiableSolrParams(rb.req.getParams());
         params.add(CommonParams.REQUEST_ID, rid);//add rid to the request so 
that shards see it
diff --git a/solr/core/src/test/org/apache/solr/handler/TestRequestId.java 
b/solr/core/src/test/org/apache/solr/handler/TestRequestId.java
new file mode 100644
index 0000000..ce819d2
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/TestRequestId.java
@@ -0,0 +1,103 @@
+/*
+ * 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.handler;
+
+import java.lang.invoke.MethodHandles;
+
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.util.LogLevel;
+import org.apache.solr.util.Log4jListAppender;
+import org.apache.solr.SolrTestCaseJ4;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LoggerContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+
+import org.junit.BeforeClass;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+@SuppressForbidden(reason="We need to use log4J2 classes directly to check 
that the MDC is working")
+public class TestRequestId extends SolrTestCaseJ4 {
+
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  
+  @BeforeClass
+  public static void beforeTests() throws Exception {
+    initCore("solrconfig.xml","schema.xml");
+  }
+
+  // NOTE: Explicitly configuring these so we know they have a LoggerConfig we 
can attach an appender to...
+  
@LogLevel("org.apache.solr.core.SolrCore.Request=INFO;org.apache.solr.handler.RequestHandlerBase=INFO")
+  public void testRequestId() {
+    
+    final String reqLogName = SolrCore.class.getName() + ".Request";
+    final String errLogName = RequestHandlerBase.class.getName();
+      
+    final Log4jListAppender reqLog = new Log4jListAppender("req-log");
+    final Log4jListAppender errLog = new Log4jListAppender("err-log");
+    try {
+      
LoggerContext.getContext(false).getConfiguration().getLoggerConfig(reqLogName).addAppender(reqLog,
 Level.INFO, null);
+      
LoggerContext.getContext(false).getConfiguration().getLoggerConfig(errLogName).addAppender(errLog,
 Level.ERROR, null);
+      LoggerContext.getContext(false).updateLoggers();
+      
+      // Sanity check that the our MDC doesn't already have some sort of rid 
set in it
+      assertNull(MDC.get(CommonParams.REQUEST_ID));
+
+      // simple request that should successfully be logged ...
+      assertQ("xxx", req("q", "*:*", CommonParams.REQUEST_ID, "xxx"), 
"//*[@numFound='0']");
+
+      // Sanity check that the test framework didn't let our "request" MDC 
info "leak" out of assertQ..
+      assertNull(MDC.get(CommonParams.REQUEST_ID));
+      
+      assertEquals(1, reqLog.getEvents().size());
+      assertEquals("xxx", 
reqLog.getEvents().get(0).getContextData().getValue("rid"));
+
+      assertEquals(0, errLog.getEvents().size());
+
+      // reques that should cause some ERROR logging...
+      // NOTE: we can't just listen for errors at the 'root' logger because 
assertQEx will 'mute' them before appenders get them
+      assertQEx("yyy", "bogus_yyy", req("q", "*:*", "sort", "bogus_yyy", 
CommonParams.REQUEST_ID, "yyy"), ErrorCode.BAD_REQUEST);
+                
+      // Sanity check that the test framework didn't let our "request" MDC 
info "leak" out of assertQEx..
+      assertNull(MDC.get(CommonParams.REQUEST_ID));
+      
+      assertEquals(2, reqLog.getEvents().size());
+      assertEquals("yyy", 
reqLog.getEvents().get(1).getContextData().getValue("rid"));
+      assertThat(reqLog.getEvents().get(1).getMessage().getFormattedMessage(), 
containsString("status="+ErrorCode.BAD_REQUEST.code));
+      
+      assertEquals(1, errLog.getEvents().size());
+      assertEquals("yyy", 
errLog.getEvents().get(0).getContextData().getValue("rid"));
+      assertNotNull(errLog.getEvents().get(0).getThrown());
+      
+
+    } finally {
+      
LoggerContext.getContext(false).getConfiguration().getLoggerConfig(reqLogName).removeAppender(reqLog.getName());
+      
LoggerContext.getContext(false).getConfiguration().getLoggerConfig(errLogName).removeAppender(errLog.getName());
+      LoggerContext.getContext(false).updateLoggers();
+    }
+  }
+
+}
diff --git 
a/solr/test-framework/src/java/org/apache/solr/util/Log4jListAppender.java 
b/solr/test-framework/src/java/org/apache/solr/util/Log4jListAppender.java
new file mode 100644
index 0000000..426df2b
--- /dev/null
+++ b/solr/test-framework/src/java/org/apache/solr/util/Log4jListAppender.java
@@ -0,0 +1,79 @@
+/*
+ * 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.util;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.solr.common.util.SuppressForbidden;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.impl.MutableLogEvent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Maintains an in memory List of log events.
+ * <p>
+ * Inspired by 
<code>org.apache.logging.log4j.core.test.appender.ListAppender</code>
+ * but we have much simpler needs.
+ * </p>
+ *
+ * TODO: Refactor into something easier to use (SOLR-15629)
+ * @lucene.internal
+ * @lucene.experimental  
+ */
+@SuppressForbidden(reason="We need to use log4J2 classes directly to check 
that the ErrorLogMuter is working")
+public final class Log4jListAppender extends AbstractAppender {
+  
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  
+  // Use Collections.synchronizedList rather than CopyOnWriteArrayList because 
we expect
+  // more frequent writes than reads.
+  private final List<LogEvent> events = Collections.synchronizedList(new 
ArrayList<>());
+  private final List<LogEvent> publicEvents = 
Collections.unmodifiableList(events);
+  
+  public Log4jListAppender(final String name) {
+    super(name, null, null, true, Property.EMPTY_ARRAY);
+    assert null != name;
+    this.start();
+  }
+  
+  @Override
+  public void append(final LogEvent event) {
+    if (event instanceof MutableLogEvent) {
+      // must take snapshot or subsequent calls to logger.log() will modify 
this event
+      events.add(((MutableLogEvent) event).createMemento());
+    } else {
+      events.add(event);
+    }
+    if (log.isDebugEnabled()) {
+      log.debug("{} intercepted a log event (#{})", this.getName(), 
events.size());
+    }
+  }
+  
+  /** Returns an immutable view of captured log events, contents can change as 
events are logged */
+  public List<LogEvent> getEvents() {
+    return publicEvents;
+  }
+}
diff --git 
a/solr/test-framework/src/test/org/apache/solr/util/TestErrorLogMuter.java 
b/solr/test-framework/src/test/org/apache/solr/util/TestErrorLogMuter.java
index 7fc062b..5a6696e 100644
--- a/solr/test-framework/src/test/org/apache/solr/util/TestErrorLogMuter.java
+++ b/solr/test-framework/src/test/org/apache/solr/util/TestErrorLogMuter.java
@@ -18,9 +18,6 @@
 package org.apache.solr.util;
 
 import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.regex.Pattern;
 
 import org.apache.solr.common.util.SuppressForbidden;
@@ -30,11 +27,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.impl.MutableLogEvent;
 
 import static org.hamcrest.core.StringContains.containsString;
 
@@ -46,7 +39,7 @@ public class TestErrorLogMuter extends SolrTestCaseJ4 {
   @LogLevel("=WARN")
   public void testErrorMutingRegex() throws Exception {
 
-    final ListAppender rootSanityCheck = new ListAppender("sanity-checker");
+    final Log4jListAppender rootSanityCheck = new 
Log4jListAppender("sanity-checker");
     try {
       
LoggerContext.getContext(false).getConfiguration().getRootLogger().addAppender(rootSanityCheck,
 Level.WARN, null);
       LoggerContext.getContext(false).updateLoggers();
@@ -80,8 +73,8 @@ public class TestErrorLogMuter extends SolrTestCaseJ4 {
   @LogLevel("=WARN") 
   public void testMultipleMuters() throws Exception {
 
-    // Add a ListAppender to our ROOT logger so we can sanity check what log 
messages it gets
-    final ListAppender rootSanityCheck = new ListAppender("sanity-checker");
+    // Add a Log4jListAppender to our ROOT logger so we can sanity check what 
log messages it gets
+    final Log4jListAppender rootSanityCheck = new 
Log4jListAppender("sanity-checker");
     try {
       
LoggerContext.getContext(false).getConfiguration().getRootLogger().addAppender(rootSanityCheck,
 Level.WARN, null);
       LoggerContext.getContext(false).updateLoggers();
@@ -116,8 +109,8 @@ public class TestErrorLogMuter extends SolrTestCaseJ4 {
   @LogLevel("=WARN") 
   public void testDeprecatedBaseClassMethods() throws Exception {
     
-    // Add a ListAppender to our ROOT logger so we can sanity check what log 
messages it gets
-    final ListAppender rootSanityCheck = new ListAppender("sanity-checker");
+    // Add a Log4jListAppender to our ROOT logger so we can sanity check what 
log messages it gets
+    final Log4jListAppender rootSanityCheck = new 
Log4jListAppender("sanity-checker");
     try {
       
LoggerContext.getContext(false).getConfiguration().getRootLogger().addAppender(rootSanityCheck,
 Level.WARN, null);
       LoggerContext.getContext(false).updateLoggers();
@@ -149,40 +142,4 @@ public class TestErrorLogMuter extends SolrTestCaseJ4 {
     assertEquals(4, rootSanityCheck.getEvents().size());
   }
   
-  /**
-   * Maintains an in memory List of log events.
-   * <p>
-   * Inspired by 
<code>org.apache.logging.log4j.core.test.appender.ListAppender</code>
-   * but we have much simpler needs.
-   */
-  @SuppressForbidden(reason="We need to use log4J2 classes directly to check 
that the ErrorLogMuter is working")
-  public static final class ListAppender extends AbstractAppender {
-    // Use Collections.synchronizedList rather than CopyOnWriteArrayList 
because we expect
-    // more frequent writes than reads.
-    private final List<LogEvent> events = Collections.synchronizedList(new 
ArrayList<>());
-    private final List<LogEvent> publicEvents = 
Collections.unmodifiableList(events);
-    
-    public ListAppender(final String name) {
-      super(name, null, null, true, Property.EMPTY_ARRAY);
-      assert null != name;
-    }
-    
-    @Override
-    public void append(final LogEvent event) {
-      if (event instanceof MutableLogEvent) {
-        // must take snapshot or subsequent calls to logger.log() will modify 
this event
-        events.add(((MutableLogEvent) event).createMemento());
-      } else {
-        events.add(event);
-      }
-      if (log.isDebugEnabled()) {
-        log.debug("{} intercepted a log event (#{})", this.getName(), 
events.size());
-      }
-    }
-
-    /** Returns an immutable view of captured log events, contents can change 
as events are logged */
-    public List<LogEvent> getEvents() {
-      return publicEvents;
-    }
-  }
 }

Reply via email to