springboot通过Aop面向切面实现彩色日志

1 篇文章 0 订阅
1 篇文章 0 订阅

通过springboot的Aop面向切面实现彩色日志使用的场景

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
在项目中我们脱离不开保留用户访问的相关信息,那么下文我们来一起实现springboot通过Aop面向切面实现彩色日志。

  • 我们在项目的resources文件夹下新建一个日志的配置文件命名为logback.xml

    logback.xml彩色日志是每天保留不同等级日志以及会记录实时日志内容,同时会将保存的内容打印在控制台,日志保留的有效期是15天,可自行配置,超过设置的天数会自动删除16天前的日志文件,其次保存的位置也可以自行配置

    	<?xml version="1.0" encoding="UTF-8"?>
    	<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    	<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    	<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    	<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false-->
    	<configuration  scan="true" scanPeriod="10 seconds">
    	
    	    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
    	
    	    <contextName>logback</contextName>
    	    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    	    <property name="log.path" value="D:/mylogs/logs" /><!--D:/mylogs/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}}"/>
    	
    	
    	    <!--输出到控制台-->
    	    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    	        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
    	        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    	            <level>info</level>
    	        </filter>
    	        <encoder>
    	            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
    	            <!-- 设置字符集 -->
    	            <charset>UTF-8</charset>
    	        </encoder>
    	    </appender>
    	
    	
    	    <!--输出到文件-->
    	
    	    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    	    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    	        <!-- 正在记录的日志文件的路径及文件名 -->
    	        <file>${log.path}/log_debug.log</file>
    	        <!--日志文件输出格式-->
    	        <encoder>
    	            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    	            <charset>UTF-8</charset> <!-- 设置字符集 -->
    	        </encoder>
    	        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    	        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    	            <!-- 日志归档 -->
    	            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    	            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    	                <maxFileSize>100MB</maxFileSize>
    	            </timeBasedFileNamingAndTriggeringPolicy>
    	            <!--日志文件保留天数-->
    	            <maxHistory>15</maxHistory>
    	        </rollingPolicy>
    	        <!-- 此日志文件只记录debug级别的 -->
    	        <filter class="ch.qos.logback.classic.filter.LevelFilter">
    	            <level>debug</level>
    	            <onMatch>ACCEPT</onMatch>
    	            <onMismatch>DENY</onMismatch>
    	        </filter>
    	    </appender>
    	
    	    <!-- 时间滚动输出 level为 INFO 日志 -->
    	    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    	        <!-- 正在记录的日志文件的路径及文件名 -->
    	        <file>${log.path}/log_info.log</file>
    	        <!--日志文件输出格式-->
    	        <encoder>
    	            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    	            <charset>UTF-8</charset>
    	        </encoder>
    	        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    	        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    	            <!-- 每天日志归档路径以及格式 -->
    	            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    	            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    	                <maxFileSize>100MB</maxFileSize>
    	            </timeBasedFileNamingAndTriggeringPolicy>
    	            <!--日志文件保留天数-->
    	            <maxHistory>15</maxHistory>
    	        </rollingPolicy>
    	        <!-- 此日志文件只记录info级别的 -->
    	        <filter class="ch.qos.logback.classic.filter.LevelFilter">
    	            <level>info</level>
    	            <onMatch>ACCEPT</onMatch>
    	            <onMismatch>DENY</onMismatch>
    	        </filter>
    	    </appender>
    	
    	    <!-- 时间滚动输出 level为 WARN 日志 -->
    	    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    	        <!-- 正在记录的日志文件的路径及文件名 -->
    	        <file>${log.path}/log_warn.log</file>
    	        <!--日志文件输出格式-->
    	        <encoder>
    	            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    	            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
    	        </encoder>
    	        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    	        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    	            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    	            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    	                <maxFileSize>100MB</maxFileSize>
    	            </timeBasedFileNamingAndTriggeringPolicy>
    	            <!--日志文件保留天数-->
    	            <maxHistory>15</maxHistory>
    	        </rollingPolicy>
    	        <!-- 此日志文件只记录warn级别的 -->
    	        <filter class="ch.qos.logback.classic.filter.LevelFilter">
    	            <level>warn</level>
    	            <onMatch>ACCEPT</onMatch>
    	            <onMismatch>DENY</onMismatch>
    	        </filter>
    	    </appender>
    	
    	
    	    <!-- 时间滚动输出 level为 ERROR 日志 -->
    	    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    	        <!-- 正在记录的日志文件的路径及文件名 -->
    	        <file>${log.path}/log_error.log</file>
    	        <!--日志文件输出格式-->
    	        <encoder>
    	            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    	            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
    	        </encoder>
    	        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    	        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    	            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    	            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    	                <maxFileSize>100MB</maxFileSize>
    	            </timeBasedFileNamingAndTriggeringPolicy>
    	            <!--日志文件保留天数-->
    	            <maxHistory>15</maxHistory>
    	        </rollingPolicy>
    	        <!-- 此日志文件只记录ERROR级别的 -->
    	        <filter class="ch.qos.logback.classic.filter.LevelFilter">
    	            <level>ERROR</level>
    	            <onMatch>ACCEPT</onMatch>
    	            <onMismatch>DENY</onMismatch>
    	        </filter>
    	    </appender>
    	
    	    <!--
    	        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
    	        以及指定<appender><logger>仅有一个name属性,
    	        一个可选的level和一个可选的addtivity属性。
    	        name:用来指定受此logger约束的某一个包或者具体的某一个类。
    	        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
    	              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
    	              如果未设置此属性,那么当前logger将会继承上级的级别。
    	        addtivity:是否向上级logger传递打印信息。默认是true-->
    	    <!--<logger name="org.springframework.web" level="info"/>-->
    	    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
    	    <!--
    	        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
    	        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
    	        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
    	     -->
    	
    	
    	    <!--
    	        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
    	        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
    	        不能设置为INHERITED或者同义词NULL。默认是DEBUG
    	        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    	    -->
    	
    	    <!--开发环境:打印控制台-->
    	    <springProfile name="dev">
    	        <logger name="com.nmys.view" level="debug"/>
    	    </springProfile>
    	
    	    <root level="info">
    	        <appender-ref ref="CONSOLE" />
    	        <appender-ref ref="DEBUG_FILE" />
    	        <appender-ref ref="INFO_FILE" />
    	        <appender-ref ref="WARN_FILE" />
    	        <appender-ref ref="ERROR_FILE" />
    	    </root>
    	
    	    <!--生产环境:输出到文件-->
    	    <!--<springProfile name="pro">-->
    	    <!--<root level="info">-->
    	    <!--<appender-ref ref="CONSOLE" />-->
    	    <!--<appender-ref ref="DEBUG_FILE" />-->
    	    <!--<appender-ref ref="INFO_FILE" />-->
    	    <!--<appender-ref ref="ERROR_FILE" />-->
    	    <!--<appender-ref ref="WARN_FILE" />-->
    	    <!--</root>-->
    	    <!--</springProfile>-->
    	
    	</configuration>
    	```
    
    
  • 创建一个Aop切面类

    @Target({ElementType.PARAMETER, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Operation {
        String name();
    }
    
    @Aspect // FOR AOP
    @Order(-99) // 控制多个Aspect的执行顺序,越小越先执行
    @Component
    public class LogAspect {
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(xxx.xxxxx.Operation)")//上述Operation类的路径地址
        public void method(){
        }
    
        /**
         * doAround:(环绕方法,统一日志处理). <br/>
         * @author fenglanglang
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("method()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            long beginTime = System.currentTimeMillis();//1、开始时间
            Date date=new Date();
            ServletRequestAttributes requestAttr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            String uri = requestAttr.getRequest().getRequestURI();
            String method=requestAttr.getRequest().getMethod();//请求方式
            String contentType=requestAttr.getRequest().getContentType();//请求类型
            String url="" + requestAttr.getRequest().getServerName() //服务器地址
                    + ":"
                    + requestAttr.getRequest().getServerPort()           //端口号
                    + requestAttr.getRequest().getRequestURI();//接口名称00
            String queryurl="";
            Enumeration queryurls=requestAttr.getRequest().getParameterNames();
            ArrayList<String> arrayList=Collections.list(queryurls);
            String[] arrStr = arrayList.toArray(new String[0]);
            for (int asd=0;asd<arrStr.length;asd++){
                String value=requestAttr.getRequest().getParameter(arrStr[asd]);
                queryurl+=arrStr[asd]+"="+(value)+",";//获取url地址和参数
    //            System.out.println(arrStr[asd]+"="+(value));
            }
            String qstitile=afterReturning(joinPoint);
            //访问目标方法的参数 可动态改变参数值
            Object[] args = joinPoint.getArgs();
            //方法名获取
            String methodName = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();
            //可能在反向代理请求进来时,获取的IP存在不正确行 这里直接摘抄一段来自网上获取ip的代码
            //调用实际方法
            Object object = joinPoint.proceed();
            long endTime = System.currentTimeMillis()-beginTime;
            String ip=getIpAddr(requestAttr.getRequest());
            String content=
                    "\n"+qstitile+
                     "\n    请求用户:"+"可根据自己需求获取返回"+
                     ",\n    请求URL:"+url+
                     ",\n    请求方式:"+method+
                     ",\n    请求时间:"+date+
                     ",\n    请求参数: {"+queryurl+"}"+
                     ",\n    请求类型:"+contentType+
                     ",\n    请求ip:"+ip+
                     ",\n    返回状态:"+"可根据自己需求获取返回"+
                     ",\n    返回说明:"+"可根据自己需求获取返回"+
                     ",\n    结束总耗时:"+endTime+"毫秒"+
                     ",\n    返回的结果:"+object.toString()+
                     "\n----------------------\n";
            logger.info(content);
            return object;
        }
    
    
    
        /**
         *
         * getIpAddr:(获取ip)
         * @author fenglanglang
         * @param request
         * @return
         */
        public static String getIpAddr(HttpServletRequest request) {
            String ipAddress = null;
            try {
                ipAddress = request.getHeader("x-forwarded-for");
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("Proxy-Client-IP");
                }
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("WL-Proxy-Client-IP");
                }
                if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getRemoteAddr();
                    if (ipAddress.equals("127.0.0.1")) {
                        // 根据网卡取本机配置的IP
                        InetAddress inet = null;
                        try {
                            inet = InetAddress.getLocalHost();
                        } catch (UnknownHostException e) {
                            System.out.println("获取ip异常:{}" + Throwables.getStackTraceAsString(e));
                        }
                        ipAddress = inet.getHostAddress();
                    }
                }
                // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
                if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                    if (ipAddress.indexOf(",") > 0) {
                        ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                    }
                }
            } catch (Exception e) {
                ipAddress = "";
            }
            return ipAddress;
        }
    
    
        /**
         * 获取类名
         * @param joinPoint
         * @return
         */
        public String afterReturning(JoinPoint joinPoint){
            Class<? extends Object> clazz =  joinPoint.getTarget().getClass();
            String controllerOperation = clazz.getName();
            if(clazz.isAnnotationPresent(Operation.class)){
                // 当前controller操作的名称
                controllerOperation = clazz.getAnnotation(Operation.class).name();
            }
            // 获取当前方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            // clazz类下的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            String methodOperation = "";
            for (Method m : methods) {
                if(m.equals(method)){
                    methodOperation = m.getName();
                    if(m.isAnnotationPresent(Operation.class)){
                        methodOperation = m.getAnnotation(Operation.class).name();
                    }
                }
            }
            controllerOperation=controllerOperation.substring(controllerOperation.lastIndexOf(".")+1,controllerOperation.length());
            return "---------执行了"+controllerOperation+"下的"+methodOperation+"操作:---------";
        }
    }
    
    
  • 通过切面日志的应用

    在方法的类注解中直接引入切面@Operation(name = “读取文档内容”)即可

    	/**
    	     * 读取信息文本文档
    	     * @return
    	     * @throws Exception
    	     */
    	    @Operation(name = "读取文档内容")
    	    @RequestMapping(value = {"/filetext"})
    	    public ModelAndView filetext(String name, ReturnVo returnVo) throws Exception{
    	        return new ModelAndView("before/public/filetext","list",a) ;
    	    }
    	```
    
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dlei东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值