• 项目请求日志详细调用打印


    项目中经常需要通过日志定位线上问题,详细的日志有助于快速定位问题,下面通过aopMDC解决.

    打印详细请求信息

    基本请求信息

    // 使用目标类进行日志打印
    logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass().getName());
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    // 打印请求相关参数
    logger.info("========================================== Start ==========================================");
    // 打印请求 url
    logger.info("URL            : {}", request.getRequestURL().toString());
    // 打印 Http method
    logger.info("HTTP Method    : {}", request.getMethod());
    // 打印调用 controller 的全路径以及执行方法
    logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
    // 打印请求的 IP
    logger.info("IP             : {}", (request.getHeader("X-Forwarded-For") == null) ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For"));
    // 打印请求Cookie
    logger.info("Cookie         : {}", cookie(request.getCookies()));
    // 打印请求入参 https://blog.csdn.net/weixin_44130081/article/details/98847575
    logger.info("Request Args   : {}", Arrays.toString(joinPoint.getArgs()));
    
    • 打印请求IP: 支持代理IP打印
    • 打印请求入参: 入参没有使用json工具(GSON和FastJSON)进行封装,当请求方法有HttpServletResponse参数,比如做文件上传或者excel导入时,会造成内存溢出.可以使用Arrays.toString打印

    异常信息打印

    // 打印异常
    logger.info("{}      : {}", e.getClass().getName(), e.getMessage());
    

    出参和请求耗时打印

    long startTime = System.currentTimeMillis();
    Object result = proceedingJoinPoint.proceed();
    // 打印出参
    GsonBuilder gsonBuilder = new GsonBuilder();
    // 处理null值 https://www.jianshu.com/p/83eb1b2bc119
    gsonBuilder.serializeSpecialFloatingPointValues();
    Gson gson = gsonBuilder.create();
    logger.info("Response Args  : {}", gson.toJson(result));
    // 执行耗时
    logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
    

    日志追踪

    日志调用追踪

    一次请求,后台会产生一连串的方法调用.如何判断在一次请求中,都调用那些方法,找出对应的日志记录,就需要对打印日志进行线程标记.

    1. resources目录下新建: logback-spring.xml,修改<pattern>规则,示例:
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{trace_uuid}] [%level] [%thread] [%c:%L] - %msg%n</pattern>
    
    • {trace_uuid}: 唯一编码
    1. 在请求执行前,存入当前线程的MDC值.比如使用aop的@Before
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        // 开始打印请求日志
        MDC.put("trace_uuid", UUID.randomUUID().toString());// 加入trace追踪
    }
    

    方法调用追踪

    在维护老项目或接手一个新项目时,一个方法可能在很多处被调用,对分析不熟悉的业务逻辑造成很大的困难.方法调用链可以清楚显示该方法被调用的路径

    1. 自定义注解@LogTrace
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogTrace {
    }
    
    1. aop对注解进行解析
    @After("@annotation(com.lyf.annotation.LogTrace)")
    public void log_trace(JoinPoint joinPoint) {
        JavaLangAccess access = SharedSecrets.getJavaLangAccess();
        Throwable throwable = new Throwable();
        int depth = access.getStackTraceDepth(throwable);
        Class clazz = joinPoint.getTarget().getClass();
        String targetName = clazz.getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        String target = targetName + "." + methodName;
        logger.info("====== StackTrace: " + target + " ======");
        for (int i = depth - 1; i > 0; i--) {//从1开始不显示当前工具类调用
            StackTraceElement ste = access.getStackTraceElement(throwable, i);
    //            for(int j=1;j<depth-i;j++){
    //                System.out.print("	");//缩进
    //            }
            String className = ste.getClassName();
            if (className.contains("lyf") && !className.contains("$$") && !className.contains("aop")) {
                logger.info(String.format("[%s.%s:%s]", ste.getClassName(), ste.getMethodName(), ste.getLineNumber()));
            }
        }
        logger.info("====== StackTrace: " + target + " ======");
    }
    
    1. 使用示例
    @LogTrace
    public User queryUser (Integer id){
        return userMapper.queryById(id);
    }
    

    完整代码

    logback-spring.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <configuration>
        <!-- 控制台输出日志 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{trace_uuid}] [%level] [%c:%L] - %msg%n</pattern>
            </encoder>
        </appender>
        <!--每天生成一个日志文件,保存30天的日志文件。-->
        <appender name="DayFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <File>logs/log.log</File>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>logs/log.%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{trace_uuid}] [%level] [%thread] [%c:%L] - %msg%n</pattern>
            </encoder>
        </appender>
        <!--指定logger name为包名或类全名 指定级别 additivity设置是否传递到root logger -->
        <logger name="slf4j" level="INFO" additivity="false">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="DayFile"/>
        </logger>
        <!--slf4j2包下的类在ERROR级别时候传递到root logger中-->
        <logger name="slf4j2" level="ERROR"/>
        <!--根logger控制-->
        <root level="INFO">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="DayFile"/>
        </root>
    </configuration>
    

    WebLogAspect.java

    package com.lyf.aop;
    
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.slf4j.MDC;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import sun.misc.JavaLangAccess;
    import sun.misc.SharedSecrets;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import java.util.Arrays;
    import java.util.UUID;
    
    @Aspect
    @Component
    public class WebLogAspect {
    
        private static Logger logger;
    
        /**
         * 以 controller 包下定义的所有请求为切入点
         */
        @Pointcut("execution(public * com.lyf.controller..*.*(..))")
        public void webLog() {
        }
    
        /**
         * 在切点之前织入
         *
         * @param joinPoint
         * @throws Throwable
         */
        @Before("webLog()")
        public void doBefore(JoinPoint joinPoint) {
            // 开始打印请求日志
            MDC.put("trace_uuid", UUID.randomUUID().toString());// 加入trace追踪
            // 使用目标类进行日志打印
            logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass().getName());
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 打印请求相关参数
            logger.info("========================================== Start ==========================================");
            // 打印请求 url
            logger.info("URL            : {}", request.getRequestURL().toString());
            // 打印 Http method
            logger.info("HTTP Method    : {}", request.getMethod());
            // 打印调用 controller 的全路径以及执行方法
            logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
            // 打印请求的 IP
            logger.info("IP             : {}", (request.getHeader("X-Forwarded-For") == null) ? request.getRemoteAddr() : request.getHeader("X-Forwarded-For"));
            // 打印请求Cookie
            logger.info("Cookie         : {}", cookie(request.getCookies()));
            // 打印请求入参 https://blog.csdn.net/weixin_44130081/article/details/98847575
            logger.info("Request Args   : {}", Arrays.toString(joinPoint.getArgs()));
        }
    
        /**
         * 在切点之后织入
         *
         * @throws Throwable
         */
        @After("webLog()")
        public void doAfter() throws Throwable {
            logger.info("=========================================== End ===========================================");
            // 每个请求之间空一行
            logger.info("☜☝☞");
        }
    
    
        /**
         * 在切点异常之后织入
         *
         * @throws Throwable
         */
        @AfterThrowing(value = "webLog()", throwing = "e")
        public void doAfterThrowing(Exception e) throws Throwable {
    // 打印异常
            logger.info("{}      : {}", e.getClass().getName(), e.getMessage());
        }
    
        /**
         * 环绕
         *
         * @param proceedingJoinPoint
         * @return
         * @throws Throwable
         */
        @Around("webLog()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = proceedingJoinPoint.proceed();
            // 打印出参
            GsonBuilder gsonBuilder = new GsonBuilder();
            // 处理null值 https://www.jianshu.com/p/83eb1b2bc119
            gsonBuilder.serializeSpecialFloatingPointValues();
            Gson gson = gsonBuilder.create();
            logger.info("Response Args  : {}", gson.toJson(result));
            // 执行耗时
            logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
            return result;
        }
    
        private String cookie(Cookie[] cookies) {
            StringBuilder stringBuilder = new StringBuilder();
            if (cookies != null && cookies.length > 0) {
                stringBuilder.append("[");
                for (int i = 0; i < cookies.length; i++) {
                    if (i > 0) {
                        stringBuilder.append(", ");
                    }
                    stringBuilder.append(cookies[i].getName()).append("=").append(cookies[i].getValue());
                }
                stringBuilder.append("]");
            }
            return stringBuilder.toString();
        }
    
        @After("@annotation(com.lyf.annotation.LogTrace)")
        public void log_trace(JoinPoint joinPoint) {
            JavaLangAccess access = SharedSecrets.getJavaLangAccess();
            Throwable throwable = new Throwable();
            int depth = access.getStackTraceDepth(throwable);
            Class clazz = joinPoint.getTarget().getClass();
            String targetName = clazz.getSimpleName();
            String methodName = joinPoint.getSignature().getName();
            String target = targetName + "." + methodName;
            logger.info("====== StackTrace: " + target + " ======");
            for (int i = depth - 1; i > 0; i--) {//从1开始不显示当前工具类调用
                StackTraceElement ste = access.getStackTraceElement(throwable, i);
    //            for(int j=1;j<depth-i;j++){
    //                System.out.print("	");//缩进
    //            }
                String className = ste.getClassName();
                if (className.contains("lyf") && !className.contains("$$") && !className.contains("aop")) {
                    logger.info(String.format("[%s.%s:%s]", ste.getClassName(), ste.getMethodName(), ste.getLineNumber()));
                }
            }
            logger.info("====== StackTrace: " + target + " ======");
        }
    }
    
  • 相关阅读:
    解决android运行速度慢的问题
    nginx配合tomcat、resin等java应用服务器提供java支持(转)
    部分面向对象习题,留给学生做不错!!!
    该内存不能为“read”或“written”
    ORACLE数据库管理系统体系结构详解
    Linux下设置ORACLE自启动
    Oracle静默安装模板
    python安装步骤
    Oracle删除重复索引
    windows操作系统下python IDLE安装
  • 原文地址:https://www.cnblogs.com/linyufeng/p/13176818.html
Copyright © 2020-2023  润新知