Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式

 

 

 


前言

此系列博客是个人在学习springBoot的学习笔记,算是一个记录吧,一方面记录学习的内容,一方面记录学习中犯下的错误。学习springBoot选择的是GitHub上一个比较著名的项目,链接如下https://github.com/xkcoding/spring-boot-demo.git。这是一个从易到难的项目,目标:完成本地实现。技术差,文笔差,学习能力差,啥都差,欢迎指正,拒绝喷人


 

一、要实现的demo?

  本demo是将原项目的两个demo合成为一个demo,因为一般情况下AOP方式和logback方式侧重点不同。aop用来记录敏感操作,logback用来本地记录日志文件,记录系统运行日志。所以这里放一起了。本demo演示了如何使用 logback 记录程序运行过程中的日志,以及如何配置 logback,可以同时生成控制台日志和文件日志记录,文件日志以日期和大小进行拆分生成,以及如何使用 aop 切面对请求进行日志记录,并且记录 UserAgent 信息。注意事项

1.默认情况下,SpringBoot 采用logback来记录日志,并输出 INFO 级别日志到控制台spring-boot-stater的依赖中包含了logback,无需导包

2.logback-spring.xml 使用这个文件名放到resource下面无需其他配置,springboot会自动找到这个日志配置

3.log标红除非你有安装lombok,不影响调试

二、实现步骤

1.新建模块

Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式

父级依赖最外层pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xkcoding</groupId>
    <artifactId>spring-boot-learning-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
 <!--   子模块-->
    <modules>
        <module>spring-boot-demo-helloworld</module>
        <module>spring-boot-demo-properties</module>
        <module>spring-boot-demo-actuator</module>
        <module>spring-boot-demo-admin</module>
        <module>spring-boot-demo-log</module>
        <!--        以后才会用先注释掉-->
        <!--
        <module>spring-boot-demo-admin</module>
        <module>spring-boot-demo-logback</module>
        <module>spring-boot-demo-log-aop</module>
        <module>spring-boot-demo-exception-handler</module>
        <module>spring-boot-demo-template-freemarker</module>
        <module>spring-boot-demo-template-thymeleaf</module>
        <module>spring-boot-demo-template-beetl</module>
        <module>spring-boot-demo-template-enjoy</module>
        <module>spring-boot-demo-orm-jdbctemplate</module>
        <module>spring-boot-demo-orm-jpa</module>
        <module>spring-boot-demo-orm-mybatis</module>
        <module>spring-boot-demo-orm-mybatis-mapper-page</module>
        <module>spring-boot-demo-orm-mybatis-plus</module>
        <module>spring-boot-demo-orm-beetlsql</module>
        <module>spring-boot-demo-upload</module>
        <module>spring-boot-demo-cache-redis</module>
        <module>spring-boot-demo-cache-ehcache</module>
        <module>spring-boot-demo-email</module>
        <module>spring-boot-demo-task</module>
        <module>spring-boot-demo-task-quartz</module>
        <module>spring-boot-demo-task-xxl-job</module>
        <module>spring-boot-demo-swagger</module>
        <module>spring-boot-demo-swagger-beauty</module>
        <module>spring-boot-demo-rbac-security</module>
        <module>spring-boot-demo-rbac-shiro</module>
        <module>spring-boot-demo-session</module>
        <module>spring-boot-demo-oauth</module>
        <module>spring-boot-demo-social</module>
        <module>spring-boot-demo-zookeeper</module>
        <module>spring-boot-demo-mq-rabbitmq</module>
        <module>spring-boot-demo-mq-rocketmq</module>
        <module>spring-boot-demo-mq-kafka</module>
        <module>spring-boot-demo-websocket</module>
        <module>spring-boot-demo-websocket-socketio</module>
        <module>spring-boot-demo-ureport2</module>
        <module>spring-boot-demo-uflo</module>
        <module>spring-boot-demo-urule</module>
        <module>spring-boot-demo-activiti</module>
        <module>spring-boot-demo-async</module>
        <module>spring-boot-demo-dubbo</module>
        <module>spring-boot-demo-war</module>
        <module>spring-boot-demo-elasticsearch</module>
        <module>spring-boot-demo-mongodb</module>
        <module>spring-boot-demo-neo4j</module>
        <module>spring-boot-demo-docker</module>
        <module>spring-boot-demo-multi-datasource-jpa</module>
        <module>spring-boot-demo-multi-datasource-mybatis</module>
        <module>spring-boot-demo-sharding-jdbc</module>
        <module>spring-boot-demo-tio</module>
        <module>spring-boot-demo-codegen</module>
        <module>spring-boot-demo-graylog</module>
        <module>spring-boot-demo-ldap</module>
        <module>spring-boot-demo-dynamic-datasource</module>
        <module>spring-boot-demo-ratelimit-guava</module>
        <module>spring-boot-demo-ratelimit-redis</module>
        <module>spring-boot-demo-elasticsearch-rest-high-level-client</module>
        <module>spring-boot-demo-https</module>
        <module>spring-boot-demo-flyway</module>-->
    </modules>
    <!--一般来说所有的父级项目的packaging都为pom,
    packaging默认类型jar类型,如果不做配置,maven会将该项目打成jar包。
    pom用在父级工程或聚合工程中,用来做jar包的版本控制,必须指明这个聚合工程的打包方式为pom-->
    <packaging>pom</packaging>
<!--    name是maven工程继承的时候会使用,url相当于简介的作用,程序中无实际作用-->
    <name>spring-boot-learning-demo</name>
    <url>http://xkcoding.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.boot.version>2.1.0.RELEASE</spring.boot.version>
        <mysql.version>8.0.12</mysql.version>
        <hutool.version>5.0.0</hutool.version>
        <guava.version>28.1-jre</guava.version>
        <user.agent.version>1.20</user.agent.version>
    </properties>
<!--阿里云远程仓库,不改下载会很慢-->
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
<!-- 好像是引用上面properties的内容               -->
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!-- hutool工具类 -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
            <!-- guava工具类 -->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>${guava.version}</version>
            </dependency>
            <!-- 解析 UserAgent 信息 -->
            <dependency>
                <groupId>eu.bitwalker</groupId>
                <artifactId>UserAgentUtils</artifactId>
                <version>${user.agent.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
<!--meavn插件-->
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.20.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring.boot.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

子类依赖(pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-boot-learning-demo</artifactId>
        <groupId>com.xkcoding</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-demo-log</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-boot-demo-log</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- 解析 UserAgent 信息 -->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
        </dependency>
</dependencies>

    <build>
        <finalName>spring-boot-demo-log</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

2.代码编写

AopLog类:

package com.xkcoding.log.aop.aspectj;

import cn.hutool.json.JSONUtil;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;

@Aspect
@Component
@Slf4j
public class AopLog {
    private static final String START_TIME="request-start";

    @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))")
    public  void log(){

    }
    @Before("log()")
    public void beforeLog(JoinPoint point){
        ServletRequestAttributes attributes  = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        log.info("【请求 URL】:{}", request.getRequestURL());
        log.info("【请求 IP】:{}", request.getRemoteAddr());
        log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
        Map<String, String[]> parameterMap = request.getParameterMap();
        log.info("【请求参数】:{},", JSONUtil.toJsonStr(parameterMap));
        Long start = System.currentTimeMillis();
        request.setAttribute(START_TIME, start);

    }
    @Around("log()")
    public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
        Object result = point.proceed();
        log.info("【返回值】:{}", JSONUtil.toJsonStr(result));
        return result;
    }
    @AfterReturning("log()")
    public void afterReturning() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        Long start = (Long) request.getAttribute(START_TIME);
        Long end = System.currentTimeMillis();
        log.info("【请求耗时】:{}毫秒", end - start);
        String header = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(header);
        log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
    }
}

测试用的controller类

package com.xkcoding.log.aop.controller;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @GetMapping("/test")
    public Dict test(String who) {
        return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who);
    }
}

项目的启动类:

package com.xkcoding.log;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;


//lombok依赖
// @Slf4j
//注解在类上,为类提供一个属性名为log的slf4j日志对象
@SpringBootApplication
@Slf4j
public class SpringBootDemoLogbackApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoLogbackApplication.class, args);
        int length = context.getBeanDefinitionNames().length;
        log.trace("Spring boot启动初始化了 {} 个 Bean", length);
        log.debug("Spring boot启动初始化了 {} 个 Bean", length);
        log.info("Spring boot启动初始化了 {} 个 Bean", length);
        log.warn("Spring boot启动初始化了 {} 个 Bean", length);
        log.error("Spring boot启动初始化了 {} 个 Bean", length);
        try {
            int i = 0;
            int j = 1 / i;
        } catch (Exception e) {
            log.error("【SpringBootDemoLogbackApplication】启动异常:", e);
        }
    }
}

3.配置文件

application.yml 

server:
  port: 8080
  servlet:
    context-path: /demo

 

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <property name="FILE_ERROR_PATTERN"
            value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %file:%line: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
  <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
   <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
      <filter class="ch.qos.logback.classic.filter.LevelFilter">
         <level>INFO</level>
      </filter>
      <encoder>
         <pattern>${CONSOLE_LOG_PATTERN}</pattern>
         <charset>UTF-8</charset>
      </encoder>
   </appender>

   <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高, 所以我们使用下面的策略,可以避免输出 Error 的日志-->
      <filter class="ch.qos.logback.classic.filter.LevelFilter">
         <!--过滤 Error-->
         <level>ERROR</level>
         <!--匹配到就禁止-->
         <onMatch>DENY</onMatch>
         <!--没有匹配到就允许-->
         <onMismatch>ACCEPT</onMismatch>
      </filter>
      <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
      <!--<File>logs/info.spring-boot-demo-logback.log</File>-->
      <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
         <FileNamePattern>logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
         <!--只保留最近90天的日志-->
         <maxHistory>90</maxHistory>
         <!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
         <!--<totalSizeCap>1GB</totalSizeCap>-->
         <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
            <maxFileSize>2MB</maxFileSize>
         </timeBasedFileNamingAndTriggeringPolicy>
      </rollingPolicy>
      <!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
      <!--<maxFileSize>1KB</maxFileSize>-->
      <!--</triggeringPolicy>-->
      <encoder>
         <pattern>${FILE_LOG_PATTERN}</pattern>
         <charset>UTF-8</charset> <!-- 此处设置字符集 -->
      </encoder>
   </appender>

   <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
      <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
         <level>Error</level>
      </filter>
      <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
      <!--<File>logs/error.spring-boot-demo-logback.log</File>-->
      <!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
         <FileNamePattern>logs/spring-boot-demo-log/error.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
         <!--只保留最近90天的日志-->
         <maxHistory>90</maxHistory>
         <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
            <maxFileSize>2MB</maxFileSize>
         </timeBasedFileNamingAndTriggeringPolicy>
      </rollingPolicy>
      <encoder>
         <pattern>${FILE_ERROR_PATTERN}</pattern>
         <charset>UTF-8</charset> <!-- 此处设置字符集 -->
      </encoder>
   </appender>

   <root level="info">
      <appender-ref ref="CONSOLE"/>
      <appender-ref ref="FILE_INFO"/>
      <appender-ref ref="FILE_ERROR"/>
   </root>
</configuration>

 


总结

文件目录Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式

logback的控制台文件

Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式

错误日志中的内容

Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式

启动结果

Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式

aop的详细内容

Spring-boot-demo学习记录(五)-spring-boot 日志持久化之AOP方式+Logback方式