目的:
统一日志输出格式
思路:
1、针对不同的调用场景定义不同的注解,目前想的是接口层和服务层。
2、我设想的接口层和服务层的区别在于:
(1)接口层可以打印客户端IP,而服务层不需要
(2)接口层的异常需要统一处理并返回,而服务层的异常只需要向上抛出即可
3、就像Spring中的@Controller、@Service、@Repository注解那样,虽然作用是一样的,但是不同的注解用在不同的地方显得很清晰,层次感一下就出来了
5、为了简化使用者的操作,采用Spring Boot自动配置
1. 注解定义
@H_404_49@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;
}
2. 定义一个类包含所有需要输出的字段
@H_404_49@ 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. 定义切面
@H_404_49@ 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默认只扫描和启动类同一级或者下一级包。
@H_404_49@ 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)
@H_404_49@org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ourhours.logging.config.SystemLogAutoConfiguration
6. 其它工具类
@H_301_413@6.1. 获取客户端IP
@H_404_49@ 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;
}
}
@H_301_413@6.2. 格式化成JSON字符串
@H_404_49@ com.alibaba.fastjson.serializer.SerializerFeature;
JsonUtil {
String toJSONString(Object object) {
JSON.toJSONString(object,SerializerFeature.DisableCircularReferenceDetect);
}
}
@H_301_413@6.3. 存取线程ID
@H_404_49@ 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. 同时还提供静态方法
@H_404_49@ 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
@H_404_49@<?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. 工程结构