Spring Cloud 2025.0.0 整合自定义日志注解完整教程
本教程基于 Spring Cloud 2025.0.0 新特性,包含虚拟线程、响应式编程、AOT 原生编译支持
一、技术栈和架构
1.1 技术选型
- Spring Cloud 2025.0.0 (代号: 2025.0.0)
- Spring Boot 4.0
- Java 21+ (支持虚拟线程)
- 响应式编程 (WebFlux + R2DBC)
- AOT 原生编译 (GraalVM 23.0+)
- 分布式追踪 (Micrometer + Brave)
- 日志收集 (Logback + ELK)
1.2 架构图
┌─────────────────────────────────────────────────────────┐
│ 自定义日志注解系统 │
├─────────────────────────────────────────────────────────┤
│ Log注解 → 切面处理 → 日志上下文 → 异步存储 → 分布式追踪 │
│ ↑ ↑ ↑ ↑ ↑ │
│ 业务方法 虚拟线程 MDC/Reactor Elasticsearch Sleuth│
└─────────────────────────────────────────────────────────┘
二、项目初始化
2.1 父工程 pom.xml
xml
<?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.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>2025.0.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-logging</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>logging-annotation</module>
<module>logging-starter</module>
<module>user-service</module>
<module>order-service</module>
<module>gateway-service</module>
</modules>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring Cloud 版本 -->
<spring-cloud.version>2025.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
<!-- 工具版本 -->
<lombok.version>1.18.30</lombok.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<guava.version>33.0.0-jre</guava.version>
<!-- 日志版本 -->
<logstash-logback.version>8.0</logstash-logback.version>
<micrometer-tracing.version>1.2.0</micrometer-tracing.version>
<!-- GraalVM -->
<graalvm.version>23.0.0</graalvm.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- Spring Boot AOT Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-tiny:latest</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<!-- GraalVM Native Image Plugin -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${graalvm.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
三、日志注解模块 (logging-annotation)
3.1 pom.xml
xml
<?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">
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-logging</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>logging-annotation</artifactId>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 响应式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 分布式追踪 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
3.2 基础注解定义
3.2.1 核心日志注解
java
package com.example.logging.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 自定义日志注解
* Spring Cloud 2025.0.0 新特性支持
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Loggable {
/**
* 模块名称
*/
String module() default "";
/**
* 操作类型
*/
LogType type() default LogType.OTHER;
/**
* 操作描述
*/
@AliasFor("value")
String description() default "";
@AliasFor("description")
String value() default "";
/**
* 是否记录方法参数
*/
boolean logParams() default true;
/**
* 是否记录返回值
*/
boolean logResult() default true;
/**
* 是否记录执行时间
*/
boolean logTime() default true;
/**
* 慢查询阈值(毫秒)
*/
long slowThreshold() default 1000;
/**
* 是否启用异步记录
*/
boolean async() default true;
/**
* 异步记录策略
*/
AsyncStrategy asyncStrategy() default AsyncStrategy.VIRTUAL_THREAD;
/**
* 是否启用虚拟线程
*/
boolean virtualThread() default true;
/**
* 日志级别
*/
LogLevel level() default LogLevel.INFO;
/**
* 是否推送到日志中心
*/
boolean pushToCenter() default true;
/**
* 业务标识表达式(SpEL)
*/
String bizNo() default "";
/**
* 是否记录堆栈信息
*/
boolean stackTrace() default false;
/**
* 日志分组
*/
String group() default "default";
/**
* 操作类型枚举
*/
enum LogType {
SELECT, // 查询
INSERT, // 新增
UPDATE, // 更新
DELETE, // 删除
IMPORT, // 导入
EXPORT, // 导出
LOGIN, // 登录
LOGOUT, // 登出
UPLOAD, // 上传
DOWNLOAD, // 下载
EXECUTE, // 执行
CALL, // 调用
OTHER // 其他
}
/**
* 异步策略枚举
*/
enum AsyncStrategy {
VIRTUAL_THREAD, // 虚拟线程
THREAD_POOL, // 线程池
REACTIVE, // 响应式
IMMEDIATE // 立即执行
}
/**
* 日志级别枚举
*/
enum LogLevel {
TRACE,
DEBUG,
INFO,
WARN,
ERROR
}
}
3.2.2 响应式日志注解
java
package com.example.logging.annotation;
import org.springframework.core.annotation.AliasFor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.annotation.*;
import java.util.concurrent.CompletableFuture;
/**
* 响应式日志注解
* 支持 Reactive Streams
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Loggable
public @interface ReactiveLog {
/**
* 是否跟踪响应式流
*/
boolean traceStream() default false;
/**
* 最大跟踪元素数量
*/
int maxTraceElements() default 10;
/**
* 响应式超时时间(毫秒)
*/
long timeout() default 30000;
/**
* 是否记录背压
*/
boolean logBackpressure() default false;
/**
* 支持的类型
*/
Class<?>[] supportTypes() default {
Mono.class,
Flux.class,
CompletableFuture.class
};
/**
* 响应式操作类型
*/
ReactiveOperation operation() default ReactiveOperation.UNKNOWN;
enum ReactiveOperation {
MONO_CREATE,
MONO_TRANSFORM,
MONO_DELAY,
FLUX_STREAM,
FLUX_WINDOW,
FLUX_BUFFER,
FUTURE_ASYNC,
FUTURE_COMPLETABLE,
UNKNOWN
}
}
3.2.3 分布式追踪注解
java
package com.example.logging.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
/**
* 分布式追踪注解
* 集成 Micrometer Tracing
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Loggable
public @interface TraceLog {
/**
* Span名称
*/
String spanName() default "";
/**
* 是否创建新Span
*/
boolean newSpan() default true;
/**
* Span类型
*/
SpanKind kind() default SpanKind.INTERNAL;
/**
* 是否记录异常栈
*/
boolean logStackTrace() default true;
/**
* 标签(key=value格式)
*/
String[] tags() default {};
/**
* 事件
*/
String[] events() default {};
/**
* 是否远程调用
*/
boolean remote() default false;
/**
* 远程服务名称
*/
String remoteService() default "";
/**
* Span类型枚举
*/
enum SpanKind {
CLIENT, // 客户端
SERVER, // 服务端
PRODUCER, // 生产者
CONSUMER, // 消费者
INTERNAL // 内部调用
}
}
3.2.4 性能监控注解
java
package com.example.logging.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 性能监控注解
* 集成 Micrometer Metrics
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Loggable
public @interface MetricLog {
/**
* 指标名称
*/
String name() default "";
/**
* 指标描述
*/
String description() default "";
/**
* 指标类型
*/
MetricType type() default MetricType.TIMER;
/**
* 是否记录分位数
*/
boolean percentiles() default true;
/**
* 分位数值
*/
double[] percentileValues() default {0.5, 0.95, 0.99};
/**
* 是否记录直方图
*/
boolean histogram() default false;
/**
* SLA配置(毫秒)
*/
long[] slas() default {10, 50, 100, 500, 1000};
/**
* 指标类型枚举
*/
enum MetricType {
COUNTER, // 计数器
TIMER, // 计时器
GAUGE, // 仪表
LONG_TASK_TIMER, // 长任务计时器
DISTRIBUTION_SUMMARY // 分布摘要
}
}
3.3 日志上下文
3.3.1 日志上下文实体
java
package com.example.logging.context;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import java.io.Serializable;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* 日志上下文
* 支持虚拟线程和响应式编程
*/
@Data
@Accessors(chain = true)
public class LogContext implements Serializable {
private static final long serialVersionUID = 1L;
// 基本信息
private String logId;
private String traceId;
private String spanId;
private String parentSpanId;
// 方法信息
private String className;
private String methodName;
private String signature;
// 业务信息
private String module;
private String operation;
private String description;
private String bizNo;
// 参数和结果
private Object[] args;
private Object result;
private Throwable throwable;
// 时间信息
private LocalDateTime startTime;
private LocalDateTime endTime;
private Duration duration;
// 线程信息
private ThreadInfo threadInfo;
// 请求信息
private RequestInfo requestInfo;
// 扩展属性
private Map<String, Object> attributes = new ConcurrentHashMap<>();
// 响应式上下文
private ReactiveContext reactiveContext;
// 追踪信息
private TraceInfo traceInfo;
/**
* 创建日志上下文
*/
public static LogContext create() {
LogContext context = new LogContext();
context.setLogId(generateLogId());
context.setStartTime(LocalDateTime.now());
context.setThreadInfo(ThreadInfo.capture());
context.setTraceInfo(TraceInfo.capture());
return context;
}
/**
* 从ServerWebExchange创建
*/
public static LogContext fromExchange(ServerWebExchange exchange) {
LogContext context = create();
ServerHttpRequest request = exchange.getRequest();
RequestInfo requestInfo = new RequestInfo();
requestInfo.setRequestId(request.getId());
requestInfo.setMethod(request.getMethod().name());
requestInfo.setUri(request.getURI().toString());
requestInfo.setHeaders(new ConcurrentHashMap<>(request.getHeaders().toSingleValueMap()));
requestInfo.setQueryParams(new ConcurrentHashMap<>(request.getQueryParams().toSingleValueMap()));
requestInfo.setRemoteAddress(request.getRemoteAddress() != null ?
request.getRemoteAddress().getAddress().getHostAddress() : "");
requestInfo.setUserAgent(request.getHeaders().getFirst("User-Agent"));
context.setRequestInfo(requestInfo);
// 设置追踪信息
String traceId = request.getHeaders().getFirst("X-B3-TraceId");
String spanId = request.getHeaders().getFirst("X-B3-SpanId");
if (traceId != null) {
context.setTraceId(traceId);
}
if (spanId != null) {
context.setSpanId(spanId);
}
return context;
}
/**
* 计算持续时间
*/
public LogContext calculateDuration() {
if (startTime != null) {
this.endTime = LocalDateTime.now();
this.duration = Duration.between(startTime, endTime);
}
return this;
}
/**
* 设置异常
*/
public LogContext withException(Throwable throwable) {
this.throwable = throwable;
return this;
}
/**
* 设置结果
*/
public LogContext withResult(Object result) {
this.result = result;
return this;
}
/**
* 设置响应式上下文
*/
public LogContext withReactiveContext(ContextView contextView) {
this.reactiveContext = ReactiveContext.from(contextView);
return this;
}
/**
* 添加属性
*/
public LogContext addAttribute(String key, Object value) {
this.attributes.put(key, value);
return this;
}
/**
* 生成日志ID
*/
private static String generateLogId() {
return java.util.UUID.randomUUID().toString().replace("-", "");
}
/**
* 线程信息
*/
@Data
@Accessors(chain = true)
public static class ThreadInfo implements Serializable {
private long threadId;
private String threadName;
private boolean virtual;
private Thread.State state;
private int priority;
private long threadGroupId;
private String threadGroupName;
public static ThreadInfo capture() {
Thread thread = Thread.currentThread();
ThreadInfo info = new ThreadInfo();
info.setThreadId(thread.threadId());
info.setThreadName(thread.getName());
info.setVirtual(thread.isVirtual());
info.setState(thread.getState());
info.setPriority(thread.getPriority());
ThreadGroup group = thread.getThreadGroup();
if (group != null) {
info.setThreadGroupId(group.getId());
info.setThreadGroupName(group.getName());
}
return info;
}
}
/**
* 请求信息
*/
@Data
@Accessors(chain = true)
public static class RequestInfo implements Serializable {
private String requestId;
private String method;
private String uri;
private Map<String, String> headers;
private Map<String, String> queryParams;
private String remoteAddress;
private String userAgent;
private Map<String, String> pathVariables;
}
/**
* 响应式上下文
*/
@Data
@Accessors(chain = true)
public static class ReactiveContext implements Serializable {
private ContextView contextView;
private boolean onVirtualThread;
private String scheduler;
private long subscriptionTime;
private long requestTime;
public static ReactiveContext from(ContextView contextView) {
ReactiveContext context = new ReactiveContext();
context.setContextView(contextView);
context.setOnVirtualThread(Thread.currentThread().isVirtual());
context.setSubscriptionTime(System.currentTimeMillis());
return context;
}
}
/**
* 追踪信息
*/
@Data
@Accessors(chain = true)
public static class TraceInfo implements Serializable {
private String traceId;
private String spanId;
private String parentSpanId;
private boolean sampled;
private String baggage;
public static TraceInfo capture() {
// 这里可以从MDC或ThreadLocal获取追踪信息
TraceInfo info = new TraceInfo();
// 实际实现中会从Tracing API获取
return info;
}
}
}
四、日志切面实现
4.1 基础切面处理器
java
package com.example.logging.aspect;
import com.example.logging.annotation.Loggable;
import com.example.logging.context.LogContext;
import com.example.logging.event.LogEvent;
import com.example.logging.service.LogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
/**
* 日志切面处理器
* Spring Cloud 2025.0.0 新特性支持
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspectHandler {
private final ApplicationEventPublisher eventPublisher;
private final LogService logService;
private final LogExpressionEvaluator expressionEvaluator;
// 虚拟线程执行器
private static final java.util.concurrent.ExecutorService VIRTUAL_EXECUTOR =
Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("log-virtual-", 0)
.factory()
);
/**
* 切入点:所有@Loggable注解的方法
*/
@Pointcut("@annotation(com.example.logging.annotation.Loggable)")
public void loggablePointcut() {}
/**
* 环绕通知
*/
@Around("loggablePointcut()")
public Object handleLog(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解配置
Loggable loggable = AnnotationUtils.findAnnotation(method, Loggable.class);
if (loggable == null) {
return joinPoint.proceed();
}
// 创建日志上下文
LogContext logContext = createLogContext(joinPoint, loggable);
// 记录开始
logStart(logContext, loggable);
Instant startTime = Instant.now();
Object result = null;
Throwable throwable = null;
try {
// 执行目标方法
result = joinPoint.proceed();
// 根据返回类型处理
if (result instanceof Mono) {
return handleMonoResult((Mono<?>) result, logContext, loggable, startTime);
} else if (result instanceof Flux) {
return handleFluxResult((Flux<?>) result, logContext, loggable, startTime);
} else if (result instanceof CompletableFuture) {
return handleFutureResult((CompletableFuture<?>) result, logContext, loggable, startTime);
}
return result;
} catch (Throwable t) {
throwable = t;
throw t;
} finally {
// 处理同步结果
handleSyncResult(result, throwable, logContext, loggable, startTime);
}
}
/**
* 创建日志上下文
*/
private LogContext createLogContext(ProceedingJoinPoint joinPoint, Loggable loggable) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogContext context = LogContext.create();
context.setClassName(joinPoint.getTarget().getClass().getName());
context.setMethodName(method.getName());
context.setSignature(signature.toString());
context.setModule(loggable.module());
context.setOperation(loggable.value());
context.setDescription(loggable.description());
// 解析业务编号
if (!loggable.bizNo().isEmpty()) {
String bizNo = expressionEvaluator.evaluateBizNo(joinPoint, loggable.bizNo());
context.setBizNo(bizNo);
}
// 记录参数
if (loggable.logParams()) {
context.setArgs(joinPoint.getArgs());
}
return context;
}
/**
* 处理Mono结果
*/
private Mono<?> handleMonoResult(Mono<?> mono, LogContext logContext,
Loggable loggable, Instant startTime) {
AtomicReference<Instant> subscribeTime = new AtomicReference<>(Instant.now());
return mono
.contextWrite(ctx -> {
// 在Reactor Context中存储日志信息
return ctx.put("log.context", logContext)
.put("log.startTime", startTime)
.put("log.subscribeTime", subscribeTime.get());
})
.doOnSubscribe(subscription -> {
subscribeTime.set(Instant.now());
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncStart(logContext)
);
} else {
logService.logSyncStart(logContext);
}
})
.doOnSuccess(result -> {
Duration duration = Duration.between(startTime, Instant.now());
logContext.withResult(result)
.calculateDuration();
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncSuccess(logContext, duration)
);
} else {
logService.logSyncSuccess(logContext, duration);
}
// 检查慢查询
if (duration.toMillis() > loggable.slowThreshold()) {
logService.logSlowQuery(logContext, duration, loggable.slowThreshold());
}
// 发布事件
if (loggable.pushToCenter()) {
eventPublisher.publishEvent(new LogEvent(logContext, loggable));
}
})
.doOnError(throwable -> {
Duration duration = Duration.between(startTime, Instant.now());
logContext.withException(throwable)
.calculateDuration();
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncError(logContext, duration, throwable)
);
} else {
logService.logSyncError(logContext, duration, throwable);
}
// 发布事件
if (loggable.pushToCenter()) {
eventPublisher.publishEvent(new LogEvent(logContext, loggable));
}
})
.doOnCancel(() -> {
Duration duration = Duration.between(startTime, Instant.now());
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncCancel(logContext, duration)
);
}
});
}
/**
* 处理Flux结果
*/
private Flux<?> handleFluxResult(Flux<?> flux, LogContext logContext,
Loggable loggable, Instant startTime) {
AtomicReference<Instant> subscribeTime = new AtomicReference<>(Instant.now());
AtomicReference<Integer> elementCount = new AtomicReference<>(0);
return flux
.contextWrite(ctx -> {
return ctx.put("log.context", logContext)
.put("log.startTime", startTime);
})
.doOnSubscribe(subscription -> {
subscribeTime.set(Instant.now());
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncStart(logContext)
);
}
})
.doOnNext(element -> {
int count = elementCount.updateAndGet(c -> c + 1);
if (loggable.async() && count <= 10) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncElement(logContext, element, count)
);
}
})
.doOnComplete(() -> {
Duration duration = Duration.between(startTime, Instant.now());
logContext.calculateDuration();
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncComplete(logContext, duration, elementCount.get())
);
}
// 发布事件
if (loggable.pushToCenter()) {
eventPublisher.publishEvent(new LogEvent(logContext, loggable));
}
})
.doOnError(throwable -> {
Duration duration = Duration.between(startTime, Instant.now());
logContext.withException(throwable)
.calculateDuration();
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncError(logContext, duration, throwable)
);
} else {
logService.logSyncError(logContext, duration, throwable);
}
// 发布事件
if (loggable.pushToCenter()) {
eventPublisher.publishEvent(new LogEvent(logContext, loggable));
}
});
}
/**
* 处理Future结果
*/
private CompletableFuture<?> handleFutureResult(CompletableFuture<?> future,
LogContext logContext,
Loggable loggable,
Instant startTime) {
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncStart(logContext)
);
} else {
logService.logSyncStart(logContext);
}
return future.whenComplete((result, throwable) -> {
Duration duration = Duration.between(startTime, Instant.now());
if (throwable != null) {
logContext.withException(throwable)
.calculateDuration();
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncError(logContext, duration, throwable)
);
} else {
logService.logSyncError(logContext, duration, throwable);
}
} else {
logContext.withResult(result)
.calculateDuration();
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncSuccess(logContext, duration)
);
} else {
logService.logSyncSuccess(logContext, duration);
}
// 检查慢查询
if (duration.toMillis() > loggable.slowThreshold()) {
logService.logSlowQuery(logContext, duration, loggable.slowThreshold());
}
}
// 发布事件
if (loggable.pushToCenter()) {
eventPublisher.publishEvent(new LogEvent(logContext, loggable));
}
});
}
/**
* 处理同步结果
*/
private void handleSyncResult(Object result, Throwable throwable,
LogContext logContext, Loggable loggable,
Instant startTime) {
Duration duration = Duration.between(startTime, Instant.now());
logContext.calculateDuration();
if (throwable != null) {
logContext.withException(throwable);
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncError(logContext, duration, throwable)
);
} else {
logService.logSyncError(logContext, duration, throwable);
}
} else {
logContext.withResult(result);
if (loggable.async()) {
VIRTUAL_EXECUTOR.submit(() ->
logService.logAsyncSuccess(logContext, duration)
);
} else {
logService.logSyncSuccess(logContext, duration);
}
// 检查慢查询
if (duration.toMillis() > loggable.slowThreshold()) {
logService.logSlowQuery(logContext, duration, loggable.slowThreshold());
}
}
// 发布事件
if (loggable.pushToCenter()) {
eventPublisher.publishEvent(new LogEvent(logContext, loggable));
}
}
/**
* 记录开始日志
*/
private void logStart(LogContext context, Loggable loggable) {
switch (loggable.level()) {
case TRACE:
log.trace("【开始】{} - {}#{} [线程:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
Thread.currentThread().getName()
);
break;
case DEBUG:
log.debug("【开始】{} - {}#{}",
context.getOperation(),
context.getClassName(),
context.getMethodName()
);
break;
case INFO:
log.info("【开始】{} - {}#{}",
context.getOperation(),
context.getClassName(),
context.getMethodName()
);
break;
case WARN:
case ERROR:
// 不记录开始日志
break;
}
}
}
4.2 响应式切面处理器
java
package com.example.logging.aspect;
import com.example.logging.annotation.ReactiveLog;
import com.example.logging.context.LogContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* 响应式日志切面
* Spring Cloud 2025.0.0 响应式增强
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class ReactiveLogAspect {
/**
* 处理响应式日志
*/
@Around("@annotation(com.example.logging.annotation.ReactiveLog)")
public Object handleReactiveLog(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ReactiveLog reactiveLog = AnnotationUtils.findAnnotation(method, ReactiveLog.class);
if (reactiveLog == null) {
return joinPoint.proceed();
}
// 创建日志上下文
LogContext logContext = LogContext.create();
logContext.setClassName(joinPoint.getTarget().getClass().getName());
logContext.setMethodName(method.getName());
Object result = joinPoint.proceed();
if (result instanceof Mono) {
return wrapMono((Mono<?>) result, logContext, reactiveLog);
} else if (result instanceof Flux) {
return wrapFlux((Flux<?>) result, logContext, reactiveLog);
}
return result;
}
/**
* 包装Mono
*/
private Mono<?> wrapMono(Mono<?> mono, LogContext context, ReactiveLog reactiveLog) {
AtomicLong subscribeTime = new AtomicLong();
AtomicLong requestTime = new AtomicLong();
return mono
.doOnSubscribe(subscription -> {
subscribeTime.set(System.currentTimeMillis());
logReactiveStart(context, "MONO", reactiveLog);
})
.doOnRequest(n -> {
requestTime.set(System.currentTimeMillis());
if (reactiveLog.logBackpressure()) {
log.debug("【MONO】请求元素: {}", n);
}
})
.doOnNext(value -> {
long processTime = System.currentTimeMillis() - requestTime.get();
if (reactiveLog.traceStream()) {
log.debug("【MONO】处理元素: {}, 耗时: {}ms",
value, processTime
);
}
})
.doOnSuccess(value -> {
long totalTime = System.currentTimeMillis() - subscribeTime.get();
logReactiveSuccess(context, "MONO", value, totalTime, reactiveLog);
})
.doOnError(throwable -> {
long totalTime = System.currentTimeMillis() - subscribeTime.get();
logReactiveError(context, "MONO", throwable, totalTime, reactiveLog);
})
.timeout(Duration.ofMillis(reactiveLog.timeout()))
.onErrorResume(throwable -> {
log.error("【MONO】响应式超时: {}ms", reactiveLog.timeout(), throwable);
return Mono.error(throwable);
})
.doFinally(signalType -> {
if (reactiveLog.traceStream()) {
log.debug("【MONO】最终信号: {}", signalType);
}
});
}
/**
* 包装Flux
*/
private Flux<?> wrapFlux(Flux<?> flux, LogContext context, ReactiveLog reactiveLog) {
AtomicLong subscribeTime = new AtomicLong();
AtomicInteger elementCount = new AtomicInteger(0);
AtomicLong totalElements = new AtomicLong(0);
return flux
.doOnSubscribe(subscription -> {
subscribeTime.set(System.currentTimeMillis());
logReactiveStart(context, "FLUX", reactiveLog);
})
.doOnRequest(n -> {
if (reactiveLog.logBackpressure()) {
log.debug("【FLUX】请求元素: {}", n);
}
})
.doOnNext(element -> {
int count = elementCount.incrementAndGet();
totalElements.incrementAndGet();
if (reactiveLog.traceStream() && count <= reactiveLog.maxTraceElements()) {
log.debug("【FLUX】元素[{}]: {}", count, element);
}
})
.doOnComplete(() -> {
long totalTime = System.currentTimeMillis() - subscribeTime.get();
logReactiveComplete(context, "FLUX", elementCount.get(), totalTime, reactiveLog);
})
.doOnError(throwable -> {
long totalTime = System.currentTimeMillis() - subscribeTime.get();
logReactiveError(context, "FLUX", throwable, totalTime, reactiveLog);
})
.timeout(Duration.ofMillis(reactiveLog.timeout()))
.onErrorResume(throwable -> {
log.error("【FLUX】响应式超时: {}ms", reactiveLog.timeout(), throwable);
return Flux.error(throwable);
})
.doFinally(signalType -> {
if (reactiveLog.traceStream()) {
log.debug("【FLUX】最终信号: {}, 总元素: {}",
signalType, totalElements.get()
);
}
});
}
private void logReactiveStart(LogContext context, String type, ReactiveLog reactiveLog) {
log.info("【响应式{}】开始 - {}#{} [线程:{}]",
type,
context.getClassName(),
context.getMethodName(),
Thread.currentThread().getName()
);
}
private void logReactiveSuccess(LogContext context, String type,
Object value, long totalTime, ReactiveLog reactiveLog) {
log.info("【响应式{}】成功 - {}#{}, 结果: {}, 耗时: {}ms",
type,
context.getClassName(),
context.getMethodName(),
value != null ? value.toString() : "null",
totalTime
);
}
private void logReactiveComplete(LogContext context, String type,
int elementCount, long totalTime, ReactiveLog reactiveLog) {
log.info("【响应式{}】完成 - {}#{}, 元素数: {}, 耗时: {}ms",
type,
context.getClassName(),
context.getMethodName(),
elementCount,
totalTime
);
}
private void logReactiveError(LogContext context, String type,
Throwable throwable, long totalTime, ReactiveLog reactiveLog) {
log.error("【响应式{}】失败 - {}#{}, 耗时: {}ms, 错误: {}",
type,
context.getClassName(),
context.getMethodName(),
totalTime,
throwable.getMessage(),
throwable
);
}
}
五、日志服务实现
5.1 日志服务接口
java
package com.example.logging.service;
import com.example.logging.context.LogContext;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 日志服务接口
*/
public interface LogService {
// 同步日志
void logSyncStart(LogContext context);
void logSyncSuccess(LogContext context, Duration duration);
void logSyncError(LogContext context, Duration duration, Throwable throwable);
// 异步日志
void logAsyncStart(LogContext context);
void logAsyncSuccess(LogContext context, Duration duration);
void logAsyncError(LogContext context, Duration duration, Throwable throwable);
void logAsyncCancel(LogContext context, Duration duration);
void logAsyncComplete(LogContext context, Duration duration, int elementCount);
void logAsyncElement(LogContext context, Object element, int index);
// 慢查询日志
void logSlowQuery(LogContext context, Duration duration, long threshold);
// 虚拟线程日志
void logVirtualThreadMetrics();
// 响应式日志
Mono<Void> logReactiveStart(LogContext context);
Mono<Void> logReactiveSuccess(LogContext context, Duration duration);
Mono<Void> logReactiveError(LogContext context, Duration duration, Throwable throwable);
}
5.2 日志服务实现
java
package com.example.logging.service.impl;
import com.example.logging.context.LogContext;
import com.example.logging.service.LogService;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* 日志服务实现
* 集成分布式追踪和性能指标
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DefaultLogServiceImpl implements LogService {
private final MeterRegistry meterRegistry;
private final ObjectProvider<Tracer> tracerProvider;
// 虚拟线程执行器
private final java.util.concurrent.ExecutorService virtualExecutor =
Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("log-virtual-", 0)
.factory()
);
// 统计信息
private final AtomicLong totalLogs = new AtomicLong(0);
private final AtomicLong errorLogs = new AtomicLong(0);
private final AtomicLong slowLogs = new AtomicLong(0);
private final AtomicInteger virtualThreadCount = new AtomicInteger(0);
// 计时器缓存
private final Map<String, Timer> timerCache = new ConcurrentHashMap<>();
@Override
public void logSyncStart(LogContext context) {
totalLogs.incrementAndGet();
log.debug("【同步开始】{} - {}#{} [追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
getTraceId()
);
// 记录指标
meterRegistry.counter("log.sync.start",
"module", context.getModule(),
"operation", context.getOperation()
).increment();
}
@Override
public void logSyncSuccess(LogContext context, Duration duration) {
log.info("【同步成功】{} - {}#{}, 耗时: {}ms [追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
getTraceId()
);
// 记录计时指标
Timer timer = getOrCreateTimer(context.getModule(), context.getOperation());
timer.record(duration);
// 记录成功率
meterRegistry.counter("log.sync.success",
"module", context.getModule()
).increment();
}
@Override
public void logSyncError(LogContext context, Duration duration, Throwable throwable) {
errorLogs.incrementAndGet();
log.error("【同步失败】{} - {}#{}, 耗时: {}ms, 错误: {} [追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
throwable.getMessage(),
getTraceId(),
throwable
);
// 记录错误指标
meterRegistry.counter("log.sync.error",
"module", context.getModule(),
"exception", throwable.getClass().getSimpleName()
).increment();
}
@Override
public void logAsyncStart(LogContext context) {
virtualExecutor.submit(() -> {
try {
totalLogs.incrementAndGet();
virtualThreadCount.incrementAndGet();
log.debug("【异步开始】{} - {}#{} [虚拟线程:{}, 追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
Thread.currentThread().threadId(),
getTraceId()
);
// 记录虚拟线程指标
meterRegistry.gauge("log.virtual.thread.count", virtualThreadCount);
meterRegistry.counter("log.async.start",
"module", context.getModule()
).increment();
} catch (Exception e) {
log.error("异步开始日志记录失败", e);
}
});
}
@Override
public void logAsyncSuccess(LogContext context, Duration duration) {
virtualExecutor.submit(() -> {
try {
log.info("【异步成功】{} - {}#{}, 耗时: {}ms [虚拟线程:{}, 追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
Thread.currentThread().threadId(),
getTraceId()
);
// 记录计时指标
Timer timer = getOrCreateTimer(context.getModule(), context.getOperation());
timer.record(duration);
// 记录成功率
meterRegistry.counter("log.async.success",
"module", context.getModule()
).increment();
} catch (Exception e) {
log.error("异步成功日志记录失败", e);
} finally {
virtualThreadCount.decrementAndGet();
}
});
}
@Override
public void logAsyncError(LogContext context, Duration duration, Throwable throwable) {
virtualExecutor.submit(() -> {
try {
errorLogs.incrementAndGet();
log.error("【异步失败】{} - {}#{}, 耗时: {}ms, 错误: {} [虚拟线程:{}, 追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
throwable.getMessage(),
Thread.currentThread().threadId(),
getTraceId(),
throwable
);
// 记录错误指标
meterRegistry.counter("log.async.error",
"module", context.getModule(),
"exception", throwable.getClass().getSimpleName()
).increment();
} catch (Exception e) {
log.error("异步错误日志记录失败", e);
} finally {
virtualThreadCount.decrementAndGet();
}
});
}
@Override
public void logAsyncCancel(LogContext context, Duration duration) {
virtualExecutor.submit(() -> {
try {
log.warn("【异步取消】{} - {}#{}, 耗时: {}ms [虚拟线程:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
Thread.currentThread().threadId()
);
meterRegistry.counter("log.async.cancel",
"module", context.getModule()
).increment();
} catch (Exception e) {
log.error("异步取消日志记录失败", e);
} finally {
virtualThreadCount.decrementAndGet();
}
});
}
@Override
public void logAsyncComplete(LogContext context, Duration duration, int elementCount) {
virtualExecutor.submit(() -> {
try {
log.info("【异步完成】{} - {}#{}, 元素数: {}, 耗时: {}ms [虚拟线程:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
elementCount,
duration.toMillis(),
Thread.currentThread().threadId()
);
meterRegistry.counter("log.async.complete",
"module", context.getModule(),
"elementCount", String.valueOf(elementCount)
).increment();
} catch (Exception e) {
log.error("异步完成日志记录失败", e);
} finally {
virtualThreadCount.decrementAndGet();
}
});
}
@Override
public void logAsyncElement(LogContext context, Object element, int index) {
virtualExecutor.submit(() -> {
try {
if (index <= 10) { // 只记录前10个元素
log.debug("【异步元素】{} - {}#{}, 元素[{}]: {} [虚拟线程:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
index,
element != null ? element.toString() : "null",
Thread.currentThread().threadId()
);
}
} catch (Exception e) {
// 忽略元素记录错误
}
});
}
@Override
public void logSlowQuery(LogContext context, Duration duration, long threshold) {
slowLogs.incrementAndGet();
virtualExecutor.submit(() -> {
try {
log.warn("【慢查询】{} - {}#{}, 耗时: {}ms, 阈值: {}ms [追踪ID:{}]",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
threshold,
getTraceId()
);
// 记录慢查询指标
meterRegistry.counter("log.slow.query",
"module", context.getModule(),
"duration", String.valueOf(duration.toMillis()),
"threshold", String.valueOf(threshold)
).increment();
} catch (Exception e) {
log.error("慢查询日志记录失败", e);
}
});
}
@Override
public void logVirtualThreadMetrics() {
virtualExecutor.submit(() -> {
try {
int count = virtualThreadCount.get();
int activeThreads = Thread.activeCount();
log.debug("【虚拟线程统计】活跃虚拟线程: {}, 总线程: {}, 总日志: {}, 错误日志: {}, 慢查询: {}",
count,
activeThreads,
totalLogs.get(),
errorLogs.get(),
slowLogs.get()
);
// 记录到指标
meterRegistry.gauge("log.metrics.total", totalLogs);
meterRegistry.gauge("log.metrics.errors", errorLogs);
meterRegistry.gauge("log.metrics.slow", slowLogs);
} catch (Exception e) {
log.error("虚拟线程指标记录失败", e);
}
});
}
@Override
public Mono<Void> logReactiveStart(LogContext context) {
return Mono.fromRunnable(() -> {
log.debug("【响应式开始】{} - {}#{}",
context.getOperation(),
context.getClassName(),
context.getMethodName()
);
}).subscribeOn(Schedulers.boundedElastic()).then();
}
@Override
public Mono<Void> logReactiveSuccess(LogContext context, Duration duration) {
return Mono.fromRunnable(() -> {
log.info("【响应式成功】{} - {}#{}, 耗时: {}ms",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis()
);
}).subscribeOn(Schedulers.boundedElastic()).then();
}
@Override
public Mono<Void> logReactiveError(LogContext context, Duration duration, Throwable throwable) {
return Mono.fromRunnable(() -> {
log.error("【响应式失败】{} - {}#{}, 耗时: {}ms, 错误: {}",
context.getOperation(),
context.getClassName(),
context.getMethodName(),
duration.toMillis(),
throwable.getMessage(),
throwable
);
}).subscribeOn(Schedulers.boundedElastic()).then();
}
/**
* 获取或创建计时器
*/
private Timer getOrCreateTimer(String module, String operation) {
String key = module + "." + operation;
return timerCache.computeIfAbsent(key, k ->
Timer.builder("log.execution.time")
.description("Method execution time")
.tag("module", module)
.tag("operation", operation)
.publishPercentiles(0.5, 0.95, 0.99)
.publishPercentileHistogram()
.register(meterRegistry)
);
}
/**
* 获取追踪ID
*/
private String getTraceId() {
try {
Tracer tracer = tracerProvider.getIfAvailable();
if (tracer != null && tracer.currentSpan() != null) {
return tracer.currentSpan().context().traceId();
}
} catch (Exception e) {
// 忽略异常
}
return "N/A";
}
/**
* 关闭资源
*/
public void shutdown() {
virtualExecutor.shutdown();
try {
if (!virtualExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
virtualExecutor.shutdownNow();
}
} catch (InterruptedException e) {
virtualExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
log.info("日志服务关闭完成");
}
}
六、日志自动配置
6.1 自动配置类
java
package com.example.logging.autoconfigure;
import com.example.logging.aspect.LogAspectHandler;
import com.example.logging.aspect.ReactiveLogAspect;
import com.example.logging.service.LogService;
import com.example.logging.service.impl.DefaultLogServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.concurrent.Executors;
/**
* 日志自动配置
*/
@Slf4j
@Configuration
@EnableAspectJAutoProxy
@EnableAsync
@EnableScheduling
@EnableConfigurationProperties(LogProperties.class)
@ConditionalOnProperty(prefix = "spring.logging", name = "enabled", havingValue = "true", matchIfMissing = true)
public class LogAutoConfiguration {
/**
* 日志切面处理器
*/
@Bean
@ConditionalOnMissingBean
public LogAspectHandler logAspectHandler() {
return new LogAspectHandler();
}
/**
* 响应式日志切面
*/
@Bean
@ConditionalOnMissingBean
public ReactiveLogAspect reactiveLogAspect() {
return new ReactiveLogAspect();
}
/**
* 日志服务
*/
@Bean
@ConditionalOnMissingBean
public LogService logService() {
return new DefaultLogServiceImpl();
}
/**
* 虚拟线程执行器
*/
@Bean(destroyMethod = "shutdown")
public java.util.concurrent.ExecutorService virtualThreadExecutor() {
return Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("global-virtual-", 0)
.factory()
);
}
/**
* 日志指标调度器
*/
@Bean
public LogMetricsScheduler logMetricsScheduler(LogService logService) {
return new LogMetricsScheduler(logService);
}
}
6.2 配置属性
java
package com.example.logging.autoconfigure;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* 日志配置属性
*/
@Data
@ConfigurationProperties(prefix = "spring.logging")
public class LogProperties {
/**
* 是否启用日志功能
*/
private boolean enabled = true;
/**
* 是否启用异步日志
*/
private boolean asyncEnabled = true;
/**
* 是否启用虚拟线程
*/
private boolean virtualThreadEnabled = true;
/**
* 是否启用响应式日志
*/
private boolean reactiveEnabled = true;
/**
* 默认日志级别
*/
private String defaultLevel = "INFO";
/**
* 慢查询阈值(毫秒)
*/
private long slowThreshold = 1000;
/**
* 是否记录方法参数
*/
private boolean logParams = true;
/**
* 是否记录返回值
*/
private boolean logResult = true;
/**
* 是否记录执行时间
*/
private boolean logTime = true;
/**
* 是否推送到日志中心
*/
private boolean pushToCenter = true;
/**
* 日志中心地址
*/
private String centerUrl = "http://localhost:8080";
/**
* 连接超时时间
*/
private Duration connectTimeout = Duration.ofSeconds(5);
/**
* 读取超时时间
*/
private Duration readTimeout = Duration.ofSeconds(10);
/**
* 重试次数
*/
private int retryTimes = 3;
/**
* 批量发送大小
*/
private int batchSize = 100;
/**
* 批量发送间隔
*/
private Duration batchInterval = Duration.ofSeconds(5);
/**
* 模块配置
*/
private Map<String, ModuleConfig> modules = new HashMap<>();
@Data
public static class ModuleConfig {
private boolean enabled = true;
private String level = "INFO";
private boolean async = true;
private boolean virtualThread = true;
private boolean logParams = true;
private boolean logResult = true;
private long slowThreshold = 1000;
}
}
七、日志 Starter 模块
7.1 pom.xml
xml
<?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">
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-logging</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>logging-starter</artifactId>
<dependencies>
<!-- 内部模块依赖 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>logging-annotation</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 配置处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Micrometer 追踪 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!-- Micrometer 指标 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<!-- Reactor -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
</dependencies>
</project>
7.2 spring.factories
properties
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.logging.autoconfigure.LogAutoConfiguration
# Spring Boot Configuration Processor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.logging.autoconfigure.LogPropertiesConfiguration
八、使用示例
8.1 用户服务示例
java
package com.example.user.service;
import com.example.logging.annotation.Loggable;
import com.example.logging.annotation.ReactiveLog;
import com.example.logging.annotation.TraceLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
/**
* 用户服务示例
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
/**
* 同步方法日志
*/
@Loggable(
module = "用户管理",
value = "创建用户",
type = Loggable.LogType.INSERT,
bizNo = "#user.username"
)
@Transactional
public User createUser(User user) {
// 业务逻辑
return userRepository.save(user);
}
/**
* 异步方法日志
*/
@Loggable(
module = "用户管理",
value = "批量导入用户",
async = true,
virtualThread = true,
slowThreshold = 5000
)
public CompletableFuture<List<User>> batchImport(List<User> users) {
return CompletableFuture.supplyAsync(() -> {
// 批量处理逻辑
return userRepository.saveAll(users);
}, Executors.newVirtualThreadPerTaskExecutor());
}
/**
* 响应式方法日志
*/
@ReactiveLog(
traceStream = true,
maxTraceElements = 20,
timeout = 30000
)
@Loggable(
module = "用户管理",
value = "查询用户列表",
async = true
)
public Flux<User> listUsers(int page, int size) {
return userRepository.findAll()
.skip(page * size)
.take(size)
.doOnNext(user ->
log.debug("处理用户: {}", user.getUsername())
);
}
/**
* 分布式追踪日志
*/
@TraceLog(
spanName = "user.update",
tags = {"userId=#userId", "operation=update"},
remote = false
)
@Loggable(
module = "用户管理",
value = "更新用户信息",
level = Loggable.LogLevel.INFO
)
public Mono<User> updateUser(Long userId, UserDTO dto) {
return userRepository.findById(userId)
.flatMap(user -> {
user.setEmail(dto.getEmail());
user.setPhone(dto.getPhone());
return userRepository.save(user);
});
}
/**
* 错误处理示例
*/
@Loggable(
module = "用户管理",
value = "删除用户",
level = Loggable.LogLevel.ERROR,
stackTrace = true
)
public void deleteUser(Long userId) {
try {
userRepository.deleteById(userId);
} catch (Exception e) {
log.error("删除用户失败: {}", userId, e);
throw new BusinessException("删除用户失败");
}
}
/**
* 虚拟线程示例
*/
@Loggable(
module = "用户管理",
value = "并发处理用户",
async = true,
virtualThread = true,
asyncStrategy = Loggable.AsyncStrategy.VIRTUAL_THREAD
)
public void processUsersConcurrently(List<Long> userIds) {
try (var scope = new java.util.concurrent.StructuredTaskScope.ShutdownOnFailure()) {
for (Long userId : userIds) {
scope.fork(() -> processUser(userId));
}
scope.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("处理中断", e);
}
}
private User processUser(Long userId) {
// 处理单个用户
return userRepository.findById(userId)
.map(user -> {
// 业务处理
return user;
})
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
}
九、配置示例
9.1 application.yml
yaml
spring:
application:
name: user-service
# 日志配置
logging:
enabled: true
async-enabled: true
virtual-thread-enabled: true
reactive-enabled: true
default-level: INFO
slow-threshold: 1000
log-params: true
log-result: true
push-to-center: true
center-url: http://log-center:8080
# 模块配置
modules:
user-service:
enabled: true
level: INFO
async: true
virtual-thread: true
slow-threshold: 500
order-service:
enabled: true
level: DEBUG
async: false
# 虚拟线程配置
threads:
virtual:
enabled: true
name-prefix: "app-virtual-"
# 响应式配置
webflux:
base-path: /api
# 分布式追踪
sleuth:
enabled: true
sampler:
probability: 1.0
propagation:
type: B3
baggage:
enabled: true
# Micrometer
micrometer:
tracing:
enabled: true
metrics:
export:
prometheus:
enabled: true
# 日志级别配置
logging:
level:
com.example: INFO
com.example.logging: DEBUG
org.springframework.web: INFO
reactor.netty: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
# Logback配置
logback:
rollingpolicy:
max-file-size: 10MB
max-history: 30
total-size-cap: 1GB
# Logstash
logstash:
enabled: true
destination: logstash:5044
# 管理端点
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus,loggers"
endpoint:
health:
show-details: always
loggers:
enabled: true
metrics:
tags:
application: ${spring.application.name}
tracing:
sampling:
probability: 0.1
十、网关集成
十一、Spring Cloud Gateway 集成
11.1 网关全局过滤器
java
package com.example.gateway.filter;
import com.example.logging.annotation.Loggable;
import com.example.logging.context.LogContext;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
/**
* Spring Cloud Gateway 全局日志过滤器
* 支持响应式日志记录和虚拟线程
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GatewayGlobalLogFilter implements GlobalFilter, Ordered {
private final Tracer tracer;
// 请求计数器
private final AtomicLong requestCounter = new AtomicLong(0);
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
@Loggable(
module = "网关",
value = "网关请求处理",
async = true,
virtualThread = true,
level = Loggable.LogLevel.INFO,
pushToCenter = true
)
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long requestId = requestCounter.incrementAndGet();
Instant startTime = Instant.now();
ServerHttpRequest request = exchange.getRequest();
// 创建日志上下文
LogContext logContext = createLogContext(exchange, requestId);
// 获取路由信息
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route != null) {
logContext.addAttribute("route.id", route.getId());
logContext.addAttribute("route.uri", route.getUri().toString());
}
// 创建Span
Span gatewaySpan = createGatewaySpan(request, logContext);
// 包装请求和响应
ServerHttpRequest decoratedRequest = decorateRequest(request, logContext);
ServerHttpResponse decoratedResponse = decorateResponse(exchange.getResponse(), logContext);
ServerWebExchange decoratedExchange = exchange.mutate()
.request(decoratedRequest)
.response(decoratedResponse)
.build();
// 记录请求开始
logRequestStart(logContext, request);
return chain.filter(decoratedExchange)
.contextWrite(ctx -> {
// 在上下文中存储日志信息
return ctx.put("gateway.logContext", logContext)
.put("gateway.startTime", startTime)
.put("gateway.requestId", requestId)
.put("gateway.span", gatewaySpan);
})
.doOnSuccess(v -> {
// 记录请求成功
long duration = Duration.between(startTime, Instant.now()).toMillis();
logRequestSuccess(logContext, duration, gatewaySpan);
})
.doOnError(throwable -> {
// 记录请求失败
long duration = Duration.between(startTime, Instant.now()).toMillis();
logRequestError(logContext, throwable, duration, gatewaySpan);
})
.doFinally(signalType -> {
// 结束Span
if (gatewaySpan != null) {
gatewaySpan.tag("signal.type", signalType.name());
gatewaySpan.end();
}
// 清理资源
DataBufferUtils.release(logContext.getAttributes().remove("request.body"));
DataBufferUtils.release(logContext.getAttributes().remove("response.body"));
log.debug("【网关】请求处理完成: {},信号: {}", requestId, signalType);
});
}
/**
* 创建日志上下文
*/
private LogContext createLogContext(ServerWebExchange exchange, long requestId) {
ServerHttpRequest request = exchange.getRequest();
LogContext context = LogContext.create();
// 请求信息
LogContext.RequestInfo requestInfo = new LogContext.RequestInfo();
requestInfo.setRequestId(String.valueOf(requestId));
requestInfo.setMethod(request.getMethod().name());
requestInfo.setUri(request.getURI().toString());
// 请求头
Map<String, String> headers = request.getHeaders().toSingleValueMap();
requestInfo.setHeaders(headers);
// 查询参数
Map<String, String> queryParams = request.getQueryParams().toSingleValueMap();
requestInfo.setQueryParams(queryParams);
// 远程地址
InetSocketAddress remoteAddress = request.getRemoteAddress();
if (remoteAddress != null) {
requestInfo.setRemoteAddress(remoteAddress.getAddress().getHostAddress());
}
// User Agent
requestInfo.setUserAgent(request.getHeaders().getFirst("User-Agent"));
context.setRequestInfo(requestInfo);
// 追踪信息
String traceId = request.getHeaders().getFirst("X-B3-TraceId");
String spanId = request.getHeaders().getFirst("X-B3-SpanId");
if (traceId != null) {
context.setTraceId(traceId);
}
if (spanId != null) {
context.setSpanId(spanId);
}
return context;
}
/**
* 创建网关Span
*/
private Span createGatewaySpan(ServerHttpRequest request, LogContext logContext) {
if (tracer == null) {
return null;
}
String spanName = "gateway:" + request.getMethod().name() + ":" +
request.getURI().getPath();
Span span = tracer.nextSpan()
.name(spanName)
.kind(Span.Kind.SERVER)
.tag("http.method", request.getMethod().name())
.tag("http.path", request.getURI().getPath())
.tag("gateway.request.id", String.valueOf(logContext.getRequestInfo().getRequestId()))
.tag("thread.virtual", String.valueOf(Thread.currentThread().isVirtual()))
.start();
// 设置追踪ID到上下文
logContext.setTraceId(span.context().traceId());
logContext.setSpanId(span.context().spanId());
return span;
}
/**
* 包装请求(记录请求体)
*/
private ServerHttpRequest decorateRequest(ServerHttpRequest request, LogContext logContext) {
// 如果不是JSON请求,直接返回原请求
MediaType contentType = request.getHeaders().getContentType();
if (contentType == null || !contentType.includes(MediaType.APPLICATION_JSON)) {
return request;
}
return new ServerHttpRequestDecorator(request) {
private final AtomicReference<byte[]> cachedBody = new AtomicReference<>();
@Override
public Flux<DataBuffer> getBody() {
return super.getBody()
.doOnNext(dataBuffer -> {
// 缓存请求体用于日志记录
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
// 限制大小,避免大请求体导致内存问题
if (bytes.length <= 1024 * 10) { // 10KB以内
cachedBody.set(bytes);
logContext.addAttribute("request.body", new String(bytes, StandardCharsets.UTF_8));
}
// 重新创建DataBuffer
DataBufferFactory factory = request.bufferFactory();
DataBuffer newBuffer = factory.wrap(bytes);
cachedBody.set(bytes);
})
.thenMany(Flux.defer(() -> {
byte[] body = cachedBody.get();
if (body != null) {
return Flux.just(request.bufferFactory().wrap(body));
}
return Flux.empty();
}));
}
};
}
/**
* 包装响应(记录响应体)
*/
private ServerHttpResponse decorateResponse(ServerHttpResponse response, LogContext logContext) {
return new ServerHttpResponseDecorator(response) {
private final AtomicReference<byte[]> cachedBody = new AtomicReference<>();
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return super.writeWith(Flux.from(body)
.doOnNext(dataBuffer -> {
// 缓存响应体用于日志记录
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
// 限制大小,避免大响应体导致内存问题
if (bytes.length <= 1024 * 10) { // 10KB以内
cachedBody.set(bytes);
logContext.addAttribute("response.body", new String(bytes, StandardCharsets.UTF_8));
logContext.addAttribute("response.status", String.valueOf(getStatusCode().value()));
}
// 重新创建DataBuffer
DataBufferFactory factory = response.bufferFactory();
DataBuffer newBuffer = factory.wrap(bytes);
cachedBody.set(bytes);
})
.thenMany(Flux.defer(() -> {
byte[] bodyBytes = cachedBody.get();
if (bodyBytes != null) {
return Flux.just(response.bufferFactory().wrap(bodyBytes));
}
return Flux.empty();
})));
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
/**
* 记录请求开始
*/
private void logRequestStart(LogContext context, ServerHttpRequest request) {
log.info("【网关请求开始】ID: {}, 方法: {}, 路径: {}, 来源IP: {}, 虚拟线程: {}",
context.getRequestInfo().getRequestId(),
request.getMethod().name(),
request.getURI().getPath(),
context.getRequestInfo().getRemoteAddress(),
Thread.currentThread().isVirtual()
);
}
/**
* 记录请求成功
*/
private void logRequestSuccess(LogContext context, long duration, Span span) {
String traceId = span != null ? span.context().traceId() : "N/A";
log.info("【网关请求成功】ID: {}, 耗时: {}ms, 追踪ID: {}, 线程: {}",
context.getRequestInfo().getRequestId(),
duration,
traceId,
Thread.currentThread().getName()
);
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("gateway.requests.success",
"method", context.getRequestInfo().getMethod(),
"path", context.getRequestInfo().getUri()
).increment();
io.micrometer.core.instrument.Timer.builder("gateway.request.duration")
.tag("status", "success")
.register(io.micrometer.core.instrument.Metrics.globalRegistry)
.record(Duration.ofMillis(duration));
}
/**
* 记录请求失败
*/
private void logRequestError(LogContext context, Throwable throwable,
long duration, Span span) {
String traceId = span != null ? span.context().traceId() : "N/A";
log.error("【网关请求失败】ID: {}, 耗时: {}ms, 追踪ID: {}, 错误: {}",
context.getRequestInfo().getRequestId(),
duration,
traceId,
throwable.getMessage(),
throwable
);
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("gateway.requests.error",
"method", context.getRequestInfo().getMethod(),
"path", context.getRequestInfo().getUri(),
"exception", throwable.getClass().getSimpleName()
).increment();
}
}
11.2 网关虚拟线程配置
java
package com.example.gateway.config;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.resources.LoopResources;
import java.time.Duration;
import java.util.concurrent.Executors;
/**
* 网关虚拟线程配置
*/
@Configuration
public class GatewayVirtualThreadConfig {
/**
* 配置Netty使用虚拟线程
*/
@Bean
public ReactiveWebServerFactory reactiveWebServerFactory(ServerProperties serverProperties) {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(httpServer -> {
// 使用虚拟线程的事件循环组
httpServer.runOn(LoopResources.create("gateway-virtual",
1, // 线程数
true, // 守护线程
true // 选择最佳事件循环
));
return httpServer;
});
return factory;
}
/**
* 配置HTTP客户端使用虚拟线程
*/
@Bean
public HttpClientCustomizer httpClientVirtualThreadCustomizer() {
return factory -> {
ConnectionProvider provider = ConnectionProvider.builder("gateway-virtual-connection")
.maxConnections(500)
.pendingAcquireTimeout(Duration.ofSeconds(60))
.evictInBackground(Duration.ofSeconds(120))
.build();
HttpClient httpClient = HttpClient.create(provider)
.runOn(LoopResources.create("client-virtual",
2,
true,
true
))
.responseTimeout(Duration.ofSeconds(30))
.compress(true);
return httpClient;
};
}
/**
* Reactor资源工厂
*/
@Bean
public ReactorResourceFactory reactorResourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setUseGlobalResources(false);
factory.setLoopResourcesSupplier(() ->
LoopResources.create("gateway-global-virtual",
Runtime.getRuntime().availableProcessors() * 2,
true,
true
)
);
return factory;
}
/**
* 虚拟线程执行器
*/
@Bean
public java.util.concurrent.ExecutorService gatewayVirtualExecutor() {
return Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("gateway-vt-", 0)
.factory()
);
}
}
十二、服务间调用日志
12.1 OpenFeign 日志拦截器
java
package com.example.feign.interceptor;
import com.example.logging.annotation.Loggable;
import com.example.logging.context.LogContext;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
/**
* OpenFeign 日志拦截器
* 自动传播追踪信息和记录调用日志
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class FeignLoggingInterceptor implements RequestInterceptor {
private final Tracer tracer;
// Feign调用计数器
private final AtomicLong feignCallCounter = new AtomicLong(0);
// 虚拟线程执行器
private final java.util.concurrent.ExecutorService virtualExecutor =
Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("feign-log-", 0)
.factory()
);
@Loggable(
module = "服务调用",
value = "Feign远程调用",
async = true,
virtualThread = true,
level = Loggable.LogLevel.INFO
)
@Override
public void apply(RequestTemplate template) {
long callId = feignCallCounter.incrementAndGet();
Instant startTime = Instant.now();
// 创建日志上下文
LogContext logContext = LogContext.create();
logContext.setOperation("Feign远程调用");
logContext.setClassName(template.feignTarget().type().getName());
logContext.setMethodName(template.methodMetadata().configKey());
logContext.addAttribute("feign.call.id", String.valueOf(callId));
logContext.addAttribute("service.url", template.feignTarget().url());
logContext.addAttribute("request.url", template.url());
logContext.addAttribute("request.headers", template.headers());
// 记录开始日志
logFeignCallStart(logContext, template);
// 注入追踪头
injectTraceHeaders(template);
// 注入自定义头
template.header("X-Feign-Call-Id", String.valueOf(callId));
template.header("X-Feign-Start-Time", String.valueOf(startTime.toEpochMilli()));
template.header("X-Is-Virtual-Thread",
String.valueOf(Thread.currentThread().isVirtual()));
// 记录请求体(如果有)
if (template.body() != null) {
String body = new String(template.body());
if (body.length() <= 1024) { // 只记录1KB以内的请求体
logContext.addAttribute("request.body", body);
}
}
// 异步记录调用信息
virtualExecutor.submit(() -> {
try {
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("feign.calls.initiated",
"service", extractServiceName(template),
"method", template.method()
).increment();
// 创建Feign调用的Span
createFeignSpan(template, logContext);
} catch (Exception e) {
log.error("Feign日志记录失败", e);
}
});
// 存储日志上下文,供响应拦截器使用
template.header("X-Log-Context", serializeLogContext(logContext));
}
/**
* 注入追踪头
*/
private void injectTraceHeaders(RequestTemplate template) {
if (tracer == null) {
return;
}
Span currentSpan = tracer.currentSpan();
if (currentSpan != null) {
// 传播追踪上下文
tracer.inject(currentSpan.context(), template, (carrier, key, value) -> {
if (carrier instanceof RequestTemplate) {
((RequestTemplate) carrier).header(key, value);
}
});
// 记录追踪信息到日志上下文
template.header("X-Parent-Trace-Id", currentSpan.context().traceId());
template.header("X-Parent-Span-Id", currentSpan.context().spanId());
}
}
/**
* 创建Feign调用Span
*/
private void createFeignSpan(RequestTemplate template, LogContext logContext) {
if (tracer == null) {
return;
}
String spanName = "feign:" + extractServiceName(template) + ":" +
template.methodMetadata().method().getName();
Span span = tracer.nextSpan()
.name(spanName)
.kind(Span.Kind.CLIENT)
.tag("rpc.system", "feign")
.tag("rpc.service", extractServiceName(template))
.tag("rpc.method", template.methodMetadata().method().getName())
.tag("http.method", template.method())
.tag("http.url", template.url())
.tag("feign.call.id", String.valueOf(logContext.getAttributes().get("feign.call.id")))
.start();
// 设置追踪ID到日志上下文
logContext.setTraceId(span.context().traceId());
logContext.setSpanId(span.context().spanId());
// 在Span上下文中执行
try (Tracer.SpanInScope scope = tracer.withSpan(span)) {
log.debug("【Feign调用Span创建】名称: {}, 追踪ID: {}",
spanName, span.context().traceId()
);
}
}
/**
* 提取服务名称
*/
private String extractServiceName(RequestTemplate template) {
Class<?> targetType = template.feignTarget().type();
FeignClient feignClient = targetType.getAnnotation(FeignClient.class);
if (feignClient != null) {
return feignClient.name();
}
return targetType.getSimpleName();
}
/**
* 记录Feign调用开始
*/
private void logFeignCallStart(LogContext context, RequestTemplate template) {
log.info("【Feign调用开始】服务: {}, 方法: {}, URL: {}, 虚拟线程: {}",
extractServiceName(template),
template.methodMetadata().method().getName(),
template.url(),
Thread.currentThread().isVirtual()
);
}
/**
* 序列化日志上下文
*/
private String serializeLogContext(LogContext context) {
try {
// 简单序列化,实际可以使用JSON
return String.format("%s|%s|%s",
context.getLogId(),
context.getTraceId(),
context.getSpanId()
);
} catch (Exception e) {
return "";
}
}
}
12.2 Feign 响应拦截器
java
package com.example.feign.interceptor;
import com.example.logging.context.LogContext;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
/**
* Feign 响应拦截器
* 记录响应日志和处理错误
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class FeignResponseInterceptor implements ErrorDecoder {
// 虚拟线程执行器
private final java.util.concurrent.ExecutorService virtualExecutor =
Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("feign-resp-", 0)
.factory()
);
@Override
public Exception decode(String methodKey, Response response) {
// 解析日志上下文
String logContextHeader = response.request().headers().get("X-Log-Context");
if (logContextHeader != null && !logContextHeader.isEmpty()) {
LogContext logContext = deserializeLogContext(logContextHeader);
String startTimeHeader = response.request().headers().get("X-Feign-Start-Time");
if (startTimeHeader != null) {
try {
long startTime = Long.parseLong(startTimeHeader);
long duration = System.currentTimeMillis() - startTime;
// 异步记录错误响应
virtualExecutor.submit(() -> {
logFeignError(logContext, methodKey, response, duration);
});
} catch (NumberFormatException e) {
log.warn("解析Feign开始时间失败", e);
}
}
}
// 使用默认错误解码器
return new ErrorDecoder.Default().decode(methodKey, response);
}
/**
* 记录Feign调用成功
*/
public void logFeignSuccess(Response response, Duration duration) {
virtualExecutor.submit(() -> {
try {
String logContextHeader = response.request().headers().get("X-Log-Context");
if (logContextHeader != null && !logContextHeader.isEmpty()) {
LogContext logContext = deserializeLogContext(logContextHeader);
log.info("【Feign调用成功】服务: {}, 方法: {}, 耗时: {}ms, 状态码: {}",
extractServiceNameFromRequest(response.request()),
extractMethodNameFromRequest(response.request()),
duration.toMillis(),
response.status()
);
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("feign.calls.success",
"service", extractServiceNameFromRequest(response.request()),
"status", String.valueOf(response.status())
).increment();
io.micrometer.core.instrument.Timer.builder("feign.call.duration")
.tag("status", "success")
.register(io.micrometer.core.instrument.Metrics.globalRegistry)
.record(duration);
}
} catch (Exception e) {
log.error("Feign成功日志记录失败", e);
}
});
}
/**
* 记录Feign调用错误
*/
private void logFeignError(LogContext context, String methodKey,
Response response, long duration) {
log.error("【Feign调用失败】服务: {}, 方法: {}, 耗时: {}ms, 状态码: {}, 错误: {}",
extractServiceName(context),
methodKey,
duration,
response.status(),
response.reason()
);
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("feign.calls.error",
"service", extractServiceName(context),
"status", String.valueOf(response.status())
).increment();
io.micrometer.core.instrument.Timer.builder("feign.call.duration")
.tag("status", "error")
.register(io.micrometer.core.instrument.Metrics.globalRegistry)
.record(Duration.ofMillis(duration));
}
/**
* 反序列化日志上下文
*/
private LogContext deserializeLogContext(String header) {
try {
String[] parts = header.split("\\|");
if (parts.length >= 3) {
LogContext context = new LogContext();
context.setLogId(parts[0]);
context.setTraceId(parts[1]);
context.setSpanId(parts[2]);
return context;
}
} catch (Exception e) {
log.warn("反序列化日志上下文失败", e);
}
return LogContext.create();
}
/**
* 从请求中提取服务名称
*/
private String extractServiceNameFromRequest(feign.Request request) {
// 从URL或header中提取服务名称
String url = request.url();
// 简化的提取逻辑
if (url.contains("/api/")) {
String[] parts = url.split("/");
for (int i = 0; i < parts.length; i++) {
if ("api".equals(parts[i]) && i + 1 < parts.length) {
return parts[i + 1];
}
}
}
return "unknown";
}
/**
* 从请求中提取方法名称
*/
private String extractMethodNameFromRequest(feign.Request request) {
String url = request.url();
String[] parts = url.split("/");
return parts.length > 0 ? parts[parts.length - 1] : "unknown";
}
/**
* 从上下文中提取服务名称
*/
private String extractServiceName(LogContext context) {
Object serviceName = context.getAttributes().get("service.url");
return serviceName != null ? serviceName.toString() : "unknown";
}
}
十三、服务网格集成
13.1 Istio 边车日志集成
java
package com.example.mesh.integration;
import com.example.logging.annotation.Loggable;
import com.example.logging.context.LogContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
/**
* 服务网格集成配置
* 支持Istio边车日志和追踪
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class ServiceMeshIntegrationConfig {
/**
* RestTemplate 拦截器(支持Istio)
*/
@Bean
@LoadBalanced
public RestTemplate meshAwareRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(
new MeshRestTemplateInterceptor()
));
return restTemplate;
}
/**
* WebClient 过滤器(支持Istio)
*/
@Bean
@LoadBalanced
public WebClient meshAwareWebClient() {
return WebClient.builder()
.filter(meshExchangeFilterFunction())
.build();
}
/**
* Mesh感知的ExchangeFilterFunction
*/
@Bean
public ExchangeFilterFunction meshExchangeFilterFunction() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
// 在请求前添加Mesh相关header
ClientRequest newRequest = ClientRequest.from(clientRequest)
.header("X-Request-Id", generateRequestId())
.header("X-Mesh-Service", getCurrentServiceName())
.header("X-Envoy-Timeout", "30s")
.build();
return Mono.just(newRequest);
});
}
/**
* RestTemplate拦截器实现
*/
@Loggable(
module = "服务网格",
value = "Mesh服务调用",
async = true,
virtualThread = true
)
private static class MeshRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final java.util.concurrent.ExecutorService virtualExecutor =
Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("mesh-rest-", 0)
.factory()
);
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
long startTime = System.currentTimeMillis();
String requestId = generateRequestId();
// 添加Mesh header
request.getHeaders().add("X-Request-Id", requestId);
request.getHeaders().add("X-Mesh-Service", getCurrentServiceName());
request.getHeaders().add("X-Caller-Service", System.getProperty("spring.application.name", "unknown"));
// 记录调用开始
logMeshCallStart(request, requestId);
try {
ClientHttpResponse response = execution.execute(request, body);
long duration = System.currentTimeMillis() - startTime;
// 异步记录调用成功
virtualExecutor.submit(() -> {
logMeshCallSuccess(request, response, requestId, duration);
});
return response;
} catch (IOException e) {
long duration = System.currentTimeMillis() - startTime;
// 异步记录调用失败
virtualExecutor.submit(() -> {
logMeshCallError(request, e, requestId, duration);
});
throw e;
}
}
private void logMeshCallStart(HttpRequest request, String requestId) {
log.info("【Mesh调用开始】ID: {}, 服务: {}, 路径: {}, 虚拟线程: {}",
requestId,
extractServiceName(request.getURI().toString()),
request.getURI().getPath(),
Thread.currentThread().isVirtual()
);
}
private void logMeshCallSuccess(HttpRequest request, ClientHttpResponse response,
String requestId, long duration) {
try {
log.info("【Mesh调用成功】ID: {}, 服务: {}, 耗时: {}ms, 状态码: {}",
requestId,
extractServiceName(request.getURI().toString()),
duration,
response.getStatusCode().value()
);
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("mesh.calls.success",
"service", extractServiceName(request.getURI().toString()),
"status", String.valueOf(response.getStatusCode().value())
).increment();
} catch (IOException e) {
log.error("记录Mesh调用成功日志失败", e);
}
}
private void logMeshCallError(HttpRequest request, IOException exception,
String requestId, long duration) {
log.error("【Mesh调用失败】ID: {}, 服务: {}, 耗时: {}ms, 错误: {}",
requestId,
extractServiceName(request.getURI().toString()),
duration,
exception.getMessage(),
exception
);
// 记录到指标
io.micrometer.core.instrument.Metrics.counter("mesh.calls.error",
"service", extractServiceName(request.getURI().toString()),
"exception", exception.getClass().getSimpleName()
).increment();
}
}
/**
* 生成请求ID
*/
private static String generateRequestId() {
return java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
/**
* 获取当前服务名称
*/
private static String getCurrentServiceName() {
return System.getProperty("spring.application.name", "unknown-service");
}
/**
* 从URL中提取服务名称
*/
private static String extractServiceName(String url) {
// 简化的服务名称提取逻辑
// 实际中可能根据服务发现机制来提取
if (url.contains("://")) {
url = url.substring(url.indexOf("://") + 3);
}
if (url.contains("/")) {
url = url.substring(0, url.indexOf("/"));
}
if (url.contains(":")) {
url = url.substring(0, url.indexOf(":"));
}
return url;
}
}
十四、监控与告警
14.1 Actuator 端点扩展
java
package com.example.logging.actuator;
import com.example.logging.context.LogContext;
import com.example.logging.service.LogService;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 自定义Actuator端点
* 提供日志相关的监控和管理功能
*/
@Slf4j
@Component
@WebEndpoint(id = "logging")
@RequiredArgsConstructor
public class LoggingActuatorEndpoint {
private final MeterRegistry meterRegistry;
private final LogService logService;
// 日志统计缓存
private final Map<String, LogStatistics> statisticsCache = new ConcurrentHashMap<>();
private final AtomicLong lastResetTime = new AtomicLong(System.currentTimeMillis());
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 获取日志统计信息
*/
@ReadOperation
public Map<String, Object> getLoggingStats() {
Map<String, Object> stats = new LinkedHashMap<>();
// 基本统计
stats.put("timestamp", LocalDateTime.now().format(TIME_FORMATTER));
stats.put("lastReset", formatTime(lastResetTime.get()));
// 线程统计
stats.put("threads", getThreadStats());
// 日志量统计
stats.put("volume", getLogVolumeStats());
// 性能统计
stats.put("performance", getPerformanceStats());
// 虚拟线程统计
stats.put("virtualThreads", getVirtualThreadStats());
// 错误统计
stats.put("errors", getErrorStats());
return stats;
}
/**
* 重置统计信息
*/
@WriteOperation
public Map<String, Object> resetStats() {
statisticsCache.clear();
lastResetTime.set(System.currentTimeMillis());
Map<String, Object> result = new HashMap<>();
result.put("message", "统计信息已重置");
result.put("resetTime", formatTime(lastResetTime.get()));
log.info("日志统计信息已重置");
return result;
}
/**
* 获取线程统计
*/
private Map<String, Object> getThreadStats() {
Map<String, Object> threadStats = new HashMap<>();
ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
while (rootGroup.getParent() != null) {
rootGroup = rootGroup.getParent();
}
Thread[] threads = new Thread[rootGroup.activeCount()];
rootGroup.enumerate(threads, true);
long virtualThreadCount = Arrays.stream(threads)
.filter(Thread::isVirtual)
.count();
long platformThreadCount = Arrays.stream(threads)
.filter(t -> !t.isVirtual())
.count();
threadStats.put("total", threads.length);
threadStats.put("virtual", virtualThreadCount);
threadStats.put("platform", platformThreadCount);
threadStats.put("daemon", Arrays.stream(threads)
.filter(Thread::isDaemon)
.count());
return threadStats;
}
/**
* 获取日志量统计
*/
private Map<String, Object> getLogVolumeStats() {
Map<String, Object> volumeStats = new HashMap<>();
// 从MeterRegistry获取指标
Collection<io.micrometer.core.instrument.Counter> counters =
meterRegistry.find("log.").counters();
Map<String, Double> counterValues = new HashMap<>();
counters.forEach(counter -> {
String name = counter.getId().getName();
double value = counter.count();
counterValues.put(name, value);
});
volumeStats.put("counters", counterValues);
volumeStats.put("totalLogs", counterValues.values().stream()
.mapToDouble(Double::doubleValue)
.sum());
return volumeStats;
}
/**
* 获取性能统计
*/
private Map<String, Object> getPerformanceStats() {
Map<String, Object> perfStats = new HashMap<>();
// 收集计时器信息
Collection<io.micrometer.core.instrument.Timer> timers =
meterRegistry.find("log.execution.time").timers();
List<Map<String, Object>> timerInfos = new ArrayList<>();
timers.forEach(timer -> {
Map<String, Object> timerInfo = new HashMap<>();
timerInfo.put("name", timer.getId().getName());
timerInfo.put("count", timer.count());
timerInfo.put("mean", timer.mean(io.micrometer.core.instrument.TimeUnit.MILLISECONDS));
timerInfo.put("max", timer.max(io.micrometer.core.instrument.TimeUnit.MILLISECONDS));
timerInfo.put("p95", timer.percentile(0.95, io.micrometer.core.instrument.TimeUnit.MILLISECONDS));
timerInfos.add(timerInfo);
});
perfStats.put("timers", timerInfos);
return perfStats;
}
/**
* 获取虚拟线程统计
*/
private Map<String, Object> getVirtualThreadStats() {
Map<String, Object> vtStats = new HashMap<>();
// 虚拟线程创建率
io.micrometer.core.instrument.Counter vtCounter =
meterRegistry.find("log.virtual.thread.count").counter();
if (vtCounter != null) {
vtStats.put("created", vtCounter.count());
}
// 活跃虚拟线程数
long activeVirtualThreads = Thread.getAllStackTraces().keySet().stream()
.filter(Thread::isVirtual)
.count();
vtStats.put("active", activeVirtualThreads);
vtStats.put("carrierThreads", getCarrierThreadCount());
return vtStats;
}
/**
* 获取载体线程数
*/
private int getCarrierThreadCount() {
// 估算载体线程数
return Runtime.getRuntime().availableProcessors() * 2;
}
/**
* 获取错误统计
*/
private Map<String, Object> getErrorStats() {
Map<String, Object> errorStats = new HashMap<>();
// 错误计数器
Collection<io.micrometer.core.instrument.Counter> errorCounters =
meterRegistry.find("log.*.error").counters();
Map<String, Double> errors = new HashMap<>();
errorCounters.forEach(counter -> {
String name = counter.getId().getName();
double value = counter.count();
errors.put(name, value);
});
errorStats.put("counters", errors);
errorStats.put("totalErrors", errors.values().stream()
.mapToDouble(Double::doubleValue)
.sum());
return errorStats;
}
/**
* 格式化时间戳
*/
private String formatTime(long timestamp) {
return LocalDateTime.ofInstant(
java.time.Instant.ofEpochMilli(timestamp),
java.time.ZoneId.systemDefault()
).format(TIME_FORMATTER);
}
/**
* 日志统计数据结构
*/
@Data
private static class LogStatistics {
private String module;
private long totalRequests;
private long successfulRequests;
private long failedRequests;
private long slowQueries;
private double avgResponseTime;
private double p95ResponseTime;
private double p99ResponseTime;
private LocalDateTime lastUpdated;
public void incrementTotal() {
totalRequests++;
lastUpdated = LocalDateTime.now();
}
public void incrementSuccess() {
successfulRequests++;
lastUpdated = LocalDateTime.now();
}
public void incrementFailure() {
failedRequests++;
lastUpdated = LocalDateTime.now();
}
public void incrementSlowQuery() {
slowQueries++;
lastUpdated = LocalDateTime.now();
}
}
}
十五、测试与验证
15.1 集成测试配置
java
package com.example.test;
import com.example.logging.annotation.Loggable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* 日志注解集成测试
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class LoggingIntegrationTest {
@Autowired
private TestService testService;
@MockBean
private LoggingMetricsCollector metricsCollector;
@Test
void testSyncMethodLogging() {
// 执行测试方法
String result = testService.syncMethod("test-input");
// 验证结果
assertThat(result).isEqualTo("SYNC-test-input");
// 验证指标被记录
verify(metricsCollector, times(1)).recordLogCount(
eq("test-module"), eq("INFO"), eq(true)
);
}
@Test
void testAsyncMethodLogging() throws Exception {
// 执行异步方法
CompletableFuture<String> future = testService.asyncMethod("async-input");
String result = future.get(5, TimeUnit.SECONDS);
// 验证结果
assertThat(result).isEqualTo("ASYNC-async-input");
// 验证虚拟线程指标
verify(metricsCollector, atLeastOnce()).recordVirtualThreadMetrics();
}
@Test
void testReactiveMethodLogging() {
// 执行响应式方法
Mono<String> mono = testService.reactiveMethod("reactive-input");
StepVerifier.create(mono)
.expectNext("REACTIVE-reactive-input")
.verifyComplete();
// 验证响应式指标
verify(metricsCollector, atLeastOnce()).recordReactiveMetrics(
anyString(), anyInt(), any()
);
}
@Test
void testErrorLogging() {
// 验证异常日志
try {
testService.errorMethod();
} catch (RuntimeException e) {
assertThat(e.getMessage()).isEqualTo("Test error");
}
// 验证错误指标
verify(metricsCollector, times(1)).recordLogCount(
eq("test-module"), eq("ERROR"), eq(false)
);
}
@Test
void testVirtualThreadMethod() throws Exception {
// 执行虚拟线程方法
CompletableFuture<String> future = testService.virtualThreadMethod("virtual-input");
String result = future.get(5, TimeUnit.SECONDS);
assertThat(result).isEqualTo("VIRTUAL-virtual-input");
// 验证虚拟线程使用情况
verify(metricsCollector, atLeastOnce()).recordVirtualThreadMetrics();
}
/**
* 测试服务
*/
@Component
static class TestService {
@Loggable(
module = "test-module",
value = "同步方法测试",
level = Loggable.LogLevel.INFO
)
public String syncMethod(String input) {
return "SYNC-" + input;
}
@Loggable(
module = "test-module",
value = "异步方法测试",
async = true,
virtualThread = true
)
public CompletableFuture<String> asyncMethod(String input) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "ASYNC-" + input;
});
}
@Loggable(
module = "test-module",
value = "响应式方法测试",
async = true
)
public Mono<String> reactiveMethod(String input) {
return Mono.fromCallable(() -> "REACTIVE-" + input)
.delayElement(java.time.Duration.ofMillis(100));
}
@Loggable(
module = "test-module",
value = "错误方法测试",
level = Loggable.LogLevel.ERROR
)
public void errorMethod() {
throw new RuntimeException("Test error");
}
@Loggable(
module = "test-module",
value = "虚拟线程方法测试",
async = true,
virtualThread = true,
asyncStrategy = Loggable.AsyncStrategy.VIRTUAL_THREAD
)
public CompletableFuture<String> virtualThreadMethod(String input) {
return CompletableFuture.supplyAsync(() -> {
// 在虚拟线程中执行
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "VIRTUAL-" + input;
});
}
}
}
十六、部署配置
16.1 Dockerfile (原生镜像)
dockerfile
# 第一阶段:构建原生镜像
FROM ghcr.io/graalvm/native-image:ol8-java21 AS builder
# 安装必要的构建工具
RUN microdnf install -y gcc gcc-c++ zlib-devel
# 设置工作目录
WORKDIR /app
# 复制构建文件
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
# 下载依赖
RUN ./mvnw dependency:go-offline -B
# 构建原生镜像
RUN ./mvnw clean package -DskipTests -Pnative
# 第二阶段:运行镜像
FROM oraclelinux:9-slim
# 安装必要的运行时库
RUN microdnf install -y \
ca-certificates \
tzdata \
glibc-langpack-en \
&& microdnf clean all
# 设置时区
ENV TZ=Asia/Shanghai
# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 设置工作目录
WORKDIR /app
# 复制原生可执行文件
COPY --from=builder /app/target/*-runner /app/application
COPY --from=builder /app/src/main/resources /app/resources
# 设置权限
RUN chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8080
EXPOSE 8081 # 监控端口
# 设置健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 运行应用
ENTRYPOINT ["/app/application"]
CMD []
16.2 docker-compose.yml
yaml
version: '3.8'
services:
# 日志中心服务
log-center:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
- "8081:8081"
environment:
- SPRING_PROFILES_ACTIVE=production
- JAVA_OPTS=-XX:+UseZGC -Xmx512m -Xms512m
- LOGGING_CONFIG_ENABLED=true
- LOGGING_VIRTUAL_THREAD_ENABLED=true
- MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,prometheus,logging
networks:
- logging-network
depends_on:
- elasticsearch
- redis
deploy:
resources:
limits:
memory: 768M
reservations:
memory: 512M
replicas: 3
placement:
constraints:
- node.role==manager
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Elasticsearch
elasticsearch:
image: elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms512m -Xmx512m
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
networks:
- logging-network
deploy:
resources:
limits:
memory: 1G
# Redis缓存
redis:
image: redis:7.2-alpine
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redisdata:/data
networks:
- logging-network
# Prometheus监控
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
networks:
- logging-network
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
# Grafana可视化
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
networks:
- logging-network
depends_on:
- prometheus
networks:
logging-network:
driver: bridge
volumes:
esdata:
driver: local
redisdata:
driver: local
prometheus-data:
driver: local
grafana-data:
driver: local
十七、最佳实践总结
17.1 使用建议
java
/**
* 日志注解最佳实践示例
*/
@Slf4j
@Service
public class LoggingBestPracticeService {
// 1. 明确的模块划分
@Loggable(
module = "用户管理",
value = "创建用户账户",
type = Loggable.LogType.INSERT,
bizNo = "#userDto.username"
)
public User createUser(UserDTO userDto) {
// 业务逻辑
}
// 2. 敏感信息脱敏
@Loggable(
module = "认证授权",
value = "用户登录认证",
logParams = false, // 不记录密码参数
logResult = false // 不记录令牌结果
)
public AuthToken login(String username, String password) {
// 认证逻辑
}
// 3. 大文件/大结果集处理
@Loggable(
module = "文件服务",
value = "下载文件",
logParams = true,
logResult = false // 不记录文件内容
)
public byte[] downloadFile(String fileId) {
// 文件下载逻辑
}
// 4. 性能敏感操作使用异步
@Loggable(
module = "报表服务",
value = "生成年度报表",
async = true,
virtualThread = true,
slowThreshold = 30000 // 30秒超时标记为慢查询
)
public CompletableFuture<Report> generateAnnualReport(ReportRequest request) {
// 报表生成逻辑
}
// 5. 分布式追踪集成
@TraceLog(
spanName = "payment.process",
tags = {"orderId=#orderId", "amount=#amount"},
remote = true,
remoteService = "payment-service"
)
@Loggable(
module = "支付服务",
value = "处理支付订单"
)
public PaymentResult processPayment(String orderId, BigDecimal amount) {
// 支付处理逻辑
}
// 6. 响应式方法
@ReactiveLog(
traceStream = true,
maxTraceElements = 50,
timeout = 15000
)
@Loggable(
module = "消息服务",
value = "流式处理消息"
)
public Flux<Message> processMessageStream(Flux<Message> messages) {
return messages
.doOnNext(msg -> log.debug("Processing message: {}", msg.getId()))
.buffer(10)
.flatMap(this::processBatch);
}
// 7. 错误处理和降级
@Loggable(
module = "外部服务",
value = "调用第三方API",
level = Loggable.LogLevel.WARN,
stackTrace = true
)
public ExternalResponse callExternalApi(ExternalRequest request) {
try {
return externalClient.call(request);
} catch (ExternalServiceException e) {
log.error("外部服务调用失败", e);
return fallbackResponse();
}
}
// 8. 虚拟线程并发处理
@Loggable(
module = "数据处理",
value = "批量数据处理",
async = true,
virtualThread = true,
asyncStrategy = Loggable.AsyncStrategy.VIRTUAL_THREAD
)
public void batchProcess(List<DataItem> items) {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
List<Future<ProcessResult>> futures = items.stream()
.map(item -> scope.fork(() -> processItem(item)))
.toList();
scope.join();
scope.throwIfFailed();
futures.forEach(future -> {
try {
ProcessResult result = future.resultNow();
log.debug("Processed item: {}", result.getItemId());
} catch (Exception e) {
log.warn("Item processing failed", e);
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("批量处理被中断", e);
}
}
// 9. 审计日志
@Loggable(
module = "审计日志",
value = "重要业务操作",
level = Loggable.LogLevel.WARN,
pushToCenter = true,
group = "audit"
)
@Transactional
public AuditRecord auditImportantOperation(AuditOperation operation) {
// 审计记录逻辑
}
}
17.2 性能优化配置
yaml
# application-performance.yml
spring:
logging:
# 生产环境推荐配置
enabled: true
async-enabled: true
virtual-thread-enabled: true
reactive-enabled: true
default-level: INFO
slow-threshold: 1000
log-params: true
log-result: true
push-to-center: true
# 模块特定配置
modules:
gateway-service:
enabled: true
level: INFO
async: true
virtual-thread: true
slow-threshold: 500
user-service:
enabled: true
level: INFO
async: true
virtual-thread: true
slow-threshold: 800
order-service:
enabled: true
level: INFO
async: false # 订单服务追求强一致性,使用同步日志
log-params: false # 订单数据量大,不记录参数
# 虚拟线程配置
threads:
virtual:
enabled: true
executor:
core-pool-size: 10
max-pool-size: 100
queue-capacity: 1000
keep-alive-seconds: 60
thread-name-prefix: "app-vt-"
# 响应式配置
webflux:
max-in-memory-size: 256KB # 限制内存使用
# 监控配置
management:
metrics:
export:
prometheus:
step: 1m
distribution:
percentiles-histogram:
http.server.requests: true
sla:
http.server.requests: 10ms, 50ms, 100ms, 500ms, 1s, 5s
# Sleuth配置
sleuth:
enabled: true
sampler:
probability: 0.1 # 生产环境采样率
propagation:
type: B3,W3C
# 日志级别控制
logging:
level:
root: INFO
com.example: DEBUG
com.example.logging: INFO
org.springframework.web: WARN
reactor.netty: WARN
io.netty: WARN
# 异步日志配置
async:
enabled: true
queue-size: 10000
discarding-threshold: 0
include-caller-data: false
never-block: true
十八、故障排查指南
18.1 常见问题解决方案
java
package com.example.logging.troubleshooting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 日志系统故障排查工具
*/
@Slf4j
@Component
public class LoggingTroubleshooter {
// 问题计数器
private final Map<String, AtomicInteger> issueCounters = new ConcurrentHashMap<>();
// 最后检测时间
private volatile long lastCheckTime = System.currentTimeMillis();
/**
* 诊断日志系统健康状况
*/
public HealthDiagnosis diagnoseHealth() {
HealthDiagnosis diagnosis = new HealthDiagnosis();
diagnosis.setTimestamp(System.currentTimeMillis());
// 检查线程状态
diagnoseThreadIssues(diagnosis);
// 检查内存状态
diagnoseMemoryIssues(diagnosis);
// 检查死锁
diagnoseDeadlocks(diagnosis);
// 检查虚拟线程泄漏
diagnoseVirtualThreadLeaks(diagnosis);
// 检查日志积压
diagnoseLogBacklog(diagnosis);
// 生成诊断报告
generateDiagnosticReport(diagnosis);
return diagnosis;
}
/**
* 诊断线程问题
*/
private void diagnoseThreadIssues(HealthDiagnosis diagnosis) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 活跃线程数
int activeThreadCount = Thread.activeCount();
diagnosis.setActiveThreads(activeThreadCount);
// 虚拟线程比例
long virtualThreadCount = getAllThreads().stream()
.filter(Thread::isVirtual)
.count();
diagnosis.setVirtualThreadRatio((double) virtualThreadCount / activeThreadCount);
// 线程峰值
diagnosis.setPeakThreadCount(threadMXBean.getPeakThreadCount());
// 检查线程泄漏
if (activeThreadCount > 1000) {
diagnosis.addIssue("THREAD_LEAK",
"活跃线程数过多: " + activeThreadCount);
incrementIssueCounter("THREAD_LEAK");
}
}
/**
* 诊断内存问题
*/
private void diagnoseMemoryIssues(HealthDiagnosis diagnosis) {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
long maxMemory = runtime.maxMemory();
diagnosis.setTotalMemory(totalMemory);
diagnosis.setUsedMemory(usedMemory);
diagnosis.setFreeMemory(freeMemory);
diagnosis.setMaxMemory(maxMemory);
diagnosis.setMemoryUsage((double) usedMemory / totalMemory);
// 检查内存使用率
if (diagnosis.getMemoryUsage() > 0.8) { // 80%阈值
diagnosis.addIssue("HIGH_MEMORY_USAGE",
"内存使用率过高: " + (diagnosis.getMemoryUsage() * 100) + "%");
incrementIssueCounter("HIGH_MEMORY_USAGE");
}
// 检查内存泄漏迹象
if (usedMemory > maxMemory * 0.9) { // 90%阈值
diagnosis.addIssue("MEMORY_LEAK_SUSPECTED",
"疑似内存泄漏,使用内存接近最大值");
incrementIssueCounter("MEMORY_LEAK_SUSPECTED");
}
}
/**
* 诊断死锁
*/
private void diagnoseDeadlocks(HealthDiagnosis diagnosis) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
diagnosis.addCriticalIssue("DEADLOCK_DETECTED",
"检测到死锁,涉及线程: " + deadlockedThreads.length);
incrementIssueCounter("DEADLOCK_DETECTED");
}
}
/**
* 诊断虚拟线程泄漏
*/
private void diagnoseVirtualThreadLeaks(HealthDiagnosis diagnosis) {
long virtualThreadCount = getAllThreads().stream()
.filter(Thread::isVirtual)
.count();
long platformThreadCount = getAllThreads().stream()
.filter(t -> !t.isVirtual())
.count();
// 虚拟线程与平台线程比例异常
if (platformThreadCount > 0 &&
virtualThreadCount / platformThreadCount > 100) {
diagnosis.addIssue("VIRTUAL_THREAD_LEAK",
"虚拟线程与平台线程比例异常: " +
virtualThreadCount + "/" + platformThreadCount);
incrementIssueCounter("VIRTUAL_THREAD_LEAK");
}