Browse Source

gateway logging json + mdc filter

demo-user 2 tháng trước cách đây
mục cha
commit
c84617b1af

+ 100 - 0
shop-recycle-gateway/src/main/java/com/shop/recycle/gateway/filter/LoggingMdcWebFilter.java

@@ -0,0 +1,100 @@
+package com.shop.recycle.gateway.filter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * WebFlux logging filter that injects MDC fields for JSON logs.
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class LoggingMdcWebFilter implements WebFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(LoggingMdcWebFilter.class);
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+        long startTime = System.currentTimeMillis();
+        String traceId = resolveTraceId(exchange);
+        String uri = exchange.getRequest().getURI().getPath();
+        String uriGroup = normalizeUri(uri);
+        String eventClass = deriveEventClass(uri);
+        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
+
+        AtomicReference<String> errorRef = new AtomicReference<>(null);
+
+        return chain.filter(exchange)
+                .doOnError(ex -> errorRef.set(ex.toString()))
+                .doFinally(signalType -> {
+                    int statusCode = exchange.getResponse().getStatusCode() != null
+                            ? exchange.getResponse().getStatusCode().value()
+                            : 200;
+                    String status = statusCode >= 400
+                            ? (statusCode >= 500 ? "server_error" : "client_error")
+                            : "success";
+
+                    MDC.put("traceId", traceId);
+                    MDC.put("uri", uri);
+                    MDC.put("uri_group", uriGroup);
+                    MDC.put("event_class", eventClass);
+                    if (userId != null) {
+                        MDC.put("userId", userId);
+                    }
+                    MDC.put("duration", String.valueOf(System.currentTimeMillis() - startTime));
+                    MDC.put("status", status);
+                    if (errorRef.get() != null) {
+                        MDC.put("error", errorRef.get());
+                    } else if (statusCode >= 400) {
+                        MDC.put("error", "HTTP_" + statusCode);
+                    }
+
+                    log.info("gateway_request");
+                    MDC.clear();
+                });
+    }
+
+    private String resolveTraceId(ServerWebExchange exchange) {
+        String traceId = exchange.getRequest().getHeaders().getFirst("X-B3-TraceId");
+        if (traceId == null || traceId.isEmpty()) {
+            traceId = UUID.randomUUID().toString();
+        }
+        return traceId;
+    }
+
+    private String normalizeUri(String uri) {
+        if (uri.startsWith("/order/")) {
+            return "/order/*";
+        }
+        if (uri.startsWith("/payment/")) {
+            return "/payment/*";
+        }
+        if (uri.startsWith("/gateway/")) {
+            return "/gateway/*";
+        }
+        return uri;
+    }
+
+    private String deriveEventClass(String uri) {
+        if (uri.contains("order")) {
+            return "order";
+        }
+        if (uri.contains("payment")) {
+            return "payment";
+        }
+        if (uri.contains("login")) {
+            return "auth";
+        }
+        return "api";
+    }
+}

+ 22 - 3
shop-recycle-gateway/src/main/resources/logback-spring.xml

@@ -3,22 +3,41 @@
     <springProperty name="app" source="spring.application.name" />
     <springProperty name="env" source="spring.profiles.active" defaultValue="local" />
 
-    <!-- JSON格式输出到控制台 -->
+    <!-- JSON console output -->
     <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
         <encoder class="net.logstash.logback.encoder.LogstashEncoder">
             <includeContext>true</includeContext>
             <customFields>{"app":"${app}","env":"${env}"}</customFields>
+            <provider class="net.logstash.logback.composite.loggingevent.LoggingEventPatternJsonProvider">
+                <pattern>
+{
+  "ts":"%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}",
+  "level":"%level",
+  "logger":"%logger",
+  "msg":"%msg",
+  "traceId":"%X{traceId:-}",
+  "uri":"%X{uri:-}",
+  "uri_group":"%X{uri_group:-}",
+  "event_class":"%X{event_class:-}",
+  "duration":"%X{duration:-}",
+  "userId":"%X{userId:-}",
+  "status":"%X{status:-}",
+  "error":"%X{error:-}",
+  "thread":"%thread"
+}
+                </pattern>
+            </provider>
         </encoder>
     </appender>
 
-    <!-- 异步Appender,将日志异步输出到JSON格式 -->
+    <!-- Async appender for JSON output -->
     <appender name="ASYNC_JSON" class="ch.qos.logback.classic.AsyncAppender">
         <queueSize>1024</queueSize>
         <discardingThreshold>0</discardingThreshold>
         <appender-ref ref="JSON_CONSOLE" />
     </appender>
 
-    <!-- 保留简单文本格式便于本地开发调试 -->
+    <!-- Plain console output for local debugging -->
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
             <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>