目的:
统一日志输出格式
思路:
1、针对不同的调用场景定义不同的注解,目前想的是接口层和服务层。
2、我设想的接口层和服务层的区别在于:
(1)接口层可以打印客户端IP,而服务层不需要
(2)接口层的异常需要统一处理并返回,而服务层的异常只需要向上抛出即可
3、就像Spring中的@Controller、@Service、@Repository注解那样,虽然作用是一样的,但是不同的注解用在不同的地方显得很清晰,层次感一下就出来了
5、为了简化使用者的操作,采用Spring Boot自动配置
1. 注解定义
package com.cjs.example.annotation; import java.lang.annotation.ElementType; java.lang.annotation.Retention; java.lang.annotation.RetentionPolicy; java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface SystemControllerLog { String description() default ""; boolean async() default false; }
SystemServiceLog { String description() ; }
2. 定义一个类包含所有需要输出的字段
com.cjs.example.service; lombok.Data; java.io.Serializable; @Data public class SystemLogStrategy implements Serializable { private boolean async; private String threadId; String location; String description; String className; String methodName; String arguments; String result; Long elapsedTime; public String format() { return "线程ID: {},注解位置: {},方法描述: {},目标类名: {},目标方法: {},调用参数: {},返回结果: {},花费时间: {}"; } Object[] args() { return new Object[]{this.threadId,this.location,1)">this.description,1)">this.className,1)">this.methodName,1)">this.arguments,1)">this.result,1)">this.elapsedTime}; } }
3. 定义切面
com.cjs.example.aspect; com.alibaba.fastjson.JSON; com.cjs.example.annotation.SystemControllerLog; com.cjs.example.annotation.SystemRpcLog; com.cjs.example.annotation.SystemServiceLog; com.cjs.example.enums.AnnotationTypeEnum; com.cjs.example.service.SystemLogStrategy; com.cjs.example.util.JsonUtil; com.cjs.example.util.ThreadUtil; org.aspectj.lang.ProceedingJoinPoint; org.aspectj.lang.Signature; org.aspectj.lang.annotation.Around; org.aspectj.lang.annotation.Aspect; org.aspectj.lang.annotation.Pointcut; org.slf4j.Logger; org.slf4j.LoggerFactory; java.lang.reflect.Method; @Aspect class SystemLogAspect { static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.); ); @Pointcut("execution(* com.ourhours..*(..)) && !execution(* com.ourhours.logging..*(..))") void pointcut() { } @Around("pointcut()" 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 连接点 * result 方法调用返回结果 * elapsedTime 方法调用花费时间 */ void printLog(ProceedingJoinPoint pjp,Object result,1)">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()); } } } * 获取注解 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(); { Class<?> clazz = Class.forName(targetClassName); Method[] methods = clazz.getMethods(); for (Method method : methods) { (methodName.equals(method.getName())) { if (args.length == method.getParameterCount()) { SystemLogStrategy strategy = SystemLogStrategy(); strategy.setClassName(className); strategy.setMethodName(methodName); SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.); systemControllerLog) { strategy.setArguments(JsonUtil.toJSONString(args)); strategy.setDescription(systemControllerLog.description()); strategy.setAsync(systemControllerLog.async()); strategy.setLocation(AnnotationTypeEnum.CONTROLLER.getName()); strategy; } SystemServiceLog systemServiceLog = method.getAnnotation(SystemServiceLog. systemServiceLog) { strategy.setArguments(JsonUtil.toJSONString(args)); strategy.setDescription(systemServiceLog.description()); strategy.setAsync(systemServiceLog.async()); strategy.setLocation(AnnotationTypeEnum.SERVICE.getName()); strategy; } ; } } } } (ClassNotFoundException e) { LOG.error(e.getMessage(),e); } ; } }
4. 配置
PS:
这里也可以用组件扫描,执行在Aspect上加@Component注解即可,但是这样的话有个问题。
就是,如果你的这个Aspect所在包不是Spring Boot启动类所在的包或者子包下就需要指定@ComponentScan,因为Spring Boot默认只扫描和启动类同一级或者下一级包。
com.cjs.example.config; com.cjs.example.aspect.SystemLogAspect; org.springframework.boot.autoconfigure.AutoConfigureOrder; org.springframework.boot.autoconfigure.condition.ConditionalOnClass; org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; org.springframework.context.annotation.Bean; org.springframework.context.annotation.Configuration; org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @AutoConfigureOrder(2147483647) @EnableAspectJAutoProxy(proxyTargetClass = true) @ConditionalOnClass(SystemLogAspect.) @ConditionalOnMissingBean(SystemLogAspect.) SystemLogAutoConfiguration { @Bean SystemLogAspect systemLogAspect() { SystemLogAspect(); } }
5. 自动配置(resources/Meta-INF/spring.factories)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ourhours.logging.config.SystemLogAutoConfiguration
6. 其它工具类
6.1. 获取客户端IP
com.cjs.example.util; org.springframework.web.context.request.RequestContextHolder; org.springframework.web.context.request.ServletRequestAttributes; javax.servlet.http.HttpServletRequest; HttpContextUtils { static HttpServletRequest getHttpServletRequest() { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); servletRequestAttributes.getRequest(); } String getIpAddress() { HttpServletRequest request = getHttpServletRequest(); String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { .equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } .equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR".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; } } } ip; } }
6.2. 格式化成JSON字符串
com.alibaba.fastjson.serializer.SerializerFeature; JsonUtil { String toJSONString(Object object) { JSON.toJSONString(object,SerializerFeature.DisableCircularReferenceDetect); } }
6.3. 存取线程ID
java.util.UUID; ThreadUtil { final ThreadLocal<String> threadLocal = new ThreadLocal<>(); String getThreadId() { String threadId = threadLocal.get(); null == threadId) { threadId = UUID.randomUUID().toString(); threadLocal.set(threadId); } threadId; } }
7. 同时还提供静态方法
com.cjs.example; Log { static Logger LOGGER = SingletonHolder{ static Log instance = Log(); } Log(){} static Log getInstance(Class<?> clazz){ LOGGER = LoggerFactory.getLogger(clazz); SingletonHolder.instance; } info(String description,Object args,Object result) { LOGGER.info("线程ID: {},返回结果: {}",ThreadUtil.getThreadId(),description,JsonUtil.toJSONString(args),JsonUtil.toJSONString(result)); } error(String description,Throwable t) { LOGGER.error("线程ID: {},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</> groupId>com.cjs.exampleartifactId>cjs-loggingversion>0.0.1-SNAPSHOTpackaging>jarnameparent> >org.springframework.boot>spring-boot-starter-parent>2.0.2.RELEASErelativePath/> <!-- lookup parent from repository --> propertiesproject.build.sourceEncoding>UTF-8project.reporting.outputEncodingjava.version>1.8aspectj.version>1.8.13servlet.versionslf4j.version>1.7.25fastjson.version>1.2.47dependenciesdependency> >spring-boot-autoconfigureoptional>true>org.springframework>spring-web>javax.servlet>javax.servlet-api>${servlet.version}scope>provided>org.aspectj>aspectjweaver>${aspectj.version}>org.slf4j>slf4j-api>${slf4j.version}>com.alibaba>fastjson>${fastjson.version}buildpluginsplugin> >org.apache.maven.plugins>maven-compiler-plugin>3.7.0configuration> sourcetargetencoding>UTF8>maven-source-pluginexecutionsexecution> id>attach-sourcesgoals> goal> project>
8. 工程结构
原文链接:https://www.f2er.com/spring/882842.html