日志唯一流水

101 阅读1分钟

系统内日志唯一流水

一次请求,在系统内部可能调用很多不同方法,在排查问题时,如果没有一个串联日志的key,会影响排查问题效率,以下是使用MDC生成日志唯一流水示例

定义annocation

@Retention(RetentionPolicy.RUNTIME)
//方法或者类上生效
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ServiceLog {
}

切面代码

@Slf4j
@Aspect
@Component
public class AopServiceLogger {
   //方法或类生效
   @Around("@within(ServiceLog) || @annotation(ServiceLog)")
    public Object log(ProceedingJoinPoint point) throws Throwable {
        return generalLogOpr(point, "{} request {}", "{} response: {} in {} ms");
    }

    private Object generalLogOpr(ProceedingJoinPoint point, String prefix, String suffix) throws Throwable {
    try{
        //设置唯一流水
        MDC.put(traceId, UUID.randomUUID().toString().replaceAll("-",""));
        String methodName = point.getTarget().getClass().getSimpleName() + "." + point.getSignature().getName();
        requestLog(methodName,prefix,point);

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object retVal = null;
        try {
            retVal = point.proceed();
        } catch (Throwable e) {
            log.error(methodName,e);
            throw e;
        }
        generalOprLogEnd(methodName,stopWatch, retVal, suffix);
        return retVal;
        }finally{
            MDC.clear();
        }
    }

    private void generalOprLogEnd(String methodName,StopWatch stopWatch, Object retVal, String suffix) {
        stopWatch.stop();
        log.info(suffix, methodName,retVal, stopWatch.getTotalTimeMillis());
    }

    private void requestLog(String methodName,String prefix,JoinPoint point) {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        StringBuilder args = new StringBuilder();
        args.append("(");
        for (int i = 0; i < point.getArgs().length; i++) {
            Object[] arg = point.getArgs();
            if (i > 0) {
                args.append(",");
            }
            args.append(parameterNames[i]).append(":").append(arg[i]);
        }
        args.append(")");
        log.info(prefix, methodName,args.toString());
    }
}

log4j2.xml中设置traceId占位符

<Properties>
    <Property name="LOG_PATTERN">%d %-5p [%t,%X{traceId}] [%c] %mm%n</Property>
    <Property name="APP_LOG_ROOT">./logs</Property>
</Properties>

需要注意的是,这种方式只能保证同一个线程的日志都有同一个流水,多线程会是不同流水,可以通过以下方式设置traceId

String traceId = MDC.get("traceId");
threadPoolTaskExecutor.execute(()->{
    MDC.put("traceId",traceId);
    try {
        //do some thing
    } finally {
        MDC.remove("traceId");
    }
});

系统间唯一流水

通过在通讯协议中携带唯一流水

比如http header中或者dubbo 自定义头来保证系统间同一次请求使用同一个唯一流水。这种方式需要自己开发

开源框架

使用开源日志追踪框架,zipkin ,sleuth 等