通过AOP定义通用的记录日志注解
需求:
(1)实现AOP日记记录
(2)可扩展的日志生成规则模块
(3)可扩展的日记记录模块
(4)打包成springboot-starter
实现AOP日记记录
定义日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
String handle() default "";//可灵活指定使用那一个日记记录器进行处理,须得是springEL表达
String type() default "default";//可灵活指定使用哪一个日志规则进行构建日志
}
定义日志拦截器
@Aspect
@Order(0)
public class CustomLogAspect {
private static final Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);
@Autowired
private ApplicationContext factory;
@Autowired
private List<LogParser> logParsers;
@Autowired
private CustomLogProperties customLogProperties;
/**
* 配置切入点
* 为了通用性这里没有采用args()处理,如果是不考虑通用性的话,建议使用args进行切面,这样可以避免使用反射操作,直接拿到方法中参数的值
* @Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&"args(account,..)")
* private void accountDataAccessOperation(Account account) {}
* https://www.programmersought.com/article/87257200177/
*/
@Pointcut("@annotation(log)")
public void logPointcut(Log log) {
}
@Around("logPointcut(log)")
public Object logAround(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
//构建SpringEL解析器,使用spEL实现从spring环境中获取到注解中指定handle
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(factory));
ExpressionParser parser = new SpelExpressionParser();
//如果注解中指定handle使用指定的handle,否则可以用全局配置
String handle = !StringUtils.isEmpty(log.handle()) ? log.handle() : customLogProperties.getDefaultHandle();
if (StringUtils.isEmpty(handle)) {
logger.info("could not found handle");
} else {
LogHandle logHandle = (LogHandle)parser.parseExpression(handle).getValue(context);
String value = null;
//此处使用迭代器链+spring的@Order注解来实现类似于责任链模式的功能
//LogParser是用于设置各种日志处理规则,配合注解中的type来实现使用那个规则构建日志,下文会有具体实现方法
for (LogParser logParser : logParsers) {
if (logParser.condition(log.type())) {
try {
value = logParser.parse(joinPoint);
} catch (Exception e) {
logger.error("failed by logParser", e.getCause());
}
}
}
logHandle.worker(value);
}
Object result = joinPoint.proceed();
return result;
}
}
可扩展的日志生成规则模块
日记记录的每一个地方使用的日志构建规则可能不一致,比如有部分涉及到HttpServletRequest需要记录访问的URL和IP等信息,而有些地方可以需要全部参数都记录下来,不同的地方构建日志数据的规则不一样,但是他们通用的点是最后出来的结果都是文本
定义抽象类LogParser
public abstract class LogParser {
/**
* 判断是否需要使用此规则进行处理
*/
public abstract boolean condition(String type);
/**
* 构建日志
*/
public abstract String parse(ProceedingJoinPoint proceedingJoinPoint) throws Throwable;
}
实现抽象类LogParser默认的构建规则
/**
* json序列化时,对HttpServletRequest处理时需要自定义JsonSerializer,这里不适合做任何处理
* 所以此模块不适用于HttpServletRequest和HttpServletResponse或是JPA代理类这些对象
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultParser extends LogParser {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
objectMapper.registerModule(javaTimeModule);
}
@Override
public boolean condition(String type) {
if (type.equals("default")) {
return true;
}
return false;
}
@Override
public String parse(ProceedingJoinPoint joinPoint) throws Throwable{
Object[] signatureArgs = joinPoint.getArgs();
String s = objectMapper.writeValueAsString(signatureArgs);
return s;
}
}
实现抽象类LogParser针对含有HttpServletRequest的构建规则
@Order(Ordered.LOWEST_PRECEDENCE)
public class HttpRequestParser extends LogParser {
@Override
public boolean condition(String type) {
if (type.equals("servlet")) {
return true;
}
return false;
}
@Override
public String parse(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
String requestURI = request.getRequestURI();
//可以使用反射来获取其他参数
return requestURI;
}
}
可扩展的日志记录模块
通过上面的日志规则构建的日志存储的地方可能不一致,有些会直接存储在日志文件里面,有些会存入数据库,有些会存入ELK中,为此需要灵活指定日志记录模块,这里可以结合SpringEL表达式,来实现指定对应的处理器
定义LogHandle抽象类
public abstract class LogHandle {
public abstract void worker(String value);
}
实现默认的LogHandle处理器
public class DefaultLogHandle extends LogHandle {
private static final Logger log = LoggerFactory.getLogger(DefaultLogHandle.class);
@Override
public void worker(String value){
log.info("日志:"+value);
}
}
打包成springboot-starter
public class LogInfo {
public static final String PREFIX = "custom.log";
}
从springboot.yml等文件中获取配置信息
@ConfigurationProperties(prefix = LogInfo.PREFIX)
public class CustomLogProperties {
private Boolean enable;
private String defaultHandle;
public String getDefaultHandle() {
return defaultHandle;
}
public void setDefaultHandle(String defaultHandle) {
this.defaultHandle = defaultHandle;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
}
定义config
@Configuration
@ConditionalOnProperty(prefix = LogInfo.PREFIX, name = "enable", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(CustomLogProperties.class)
public class CustomLogConfiguration {
@Autowired
private CustomLogProperties customLogProperties;
@Bean
public CustomLogAspect customLogAspect() {
return new CustomLogAspect();
}
@Bean(value = "defaultLogHandle")
public DefaultLogHandle defaultLogHandle() {
return new DefaultLogHandle();
}
@Bean
public DefaultParser defaultParser() {
return new DefaultParser();
}
@Bean
public HttpRequestParser httpRequestParser() {
return new HttpRequestParser();
}
}
resource下的META-INF编写spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.custom.CustomLogConfiguration
扩展使用
在其他项目中自定义日志构建规则
@Component
@Order(1)
public class DemoParser extends LogParser {
@Override
public boolean condition(String type) {
if (type.equals("demo")) {
return true;
}
return false;
}
@Override
public String parse(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
return "demo";
}
}
在其他项目中自定义日志记录器
@Component
public class DemoLogHandler extends LogHandle {
private static final Logger log = LoggerFactory.getLogger(DemoLogHandler.class);
@Override
public void worker(String value) {
log.info("DemoLogHandler:{}",value);
}
}
调用自定义的日志构建规则和日志记录器
@Log(value = "hello",handle = "@demoLogHandler",type = "demo")
public String hello(String name) {
return name;
}
源码地址:https://gitee.com/ChaiYe/custom-log-starter
原创文章,转载请注明出处:https://www.jianshu.com/p/239ebe61fae9