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

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new a186c1cd6d fix(log): reduce routine matching and configuration-sync 
log volume (#7643) (#7663)
a186c1cd6d is described below

commit a186c1cd6d305361097ae9430eee36a080351bb0
Author: konlue <[email protected]>
AuthorDate: Sat Oct 17 20:47:34 2026 +0800

    fix(log): reduce routine matching and configuration-sync log volume (#7643) 
(#7663)
    
    * fix(plugin-base): route match-success logging through the logged flag
    
    Two cases where the code contradicted itself.
    
    #7643's scope asks for routine match-success diagnostics to sit behind 
DEBUG or the
    explicit matching-log setting, without duplicating messages. printLog 
already sits
    behind the setting: it checks selectorData.getLogged() and 
ruleData.getLoged() and
    carries the selector or rule name. defaultMatchSelector and 
defaultMatchRule logged
    the same event a second time with no switch at all, so a user who had not 
enabled
    logging still received one INFO line per matched request while the 
cache-hit path
    stayed quiet. Both lines arrived from the same refactor (#4578) that 
introduced
    printLog, and both are unreachable-as-documented once printLog runs, so 
they are
    removed rather than demoted -- demoting them to DEBUG would have left two 
DEBUG lines
    describing one match, which is the duplication the scope warns against. The 
surviving
    INFO line is the one that names the selector or rule.
    
    ShenyuWebsocketClient#handleResult logged the entire raw frame at INFO with 
no switch
    and no level check, emitted before the GsonUtils.fromJson call, so a 
malformed frame
    was written out in full before the catch discarded it. The payload moves to 
DEBUG
    unchanged and INFO carries only groupType, eventType, namespaceId and 
payload size,
    logged after parsing so the group type is available. Parse failures keep 
their WARN.
    
    The two removed lines cost 51 and 44 bytes of message text per match (95 
bytes before
    logback's line prefix) for a default plugin name; a deployment matching on 
every
    request wrote that much per request to INFO regardless of the logged 
setting. That
    figure is derived from the message constants, not measured, and I have no 
controlled
    environment here to report a before/after rate, an allocation rate or a p99 
-- the
    repo has no JMH infrastructure and I don't have a bare-metal Linux box, so 
I am not
    going to present numbers I can't defend.
    
    Tests cover both directions of the setting and the routing result: two 
cases assert no
    selector or rule line reaches INFO when the flag is false, two assert 
exactly one line
    does reach INFO when it is true and that it is the name-carrying one, and 
one asserts
    doExecute still receives the same selector and rule. Removing the two 
getLogged and
    getLoged guards makes the disabled cases fail, so they do pin the 
behaviour. The
    exactly-once assertions also pin the de-duplication. logback-classic is 
added with
    test scope, following shenyu-plugin-wasm-api and shenyu-sync-data-http, 
since this
    module had only slf4j-api on the test classpath.
    
    No new configuration flag and no change to matching or routing logic.
    
    Fixes #7643
    
    * fix(sync): report websocket payload size in bytes
    
    Review follow-ups on the log-noise change:
    - ShenyuWebsocketClient: count the payload as UTF-8 bytes instead of
      UTF-16 chars, so the INFO line's "bytes" unit stays accurate for
      non-ASCII configuration content.
    - LogCapture: drop the unused originalLevel field and its no-op restore
      (the logger level is never modified during capture), and add the
      missing trailing newline.
    
    AbstractShenyuPluginTest 13/13, ShenyuWebsocketClientTest 34/34.
---
 shenyu-plugin/shenyu-plugin-base/pom.xml           |   5 +
 .../shenyu/plugin/base/AbstractShenyuPlugin.java   |   3 -
 .../plugin/base/AbstractShenyuPluginTest.java      | 107 +++++++++++++++++++++
 .../org/apache/shenyu/plugin/base/LogCapture.java  |  78 +++++++++++++++
 .../websocket/client/ShenyuWebsocketClient.java    |   7 +-
 5 files changed, 196 insertions(+), 4 deletions(-)

diff --git a/shenyu-plugin/shenyu-plugin-base/pom.xml 
b/shenyu-plugin/shenyu-plugin-base/pom.xml
index a34194e5e7..b200f17a99 100644
--- a/shenyu-plugin/shenyu-plugin-base/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-base/pom.xml
@@ -59,5 +59,10 @@
             <groupId>com.google.re2j</groupId>
             <artifactId>re2j</artifactId>
         </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
 
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
index 4b5a467f0c..41eaf4dff6 100644
--- 
a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
+++ 
b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
@@ -28,7 +28,6 @@ import org.apache.shenyu.common.dto.RuleData;
 import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.enums.MatchModeEnum;
 import org.apache.shenyu.common.enums.SelectorTypeEnum;
-import org.apache.shenyu.common.utils.LogUtils;
 import org.apache.shenyu.plugin.api.ShenyuPlugin;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
 import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
@@ -292,7 +291,6 @@ public abstract class AbstractShenyuPlugin implements 
ShenyuPlugin {
         Pair<Boolean, SelectorData> matchSelectorPair = 
matchSelector(exchange, selectors);
         SelectorData selectorData = matchSelectorPair.getRight();
         if (Objects.nonNull(selectorData)) {
-            LogUtils.info(LOG, "{} selector match success from default 
strategy", named());
             // cache selector data
             if (matchSelectorPair.getLeft()) {
                 cacheSelectorData(path, selectorData);
@@ -312,7 +310,6 @@ public abstract class AbstractShenyuPlugin implements 
ShenyuPlugin {
         Pair<Boolean, RuleData> matchRulePair = matchRule(exchange, rules);
         RuleData ruleData = matchRulePair.getRight();
         if (Objects.nonNull(ruleData)) {
-            LOG.info("{} rule match path from default strategy", named());
             // cache rule data
             if (matchRulePair.getLeft()) {
                 cacheRuleData(path, ruleData);
diff --git 
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
 
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
index e86c4d0091..d289489944 100644
--- 
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
@@ -41,7 +41,10 @@ import reactor.test.StepVerifier;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -248,6 +251,110 @@ public final class AbstractShenyuPluginTest {
         verify(testShenyuPlugin).doExecute(exchange, shenyuPluginChain, 
selectorData, ruleData);
     }
 
+    @Test
+    public void defaultStrategySelectorMatchShouldNotLogWhenLoggingDisabled() {
+        List<ConditionData> conditionDataList = 
Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.selectorData.setSort(1);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(false);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        try (LogCapture capture = 
LogCapture.attach(AbstractShenyuPlugin.class)) {
+            StepVerifier.create(testShenyuPlugin.execute(exchange, 
shenyuPluginChain)).expectSubscription().verifyComplete();
+            assertTrue(capture.infoMessages().stream().noneMatch(message -> 
message.contains("selector")),
+                    "no selector match line may reach INFO when the selector's 
logged flag is false, got: " + capture.infoMessages());
+        }
+    }
+
+    @Test
+    public void 
defaultStrategySelectorMatchShouldLogExactlyOnceWhenLoggingEnabled() {
+        List<ConditionData> conditionDataList = 
Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.selectorData.setSort(1);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        try (LogCapture capture = 
LogCapture.attach(AbstractShenyuPlugin.class)) {
+            StepVerifier.create(testShenyuPlugin.execute(exchange, 
shenyuPluginChain)).expectSubscription().verifyComplete();
+            List<String> selectorLines = capture.infoMessages().stream()
+                    .filter(message -> message.contains("selector"))
+                    .collect(Collectors.toList());
+            assertEquals(1, selectorLines.size(),
+                    "exactly one selector match line is expected at INFO when 
logged is true, got: " + selectorLines);
+            assertTrue(selectorLines.get(0).contains("selector success match"),
+                    "the surviving line should be the one that carries the 
selector name, got: " + selectorLines.get(0));
+        }
+    }
+
+    @Test
+    public void defaultStrategyRuleMatchShouldNotLogWhenLoggingDisabled() {
+        List<ConditionData> conditionDataList = 
Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.ruleData.setLoged(false);
+        this.selectorData.setSort(1);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(false);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        try (LogCapture capture = 
LogCapture.attach(AbstractShenyuPlugin.class)) {
+            StepVerifier.create(testShenyuPlugin.execute(exchange, 
shenyuPluginChain)).expectSubscription().verifyComplete();
+            assertTrue(capture.infoMessages().stream().noneMatch(message -> 
message.contains("rule")),
+                    "no rule match line may reach INFO when the rule's loged 
flag is false, got: " + capture.infoMessages());
+        }
+    }
+
+    @Test
+    public void 
defaultStrategyRuleMatchShouldLogExactlyOnceWhenLoggingEnabled() {
+        List<ConditionData> conditionDataList = 
Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.ruleData.setLoged(true);
+        this.selectorData.setSort(1);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        try (LogCapture capture = 
LogCapture.attach(AbstractShenyuPlugin.class)) {
+            StepVerifier.create(testShenyuPlugin.execute(exchange, 
shenyuPluginChain)).expectSubscription().verifyComplete();
+            List<String> ruleLines = capture.infoMessages().stream()
+                    .filter(message -> message.contains("rule"))
+                    .collect(Collectors.toList());
+            assertEquals(1, ruleLines.size(),
+                    "exactly one rule match line is expected at INFO when 
loged is true, got: " + ruleLines);
+            assertTrue(ruleLines.get(0).contains("rule success match"),
+                    "the surviving line should be the one that carries the 
rule name, got: " + ruleLines.get(0));
+        }
+    }
+
+    @Test
+    public void defaultStrategyMatchLogsShouldNotChangeRoutingResults() {
+        List<ConditionData> conditionDataList = 
Collections.singletonList(conditionData);
+        this.ruleData.setConditionDataList(conditionDataList);
+        this.ruleData.setMatchMode(0);
+        this.selectorData.setSort(1);
+        this.selectorData.setMatchMode(0);
+        this.selectorData.setLogged(true);
+        this.selectorData.setConditionList(conditionDataList);
+        BaseDataCache.getInstance().cachePluginData(pluginData);
+        BaseDataCache.getInstance().cacheSelectData(selectorData);
+        BaseDataCache.getInstance().cacheRuleData(ruleData);
+        StepVerifier.create(testShenyuPlugin.execute(exchange, 
shenyuPluginChain)).expectSubscription().verifyComplete();
+        verify(testShenyuPlugin).doExecute(exchange, shenyuPluginChain, 
selectorData, ruleData);
+    }
+
     @AfterEach
     public void clear() {
         MatchDataCache.getInstance().cleanSelectorData();
diff --git 
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/LogCapture.java
 
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/LogCapture.java
new file mode 100644
index 0000000000..5d86210fd3
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/LogCapture.java
@@ -0,0 +1,78 @@
+/*
+ * 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.shenyu.plugin.base;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Captures log events emitted by a single logger, so that a test can assert on
+ * what a code path did (or did not) log.
+ *
+ * <p>The appender is detached and stopped when the instance is closed, so a
+ * capture never leaks into subsequent tests running in the same JVM.
+ */
+final class LogCapture implements AutoCloseable {
+
+    private final Logger logger;
+
+    private final ListAppender<ILoggingEvent> appender;
+
+    private LogCapture(final Logger logger) {
+        this.logger = logger;
+        this.appender = new ListAppender<>();
+        this.appender.setContext(logger.getLoggerContext());
+        this.appender.start();
+        logger.addAppender(this.appender);
+    }
+
+    /**
+     * Attach a capture to the logger of the given type.
+     *
+     * @param type the class whose logger should be captured
+     * @return the capture, to be used as a try-with-resources resource
+     */
+    static LogCapture attach(final Class<?> type) {
+        Logger logger = (Logger) LoggerFactory.getLogger(type);
+        return new LogCapture(logger);
+    }
+
+    /**
+     * All formatted messages that were logged at INFO level during the 
capture.
+     *
+     * @return the INFO messages
+     */
+    List<String> infoMessages() {
+        return appender.list.stream()
+                .filter(event -> Level.INFO.equals(event.getLevel()))
+                .map(ILoggingEvent::getFormattedMessage)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void close() {
+        logger.detachAppender(appender);
+        appender.stop();
+    }
+}
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
index 64522afd22..2ad99b3569 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
@@ -31,6 +31,7 @@ import org.apache.shenyu.common.timer.TimerTask;
 import org.apache.shenyu.common.timer.WheelTimerFactory;
 import org.apache.shenyu.common.utils.GsonUtils;
 import org.apache.shenyu.common.utils.JsonUtils;
+import org.apache.shenyu.common.utils.LogUtils;
 import org.apache.shenyu.common.utils.SystemInfoUtils;
 import 
org.apache.shenyu.plugin.sync.data.websocket.handler.WebsocketDataHandler;
 import org.apache.shenyu.sync.data.api.AuthDataSubscriber;
@@ -49,6 +50,7 @@ import 
org.apache.shenyu.common.concurrent.ShenyuThreadFactory;
 import org.apache.shenyu.common.concurrent.ShenyuThreadPoolExecutor;
 
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -436,7 +438,7 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
      * @param result result
      */
     private void handleResult(final String result) {
-        LOG.info("server [{}] handleResult({})", this.getURI().toString(), 
result);
+        LogUtils.debug(LOG, "server [{}] handleResult({})", 
this.getURI().toString(), result);
         WebsocketData<?> websocketData;
         try {
             websocketData = GsonUtils.getInstance().fromJson(result, 
WebsocketData.class);
@@ -447,6 +449,9 @@ public final class ShenyuWebsocketClient extends 
WebSocketClient {
             LOG.warn("Failed to parse websocket message from server[{}], the 
message will be ignored", this.getURI(), ex);
             return;
         }
+        LOG.info("server [{}] received config message: groupType: {}, 
eventType: {}, namespaceId: {}, size: {} bytes",
+                this.getURI(), websocketData.getGroupType(), 
websocketData.getEventType(),
+                websocketData.getNamespaceId(), 
result.getBytes(StandardCharsets.UTF_8).length);
         ConfigGroupEnum groupEnum;
         String eventType;
         String json;

Reply via email to