终于不要再问运维要日志了

11/12/2024

# 有话说

不知道大家是否和我一样每次去接第三方数据,或者说推数据给第三方的时候都会打印日志,随后屁颠屁颠的去找运维要日志文件,看是否推送成功。

应该有不少人是这样的,日志打印都放到某一个日志文件夹下,每次找日志都很麻烦

存在的问题:

  1. 会有很多日志文件产生(实时性很强的或者说数据大的)
  2. 不容易定位数据
  3. 如果日志文件你可以查看(有权限)还好,如果你不能查看,每次都要去麻烦别人
  4. 不好分类或者发现问题(第三方很多,数据偶尔会断掉)

如果你所在公司比较厉害,可能使用如下方法

  1. 使用数据库来存储(单表数据会很大)
  2. 专用的日志系统 从零开始,用Docker-compose打造SkyWalking、Elasticsearch和Spring Cloud的完美融合 (opens new window)

还有很多比较牛X的方法大家可以推荐一哈

# 构思

基础逻辑如下

image-20241122141415187

# 为什么使用@after通知

在 AOP(Aspect-Oriented Programming)中,afterafterReturning 是两种常见的后置通知,它们的执行时机有所不同:


# after(后置通知)

  • 执行时机:方法执行 结束后 无论结果如何(正常返回、抛出异常等),都会执行。

  • 适合用于清理资源或记录日志等不依赖方法结果的操作。

  • 示例

    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice() {
        System.out.println("After method execution");
    }
    
    1
    2
    3
    4
    • 不管方法是成功返回还是抛出了异常,都会打印日志。

# afterReturning(返回通知)

  • 执行时机:方法执行 成功返回结果后 才会执行(即未抛出异常时)。

  • 适合用于需要处理方法返回值的场景。

  • 示例

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturningAdvice(Object result) {
        System.out.println("Method returned with result: " + result);
    }
    
    1
    2
    3
    4
    • 只有方法正常返回时才会打印结果日志。

# 谁更“后”一点?

  • afterafterReturning 更后

    ,因为:

    • afterReturning 只会在方法正常返回后执行;
    • after 无论方法是正常返回还是抛出异常,都会在方法执行完成的最后时刻触发。

# 执行时机对比

以下是执行流程示例:

# 方法执行的四种阶段:
  1. 前置通知@Before

  2. 目标方法执行

  3. 后置通知

    • 如果是正常返回:@AfterReturning -> @After
    • 如果是异常:@AfterThrowing -> @After
# 对比:
通知类型 执行条件 触发时机
@After 无论方法是否抛异常 方法执行结束后
@AfterReturning 方法正常返回时才触发 方法执行结束后

# 示例说明

假设目标方法如下:

public String testMethod() {
    return "success";
}
1
2
3
  1. 如果执行正常:
    • 执行顺序@Before -> 方法体 -> @AfterReturning -> @After
  2. 如果方法抛出异常:
    • 执行顺序@Before -> 方法体 -> @AfterThrowing -> @After

# 总结

  • @After 的触发范围更广,始终是最后一个执行的通知。
  • @AfterReturning 只在方法正常返回时触发,发生在 @After 之前。

# 为什么使用先创建表

如果我们采用的是时间纬度的分表,可以创建12个表对应的12个月,如果采用实时创建的话对于创建时机可能要仔细定夺,所以还不如直接给它创建完

# 注解实现

package com.todoitbo.baseSpringbootDasmart.annotation;

import java.lang.annotation.*;

/**
 * 自定义操作日志注解
 *
 * @author bo
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface  SysLog {

    /**
     * 功能模块
     *
     * @return java.lang.String
     */
    String operModule() default "";

    /**
     * 请求类型
     *
     * @return java.lang.String
     */
    String operType() default "";

    /**
     * description: describe
     
     * @return java.lang.String
     * @since 2024/11/22
     */
    String describe() default "";

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# AOP实现

/**
  * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
*/
@Pointcut("@annotation(com.todoitbo.baseSpringbootDasmart.annotation.SysLog)")
public void operLogPointCut() {

}

/**
  * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
  *
  * @param joinPoint 切入点
  * @param keys      返回结果
*/
@AfterReturning(value = "operLogPointCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {
  // 获取RequestAttributes
  RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  // 从获取RequestAttributes中获取HttpServletRequest的信息
  HttpServletRequest request = (HttpServletRequest) requestAttributes
    .resolveReference(RequestAttributes.REFERENCE_REQUEST);
  // 获取用户信息
  SysLoginInfo sysLoginInfo = LoginContextUtil.getSysLoginInfo();
  SysOperationLog sysOperationLog = new SysOperationLog();
  try {
    // 从切面织入点处通过反射机制获取织入点处的方法
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    // 获取切入点所在的方法
    Method method = signature.getMethod();
    // 获取操作
    SysLog opLog = method.getAnnotation(SysLog.class);
    if (opLog != null) {
      sysOperationLog.setOpDescribe(opLog.describe());
      sysOperationLog.setModule(opLog.operModule());
    }
    // 获取请求的类名
    String className = joinPoint.getTarget().getClass().getName();
    // 获取请求的方法名
    String methodName = method.getName();
    methodName = className + "." + methodName;
    sysOperationLog.setId(IdUtil.getSnowflake().nextId());
    sysOperationLog.setMethod(methodName);
    sysOperationLog.setRequest(JSONObject.toJSONString(joinPoint.getArgs()));
    sysOperationLog.setResponse(JSONObject.toJSONString(keys));
    sysOperationLog.setUrl(request.getRequestURI());
    sysOperationLog.setIp(IpUtil.getIpAddr(request));
    sysOperationLog.setUserId(ObjectUtil.isNotEmpty(sysLoginInfo) ? sysLoginInfo.getSysUser().getId() : 0);
    sysOperationLog.setUserName(ObjectUtil.isNotEmpty(sysLoginInfo) ? sysLoginInfo.getSysUser().getUserName() : "无登录信息!");
    sysOperationLogService.insertSysOperationLog(sysOperationLog);
  } catch (Exception e) {
    throw new BusinessException("保存操作日志失败!");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 效果展示

依次调用post和get方法

image-20241122172843535

image-20241122172855618

Last Updated: 12/19/2024, 3:29:07 PM