Java API 接口日志管理实战指南
一 架构与规范
二 快速落地方案
方案A(Spring Boot 常用):SLF4J + Logback
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
</dependency>
<configuration>
<property name="LOG_HOME" value="logs"/>
<property name="LOG_PATTERN"
value='{"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}","level":"%level","thread":"%thread","logger":"%logger","message":%msg%n%ex{short}}'/>
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/api.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/api.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="JSON_FILE"/>
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PaymentService {
private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
public void pay(String orderId, BigDecimal amount) {
log.info("Start pay, orderId={}, amount={}", orderId, amount);
// ...
log.debug("Query executed for orderId={}", orderId);
}
}
说明:Spring Boot 默认使用Logback;如需更高吞吐可切换至Log4j2 异步日志。
方案B(高并发/异步优先):SLF4J + Log4j2
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.23.1</version>
</dependency>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JsonLayout compact="true" eventEol="true"/>
</Console>
<RollingFile name="RollingJson"
fileName="logs/api.log"
filePattern="logs/api.%d{yyyy-MM-dd}.%i.log.gz">
<JsonLayout compact="true"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<DefaultRolloverStrategy max="60"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="RollingJson"/>
</Root>
</Loggers>
</Configuration>
三 统一拦截与链路追踪
@Aspect @Component
public class LoggingAspect {
@Around("@annotation(loggable)")
public Object around(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable {
long start = System.currentTimeMillis();
String iface = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();
MDC.put("traceId", UUID.randomUUID().toString());
try {
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;
// 构造日志对象(含 traceId、iface、cost、result/异常等),序列化为JSON输出
return result;
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
// 记录异常日志(含堆栈)
throw e;
} finally {
MDC.remove("traceId");
}
}
}
四 指标与告警联动
@Bean
MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Autowired MeterRegistry registry;
private final Counter calls = registry.counter("api.calls.total", "interface", "PaymentService.pay");
private final Timer timer = registry.timer("api.duration.seconds", "interface", "PaymentService.pay");
public void pay(String orderId, BigDecimal amount) {
Timer.Sample sample = Timer.start(registry);
try {
// ...业务逻辑
calls.increment();
} finally {
sample.stop(timer);
}
}
五 生产级优化与风险控制
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。