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.新建模块
父级依赖最外层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>
总结
文件目录
logback的控制台文件
错误日志中的内容
启动结果
aop的详细内容