Quellcode durchsuchen

fix(聚合支付): 上游网络异常补日志,带 host 与底层原因

业务服务层对调用方只说「上游网络异常」,IOException 的 cause 却从未落日志,
线上只能看到 500 文案,分不清是超时、DNS 还是 TLS。

- JdkUnifiedTransport:catch IOException 时记 host、异常类型与 message;
  不记请求体,避免把下单参数写进日志
- 新增单测:本机 HttpServer 制造读超时,断言日志含固定锚点与 timeout
  正控先断言日志非空,避免 appender 没挂上时在空列表上假绿
alphaH vor 1 Woche
Ursprung
Commit
2828be97ba

+ 8 - 0
java/src/main/java/com/payment/platform/module/payment/unified/client/JdkUnifiedTransport.java

@@ -1,6 +1,8 @@
 package com.payment.platform.module.payment.unified.client;
 
 import com.payment.platform.module.payment.unified.config.UnifiedPayProperties;
+import com.payment.platform.module.payment.unified.util.UnifiedLogSanitizer;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 import java.io.IOException;
@@ -18,6 +20,7 @@ import java.time.Duration;
  * ({@code WebMvcConfig} 那个故意没有超时)。非 2xx 与 IO 异常统一包成
  * {@link UnifiedApiException#CODE_TRANSPORT}。
  */
+@Slf4j
 @Component
 public class JdkUnifiedTransport implements UnifiedTransport {
 
@@ -61,6 +64,11 @@ public class JdkUnifiedTransport implements UnifiedTransport {
             }
             return response.body();
         } catch (IOException e) {
+            // 只记 host 与底层异常:业务服务层只把 "上游网络异常" 交给调用方,
+            // 不补这一行的话线上永远分不清是超时、DNS 还是 TLS。
+            log.warn("[统一支付] 上游网络异常 host={} cause={}: {}",
+                    request.uri().getHost(), e.getClass().getSimpleName(),
+                    UnifiedLogSanitizer.flatten(e.getMessage()));
             throw new UnifiedApiException(UnifiedApiException.CODE_TRANSPORT, "上游网络异常", e);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();

+ 47 - 0
java/src/test/java/com/payment/platform/module/payment/unified/client/JdkUnifiedTransportTest.java

@@ -1,13 +1,18 @@
 package com.payment.platform.module.payment.unified.client;
 
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
 import com.payment.platform.module.payment.unified.config.UnifiedPayProperties;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpServer;
 import org.junit.jupiter.api.Test;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -49,6 +54,48 @@ class JdkUnifiedTransportTest {
         }
     }
 
+    @Test
+    void timeout_logsCauseSoProductionCanDiagnoseIt() throws Exception {
+        Logger logger = (Logger) LoggerFactory.getLogger(JdkUnifiedTransport.class);
+        ListAppender<ILoggingEvent> appender = new ListAppender<>();
+        appender.start();
+        logger.addAppender(appender);
+
+        HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+        server.createContext("/slow", exchange -> {
+            sleepQuietly(300);
+            try {
+                respond(exchange, 200, "{\"code\":0}");
+            } catch (IOException ignored) {
+                // 客户端已超时断开,写响应失败是预期
+            }
+        });
+        server.start();
+        try {
+            UnifiedPayProperties props = new UnifiedPayProperties();
+            props.setConnectTimeoutMs(1000);
+            props.setReadTimeoutMs(100);
+            JdkUnifiedTransport transport = new JdkUnifiedTransport(props);
+
+            assertThrows(UnifiedApiException.class,
+                    () -> transport.post(base(server) + "/slow", "{}"));
+
+            List<String> logs = appender.list.stream()
+                    .map(ILoggingEvent::getFormattedMessage)
+                    .toList();
+            // 正控:没挂上 appender 时下面的断言会变成空列表上的假绿
+            assertFalse(logs.isEmpty(),
+                    "上游网络异常必须留日志,否则线上只能看到 500 文案");
+            String text = String.join("\n", logs);
+            assertTrue(text.contains("上游网络异常"), "缺固定锚点: " + text);
+            assertTrue(text.toLowerCase().contains("timeout"),
+                    "日志应带底层异常类型/信息,才能区分超时、DNS、TLS: " + text);
+        } finally {
+            logger.detachAppender(appender);
+            server.stop(0);
+        }
+    }
+
     @Test
     void success_returnsResponseBodyVerbatim() throws Exception {
         HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);