最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

自定义 Spring 通用日志注解

来源:博客园


(资料图片)

目录
  • 自定义 Spring 通用日志注解
    • 1. 注解@Metrics
    • 2. 切面MetricsAspect
    • 3. 自动注入AutoConfiguration
    • 4. 配置文件MetricsProperties
    • 5. 其它配置
      • 配置自动注入
      • 配置文件提示

自定义 Spring 通用日志注解

1. 注解@Metrics

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface Metrics {    /**     * 在方法成功执行后打点,记录方法的执行时间发送到指标系统,默认开启     */    boolean recordSuccessMetrics() default true;    /**     * 在方法成功失败后打点,记录方法的执行时间发送到指标系统,默认开启     */    boolean recordFailMetrics() default true;    /**     * 通过日志记录请求参数,默认开启     */    boolean logParameters() default true;    /**     * 通过日志记录方法返回值,默认开启     */    boolean logReturn() default true;    /**     * 出现异常后通过日志记录异常信息,默认开启     */    boolean logException() default true;    /**     * 出现异常后忽略异常返回默认值,默认关闭     */    boolean ignoreException() default false;

2. 切面MetricsAspect

@Aspect@Slf4j@Order(Ordered.HIGHEST_PRECEDENCE)public class MetricsAspect {    /**     * 让Spring帮我们注入ObjectMapper,以方便通过JSON序列化来记录方法入参和出参     */    @Resource    private ObjectMapper objectMapper;    /**     * 实现一个返回Java基本类型默认值的工具。其实,你也可以逐一写很多if-else判断类型,然后手动设置其默认值。     * 这里为了减少代码量用了一个小技巧,即通过初始化一个具有1个元素的数组,然后通过获取这个数组的值来获取基本类型默认值     */    private static final Map, Object> DEFAULT_VALUES = Stream            .of(boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, short.class)            .collect(toMap(clazz -> clazz, clazz -> Array.get(Array.newInstance(clazz, 1), 0)));    public static  T getDefaultValue(Class clazz) {        //noinspection unchecked        return (T) DEFAULT_VALUES.get(clazz);    }    /**     * 标记了Metrics注解的方法进行匹配     */    @Pointcut("@annotation(com.common.config.metrics.annotation.Metrics)")    public void withMetricsAnnotationMethod() {    }    /**     * within指示器实现了匹配那些类型上标记了@RestController注解的方法     * 注意这里使用了@,标识了对注解标注的目标进行切入     */    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")    public void controllerBean() {    }    @Pointcut("@within(com.common.config.metrics.annotation.Metrics)")    public void withMetricsAnnotationClass() {    }    @Around("controllerBean() || withMetricsAnnotationMethod() || withMetricsAnnotationClass()")    public Object metrics(ProceedingJoinPoint pjp) throws Throwable {        // 通过连接点获取方法签名和方法上Metrics注解,并根据方法签名生成日志中要输出的方法定义描述        MethodSignature signature = (MethodSignature) pjp.getSignature();        Metrics metrics = signature.getMethod().getAnnotation(Metrics.class);        String name = String.format("【%s】【%s】", signature.getDeclaringType().toString(), signature.toLongString());        if (metrics == null) {            @Metrics            final class InnerClass {            }            metrics = InnerClass.class.getAnnotation(Metrics.class);        }        // 尝试从请求上下文获得请求URL        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();        if (requestAttributes != null) {            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();            name += String.format("【%s】", request.getRequestURL().toString());        }        // 入参的日志输出        if (metrics.logParameters()) {            log.info(String.format("【入参日志】调用 %s 的参数是:【%s】", name, objectMapper.writeValueAsString(pjp.getArgs())));        }        // 连接点方法的执行,以及成功失败的打点,出现异常的时候记录日志        Object returnValue;        Instant start = Instant.now();        try {            returnValue = pjp.proceed();            if (metrics.recordSuccessMetrics()) {                // 在生产级代码中,应考虑使用类似Micrometer的指标框架,把打点信息记录到时间序列数据库中,实现通过图表来查看方法的调用次数和执行时间,                log.info(String.format("【成功打点】调用 %s 成功,耗时:%d ms", name, Duration.between(start, Instant.now()).toMillis()));            }        } catch (Exception ex) {            if (metrics.recordFailMetrics()) {                log.info(String.format("【失败打点】调用 %s 失败,耗时:%d ms", name, Duration.between(start, Instant.now()).toMillis()));            }            if (metrics.logException()) {                log.error(String.format("【异常日志】调用 %s 出现异常!", name), ex);            }            if (metrics.ignoreException()) {                returnValue = getDefaultValue(signature.getReturnType());            } else {                throw ex;            }        }        // 返回值输出        if (metrics.logReturn()) {            log.info(String.format("【出参日志】调用 %s 的返回是:【%s】", name, returnValue));        }        return returnValue;    }

3. 自动注入AutoConfiguration

@AutoConfiguration@Slf4j@EnableConfigurationProperties(MetricsProperties.class)@ConditionalOnProperty(prefix = "common.metrics", name = {"keep-alive"}, havingValue = "true", matchIfMissing = true)public class AspectAutoConfiguration {    public AspectAutoConfiguration() {        log.info("AspectAutoConfiguration initialize.");    }    @Bean    public MetricsAspect metricsAspect() {        return new MetricsAspect();    }}

4. 配置文件MetricsProperties

@ConfigurationProperties(prefix = "common.metrics")public class MetricsProperties {    public Boolean getKeepAlive() {        return keepAlive;    }    public void setKeepAlive(Boolean keepAlive) {        this.keepAlive = keepAlive;    }    private Boolean keepAlive = true;}

5. 其它配置

配置自动注入

配置resource.META-INF.spring.org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,增加RunsOnProfilesAutoConfiguration类路径。

配置文件提示

{  "groups": [],  "properties": [    {      "name": "common.metrics.keepAlive",      "type": "java.lang.Boolean",      "sourceType": "com.common.config.metrics.properties.MetricsProperties"    }  ]}

关键词: