(1)在resources目录下编写log4j2.xml
(2)编写测试类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log4j2Test {
// Logger和LoggerFactory导入的是org.slf4j包
private final static Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
public static void main(String[] args) {
long beginTime = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
}
try {
Thread.sleep(1000 * 61);
} catch (InterruptedException e) {}
logger.info("请求处理结束,耗时:{}毫秒", (System.currentTimeMillis() - beginTime)); //第一种用法
logger.info("请求处理结束,耗时:" + (System.currentTimeMillis() - beginTime) + "毫秒"); //第二种用法
}
}
(3)启动测试类观察测试结果
使用AOP注解日志后的项目结构
(1)添加pom
parent的pom中的dependencyManagement添加
com.alibaba
fastjson
1.2.4
子pom添加
org.springframework.boot
spring-boot-starter-aop
com.alibaba
fastjson
(2)定义注解
/**
*自定义注解 拦截Controller
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemControllerLog {
String description() default "";
boolean async() default false;
}
/**
*自定义注解 拦截service
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemServiceLog {
String description() default "";
boolean async() default false;
}
(3)定义一个类包含所有需要输出的字段
@Data
public class SystemLogStrategy implements Serializable {
private boolean async;
private String threadId;
private String location;
private String description;
private String className;
private String methodName;
private String arguments;
private String result;
private Long elapsedTime;
public String format() {
return "线程ID: {}, 注解位置: {}, 方法描述: {}, 目标类名: {}, 目标方法: {}, 调用参数: {}, 返回结果: {}, 花费时间: {}";
}
public Object[] args() {
return new Object[]{this.threadId, this.location, this.description, this.className, this.methodName, this.arguments, this.result, this.elapsedTime};
}
}
(4)定义切面
/**
* 切点类
* @author zhangsan
* @since 2019-04-05
* @version 1.0
*/
@Aspect
@Component
public class SystemLogAspect {
private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);
@Pointcut("execution(* com.tmall..*(..)) && !execution(* com.tmall.infrastructure.logging..*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object doInvoke(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
LOG.error(throwable.getMessage(), throwable);
throw new RuntimeException(throwable);
} finally {
long end = System.currentTimeMillis();
long elapsedTime = end - start;
printLog(pjp, result, elapsedTime);
}
return result;
}
/**
* 打印日志
* @param pjp 连接点
* @param result 方法调用返回结果
* @param elapsedTime 方法调用花费时间
*/
private void printLog(ProceedingJoinPoint pjp, Object result, long elapsedTime) {
SystemLogStrategy strategy = getFocus(pjp);
if (null != strategy) {
strategy.setThreadId(ThreadUtils.getThreadId());
strategy.setResult(JsonUtil.toJSONString(result));
strategy.setElapsedTime(elapsedTime);
if (strategy.isAsync()) {
new Thread(()->LOG.info(strategy.format(), strategy.args())).start();
}else {
LOG.info(strategy.format(), strategy.args());
}
}
}
/**
* 获取注解
*/
private SystemLogStrategy getFocus(ProceedingJoinPoint pjp) {
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = pjp.getArgs();
String targetClassName = pjp.getTarget().getClass().getName();
try {
Class> clazz = Class.forName(targetClassName);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName())) {
if (args.length == method.getParameterCount()) {
SystemLogStrategy strategy = new SystemLogStrategy();
strategy.setClassName(className);
strategy.setMethodName(methodName);
SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
if (null != systemControllerLog) {
strategy.setArguments(JsonUtil.toJSONString(args));
strategy.setDescription(systemControllerLog.description());
strategy.setAsync(systemControllerLog.async());
strategy.setLocation(AnnotationTypeEnum.CONTROLLER.getName());
return strategy;
}
SystemServiceLog systemServiceLog = method.getAnnotation(SystemServiceLog.class);
if (null != systemServiceLog) {
strategy.setArguments(JsonUtil.toJSONString(args));
strategy.setDescription(systemServiceLog.description());
strategy.setAsync(systemServiceLog.async());
strategy.setLocation(AnnotationTypeEnum.SERVICE.getName());
return strategy;
}
return null;
}
}
}
} catch (ClassNotFoundException e) {
LOG.error(e.getMessage(), e);
}
return null;
}
}
(5)切面涉及的工具类
public class ThreadUtils {
private static final ThreadLocal threadLocal = new ThreadLocal<>();
public static String getThreadId() {
String threadId = threadLocal.get();
if (null == threadId) {
threadId = UUID.randomUUID().toString();
threadLocal.set(threadId);
}
return threadId;
}
}
public class JsonUtil {
public static String toJSONString(Object object) {
return JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);
}
}
(6)测试AOP注解日志是否集成成功
启动Spring Boot后访问这两个请求无注解请求没有日志输出,有注解的请求显示结果为
15:16:42.498 [http-nio-8082-exec-1] INFO com.tmall.infrastructure.logging.SystemLogAspect - 线程ID: c108cde7-33e1-44ab-a5d0-5d7b8297779f, 注解位置: SystemControllerLog, 方法描述: test, 目标类名: com.tmall.ui.controller.Demo, 目标方法: index, 调用参数: [{"name":"FreeMarker 模版引擎 "}], 返回结果: "test", 花费时间: 1
(7)多环境配置文件
①将原log4j2.xml更名为log4j2-dev.xml
②编写不同环境配置文件
#application-dev.properties
logging.config=classpath:log4j2-dev.xml
#application-prod.properties
logging.config=classpath:logback-prod.xml
public class LogUtils {
//增加日志连接符
private static void addLogItem(StringBuilder sb, String key, String value) {
sb.append(key);
sb.append("=");
sb.append("\"");
sb.append(value);
sb.append("\"");
sb.append(" ");
}
//用于组装日志信息
private static String getBatchLogMessage(String logLevel, String className, String methodName,String resultString) {
StringBuilder sb = new StringBuilder(); //用于存放一条日志信息
Map logItemMap = new LinkedHashMap(); //用于存一条日志信息key-value的映射
logItemMap.put("time",""+new Timestamp(System.currentTimeMillis()));
logItemMap.put("priority", logLevel);
logItemMap.put("class", className);
logItemMap.put("method", methodName);
logItemMap.put("result_string", resultString);
for (String key : logItemMap.keySet()) {
LogUtils.addLogItem(sb, key, logItemMap.get(key));
}
return sb.toString();
}
//用于组装输出日志的信息
public static void writeLog(Logger logger, String logLevel, String log) {
if ("INFO".equals(logLevel)) {
logger.info(log);
}
else if ("ERROR".equals(logLevel)) {
logger.error(log);
}
else if ("WARN".equals(logLevel)) {
logger.warn(log);
}
else if ("DEBUG".equals(logLevel)) {
logger.debug(log);
}
}
public static void writeExceptionLog(Logger logger, Class clazz, String methodName, Exception e) {
String log = getBatchLogMessage("ERROR", clazz.getName(), methodName,e.getMessage());
writeLog(logger, "ERROR", log);
}
public static void writeInfoLog(Logger logger, Class clazz, String methodName, String infoMessage) {
String log = getBatchLogMessage("INFO", clazz.getName(), methodName, infoMessage);
writeLog(logger, "INFO", log);
}
public static void writeDebugLog(Logger logger, Class clazz, String methodName, String infoMessage) {
String log = getBatchLogMessage("DEBUG", clazz.getName(), methodName, infoMessage);
writeLog(logger, "DEBUG", log);
}
public static void writeWarnLog(Logger logger, Class clazz, String methodName, String warnMessage) {
String log = getBatchLogMessage("WARN", clazz.getName(), methodName, warnMessage);
writeLog(logger, "WARN", log);
}
public static void writeErrorLog(Logger logger, Class clazz, String methodName, String errorMessage) {
String log = getBatchLogMessage("ERROR", clazz.getName(), methodName, errorMessage);
writeLog(logger, "ERROR", log);
}
}
/**
* 用于LOG的显示。
*
*/
public class LogConstants {
public static final String LEVEL_INFO = "INFO";
public static final String LEVEL_WARN = "WARN";
public static final String LEVEL_ERROR = "ERROR";
public static final String LEVEL_DEBUG = "DEBUG";
}
@Data
@NoArgsConstructor
public class ResultBean implements Serializable {
private static final long serialVersionUID = 1L;
public static final int NO_LOGIN = -1;
public static final int SUCCESS = 1;
public static final int FAIL = 0;
public static final int NO_PERMISSION = 2;
public static final int USERNAME_EXIST = -909;
private String msg = "success";
private int code = SUCCESS;
private T data;
public ResultBean(T data) {
super();
this.data = data;
}
public ResultBean(Throwable e) {
super();
this.msg = e.toString();
this.code = FAIL;
}
public ResultBean setMsg(String msg) {
this.msg = msg;
return this;
}
public ResultBean setCode(int code) {
this.code = code;
return this;
}
public ResultBean setData(T data) {
this.data = data;
return this;
}
}
@Getter
public class PageResultBean extends ResultBean implements Serializable {
// 总记录数
private long totalRecord;
//总页数
private int pageCount;
//当前页码
private int pageNo;
//当前页的记录数量
private int pageSize;
public PageResultBean(PageInfo pageInfo) {
super.setData((T) pageInfo.getList());
this.setPageNo(pageInfo.getPageNum())
.setPageSize(pageInfo.getPageSize())
.setPageCount(pageInfo.getPages())
.setTotalRecord(pageInfo.getTotal());
}
public PageResultBean setTotalRecord(long totalRecord) {
this.totalRecord = totalRecord;
return this;
}
public PageResultBean setPageCount(int pageCount) {
this.pageCount = pageCount;
return this;
}
public PageResultBean setPageNo(int pageNo) {
this.pageNo = pageNo;
return this;
}
public PageResultBean setPageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}
@Override
public String toString() {
return "PageResultBean{" +
"totalRecord=" + totalRecord +
", pageCount=" + pageCount +
", pageNo=" + pageNo +
", pageSize=" + pageSize +
'}';
}
@Override
public PageResultBean setMsg(String msg) {
super.setMsg(msg);
return this;
}
@Override
public PageResultBean setCode(int code) {
super.setCode(code);
return this;
}
@Override
public PageResultBean setData(T data) {
super.setData(data);
return this;
}
}
(1)创建自定义异常
根据需求创建一定数量的自定义异常
public class QueryException extends RuntimeException{
// 错误码
private Integer code;
public QueryException(String message){
super(message);
}
public QueryException(Integer code, String message){
super(message);
this.code = code;
}
public Integer getCode(){
return code;
}
public void setCode(Integer code){
this.code = code;
}
}
(2)创建结果枚举信息类
该类用于定义结果的返回代码、异常处理种类信息以及提示级错误的代码和错误信息。
public enum ResultEnum{
SUCCESS(200,"成功"),
/**系统异常. ErrorCode : -1 */
SYSTEM_EXCEPTION(-1,"系统异常"),
/**未知异常. ErrorCode : 01*/
UN_KNOWN_EXCEPTION(01,"未知异常"),
/** 服务异常. ErrorCode : 02 */
SERVICE_EXCEPTION(02,"服务异常"),
/**查询异常. ErrorCode : 03*/
QUERY_EXCEPTION(03,"查询异常"),
/**数据库操作异常. ErrorCode : 05 */
DB_EXCEPTION(05,"数据库操作异常"),
/**参数验证错误. ErrorCode : 06*/
PARAM_EXCEPTION(06,"参数验证错误"),
/**权限验证错误. ErrorCode : 101*/
NO_LOGIN(101,"未登录"),
/**权限验证错误. ErrorCode : 101*/
NO_PERMISSION(102,"无权限"),
USERNAME_EXIST(103,"用户已存在");
private Integer code;
private String msg;
ResultEnum(Integer code,String msg){
this.code = code;
this.msg = msg;
}
public Integer getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
(3)创建结果组装器
public class ResultAssembler {
/** 操作成功的处理流程 */
public static ResultBean getSuccess(Object object){
ResultBean ResultBean = new ResultBean();
//设置操作成功的返回码
ResultBean.setCode(ResultConstants.RESULT_CODE_SUCCESS);
//设置操作成功的消息
ResultBean.setMsg(ResultConstants.RESULT_MESSAGE_SUCCESS);
ResultBean.setData(object);
return ResultBean;
}
/** 重载返回成功的方法,因为有时候我们不需要任何的消息数据被返回*/
public static ResultBean getSuccess(){
return getSuccess(null);
}
/**
* 操作失败的处理流程
* @param code 错误码
* @param msg 错误消息
* @param o 错误数据
* @return
*/
public static ResultBean getError(Integer code, String msg, Object o){
ResultBean ResultBean = new ResultBean();
ResultBean.setCode(code);
ResultBean.setMsg(msg);
ResultBean.setData(o);
return ResultBean;
}
/**
* 重载,操作失败的方法(因为操作失败一般都不需要返回数据内容)
* @param code
* @param msg
* @return
*/
public static ResultBean getError(Integer code,String msg){
return getError(code, msg, null);
}
}
(4)定义异常统一处理类
@ControllerAdvice
public class ExceptionHandle {
//增加异常日志打印
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
//设置异常错误的页面
public static final String DEFAULT_ERROR_VIEW_404 = "error404";
public static final String DEFAULT_ERROR_VIEW_500 = "error500";
@ExceptionHandler(value = QueryException.class)
@ResponseBody
public Object QueryExceptionHandler(HttpServletRequest req, QueryException e) throws Exception {
logger.error("【查询异常】={}",e);
return ResultAssembler.getError(ResultEnum.QUERY_EXCEPTION.getCode(),ResultEnum.QUERY_EXCEPTION.getMsg());
}
@ExceptionHandler(value = ServiceException.class)
@ResponseBody
public Object ServiceExceptionHandler(HttpServletRequest req, ServiceException e) throws Exception {
logger.error("【服务异常】={}",e);
return ResultAssembler.getError(ResultEnum.SERVICE_EXCEPTION.getCode(),ResultEnum.SERVICE_EXCEPTION.getMsg());
}
@ExceptionHandler(value = DBException.class)
@ResponseBody
public Object DBExceptionHandler(HttpServletRequest req, DBException e) throws Exception {
logger.error("【数据库异常】={}",e);
return ResultAssembler.getError(ResultEnum.DB_EXCEPTION.getCode(),ResultEnum.DB_EXCEPTION.getMsg());
}
@ExceptionHandler(value = UnKnownException.class)
@ResponseBody
public Object UnKnownExceptionHandler(HttpServletRequest req, UnKnownException e) throws Exception {
logger.error("【未知异常】={}",e);
return ResultAssembler.getError(ResultEnum.UN_KNOWN_EXCEPTION.getCode(),ResultEnum.UN_KNOWN_EXCEPTION.getMsg());
}
}
(5)最终代码结构图
(6)测试统一异常处理
(1)建立404异常处理类
@Controller
public class NoFoundExceptionHandler implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping(value = "/error")
public String error(HttpServletResponse resp, HttpServletRequest req) {
// 错误处理逻辑
return "404error";
}
}
(2)定义404error.html页面
404error
(3)测试