springboot aop + logback + 统一异常处理 打印日志
1.src/resources路径下新建logback.xml
- 控制台彩色日志打印
- info日志和异常日志分不同文件存储
- 每天自动生成日志
- 结合myibatis方便日志打印(debug模式)
<?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>
<!--myibatis log configure-->
<logger name="com.apache.ibatis" 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>
2.定义一个log实体 方便维护和后续的扩展
package com.wdcloud.categoryserver.log;
import com.wdcloud.categoryserver.common.entity.User;
/**
* @describe: 日志打印实体
* @author: zhuchunwang
* @date: 2018/5/29 17:40
* @version: 1.0
*/
public class LogEntity {
private long id;
/**
* 请求地址
*/
private String url;
/**
* 请求IP
*/
private String ip;
/**
* 请求方式
*/
private String httpMethod;
/**
* 请求类,方法
*/
private String classMethod;
/**
* 方法参数
*/
private String args;
/**
* 请求参数
*/
private String reqParams;
/**
* 响应参数
*/
private String respParams;
/**
* 响应时间
*/
private long spendTime;
/**
* 日志类型(web、service)
*/
private String logType;
/**
* 用户信息
*/
private User user;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(String httpMethod) {
this.httpMethod = httpMethod;
}
public String getClassMethod() {
return classMethod;
}
public void setClassMethod(String classMethod) {
this.classMethod = classMethod;
}
public String getArgs() {
return args;
}
public void setArgs(String args) {
this.args = args;
}
public String getReqParams() {
return reqParams;
}
public void setReqParams(String reqParams) {
this.reqParams = reqParams;
}
public String getRespParams() {
return respParams;
}
public void setRespParams(String respParams) {
this.respParams = respParams;
}
public long getSpendTime() {
return spendTime;
}
public void setSpendTime(long spendTime) {
this.spendTime = spendTime;
}
public String getLogType() {
return logType;
}
public void setLogType(String logType) {
this.logType = logType;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
3.定义一个controller 的切面
package com.wdcloud.categoryserver.log;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wdcloud.categoryserver.common.constant.AppConstants;
import com.wdcloud.categoryserver.utils.AppUtil;
import com.wdcloud.categoryserver.utils.JsonUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @describe: 实现controller的日志切面
* @author: zhuchunwang
* @date: 2018/5/29 17:40
* @version: 1.0
*/
@Aspect
@Component
@Order(1)
public class ControllerLogAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
ThreadLocal<Long> startTime = new ThreadLocal<>();
ThreadLocal<LogEntity> webLogThreadLocal = new ThreadLocal<>();
/**
* 定义一个切入点.
* 解释下:
* <p>
* ~ 第一个 * 代表任意修饰符及任意返回值.
* ~ 第二个 * 任意包名
* ~ 第三个 * 代表任意方法.
* ~ 第四个 * 定义在web包或者子包
* ~ 第五个 * 任意方法
* ~ .. 匹配任意数量的参数.
*/
@Pointcut("execution(* com.wdcloud.categoryserver.business.*.controller.*.*(..))")
public void serviceAspect() {
}
@Before("serviceAspect()")
public void doBefore(JoinPoint joinPoint) {
// 接收到请求,记录请求内容
startTime.set(System.currentTimeMillis());
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
webLogThreadLocal.set(new LogEntity());
webLogThreadLocal.get().setHttpMethod(request.getMethod());
webLogThreadLocal.get().setClassMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
webLogThreadLocal.get().setUrl(request.getRequestURL().toString());
webLogThreadLocal.get().setIp(request.getRemoteAddr());
webLogThreadLocal.get().setArgs(AppUtil.getArgs(joinPoint));
webLogThreadLocal.get().setLogType(AppConstants.LOG_TYPE_HTTP);
webLogThreadLocal.get().setReqParams(JsonUtil.objectToJson(request.getParameterMap()));
webLogThreadLocal.get().setUser(AppUtil.getUser(request));
}
/**
* 异常通知 用于拦截service层记录异常日志
*
* @param
* @param
*/
@AfterReturning(returning = "result", pointcut = "serviceAspect()")
public void doAfterReturning(Object result) {
// 处理完请求,返回内容
ObjectMapper mapper = new ObjectMapper();
try {
webLogThreadLocal.get().setRespParams(mapper.writeValueAsString(result));
} catch (JsonProcessingException e) {
logger.error(AppUtil.getExceptionDetail(e));
}
webLogThreadLocal.get().setSpendTime(System.currentTimeMillis() - startTime.get());
try {
logger.info(">>>"+mapper.writeValueAsString(webLogThreadLocal.get()));
} catch (JsonProcessingException e) {
logger.error(AppUtil.getExceptionDetail(e));
}
}
}
4.定义一个供dubbo调用的service切面
package com.wdcloud.categoryserver.log;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wdcloud.categoryserver.common.constant.AppConstants;
import com.wdcloud.categoryserver.utils.AppUtil;
import com.wdcloud.categoryserver.utils.JsonUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* @describe: 实现controller的日志切面
* @author: zhuchunwang
* @date: 2018/5/29 17:40
* @version: 1.0
*/
@Aspect
@Component
@Order(1)
public class ServiceLogAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
ThreadLocal<Long> startTime = new ThreadLocal<>();
ThreadLocal<LogEntity> webLogThreadLocal = new ThreadLocal<>();
/**
* 定义一个切入点.
* 解释下:
* <p>
* ~ 第一个 * 代表任意修饰符及任意返回值.
* ~ 第二个 * 任意包名
* ~ 第三个 * 代表任意方法.
* ~ 第四个 * 定义在web包或者子包
* ~ 第五个 * 任意方法
* ~ .. 匹配任意数量的参数.
*/
@Pointcut("execution(* com.wdcloud.categoryserver.front.*.service.*.*(..))")
public void serviceAspect() {
}
@Before("serviceAspect()")
public void doBefore(JoinPoint joinPoint) {
// 接收到请求,记录请求内容
startTime.set(System.currentTimeMillis());
webLogThreadLocal.set(new LogEntity());
webLogThreadLocal.get().setClassMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
webLogThreadLocal.get().setArgs(AppUtil.getArgs(joinPoint));
webLogThreadLocal.get().setLogType(AppConstants.LOG_TYPE_DUBBO);
}
/**
* 异常通知 用于拦截service层记录异常日志
*
* @param
* @param
*/
@AfterReturning(returning = "result", pointcut = "serviceAspect()")
public void doAfterReturning(Object result) {
// 处理完请求,返回内容
ObjectMapper mapper = new ObjectMapper();
try {
webLogThreadLocal.get().setRespParams(mapper.writeValueAsString(result));
} catch (JsonProcessingException e) {
logger.error(AppUtil.getExceptionDetail(e));
}
webLogThreadLocal.get().setSpendTime(System.currentTimeMillis() - startTime.get());
try {
logger.info(">>>"+mapper.writeValueAsString(webLogThreadLocal.get()));
} catch (JsonProcessingException e) {
logger.error(AppUtil.getExceptionDetail(e));
}
}
}
5.统一异常处理
package com.wdcloud.categoryserver.common.exception;
import com.wdcloud.categoryserver.common.constant.AppConstants;
import com.wdcloud.categoryserver.common.constant.CodeConstants;
import com.wdcloud.categoryserver.common.entity.BaseView;
import com.wdcloud.categoryserver.log.LogEntity;
import com.wdcloud.categoryserver.utils.AppUtil;
import com.wdcloud.categoryserver.utils.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import javax.servlet.http.HttpServletRequest;
/**
* @describe: 全局异常处理
* @author: zhuchunwang
* @date: 2018/5/29 17:40
* @version: 1.0
*/
@ControllerAdvice(annotations = {RestController.class})
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 默认未知异常
* @param request
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public BaseView defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
BaseView baseView = new BaseView(CodeConstants.SYSTEM_EXCEPTION,CodeConstants.SYSTEM_EXCEPTION_MSG);
printLog(request,e,baseView);
return baseView;
}
/**
* 参数异常
* @param request
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = {HttpMessageNotReadableException.class, MissingServletRequestPartException.class ,MissingServletRequestParameterException.class, MultipartException.class})
@ResponseBody
public BaseView httpMessageNotReadableExceptionErrorHandler(HttpServletRequest request, Exception e) throws Exception {
BaseView baseView = new BaseView(CodeConstants.PARAMETER_ERROR,CodeConstants.PARAMETER_ERROR_MSG);
printLog(request,e,baseView);
return baseView;
}
/**
* contentType异常
* @param request
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = {HttpMediaTypeNotSupportedException.class})
@ResponseBody
public BaseView httpMediaTypeNotSupportedExceptionHandler(HttpServletRequest request, Exception e) throws Exception {
BaseView baseView = new BaseView(CodeConstants.CONTENTTYPE_ERROR,CodeConstants.CONTENTTYPE_ERROR_MSG);
printLog(request,e,baseView);
return baseView;
}
/**
* 异常信息打印日志
* @param request
* @param e
* @param baseView
*/
private void printLog(HttpServletRequest request,Exception e,BaseView baseView){
logger.error(AppUtil.getExceptionDetail(e));
LogEntity logEntity = new LogEntity();
logEntity.setHttpMethod(request.getMethod());
logEntity.setUrl(request.getRequestURL().toString());
logEntity.setIp(request.getRemoteAddr());
logEntity.setArgs(AppUtil.getRequestBody(request));
logEntity.setLogType(AppConstants.LOG_TYPE_HTTP);
logEntity.setReqParams(JsonUtil.objectToJson(request.getParameterMap()));
logEntity.setRespParams(JsonUtil.objectToJson(baseView));
logger.error(">>>"+JsonUtil.objectToJson(logEntity));
}
}
springboot aop + logback + 统一异常处理 打印日志的更多相关文章
- SpringBoot系列——自定义统一异常处理
前言 springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理. 1.使用@ControllerAd ...
- 关于spring 事务 和 AOP 管理事务和打印日志问题
关于spring 事务 和 AOP 管理事务和打印日志问题 1. 就是支持事务注解的(@Transactional) . 可以在server层总使用@Transactional,进行方法内的事务管 ...
- springboot 整合logback(有全套的日志配置文件)
logback介绍:基于Log4j基础上大量改良,不能单独使用,推荐配合日志框架SLF4J来使用,可以和springboot很好的整合,也是springboot默认推荐的. 1.在resources ...
- springboot整合logback集成elk实现日志的汇总、分析、统计和检索功能
在Spring Boot当中,默认使用logback进行log操作.logback支持将日志数据通过提供IP地址.端口号,以Socket的方式远程发送.在Spring Boot中,通常使用logbac ...
- SpringBoot(七)_统一异常处理
我感觉看了这节课,给我的思考还是很多的,感觉受益良多.废话不多说,一起学习. 统一的 外层结构返回 这样利于代码看着也规范,前端处理也统一 # 错误返回 { "code": 1, ...
- SpringBoot | 第二十四章:日志管理之AOP统一日志
前言 上一章节,介绍了目前开发中常见的log4j2及logback日志框架的整合知识.在很多时候,我们在开发一个系统时,不管出于何种考虑,比如是审计要求,或者防抵赖,还是保留操作痕迹的角度,一般都会有 ...
- Spring Boot 2.0 教程 | AOP 切面统一打印请求日志
欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...
- SpringBoot整合Logback日志框架配置全解析
目录 本篇要点 一.Logback日志框架介绍 二.SpringBoot与Logback 1.默认日志格式 2.控制台输出 3.文件输出 4.日志级别 5.日志组 6.自定义log配置 三.logba ...
- SpringBoot统一异常处理后TX-LCN分布式事务无法捕获异常进行回滚
通常我们使用SpringBoot都会进行统一异常处理,例如写一个BaseController,在BaseController里进行统一异常处理,然后其他的Controller都继承BaseContro ...
随机推荐
- 带着萌新看springboot源码07
[修改]很长时间没看这个,有点弄混淆了.bean后置处理器(BeanPostProcessor)应该是在bean创建实例并且赋值好了之后,调用初始化方法(相当于xml配置中<bean init= ...
- 【Python3爬虫】12306爬虫
此次要实现的目标是登录12306网站和查看火车票信息. 具体步骤 一.登录 登录功能是通过使用selenium实现的,用到了超级鹰来识别验证码.没有超级鹰账号的先注册一个账号,充值一点题分,然后把下载 ...
- 【ASP.NET Core快速入门】(六)配置的热更新、配置的框架设计
配置的热更新 什么是热更新:一般来说,我们创建的项目都无法做到热更新:即项目无需重启,修改配置文件后读取到的信息就是修改配置之后的 我们只需要吧项目中用到的IOptions改成IOptionsSnap ...
- so beautiful so white
Topic Link http://ctf5.shiyanbar.com/stega/white.zip 1) 打开压缩包,接着打开图片,发现是白色的但根据提示 压缩包的密码就藏在这幅白色图片中,仔 ...
- Spring拓展接口之FactoryBean,我们来看看其源码实现
前言 开心一刻 那年去相亲,地点在饭店里,威特先上了两杯水,男方绅士的喝了一口,咧嘴咋舌轻放桌面,手抚额头闭眼一脸陶醉,白水硬是喝出了82年拉菲的感觉.如此有生活情调的幽默男人,果断拿下,相处后却发现 ...
- 痞子衡嵌入式:ARM Cortex-M文件那些事(1)- 源文件(.c/.h/.s)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是嵌入式开发里的source文件. 众所周知,嵌入式开发属于偏底层的开发,主要编程语言是C和汇编.所以本文要讲的source文件主要指的就是 ...
- 大前端的自动化工厂(3)—— babel
一. 关于babel babel是ES6+语法的编译器,官方网址:www.babeljs.io,用于将旧版本浏览器无法识别的语法和特性转换成为ES5语法,使代码能够适用更多环境. 最初的babel使用 ...
- 【转】java缩放图片、java裁剪图片代码工具类
一首先看下效果 二工具类 三测试类 在系统的上传图片功能中,我们无法控制用户上传图片的大小,用户可能会上传大到几十M小到1k的的图片,一方面图片太大占据了太多的空间,另一方面,我们没办法在页面上显示统 ...
- Asp.Net路由重写为用户名或者ID
有一个需求如下:指定某个Area的路由(Area:Wx)在其后面添加用户名或者ID作为URL参数,即像下面的样子: /Wx/xiaoming/ /Wx/xiaoming/photo /Wx/xiaom ...
- vue px转换为rem
前端开发中还原设计图的重要性毋庸置疑,目前来说应用最多的应该也还是使用rem.然而很多人依然还是处于刀耕火种的时代,要么自己去计算rem值,要么依靠编辑器安装插件转换. 而本文的目标就是通过一系列的配 ...