目录
- 前言说明
- 技术栈
- 解决方案-流程
- 实践
- pom文件
- logback-spring.xml
- SpringBoot配置文件
- mysql脚本
- 业务流程
- 自定义log注解
- serviceLog实体创建
- 发布事件ApplicationContext管理器
- 运行日志监听事件创建
- XwlLogInfoMapper 创建
- 运行日志监听器
- 切面拦截log注解封装运行日志
- XwlUser实体创建
- XwlUserMapper创建
- 异常枚举类创建
- 自定义异常创建
- utils工具类
- controller
- 启动类
- 测试运行日志进库以及文件日志记录
- postman测试工具执行正常访问
- 查看控制台
- 查看文件日志
- 查看数据库运行日志
- 运行日志结语
- 测试异常日志进库以及文件日志记录
- 异常实体创建
- 异常日志监听事件
- XwlLogErrorMapper创建
- 异常日志监听器
- 自定义异常拦截器
- postman测试工具执行异常访问
- 查看控制台
- 查看文件日志
- 查看数据库异常日志
- 拓展其它接口测试
- 随意测试如下
- 拓展语录
- 博主联系方式
- 结语
前言说明
通常一个系统权限是最主要的,因为这涉及到用户信息的安全性,所以都很重视权限的控制;那么本章给大家再说一下除了权限重要还有什么等同于权限的重要性东西呢?那就是日志了,一个好的日志设计能够让开发人员甚至用户少走弯路,系统上线出问题之后找开发人员也不可能一下子就知道是哪里出的问题,那么有了日志,相关人员可以根据用户反馈快速定位并分析问题,由此可见,日志的作用性也是不容忽视的!本章就给大家分享一套我自己搭的日志系统
技术栈
SpringBoot | SpringCloud | SpringCloud-netfilix |
---|---|---|
2.0.4.RELEASE | Finchley.SR4 | 2.0.4.RELEASE |
mybatis-plus | mysql | lombok | redis |
---|---|---|---|
2.2.0 | 5.1.47 | 1.16.20 | 2.0.0.RELEASE |
解决方案-流程
以SpringBoot为主SpringCloud为次,通过配置logback配置文件(日志输出级别和日志输出格式以及日志的生成规则),引用logback配置文件,设计日志自定义注解。
- 运行日志
使用Spring AOP 切面编程思想完成切割,封装运行日志信息体,由spring event监听事件完成对当前日志业务进行一系列操作从而使运行日志完成进库(mysql) - 异常日志
程序运行期间难免出现未知错误,自定义异常控制,使用Spring 一个特定注解拦截自定义异常,获取异常信息体的同时并组装异常信息体,由spring event监听事件完成对当前日志业务进行一系列操作从而使运行日志完成进库(mysql) - 文件日志记录
通过logback配置文件由框架自动生成并记录,无需过多处理。
实践
pom文件
管理工程所有依赖包
<?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>xwl-practice</artifactId>
<groupId>practice</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-logback</artifactId>
<packaging>jar</packaging>
<properties>
<spring.cloud-version>Finchley.SR4</spring.cloud-version>
<spring.cloud-other-version>2.0.4.RELEASE</spring.cloud-other-version>
<boot-version>2.0.4.RELEASE</boot-version>
<mysql-version>5.1.47</mysql-version>
<mybatis-plus-version>2.2.0</mybatis-plus-version>
</properties>
<dependencies>
<!--SpringBoot核心依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${boot-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${boot-version}</version>
</dependency>
<!--SpringBoot核心依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<!--TODO 必须添加如下设置才可导入Cloud-->
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
<version>${spring.cloud-other-version}</version>
</dependency>
<!--TODO Cloud自动化配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>${spring.cloud-other-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<!--maven编译器指定jdk版本-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
注意:pom文件头部parent父依赖你们根据你们自己的环境进行设置
- 场景1(无父工程,你当前工程就是一个主工程或单体应用,就需要改一下)
将如下配置换成你自己的工程artifactId和groupid以及版本号
<parent>
<artifactId>xwl-practice</artifactId>
<groupId>practice</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
替换如下图的SpringBoot的父依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
- 场景2(有父工程,那么就需要将parent里面的依赖关系改为你自己的父工程依赖)
logback-spring.xml
注:每天都会生成新的日期文件夹,便于查看~~~
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--引入配置文件中定义log路径-->
<springProperty scope="context" name="log.path" source="logs.filePath"/>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径D:\\logback\\wcs_printers-->
<property value="${log.path}" name="LOG_HOME"/>
<!-- 控制台输出 TRACE < DEBUG < INFO < WARN < ERROR-->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 1格式化输出:%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>
<!--临界值日志过滤级别配置 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 1在日志级别的基础上过滤掉trace级别以下的日志 -->
<level>TRACE</level>
</filter>
</appender>
<!-- info级别,按照每天生成日志文件 -->
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--只保留固定配置级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 之前的 rollingPolicy和triggeringPolicy冲突了 用一个新的标签 结合两者 -->
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}\%d{yyyy-MM-dd,aux}\info-log-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!-- 日志文件保留天数 -->
<MaxHistory>5</MaxHistory>
<!-- 日志文件最大尺寸 -->
<maxFileSize>50MB</maxFileSize>
</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>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- error级别,按照每天生成日志文件 -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--只保留固定配置级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 之前的 rollingPolicy和triggeringPolicy冲突了 用一个新的标签 结合两者 -->
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}\%d{yyyy-MM-dd,aux}\error-log-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!-- 日志文件保留天数 -->
<MaxHistory>5</MaxHistory>
<!-- 日志文件最大尺寸 -->
<maxFileSize>50MB</maxFileSize>
</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>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 日志级别排序为: TRACE < DEBUG < INFO < WARN < ERROR -->
<!-- 日志输出级别 level="ERROR"-->
<root level="INFO">
<appender-ref ref="stdout"/>
<appender-ref ref="info"/>
<appender-ref ref="error"/>
</root>
</configuration>
SpringBoot配置文件
注:logging.config(加载logback配置日志文件),logs.filePath(日志输出目录)
server:
port: 12345
spring:
redis:
host: localhost
port: 6379
application:
name: 日志服务
datasource:
url: jdbc:mysql://localhost:3306/xwl?characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
eureka:
client:
register-with-eureka: false
fetch-registry: false
mybatis-plus:
mapper-locations: classpath:com.itxwl/mapper/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#开启驼峰命名转换
map-underscore-to-camel-case: true
logging:
config: classpath:logback-spring.xml
logs:
filePath: D:\\logback\\logs
mysql脚本
说明:info->运行表,error->异常表,user->用户表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for xwl_log_error
-- ----------------------------
DROP TABLE IF EXISTS `xwl_log_error`;
CREATE TABLE `xwl_log_error` (
`id` bigint(50) NOT NULL COMMENT '主键id',
`interface_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '接口类名称',
`interface_url` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT 'url路径',
`method_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '操作方式',
`api_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT 'API标题名称',
`method_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '方法名称',
`service_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT 'IP地址',
`stack_trace` varchar(20000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '堆栈信息体',
`ex_msg` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '异常名称',
`line_number` int(11) NULL DEFAULT NULL COMMENT '错误行数',
`params` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '当前接口参数体',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间',
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '操作人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for xwl_log_info
-- ----------------------------
DROP TABLE IF EXISTS `xwl_log_info`;
CREATE TABLE `xwl_log_info` (
`id` bigint(50) NOT NULL COMMENT '主键id',
`interface_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '接口类名称',
`interface_url` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT 'url路径',
`method_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '操作方式',
`method_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '方法名称',
`service_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT 'IP地址',
`api_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '接口名称',
`params` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '当前接口参数体',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间',
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '操作人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for xwl_user
-- ----------------------------
DROP TABLE IF EXISTS `xwl_user`;
CREATE TABLE `xwl_user` (
`id` bigint(50) NOT NULL,
`user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
`user_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
业务流程
自定义log注解
import java.lang.annotation.*;
/**
* @author xueWenLiang
* @date 2021/5/10 14:10
* @Description 描述信息
*/
//标注API
@Documented
//配置作用范围
@Target({ElementType.METHOD})
//运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface XwlLog {
/**
* 日志描述信息
* @return
*/
String describution() default "";
}
serviceLog实体创建
/**
* @author xueWenLiang
* @date 2021/5/11 19:05
* @Description 描述信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("xwl_log_info")
public class ServiceLog {
private Long id;
//接口名称
private String interfaceName;
//接口路径
private String interface_url;
//方法类型
private String methodType;
//方法名称
private String methodName;
//访问路径
private String serviceIp;
//接口名称
private String apiName;
//参数体
private String params;
//创建时间
private Date createTime;
//用户名
private String userName;
}
发布事件ApplicationContext管理器
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Service;
/**
* @author xuewenliang
* @date 2021-05-12 Spring 工具类
*/
@Slf4j
@Service
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
/**
* 发布事件
* @param event
*/
public static void publishEvent(ApplicationEvent event) {
if (applicationContext == null) {
return;
}
applicationContext.publishEvent(event);
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
@SneakyThrows
public void destroy() {
SpringContextHolder.clearHolder();
}
}
运行日志监听事件创建
import com.itxwl.domain.ServiceLog;
import org.springframework.context.ApplicationEvent;
/**
* @author xueWenLiang
* @date 2021/5/11 19:06
* @Description 系统运行日志监听事件
*/
public class XwlLogEvent extends ApplicationEvent {
public XwlLogEvent(ServiceLog source) {
super(source);
}
}
XwlLogInfoMapper 创建
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.itxwl.domain.ServiceLog;
import org.apache.ibatis.annotations.Mapper;
/**
* @author xueWenLiang
* @date 2021/5/11 19:15
* @Description 描述信息
*/
@Mapper
public interface XwlLogInfoMapper extends BaseMapper<ServiceLog> {
}
运行日志监听器
import com.itxwl.domain.ServiceLog;
import com.itxwl.domain.event.XwlLogEvent;
import com.itxwl.mapper.XwlLogInfoMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @author xueWenLiang
* @date 2021/5/11 19:13
* @Description 日志监听事件->
*/
@Component
@AllArgsConstructor
@Slf4j
public class XwlLogListener {
private final XwlLogInfoMapper xwlLogInfoMapper;
@Async
@Order
@EventListener(XwlLogEvent.class)
public void saveXwlLogInfo(XwlLogEvent xwlLogEvent){
ServiceLog serviceLog = (ServiceLog) xwlLogEvent.getSource();
xwlLogInfoMapper.insert(serviceLog);
}
}
切面拦截log注解封装运行日志
import com.itxwl.annotation.XwlLog;
import com.itxwl.domain.ServiceLog;
import com.itxwl.domain.event.XwlLogEvent;
import com.itxwl.utils.SpringContextHolder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;
/**
* @author xueWenLiang
* @date 2021/5/11 15:40
* @Description 切面拦截日志注解封装日志运行信息体
*/
@Aspect
@Component
@Order(1)
@Slf4j
@SuppressWarnings("all")
public class XwlLogAspect {
private static XwlLogAspect xwlLogAspect=new XwlLogAspect();
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init(){
xwlLogAspect.redisTemplate=redisTemplate;
}
/**
* 切面拦截 构建异常信息体
* @param point
* @param xwlLog
* @return
*/
@Around("@annotation(xwlLog)")
@SneakyThrows
public Object around(ProceedingJoinPoint point, XwlLog xwlLog) {
ServiceLog serviceLog = new ServiceLog();
//类名称
String className = point.getTarget().getClass().getName();
//方法名称
String methodName = point.getSignature().getName();
//获取request域
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//访问路径url
String requestURI = request.getRequestURI();
StringBuffer stringBuffer=new StringBuffer();
Map<String, String[]> parameterMap = request.getParameterMap();
for (String key : parameterMap.keySet()) {
//如果参数值不存在值->直接添加参数||反之前缀添加符号
if (StringUtils.isEmpty(stringBuffer)){
stringBuffer.append(key+":"+parameterMap.get(key)[0]);
}else {
stringBuffer.append(","+key+":"+parameterMap.get(key)[0]);
}
}
//IP地址
String remoteAddr = request.getRemoteAddr();
//获取请求方式
String methodType = request.getMethod();
//创建时间
// TODO 操作人暂定->可以通过拦截器拦截请求信息->threadLocal存储并获取
//异常名称->描述
String describution = xwlLog.describution();
serviceLog.setInterfaceName(className);
serviceLog.setMethodName(methodName);
serviceLog.setInterface_url(requestURI);
serviceLog.setParams(stringBuffer.toString());
serviceLog.setServiceIp(remoteAddr);
serviceLog.setMethodType(methodType);
serviceLog.setCreateTime(new Date());
serviceLog.setApiName(describution);
xwlLogAspect.redisTemplate.opsForValue().set("logKey",describution);
serviceLog.setUserName("张三");
SpringContextHolder.publishEvent(new XwlLogEvent(serviceLog));
return point.proceed();
}
}
XwlUser实体创建
import com.baomidou.mybatisplus.annotations.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author xueWenLiang
* @date 2021/4/29 17:04
* @Description 描述信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("xwl_user")
public class XwlUser {
@JsonSerialize(nullsUsing = ToStringSerializer.class)
private Long id;
private String userName;
private String userAddress;
}
XwlUserMapper创建
/**
* @author xueWenLiang
* @date 2021/5/13 16:50
* @Description 描述信息
*/
@Mapper
public interface XwlUserMapper extends BaseMapper<XwlUser> {
}
异常枚举类创建
/**
* @author xueWenLiang
* @date 2021/5/12 14:08
* @Description 异常描述枚举类
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("all")
public enum ApiReturnMsgEnmu {
THROW_ERROR_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR.value(),"服务器异常"),
LEVEL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(),"计算异常"),
SAVE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(),"保存用户失败");
private int code;
private String msg;
}
自定义异常创建
/**
* @author xueWenLiang
* @date 2021/5/12 14:01
* @Description 自定义异常拓展类
*/
@Component
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ApiErrorException extends RuntimeException{
private ApiReturnMsgEnmu apiReturnMsgEnmu;
/**
* 异常类->用途:后续异常封装部分信息进库使用
*/
private Exception exception;
/**
* 单枚举异常编号及描述信息构造
* @param apiReturnMsgEnum
*/
public ApiErrorException(ApiReturnMsgEnmu apiReturnMsgEnum){
this.apiReturnMsgEnmu=apiReturnMsgEnum;
}
}
utils工具类
import org.springframework.stereotype.Component;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @author xueWenLiang
* @date 2021/5/12 14:30
* @Description 描述信息
*/
@Component
public class MyUtils {
/**
* 获取异常的堆栈信息
*
* @param t
* @return
*/
public static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
//将异常信息输出在控制台
t.printStackTrace(pw);
//将异常信息返回
return sw.toString();
} finally {
pw.close();
}
}
}
controller
import com.itxwl.annotation.XwlLog;
import com.itxwl.config.enums.ApiReturnMsgEnmu;
import com.itxwl.config.exception.ApiErrorException;
import com.itxwl.domain.XwlUser;
import com.itxwl.mapper.XwlUserMapper;
import com.itxwl.utils.MyUtils;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
/**
* @author xueWenLiang
* @date 2021/5/10 14:57
* @Description 描述信息
*/
@RestController
@RequestMapping("my")
@AllArgsConstructor
public class LogBackController {
private XwlUserMapper xwlUserMapper;
private final Logger logger = LoggerFactory.getLogger(LogBackController.class);
@GetMapping("testError")
@XwlLog(describution = "测试")
public Object testError(@RequestParam(value = "number", required = true) Integer number) {
logger.info("[进入测试接口]:{}", "参数值" + number);
int x = 0;
try {
x = 5 / number;
} catch (Exception e) {
logger.error("[测试失败->错误信息]:{}", MyUtils.getStackTrace(e));
throw new ApiErrorException(ApiReturnMsgEnmu.THROW_ERROR_EXCEPTION);
}
return (Object) x;
}
}
注意:启动之前别忘了把redis开开,因为工程引入了redis,后续有用
启动类
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author xueWenLiang
* @date 2021/5/10 13:55
* @Description 描述信息
*/
@EnableEurekaClient
@SpringBootApplication
@MapperScan("com.itxwl.mapper")
public class MyLogBackApplication {
public static void main(String[] args) {
try {
SpringApplication.run(MyLogBackApplication.class,args);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试运行日志进库以及文件日志记录
postman测试工具执行正常访问
访问成功–>并正确得到返回值
查看控制台
查看文件日志
查看数据库运行日志
运行日志结语
可以看到,一套完整的运行日志完美输出,用户每一次对系统的访问都将入库入文件,为后续各种问题保驾护航。
测试异常日志进库以及文件日志记录
异常实体创建
/**
* @author xueWenLiang
* @date 2021/5/12 14:25
* @Description 描述信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("xwl_log_error")
public class ErrorLog extends ServiceLog {
private String stackTrace;
private String exMsg;
private Integer lineNumber;
/**
* 异常体->不参与业务剖析
*/
@JsonIgnore
@TableField(exist = false)
private Exception exception;
}
异常日志监听事件
import com.itxwl.domain.ErrorLog;
import org.springframework.context.ApplicationEvent;
/**
* @author xueWenLiang
* @date 2021/5/12 14:27
* @Description 系统业务异常监听类
*/
public class XwlLogErrorEvent extends ApplicationEvent {
public XwlLogErrorEvent(ErrorLog source) {
super(source);
}
}
XwlLogErrorMapper创建
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.itxwl.domain.ErrorLog;
import org.apache.ibatis.annotations.Mapper;
/**
* @author xueWenLiang
* @date 2021/5/12 15:00
* @Description 描述信息
*/
@Mapper
public interface XwlLogErrorMapper extends BaseMapper<ErrorLog> {
}
异常日志监听器
import com.itxwl.domain.ErrorLog;
import com.itxwl.domain.event.XwlLogErrorEvent;
import com.itxwl.mapper.XwlLogErrorMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @author xueWenLiang
* @date 2021/5/12 14:59
* @Description 异常日志监听事件->
*/
@Component
@AllArgsConstructor
@Slf4j
public class XwlLogErrorListener {
private final XwlLogErrorMapper xwlLogErrorMapper;
@Async
@Order
@EventListener(XwlLogErrorEvent.class)
public void xwlLogErrorInsert(XwlLogErrorEvent xwlLogErrorEvent){
ErrorLog errorLog = (ErrorLog) xwlLogErrorEvent.getSource();
xwlLogErrorMapper.insert(errorLog);
}
}
自定义异常拦截器
import com.itxwl.config.enums.ApiReturnMsgEnmu;
import com.itxwl.config.exception.ApiErrorException;
import com.itxwl.domain.ErrorLog;
import com.itxwl.domain.event.XwlLogErrorEvent;
import com.itxwl.utils.MyUtils;
import com.itxwl.utils.SpringContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author xueWenLiang
* @date 2021/5/12 9:50
* @Description 异常过滤器->封装异常体进库
*/
@Configuration
@ResponseBody
@RestControllerAdvice
public class ExceptionFilterReturnConfig {
private static ExceptionFilterReturnConfig exceptionFilterReturnConfig=new ExceptionFilterReturnConfig();
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init(){
exceptionFilterReturnConfig.redisTemplate=redisTemplate;
}
/**
* 1.返回具体异常信息体
* 2.异常日志进库->发起监听事件
* @param apiErrorException
* @return
*/
@ExceptionHandler(ApiErrorException.class)
@SuppressWarnings("all")
public ResponseEntity<Map<String, Object>> apiErrorException(ApiErrorException apiErrorException) {
Map<String, Object> map = new HashMap<>();
ApiReturnMsgEnmu apiReturnMsgEnmu = apiErrorException.getApiReturnMsgEnmu();
int code = apiReturnMsgEnmu.getCode();
String msg = apiReturnMsgEnmu.getMsg();
//构建异常返回体
map.put("code", code);
map.put("msg", msg);
//发布日志事件
ErrorLog errorLog = new ErrorLog();
StackTraceElement traceElement = apiErrorException.getStackTrace()[0];
//类名称
String className = traceElement.getClassName();
//方法名称
String methodName = traceElement.getMethodName();
//获取request域
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//访问路径url
String requestURI = request.getRequestURI();
StringBuffer stringBuffer=new StringBuffer();
Map<String, String[]> parameterMap = request.getParameterMap();
for (String key : parameterMap.keySet()) {
//如果参数值不存在值->直接添加参数||反之前缀添加符号
if (StringUtils.isEmpty(stringBuffer)){
stringBuffer.append(key+":"+parameterMap.get(key)[0]);
}else {
stringBuffer.append(","+key+":"+parameterMap.get(key)[0]);
}
}
//IP地址
String remoteAddr = request.getRemoteAddr();
//获取请求方式
String methodType = request.getMethod();
//创建时间
// TODO 操作人暂定->可以通过拦截器拦截请求信息->threadLocal存储并获取
errorLog.setInterfaceName(className);
errorLog.setMethodName(methodName);
errorLog.setInterface_url(requestURI);
errorLog.setParams(stringBuffer.toString());
errorLog.setServiceIp(remoteAddr);
errorLog.setMethodType(methodType);
errorLog.setCreateTime(new Date());
errorLog.setUserName("张三");
errorLog.setApiName((String) exceptionFilterReturnConfig.redisTemplate.opsForValue().get("logKey"));
String stackTrace = MyUtils.getStackTrace(apiErrorException);
System.out.println(stackTrace.length());
errorLog.setStackTrace(stackTrace);
//异常描述
errorLog.setExMsg(msg);
//行数
errorLog.setLineNumber(traceElement.getLineNumber());
SpringContextHolder.publishEvent(new XwlLogErrorEvent(errorLog));
return ResponseEntity.ok(map);
}
}
postman测试工具执行异常访问
查看控制台
查看文件日志
查看数据库异常日志
拓展其它接口测试
@GetMapping("gaga")
@XwlLog(describution = "计算器")
public Object gaga(@RequestParam(value = "size", required = true) Integer size) {
logger.info("[进入计算器接口]:{}", "参数值" + size);
int x = 0;
try {
x = 5 / size;
} catch (Exception e) {
logger.error("[计算器执行失败->错误信息]:{}", MyUtils.getStackTrace(e));
throw new ApiErrorException(ApiReturnMsgEnmu.LEVEL_ERROR);
}
return (Object) x;
}
@PostMapping("saveUser")
@XwlLog(describution = "保存用户")
public Boolean gaga(XwlUser xwlUser) {
logger.info("[开始保存用户]:{}", "参数值" + xwlUser.toString());
if (ObjectUtils.isEmpty(xwlUser)) {
logger.info("用户信息为空");
}
int x = 0;
try {
x = xwlUserMapper.insert(xwlUser);
} catch (Exception e) {
logger.error("[计算器执行失败->错误信息]:{}", MyUtils.getStackTrace(e));
throw new ApiErrorException(ApiReturnMsgEnmu.SAVE_ERROR);
}
return x > 0 ? true : false;
}
随意测试如下
http://localhost:12345/my/gaga?size=0
http://localhost:12345/my/gaga?size=5
http://localhost:12345/my/saveUser?userName=dsadsa&userAddress=dsada
拓展语录
中间我在自定义拦截器的时候使用了redis缓存获取接口运行时的描述说明,我是暂时想到这个方法,不知道你们有没有好的想法或者代码有任何不当不处都可以联系我
博主联系方式
微信:x331191249 QQ:2509647976
结语
敢于实践才是真理,想象谁都会看谁都会看,关键看实践过程->结果