Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 313|回复: 0

Spring Boot与Logback的运用(自定义异常+AOP)

[复制链接]
  • TA的每日心情
    奋斗
    3 天前
  • 签到天数: 790 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722766
    发表于 2021-6-21 11:03:31 | 显示全部楼层 |阅读模式

    在开发以及调试过程中,程序员对日志的需求是非常大的,出了什么问题,都要通过日志去进行排查,但是如果日志不清或者杂乱无章,则不利于维护

    这边就比较详细的列举几种类型的日志,供大家参考

    首先明白logback日志是Spring Boot自带的,不需要引入额外的包

                <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-access</artifactId>
                    <version>${logback.version}</version>
                </dependency>
                <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                    <version>${logback.version}</version>
                </dependency>
                <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-core</artifactId>
                    <version>${logback.version}</version>
                </dependency>

    点进pom里的核心依赖,就能看见上面几个,是由Spring Boot自动依赖配置好的,我们只要直接使用就好了

    比较简单的是直接在application的配置文件里 写参数配置就行了,他提供了日志级别,日志输出路径等,也能满足基本的日志输出

    我们这通过xml文件进行配置 logback-spring.xml

    这样就能直接引用到xml了,但是为什么能引用到了

    就是在logback里有个默认的机制,内部会有几种标准的文件格式,在LogbackLoggingSystem里标注了

    @Override
        protected String[] getStandardConfigLocations() {
            return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
                    "logback.xml" };
        }

    所以最为标准的为这里面的四种文件格式,但是如果项目中没有,他还提供了扩展文件格式 就是在后面拼上-spring,例如logback.xml 扩展为logback-spring.xml

    ok

    下面看下xml里面的内容:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="false">
        <!--定义日志文件的存储地址 可以在LogBack 的配置中使用相对路径-->
        <property name="LOG_HOME" value="logs" />
        
    
        <!-- 彩色日志 -->
        <!-- 彩色日志依赖的渲染类 -->
        <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
        <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
        <conversionRule conversionWord="wEx"
                        converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
        <!-- 彩色日志格式 -->
        <property name="CONSOLE_LOG_PATTERN"
                  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
        <!-- Console 输出设置 -->
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                <charset>utf8</charset>
            </encoder>
        </appender>
    
        <!-- 按照每天生成日志文件 -->
        <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/category-server-log.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--日志文件保留天数-->
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
            <!--日志文件最大的大小
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <MaxFileSize>10MB</MaxFileSize>
            </triggeringPolicy>-->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>WARN</level>
                <onMatch>DENY</onMatch>
                <onMismatch>NEUTRAL</onMismatch>
            </filter>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>DENY</onMatch>
                <onMismatch>NEUTRAL</onMismatch>
            </filter>
    
        </appender>
    
        <!-- 出错日志 appender  -->
        <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!-- 按天回滚 daily -->
                <!-- log.dir 在maven profile里配置 -->
                <FileNamePattern>${LOG_HOME}/category-server-error-log.%d{yyyy-MM-dd}.log</FileNamePattern>
                <!-- 日志最大的历史 60天 -->
                <maxHistory>60</maxHistory>
            </rollingPolicy>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>WARN</level>
            </filter>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
        </appender>
        
        <!-- 自己打印的日志文件,用于记录重要日志信息 -->
        <appender name="MY_INFO_FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/category-server-myinfo-log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
                <!--日志文件保留天数-->
                <MaxHistory>15</MaxHistory>
                <!--日志文件最大的大小-->
                <MaxFileSize>10MB</MaxFileSize>
            </rollingPolicy>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>DEBUG</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>  
            </filter>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
        <logger name="my_info" additivity="true">
            <appender-ref ref="MY_INFO_FILE"/>
        </logger>
        <!--myibatis log configure-->
        <logger name="com.example.demo" level="TRACE"/>
        <logger name="java.sql.Connection" level="DEBUG"/>
        <logger name="java.sql.Statement" level="DEBUG"/>
        <logger name="java.sql.PreparedStatement" level="DEBUG"/>
            <!-- 日志输出级别 -->
            <root level="INFO">
                <appender-ref ref="CONSOLE" />
                <appender-ref ref="FILE" />
                <appender-ref ref="ERROR_FILE" />
            </root>
    </configuration>

    这里一共有四块内容,第一是console的日志输出,第二是系统运行日志,第三是警告以上的日志输出(基本上是程序出错日志),第四种是自定义日志

    每一块日志由一个appender标签引入

    CONSOLE是控制台日志输出,只要规定个格式就行了

    FILE是系统运行日志,系统的所有运行信息都会保留,正常我们会把这部分信息保存在硬盘日志文件中,按天按文件大小保存,因为这个内容实在是比较多

    ERROR_FILE是WARN级别以上的日志,这块是开发人员和运维人员最多关注的,因为基本上所有的bug都会在这个里面体现

    MY_INFO_FILE是自定义日志,想定义自己的日志文件,记录一些重要的信息

    这里的日志都是以文件的形式保存在本地,当然像WARN级别以上日志可以异步保存到数据库

     

    日志文件定义好后,接下来就要开始定义业务逻辑了

    在针对一些异常日志,我们想尽可能完整准确的抛出异常,一眼就能知道是什么问题,这里我们就需要自定义异常,最多的就是像空指针,数组越界等常见异常

    定义基础异常类BaseException继承他的父类RuntimeException

    public class BaseException extends RuntimeException {
    
        private static final long serialVersionUID = 1L;
    
        public BaseException() {
            super();
            // TODO Auto-generated constructor stub
        }
    
        public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
            // TODO Auto-generated constructor stub
        }
    
        public BaseException(String message, Throwable cause) {
            super(message, cause);
            // TODO Auto-generated constructor stub
        }
    
        public BaseException(String message) {
            super(message);
            // TODO Auto-generated constructor stub
        }
    
        public BaseException(Throwable cause) {
            super(cause);
            // TODO Auto-generated constructor stub
        }
        
        
        
    }

    然后全局异常处理类:GlobalExceptionHandler

    @CrossOrigin
    @RestControllerAdvice
    public class GlobalExceptionHandler{
    
        private static Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
        
        private static final String APPLICATION_JSON = "application/json";
        private static final String UTF_8 = "UTF-8";
         
        /**
         * BaseException 处理类
        * @Title: HandleBaseException  
        * @Description: TODO
        * @param @param e
        * @param @return
        * @return ResponseMsg
        * @throws
         */
        @ExceptionHandler(BaseException.class)
        @ResponseBody
        public ResponseMsg HandleBaseException(RuntimeException e){
            //只能输出捕获到的异常,未捕获到的异常不输出到日志,或者通过aop拦截器拦截所有方法
            LOGGER.error(getExceptionDetail(e));
            //返回失败信息
            Route route = new Route();
            ResponseMsg responseMsg = new ResponseMsg(route,ReturnMsgEnum.INTERNAL_ERROR.getCode(),
                     ReturnMsgEnum.INTERNAL_ERROR.getMsg(), "");
            return responseMsg;
        }
        
        @ExceptionHandler(GlobalException.class)
        @ResponseBody
        public ResponseMsg HandleGlobalException(Exception e){
            //只能输出捕获到的异常,未捕获到的异常不输出到日志,或者通过aop拦截器拦截所有方法
            LOGGER.error(getExceptionDetail(e));
            //返回失败信息
            Route route = new Route();
            ResponseMsg responseMsg = new ResponseMsg(route,ReturnMsgEnum.INTERNAL_ERROR.getCode(),
                     ReturnMsgEnum.INTERNAL_ERROR.getMsg(), "系统未捕获该异常");
            return responseMsg;
        }
        
        public String getExceptionDetail(Exception e) {
            StringBuffer stringBuffer = new StringBuffer(e.toString() + "\n");
            StackTraceElement[] messages = e.getStackTrace();
            int length = messages.length;
            for (int i = 0; i < length; i++) {
                stringBuffer.append("\t"+messages.toString()+"\n");
            }
            return stringBuffer.toString();
        }
        
    }
    @RestControllerAdvice:表明他是一个Controller 并且是异常拦截的统一处理类
    定义针对自定义异常的处理方法:用
    @ExceptionHandler(BaseException.class)注解标注
    BaseException就是刚才的自定义异常
    之后所有抛出的BaseException都会由他处理

    自定义异常我们都能轻松捕获到了,并且输出到日志里了

    如果有些异常我们没有捕获到,我们就可以定义一个切面,让所有方法都经过这个切面处理
    /**
     * 处理未捕获到的异常
    * @ClassName: SpringAOP  
    * @author Mr.Chengjq
    * @date 2018年10月17日  
    * @Description: TODO
     */
    @Aspect
    @Configuration
    public class SpringAOP {
        private static final Logger logger = LoggerFactory.getLogger(SpringAOP.class);
         
        /**
         * 定义切点Pointcut
         * 第一个*号:表示返回类型, *号表示所有的类型
         * 第二个*号:表示类名,*号表示所有的类
         * 第三个*号:表示方法名,*号表示所有的方法
         * 后面括弧里面表示方法的参数,两个句点表示任何参数
         */
        @Pointcut("execution(* com.example.demo..*.*(..))")
        public void executionService() {
     
        }
     
     
        /**
         * 方法调用之前调用
         * @param joinPoint
         */
        @Before(value = "executionService()")
        public void doBefore(JoinPoint joinPoint){
     
            //添加日志打印
            String requestId = String.valueOf(UUID.randomUUID());
            MDC.put("requestId",requestId);
            logger.info("=====>@Before:请求参数为:{}",Arrays.toString(joinPoint.getArgs()));
     
        }
     
        /**
         * 方法之后调用
         * @param joinPoint
         * @param returnValue 方法返回值
         */
        @AfterReturning(pointcut = "executionService()",returning="returnValue")
        public void  doAfterReturning(JoinPoint joinPoint,Object returnValue){
     
            logger.info("=====>@AfterReturning:响应参数为:{}",returnValue);
            // 处理完请求,返回内容
            MDC.clear();
        }
     
        /**
         * 统计方法执行耗时Around环绕通知
         * @param joinPoint
         * @return
         */
        @Around("executionService()")
        public Object timeAround(ProceedingJoinPoint joinPoint) throws Throwable{
     
            //获取开始执行的时间
            long startTime = System.currentTimeMillis();
     
            // 定义返回对象、得到方法需要的参数
            Object obj = null;
            //Object[] args = joinPoint.getArgs();
            try {
                obj = joinPoint.proceed();
            } catch (Throwable e) {
                // TODO: handle exception
                logger.error(getExceptionDetail(e));
                throw new GlobalException();
            }
            
            // 获取执行结束的时间
            long endTime = System.currentTimeMillis();
            //MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
            // 打印耗时的信息
            logger.info("=====>处理本次请求共耗时:{} ms",endTime-startTime);
            return obj;
        }
        
        
        public String getExceptionDetail(Throwable e) {
            StringBuffer stringBuffer = new StringBuffer(e.toString() + "\n");
            StackTraceElement[] messages = e.getStackTrace();
            int length = messages.length;
            for (int i = 0; i < length; i++) {
                stringBuffer.append("\t"+messages.toString()+"\n");
            }
            return stringBuffer.toString();
        }
    
    }
    这个切面里未捕获到的异常也全部做特定处理



     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-13 15:54 , Processed in 1.135581 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表