如何为@Slf4j生成的每条日志添加Prometheus指标?
无需修改@Slf4j用法实现日志级别Prometheus指标统计
方案一:日志框架原生扩展(推荐)
Lombok的@Slf4j本质是生成对应日志框架(如Logback、Log4j2)的Logger实例,直接在日志框架层面添加统计逻辑是最优雅的方案——完全不侵入业务代码,性能也最优。
以Logback为例实现
- 自定义Prometheus统计Appender
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; import io.prometheus.client.Counter; public class PrometheusLogAppender extends AppenderBase<ILoggingEvent> { // 定义Prometheus计数器,按日志级别、Logger名称维度统计 private static final Counter LOG_COUNTER = Counter.build() .name("application_logs_total") .help("Total logs counted by level and logger class") .labelNames("level", "logger_name") .register(); @Override protected void append(ILoggingEvent event) { // 根据日志事件更新计数器 LOG_COUNTER.labels(event.getLevel().toString(), event.getLoggerName()) .inc(); } }
- 在logback.xml中配置该Appender
将自定义Appender添加到根Logger或指定Logger的引用列表中,不影响原有日志输出逻辑:
<configuration> <!-- 原有日志Appender配置(如控制台、文件输出) --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!-- ... 原有配置 ... --> </appender> <!-- 新增Prometheus统计Appender --> <appender name="PROMETHEUS" class="com.your.package.PrometheusLogAppender" /> <root level="info"> <appender-ref ref="CONSOLE" /> <appender-ref ref="PROMETHEUS" /> </root> </configuration>
配置完成后,所有@Slf4j生成的Logger输出的日志都会被自动统计,Prometheus可直接拉取application_logs_total指标。
Log4j2实现思路
自定义继承AbstractAppender的统计类,在append方法中处理LogEvent并更新Prometheus计数器,最后在log4j2.xml中配置该Appender即可,逻辑与Logback一致。
方案二:Spring AOP切面实现
如果无法修改日志框架配置,可以用AOP拦截Logger的日志方法调用,同样无需改动@Slf4j的使用代码。
具体实现
- 确保项目引入Spring AOP依赖(Spring Boot项目可直接用
spring-boot-starter-aop)。 - 定义日志统计切面
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.springframework.stereotype.Component; import io.prometheus.client.Counter; @Aspect @Component public class LogMetricsAspect { private static final Counter LOG_COUNTER = Counter.build() .name("application_logs_total") .help("Total logs counted by level and logger class") .labelNames("level", "logger_name") .register(); // 切点:拦截org.slf4j.Logger所有级别的日志方法 @Pointcut("execution(* org.slf4j.Logger.trace(..)) || " + "execution(* org.slf4j.Logger.debug(..)) || " + "execution(* org.slf4j.Logger.info(..)) || " + "execution(* org.slf4j.Logger.warn(..)) || " + "execution(* org.slf4j.Logger.error(..))") public void logMethodPointcut() {} // 日志方法执行完成后更新计数器 @AfterReturning("logMethodPointcut()") public void countLogEvents(JoinPoint joinPoint) { Logger targetLogger = (Logger) joinPoint.getTarget(); String loggerName = targetLogger.getName(); // 从方法名获取日志级别(如info、error) String logLevel = joinPoint.getSignature().getName().toUpperCase(); LOG_COUNTER.labels(logLevel, loggerName).inc(); } }
- 注意事项
- 该切面会拦截所有
org.slf4j.Logger实例的方法调用,包括手动创建的Logger,不止@Slf4j生成的。 - 性能上略逊于日志框架扩展方案,但绝大多数业务场景下可忽略这个开销。
方案对比
| 方案 | 核心优势 | 局限性 |
|---|---|---|
| 日志框架原生扩展 | 性能最优,无额外开销,完全贴合日志体系标准 | 需要修改对应日志框架的配置文件 |
| Spring AOP切面 | 无需改动日志框架配置,实现简单快捷 | 依赖Spring环境,存在轻微方法拦截开销 |
优先推荐日志框架原生扩展方案,它是最贴合日志系统设计的实现方式,完全满足你“不修改现有@Slf4j用法”的需求。
内容的提问来源于stack exchange,提问作者Duje Drobac
相关产品推荐
相关产品推荐

