Spring AOP实现统一日志输出
目的:
统一日志输出格式
思路:
1、针对不同的调用场景定义不同的注解,目前想的是接口层和服务层。
2、我设想的接口层和服务层的区别在于:
(1)接口层可以打印客户端IP,而服务层不需要
(2)接口层的异常需要统一处理并返回,而服务层的异常只需要向上抛出即可
3、就像Spring中的@Controller、@Service、@Repository注解那样,虽然作用是一样的,但是不同的注解用在不同的地方显得很清晰,层次感一下就出来了
4、AOP去拦截特定注解的方法调用
5、为了简化使用者的操作,采用Spring Boot自动配置
1. 注解定义
package com.cjs.example.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemControllerLog { String description() default ""; boolean async() default false; }
package com.cjs.example.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemServiceLog { String description() default ""; boolean async() default false; }
2. 定义一个类包含所有需要输出的字段
package com.cjs.example.service; import lombok.Data;
import java.io.Serializable; @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};
} }
3. 定义切面
package com.cjs.example.aspect; import com.alibaba.fastjson.JSON;
import com.cjs.example.annotation.SystemControllerLog;
import com.cjs.example.annotation.SystemRpcLog;
import com.cjs.example.annotation.SystemServiceLog;
import com.cjs.example.enums.AnnotationTypeEnum;
import com.cjs.example.service.SystemLogStrategy;
import com.cjs.example.util.JsonUtil;
import com.cjs.example.util.ThreadUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.lang.reflect.Method; @Aspect
public class SystemLogAspect { private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class); private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class); @Pointcut("execution(* com.ourhours..*(..)) && !execution(* com.ourhours.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(ThreadUtil.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;
} }
4. 配置
PS:
这里也可以用组件扫描,执行在Aspect上加@Component注解即可,但是这样的话有个问题。
就是,如果你的这个Aspect所在包不是Spring Boot启动类所在的包或者子包下就需要指定@ComponentScan,因为Spring Boot默认只扫描和启动类同一级或者下一级包。
package com.cjs.example.config; import com.cjs.example.aspect.SystemLogAspect;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration
@AutoConfigureOrder(2147483647)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnClass(SystemLogAspect.class)
@ConditionalOnMissingBean(SystemLogAspect.class)
public class SystemLogAutoConfiguration { @Bean
public SystemLogAspect systemLogAspect() {
return new SystemLogAspect();
}
}
5. 自动配置(resources/META-INF/spring.factories)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ourhours.logging.config.SystemLogAutoConfiguration
6. 其它工具类
6.1. 获取客户端IP
package com.cjs.example.util; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; public class HttpContextUtils { public static HttpServletRequest getHttpServletRequest() {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return servletRequestAttributes.getRequest();
} public static String getIpAddress() {
HttpServletRequest request = getHttpServletRequest();
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
}else if (ip != null && ip.length() > 15) {
String[] ips = ip.split(",");
for (int index = 0; index < ips.length; index++) {
String strIp = (String) ips[index];
if (!("unknown".equalsIgnoreCase(strIp))) {
ip = strIp;
break;
}
}
}
return ip;
}
}
6.2. 格式化成JSON字符串
package com.cjs.example.util; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature; public class JsonUtil { public static String toJSONString(Object object) {
return JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);
} }
6.3. 存取线程ID
package com.cjs.example.util; import java.util.UUID; public class ThreadUtil { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static String getThreadId() {
String threadId = threadLocal.get();
if (null == threadId) {
threadId = UUID.randomUUID().toString();
threadLocal.set(threadId);
}
return threadId;
} }
7. 同时还提供静态方法
package com.cjs.example; import com.cjs.example.util.JsonUtil;
import com.cjs.example.util.ThreadUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class Log { private static Logger LOGGER = null; private static class SingletonHolder{
public static Log instance = new Log();
} private Log(){} public static Log getInstance(Class<?> clazz){
LOGGER = LoggerFactory.getLogger(clazz);
return SingletonHolder.instance;
} public void info(String description, Object args, Object result) {
LOGGER.info("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", ThreadUtil.getThreadId(), description, JsonUtil.toJSONString(args), JsonUtil.toJSONString(result));
} public void error(String description, Object args, Object result, Throwable t) {
LOGGER.error("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", ThreadUtil.getThreadId(), description, JsonUtil.toJSONString(args), JsonUtil.toJSONString(result), t);
} }
8. 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.cjs.example</groupId>
<artifactId>cjs-logging</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>cjs-logging</name> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<aspectj.version>1.8.13</aspectj.version>
<servlet.version>4.0.0</servlet.version>
<slf4j.version>1.7.25</slf4j.version>
<fastjson.version>1.2.47</fastjson.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
<optional>true</optional>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build> </project>
8. 工程结构
Spring AOP实现统一日志输出的更多相关文章
- Spring MVC 中使用AOP 进行统一日志管理--XML配置实现
1.介绍 上一篇博客写了使用AOP进行统一日志管理的注解版实现,今天写一下使用XML配置实现版本,与上篇不同的是上次我们记录的Controller层日志,这次我们记录的是Service层的日志.使用的 ...
- Logback 整合 RabbitMQ 实现统一日志输出
原文地址:Logback 整合 RabbitMQ 实现统一日志输出 博客地址:http://www.extlight.com 一.前言 公司项目做了集群实现请求分流,由于线上或多或少会出现请求失败或系 ...
- AOP Aspect 统一日志、异常处理、数据格式 【转】
package com.gsww.chis.aop; import java.util.Arrays; import com.google.common.base.Throwables; import ...
- Spring aop 记录操作日志 Aspect
前几天做系统日志记录的功能,一个操作调一次记录方法,每次还得去收集参数等等,太尼玛烦了.在程序员的世界里,当你的一个功能重复出现多次,就应该想想肯定有更简单的实现方法.于是果断搜索各种资料,终于搞定了 ...
- Spring MVC 中使用AOP 进行统一日志管理--注解实现
1.AOP简介 AOP称为面向切面编程 AOP的基本概念 (1)Aspect(切面):通常是一个类,里面可以定义切入点和通知 (2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的 ...
- 利用spring AOP 实现统一校验
开发环境 JDK: 1.7 spring: 4.0.6 aspect: 1.7.4 应用背景 在APP与后台通讯的过程中,我们一般都会有个authToken的字符串校验,判断那些请求是需要校验用户 ...
- spring aop实现log 日志跟踪
之前写的那篇是基于springboot的(https://www.cnblogs.com/yaoyuan2/p/10302802.html),由于遗留项目用的是spring,因此需要在spring基础 ...
- 使用spring aop 记录接口日志
spring配置文件中增加启用aop的配置 <!-- 增加aop 自动代理配置 --> <aop:aspectj-autoproxy /> 切面类配置 package com. ...
- [编码实践]SpringBoot实战:利用Spring AOP实现操作日志审计管理
设计原则和思路: 元注解方式结合AOP,灵活记录操作日志 能够记录详细错误日志为运营以及审计提供支持 日志记录尽可能减少性能影响 操作描述参数支持动态获取,其他参数自动记录. 1.定义日志记录元注解, ...
随机推荐
- TortoiseSVN--clearup清理失败解决办法
工作中经常遇到update.commit 失败导致冲突问题,需要用clear up来清除问题,个别异常情况导致clear up失败,进入死循环!可以使用sqlite3.exe清理一下wc.db文件的队 ...
- JAVA基础复习与总结<五> String类_File类_Date类
String类 .Java字符串就是Unicode字符序列,例如串“Java”就是4个Unicoe字符组成. .Java没有内置的字符串类型,而是在标准java类库中提供了一个预定义的类String, ...
- MyBatis3系列__04CRUD以及参数处理
本文将会简单介绍一下MyBatis的CRUD以及结合源码讲解一下MyBatis对参数的处理. 作为一个ORM框架,最基本的使用也就是CRUD了,MyBatis提供了两种方法:xml配置文件和动态注解. ...
- Java EE 导图
- 4.24Linux(4)
2019-4-24 21:35:13 学完了Linux装python编译安装感觉有种控制电脑的感觉!感觉好爽!!!! 主要是Linux用习惯就感觉好爽!!! 越努力,越幸运!永远不要高估自己!! 等学 ...
- css知识总结
---# 学习目标:> 1. 学会使用CSS选择器> 2. 熟记CSS样式和外观属性> 3. 熟练掌握CSS各种选择器> 4. 熟练掌握CSS各种选择器> 5. 熟练掌握 ...
- Servlet 自定义标签
自定义标签 1)用户定义的一种jsp标记,当一个含有自定义标签的jsp页面被jsp引擎编译成servlet时,tag标签被转化成了对一个称为 标签处理类 的对象的操作.于是,当jsp页面被jsp引擎转 ...
- elasticsearch 占CPU过高
一.线上有一台服务器cpu一直跑满,最终定位导是elasticsearch导致的 二.通过一波查找更改jvm和删除 修改后没有生效笔记尴尬 然后网友说删除索引试了试就可以了 哈哈 curl http ...
- JS基础整理
使用JS的三种方式 1.直接在html标签中,使用事件属性,调用js代码 <button onclick="alert('弹框')">弹框!</button> ...
- [python] 溜了,溜了,七牛云图片资源批量下载 && 自建图床服务器
故事背景: 七牛云最近一波测试域名操作真是把我坑死了!这简直和百度赠送你2T网盘,之后再限速一样骚操作.于是,痛定思痛自己买个云主机.自己搭图床应用! 1.七牛图片批量下载到本地 1.1 曲折尝试 当 ...