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


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

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

    首先明白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() + "
    ");
            StackTraceElement[] messages = e.getStackTrace();
            int length = messages.length;
            for (int i = 0; i < length; i++) {
                stringBuffer.append("	"+messages[i].toString()+"
    ");
            }
            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() + "
    ");
            StackTraceElement[] messages = e.getStackTrace();
            int length = messages.length;
            for (int i = 0; i < length; i++) {
                stringBuffer.append("	"+messages[i].toString()+"
    ");
            }
            return stringBuffer.toString();
        }
    
    }
    这个切面里未捕获到的异常也全部做特定处理



  • 相关阅读:
    SqlServer存储过程调用接口
    Zend引擎探索 之 PHP中前置递增不返回左值
    在CentOS上为Docker开启SELinux
    PSR-4 自动加载器
    [译]漫画SELinux概念
    NodeMCU Builder, yet another NodeMCU IDE
    Mac OS X更新VirtualBox以后Genymotion无法启动的一种情况
    Win32API起始处的mov edi, edi与用户空间InlineHook
    DLL的导出函数重定向机制
    一个Win32API Trace Tool的设计与实现
  • 原文地址:https://www.cnblogs.com/qiyuan880794/p/9829854.html
Copyright © 2020-2023  润新知