zhangyi 6 месяцев назад
Сommit
9c516e1d08

+ 104 - 0
pom.xml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.5.7</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>cn.seecoder</groupId>
+    <artifactId>SeecoderLog</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>SeecoderLog</name>
+    <description>SeecoderLog</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.11</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>32.0.1-android</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.18.0</version>
+        </dependency>
+
+
+
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 45 - 0
src/main/java/cn/seecoder/seecoderlog/configure/SeecoderAutoConfiguration.java

@@ -0,0 +1,45 @@
+package cn.seecoder.seecoderlog.configure;
+
+import cn.seecoder.seecoderlog.interceptor.SeecoderHttpClientInterceptor;
+import cn.seecoder.seecoderlog.interceptor.SeecoderRequestInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Configuration
+public class SeecoderAutoConfiguration implements WebMvcConfigurer {
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册请求拦截器
+        registry.addInterceptor(new SeecoderRequestInterceptor())
+                .addPathPatterns("/**");
+    }
+
+    // 提供 HttpClient 请求发送拦截器的 Bean
+    @Bean
+    public SeecoderHttpClientInterceptor seecoderHttpClientInterceptor() {
+        return new SeecoderHttpClientInterceptor();
+    }
+
+    // 配置带拦截器的 RestTemplate Bean
+    @Bean
+    public RestTemplate restTemplate(@Autowired SeecoderHttpClientInterceptor seecoderHttpClientInterceptor) {
+        RestTemplateBuilder builder = new RestTemplateBuilder();
+
+        // 添加自定义的HTTP客户端拦截器
+        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+        interceptors.add(seecoderHttpClientInterceptor);
+
+        return builder
+                .additionalInterceptors(interceptors)
+                .build();
+    }
+}

+ 27 - 0
src/main/java/cn/seecoder/seecoderlog/context/TraceContext.java

@@ -0,0 +1,27 @@
+package cn.seecoder.seecoderlog.context;
+
+public class TraceContext {
+
+    private static final ThreadLocal<TraceInfo> traceHolder = new ThreadLocal<>();
+
+    /**
+     * 设置当前线程的Trace信息
+     */
+    public static void setTraceInfo(TraceInfo traceInfo) {
+        traceHolder.set(traceInfo);
+    }
+
+    /**
+     * 获取当前线程的Trace信息
+     */
+    public static TraceInfo getTraceInfo() {
+        return traceHolder.get();
+    }
+
+    /**
+     * 清除当前线程的Trace信息
+     */
+    public static void clear() {
+        traceHolder.remove();
+    }
+}

+ 103 - 0
src/main/java/cn/seecoder/seecoderlog/context/TraceInfo.java

@@ -0,0 +1,103 @@
+package cn.seecoder.seecoderlog.context;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+@Data
+@AllArgsConstructor
+public class TraceInfo {
+    // 进程ID
+    private static final String PROCESS_ID = String.valueOf(ProcessHandle.current().pid() % 1000);
+
+    // 递增计数器
+    private static volatile int counter = 0;
+
+    private String traceId;
+    private String rpcId;
+
+    // 在现有字段中添加时间戳记录
+    private static volatile long lastTimestamp = 0;
+
+    /**
+     * 根据当前RPC ID生成子级RPC ID
+     */
+    public static String generateChildRpcId(String currentRpcId) {
+        if (StringUtils.isEmpty(currentRpcId)) {
+            return "1";
+        }
+        return currentRpcId + ".1";
+    }
+
+    /**
+     * 根据当前RPC ID生成同级下一个RPC ID
+     */
+    public static String generateNextRpcId(String currentRpcId) {
+        return incurRpcId(currentRpcId);
+    }
+
+    public void updateNextRpcId(){
+        rpcId = incurRpcId(rpcId);
+    }
+
+    /**
+     * 生成新的TraceId
+     */
+    public static String generateTraceId() {
+        try {
+            // 获取本机IP地址并转换为16进制
+            InetAddress inetAddress = InetAddress.getLocalHost();
+            byte[] ipBytes = inetAddress.getAddress();
+            StringBuilder ipHex = new StringBuilder();
+            for (byte b : ipBytes) {
+                ipHex.append(String.format("%02x", b & 0xFF));
+            }
+
+            // 毫秒时间戳
+            long timestamp = System.currentTimeMillis();
+
+            // 4位递增数
+            int count = getNextCounter();
+
+            // 生成traceId: IP十六进制 + 时间戳 + 递增数 + 进程ID + 标识位(0)
+            return String.format("%s%013d%04d%s0", ipHex, timestamp, count, PROCESS_ID);
+        } catch (UnknownHostException e) {
+            // 如果无法获取IP,则使用随机数替代
+            long random = (long) (Math.random() * 100000000);
+            long timestamp = System.currentTimeMillis();
+            int count = getNextCounter();
+            return String.format("%08x%013d%04d%s0", random, timestamp, count, PROCESS_ID);
+        }
+    }
+
+    /**
+     * 获取下一个递增计数器值
+     * 修改为时间戳绑定的递增计数器
+     */
+    private static synchronized int getNextCounter() {
+        long currentTimestamp = System.currentTimeMillis();
+
+        // 如果时间戳变化,则重置计数器
+        if (currentTimestamp != lastTimestamp) {
+            counter = 0;
+            lastTimestamp = currentTimestamp;
+        }
+
+        // 递增并返回计数器值,保持原有的取模逻辑
+        return ++counter % 10000;
+    }
+
+    private static String incurRpcId(String currentRpcId){
+        if (StringUtils.isEmpty(currentRpcId)){
+            return "1";
+        }
+        String[] parts = currentRpcId.split("\\.");
+        int lastPart = Integer.parseInt(parts[parts.length - 1]);
+        parts[parts.length - 1] = String.valueOf(lastPart + 1);
+
+        return String.join(".", parts);
+    }
+}

+ 31 - 0
src/main/java/cn/seecoder/seecoderlog/interceptor/SeecoderHttpClientInterceptor.java

@@ -0,0 +1,31 @@
+package cn.seecoder.seecoderlog.interceptor;
+
+import cn.seecoder.seecoderlog.context.TraceContext;
+import cn.seecoder.seecoderlog.context.TraceInfo;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+
+import java.io.IOException;
+
+public class SeecoderHttpClientInterceptor implements ClientHttpRequestInterceptor {
+
+    @Override
+    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+        TraceInfo traceInfo = TraceContext.getTraceInfo();
+
+        if (traceInfo != null) {
+            // 设置traceId
+            request.getHeaders().add("X-Trace-Id", traceInfo.getTraceId());
+
+            // 设置rpcId
+            request.getHeaders().add("X-Rpc-Id", traceInfo.getRpcId());
+
+            // 增加rpcId层级
+            traceInfo.updateNextRpcId();
+        }
+
+        return execution.execute(request, body);
+    }
+}

+ 48 - 0
src/main/java/cn/seecoder/seecoderlog/interceptor/SeecoderRequestInterceptor.java

@@ -0,0 +1,48 @@
+package cn.seecoder.seecoderlog.interceptor;
+
+import cn.seecoder.seecoderlog.context.TraceContext;
+import cn.seecoder.seecoderlog.context.TraceInfo;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+
+public class SeecoderRequestInterceptor implements HandlerInterceptor {
+
+    private static final String START_TIME = "startTime";
+    private static final String TRACE_INFO_ATTR = "traceInfo";
+
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        // 记录请求开始时间
+        request.setAttribute(START_TIME, System.currentTimeMillis());
+
+        // 处理traceId和rpcId
+        String traceId = request.getHeader("X-Trace-Id");
+        if (traceId == null || traceId.isEmpty()) {
+            // 生成新的traceId
+            traceId = TraceInfo.generateTraceId();
+        }
+
+        String parentRpcId = request.getHeader("X-Rpc-Id");
+        String rpcId = TraceInfo.generateChildRpcId(parentRpcId);
+
+        TraceInfo traceInfo = new TraceInfo(traceId, rpcId);
+
+        // 设置到当前线程上下文
+        TraceContext.setTraceInfo(traceInfo);
+        request.setAttribute(TRACE_INFO_ATTR, traceInfo);
+
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+    }
+}

+ 55 - 0
src/main/java/cn/seecoder/seecoderlog/utils/SeecoderLogUtils.java

@@ -0,0 +1,55 @@
+package cn.seecoder.seecoderlog.utils;
+
+import cn.seecoder.seecoderlog.context.TraceContext;
+import cn.seecoder.seecoderlog.context.TraceInfo;
+import com.google.common.base.Joiner;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+
+public class SeecoderLogUtils {
+    private static final Logger seecoderLogger = LoggerFactory.getLogger("SEECODER");
+
+    public static String getTraceId(){
+        TraceInfo traceInfo = TraceContext.getTraceInfo();
+        if (traceInfo != null && traceInfo.getTraceId() != null) {
+            return traceInfo.getTraceId();
+        }
+        return StringUtils.EMPTY;
+
+    }
+
+    public static String getRpcId(){
+        TraceInfo traceInfo = TraceContext.getTraceInfo();
+        if (Objects.nonNull(traceInfo) && Objects.nonNull(TraceContext.getTraceInfo())){
+            return traceInfo.getRpcId();
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * info级别日志
+     */
+    public static void info(String bizName, String... context) {
+        seecoderLogger.info("{}|{}|{}|{}", bizName, getTraceId(), getRpcId(),
+                Joiner.on("|").useForNull("").join(context));
+    }
+
+    /**
+     * warn级别日志
+     */
+    public static void warn(String bizName, String... context) {
+        seecoderLogger.warn("{}|{}|{}|{}", bizName, getTraceId(), getRpcId(),
+                Joiner.on("|").useForNull("").join(context));
+    }
+
+    /**
+     * error级别日志
+     */
+    public static void error(Throwable e, String bizName, String... context) {
+        seecoderLogger.error("{}|{}|{}|{}", bizName, getTraceId(), getRpcId(),
+                Joiner.on("|").useForNull("").join(context), e);
+    }
+}

+ 35 - 0
src/main/resources/ logback-spring.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 从Spring Boot配置中获取appName -->
+    <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="unknown"/>
+
+    <!-- 控制台输出配置 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [${appName}] [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <!-- 控制台异步日志记录配置 -->
+    <appender name="ASYNC_CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
+        <appender-ref ref="CONSOLE"/>
+        <!-- 内部实现是ArrayBlockingQueue,queSize默认为256 -->
+        <queueSize>512</queueSize>
+        <!-- 当队列容量小于该值时,若日志level为trace,debug、info,是否丢失日志事件,默认为true -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 使用offer方法记录,队列满时直接丢弃,不阻塞 -->
+        <neverBlock>true</neverBlock>
+        <!-- 可选配置:是否在JVM关闭时刷新日志 -->
+        <includeCallerData>false</includeCallerData>
+    </appender>
+
+    <!-- SEECODER日志记录器配置 -->
+    <logger name="SEECODER" level="INFO" additivity="false">
+        <appender-ref ref="ASYNC_CONSOLE"/>
+    </logger>
+
+    <!-- 根日志记录器 -->
+    <root level="INFO">
+        <appender-ref ref="ASYNC_CONSOLE"/>
+    </root>
+</configuration>

+ 2 - 0
src/main/resources/META-INF/ spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+cn.seecoder.seecoderlog.configure.SeecoderAutoConfiguration

+ 1 - 0
src/main/resources/application.properties

@@ -0,0 +1 @@
+spring.application.name=SeecoderLog

+ 13 - 0
src/test/java/cn/seecoder/seecoderlog/SeecoderLogApplicationTests.java

@@ -0,0 +1,13 @@
+package cn.seecoder.seecoderlog;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SeecoderLogApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}