首页
学习
活动
专区
圈层
工具
发布

解决日志开关需求:SpringBoot 实现热插拔 AOP

在项目里,日志开关这个需求其实特别常见,尤其是 SpringBoot 项目跑到线上以后,经常会有人提「能不能不重启就把某个模块的日志打开」这种需求。很多人一开始想到的就是去改 logback 配置文件,但这样要么得重启,要么得写监听器,还挺麻烦的。其实用 Spring AOP 加一点点热插拔思路,就能比较优雅地搞定。

我之前遇到过一次,运维同事说线上一个接口偶尔耗时很长,让我加点日志看看。但是那会接口是个高频调用的,直接打开 DEBUG 日志简直要命,日志量暴涨,磁盘都快打满了。所以思路就是:做个开关,能随时打开或关闭某些类或方法的日志,而且不用重启。

思路设计

用 AOP 的好处就是,它本身就能在方法调用前后织入逻辑,我们只要在合适的地方判断下「这个方法是不是需要打印日志」,就能做到动态控制。剩下的就是怎么存这个「开关」信息了,常见的办法是放在内存里(比如一个ConcurrentHashMap),再暴露个接口让运维随时改。

核心代码

首先定义个注解,用来标记哪些方法可以被日志 AOP 接管:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface LogSwitch {

  String value() default ""; // 给开关一个名字,方便控制

}

然后写一个切面类:

@Aspect

@Component

public class LogAspect {

  // 用来存开关状态

  private final Map<String, Boolean> logSwitchMap = new ConcurrentHashMap<>();

  // 提供修改开关的方法

  public void setSwitch(String key, boolean enabled) {

      logSwitchMap.put(key, enabled);

  }

  @Around("@annotation(logSwitch)")

  public Object around(ProceedingJoinPoint pjp, LogSwitch logSwitch) throws Throwable {

      String key = logSwitch.value();

      boolean enabled = logSwitchMap.getOrDefault(key, false);

      if (enabled) {

          long start = System.currentTimeMillis();

          System.out.println("【日志开关已打开】调用方法: " + pjp.getSignature());

          Object result = pjp.proceed();

          long end = System.currentTimeMillis();

          System.out.println("方法执行结束,耗时: " + (end - start) + "ms,返回值: " + result);

          return result;

      } else {

          // 开关没开就直接执行

          return pjp.proceed();

      }

  }

}

这里用了@Around环绕通知,如果开关打开就打印日志,否则就安安静静执行。

如何动态开关

再写个 Controller,把开关暴露出来:

@RestController

@RequestMapping("/log")

public class LogSwitchController {

  private final LogAspect logAspect;

  public LogSwitchController(LogAspect logAspect) {

      this.logAspect = logAspect;

  }

  @PostMapping("/switch")

  public String setSwitch(@RequestParam String key, @RequestParam boolean enabled) {

      logAspect.setSwitch(key, enabled);

      return "开关 " + key + " 已设置为: " + enabled;

  }

}

这样只要调用/log/switch?key=test&enabled=true就能实时打开某个日志开关,不用重启应用。

使用示例

假设你在某个 service 方法上加了注解:

@Service

public class UserService {

  @LogSwitch("userQuery")

  public String getUserById(String id) {

      return "用户:" + id;

  }

}

一开始开关默认是关的,调用不会打印额外日志;当你通过接口打开userQuery这个开关后,再调用getUserById方法,就能看到详细日志了。

这种方案优点就是轻量级,不用改动 logback 配置,也不用重启。缺点是如果你想控制很细粒度的日志(比如某些第三方库内部),就不太适合,还是得回到日志框架本身去做。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/Oy3KZcePV04HZfKCkFoMsp5A0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券