SpringBoot 2.x 自定义注解annotation实现MicroMeter埋点
介绍
Micrometer 为 Java 平台上的性能数据收集提供了一个通用的 API,应用程序只需要使用 Micrometer 的通用 API 来收集性能指标即可。Micrometer 会负责完成与不同监控系统的适配工作。这就使得切换监控系统变得很容易。Micrometer 还支持推送数据到多个不同的监控系统。
在Springboot 2.X中,除了通过业务代码的方式埋点,官方也提供了@Timed,@Counted注解实现。
遗憾的是,官方对于自定义的Tag只做了简单的适配,不能满足我们一些自定义的指标需求。为了满足项目需求,所以我们需要自定义注解来实现MicroMeter的埋点。
依赖
除了基础的springboot依赖以外,我们还需要引入aop的依赖和actuator。actuator默认是依赖了micrometer,并通过micrometer对外提供endpoint。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>
实践
这里我们以micrometer常见的Timer指标为例,自定义@Timed注解
自定义注解
为了更好的拓展官方定义的指标,直接沿用了一些官方的定义。针对Tag指标,我们做了单独的处理。
@Timed
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Timed {String value() default "";boolean longTask() default false;double[] percentiles() default {};boolean histogram() default false;String description() default "";
}
@Tag
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = Tags.class)
public @interface Tag {String key();String value();
}
@Tags
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Tags {Tag[] value();
}
类和接口
为了能支持自定义tag的解析,因此我们需要定义一个tag解析器。为了能更好的拓展,所以我们需要先定义一个接口
TagParser
public interface TagParser {/*** 获取tag解析器类型* @return*/String getType();/*** tag解析* @param tagDefinition tag定义* @param args 参数,包含:入参,返回值* @return* @throws JsonProcessingException*/Tag parse(TagDefinition tagDefinition, Object[] args) throws JsonProcessingException;
}
AbstractDefaultTagParser
AbstractDefaultTagParser存放公共的属性和方法
public abstract class AbstractDefaultTagParser implements TagParser {final static String POINT = ".";final static String REGEX_POINT = "\\.";final static String DEFAULT_TAG_VALUE = "None";@Overridepublic String getType() {return null;}@Overridepublic Tag parse(TagDefinition tagDefinition,Object[] args) throws JsonProcessingException {return null;}Integer regexParam(String regexString) {String reg = "[^0-9]";//Pattern类的作用在于编译正则表达式后创建一个匹配模式.Pattern pattern = Pattern.compile(reg);//Matcher类使用Pattern实例提供的模式信息对正则表达式进行匹配Matcher matcher = pattern.matcher(regexString);return Integer.parseInt(matcher.replaceAll("").trim());}
}
ArgTagParser
ArgTagParser 用于处理入参tag类型
public class ArgTagParser extends AbstractDefaultTagParser {public final static String ARG_TAG_PARSER_TYPE = "ARG";@Overridepublic String getType() {return ARG_TAG_PARSER_TYPE;}@Overridepublic Tag parse(TagDefinition tagDefinition, Object[] args) throws JsonProcessingException {if (null == args){return Tag.of(tagDefinition.getKey(),DEFAULT_TAG_VALUE);}String value = tagDefinition.getValue();Object arg;if (value.contains(POINT)) {String[] params = value.split(REGEX_POINT);arg = args[regexParam(params[0])];ObjectMapper objectMapper = new ObjectMapper();Map map = objectMapper.readValue(objectMapper.writeValueAsString(arg), Map.class);String param = null == map.get(params[1]) ? DEFAULT_TAG_VALUE : objectMapper.writeValueAsString(map.get(params[1]));return Tag.of(tagDefinition.getKey(), param);}arg = args[regexParam(value)];return Tag.of(tagDefinition.getKey(),(String) arg);}
}
TagDefinition
TagDefinition存放Tag的定义信息
public class TagDefinition {private String type;private String key;private String value;public String getType() {return type;}public void setType(String type) {this.type = type;}public String getKey() {return key;}public void setKey(String key) {this.key = key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}
}
TimedAspect
@Timed注解切面,实现了自定义Tag处理逻辑。
@Slf4j
@Aspect
public class TimedAspect {public static final String DEFAULT_METRIC_NAME = "method.timed";public static final String DEFAULT_EXCEPTION_TAG_VALUE = "none";public static final String EXCEPTION_TAG = "exception";public static final String TAG_VALUE_ARG = "arg";public static final String TAG_VALUE_RESULT = "resultObj";private final MeterRegistry registry;private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;public TimedAspect() {this(Metrics.globalRegistry);}public TimedAspect(MeterRegistry registry) {this(registry, pjp ->Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),"method", pjp.getStaticPart().getSignature().getName()));}public TimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {this.registry = registry;this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;}@Around("@annotation(com.seeyon.design.metrics.annotations.annotation.Timed)")public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {Method method = ((MethodSignature) pjp.getSignature()).getMethod();Timed timed = method.getAnnotation(Timed.class);com.seeyon.design.metrics.annotations.annotation.Tags tags = method.getAnnotation(com.seeyon.design.metrics.annotations.annotation.Tags.class);if (timed == null) {method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());timed = method.getAnnotation(Timed.class);}final String metricName = timed.value().isEmpty() ? DEFAULT_METRIC_NAME : timed.value();final boolean stopWhenCompleted = CompletionStage.class.isAssignableFrom(method.getReturnType());if (!timed.longTask()) {return processWithTimer(pjp, timed, metricName, stopWhenCompleted, tags);} else {return processWithLongTaskTimer(pjp, timed, metricName, stopWhenCompleted, tags);}}private Object processWithTimer(ProceedingJoinPoint pjp, Timed timed, String metricName, boolean stopWhenCompleted, com.seeyon.design.metrics.annotations.annotation.Tags tags) throws Throwable {Object resultBody = null;Timer.Sample sample = Timer.start(registry);if (stopWhenCompleted) {try {return ((CompletionStage<?>) pjp.proceed()).whenComplete((result, throwable) ->record(pjp, timed, metricName, sample, getExceptionTag(throwable), tags, result));} catch (Exception ex) {record(pjp, timed, metricName, sample, ex.getClass().getSimpleName(), tags);throw ex;}}String exceptionClass = DEFAULT_EXCEPTION_TAG_VALUE;try {resultBody = pjp.proceed();return resultBody;} catch (Exception ex) {exceptionClass = ex.getClass().getSimpleName();throw ex;} finally {record(pjp, timed, metricName, sample, exceptionClass, tags, resultBody);}}private void record(ProceedingJoinPoint pjp, Timed timed, String metricName, Timer.Sample sample, String exceptionClass, com.seeyon.design.metrics.annotations.annotation.Tags tags) {record(pjp, timed, metricName, sample, exceptionClass, tags, null);}private void record(ProceedingJoinPoint pjp, Timed timed, String metricName, Timer.Sample sample, String exceptionClass, com.seeyon.design.metrics.annotations.annotation.Tags tags, Object resultBody) {try {sample.stop(Timer.builder(metricName).description(timed.description().isEmpty() ? null : timed.description()).tags(getExtraTags(pjp, tags, resultBody)).tags(EXCEPTION_TAG, exceptionClass).tags(tagsBasedOnJoinPoint.apply(pjp)).publishPercentileHistogram(timed.histogram()).publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles()).register(registry));} catch (Exception e) {log.error("metrics timer error",e);}}private List<Tag> getExtraTags(ProceedingJoinPoint pjp, com.seeyon.design.metrics.annotations.annotation.Tags tags, Object resultBody) throws JsonProcessingException {List<Tag> tagList = new ArrayList<>();if (null == tags) {return tagList;}com.seeyon.design.metrics.annotations.annotation.Tag[] value = tags.value();List<TagDefinition> tagDefinitionList = new ArrayList<>();for (com.seeyon.design.metrics.annotations.annotation.Tag tag : value) {String key = tag.key();String param = tag.value();TagDefinition tagDefinition = new TagDefinition();tagDefinition.setKey(key);tagDefinition.setValue(param);if (param.startsWith(TAG_VALUE_ARG)) {tagDefinition.setType(ArgTagParser.ARG_TAG_PARSER_TYPE);} else if (param.startsWith(TAG_VALUE_RESULT)) {tagDefinition.setType(ResultObjTagParser.RESULT_TAG_PARSER_TYPE);}tagDefinitionList.add(tagDefinition);}Map<String, TagParser> beansOfType = BeanUtils.getBeansOfType(TagParser.class);for (Map.Entry<String, TagParser> parserEntry : beansOfType.entrySet()) {TagParser tagParser = parserEntry.getValue();Object[] args = null;for (TagDefinition tagDefinition : tagDefinitionList) {if (tagDefinition.getType().equals(tagParser.getType())) {if (tagParser.getType().equals(ArgTagParser.ARG_TAG_PARSER_TYPE)){args = pjp.getArgs();}else if (tagParser.getType().equals(ResultObjTagParser.RESULT_TAG_PARSER_TYPE)){if (null != resultBody){args = new Object[]{resultBody};}}Tag tag = tagParser.parse(tagDefinition, args);tagList.add(tag);}}}return tagList;}private String getExceptionTag(Throwable throwable) {if (throwable == null) {return DEFAULT_EXCEPTION_TAG_VALUE;}if (throwable.getCause() == null) {return throwable.getClass().getSimpleName();}return throwable.getCause().getClass().getSimpleName();}private Object processWithLongTaskTimer(ProceedingJoinPoint pjp, Timed timed, String metricName, boolean stopWhenCompleted, com.seeyon.design.metrics.annotations.annotation.Tags tags) throws Throwable {Optional<LongTaskTimer.Sample> sample = buildLongTaskTimer(pjp, timed, metricName, tags).map(LongTaskTimer::start);if (stopWhenCompleted) {try {return ((CompletionStage<?>) pjp.proceed()).whenComplete((result, throwable) -> sample.ifPresent(this::stopTimer));} catch (Exception ex) {sample.ifPresent(this::stopTimer);throw ex;}}try {return pjp.proceed();} finally {sample.ifPresent(this::stopTimer);}}private void stopTimer(LongTaskTimer.Sample sample) {try {sample.stop();} catch (Exception e) {// ignoring on purpose}}/*** Secure long task timer creation - it should not disrupt the application flow in case of exception*/private Optional<LongTaskTimer> buildLongTaskTimer(ProceedingJoinPoint pjp, Timed timed, String metricName, com.seeyon.design.metrics.annotations.annotation.Tags tags) {try {return Optional.of(LongTaskTimer.builder(metricName).description(timed.description().isEmpty() ? null : timed.description()).tags(getExtraTags(pjp, tags,null)).tags(tagsBasedOnJoinPoint.apply(pjp)).register(registry));} catch (Exception e) {return Optional.empty();}}}
使用
在需要监控的方法中首选标明监控类型,这里用@Timed作为监控指标记录。除了@Timed自带的属性以外,我们还额外定义了id和name2个指标,这2个指标也是定义在User实体类中的属性。
@Timed(value = "timed.test.xiaoama",description = "timed metrics test for xiaoama")@Tags({@Tag(key = "id",value = "arg[0].id") , @Tag(key = "name",value = "arg[0].name")})public String doBusiness(User user){return "i am do business service"+user.getId()+":"+user.getName();}
配置完成后,调用doBusiness方法,metrics指标就能收集到第三方的监控平台上,这里我们用的是Prometheus。登录Prometheus查看我们定义的指标,通过@Tag额外定义name和id字段也都被我们监控指标成功收集到。
counted_test_xiaoama_total{application="jk", class="com.xxx.jk.service.BusinessService", exception="none", id="12", instance="xxx.xx.xxx.xx:xxx", job="spring-actuator", method="doBusinessV2", name="xiaoama_12", result="i am do business service12:xiaoama_12"}
SpringBoot 2.x 自定义注解annotation实现MicroMeter埋点相关推荐
- spring中自定义注解(annotation)与AOP中获取注解___使用aspectj的@Around注解实现用户操作和操作结果日志
spring中自定义注解(annotation)与AOP中获取注解 一.自定义注解(annotation) 自定义注解的作用:在反射中获取注解,以取得注解修饰的类.方法或属性的相关解释. packag ...
- java自定义注解annotation记录操作日志
说到注解我们平常用的可以说非常多啦,说几个常用的的注解 @RestController @Service @Autowired 这些都是我们平常使用spring框架最常见的注解了,我们只知道它们非常好 ...
- SpringBoot整合AOP + 自定义注解实现简单的权限验证
1.简介 主要通过自定义注解,使用SpringAOP的环绕通知拦截请求,判断该方法是否有自定义注解,然后判断该用户是否有该权限,这里做的比较简单. 2.项目搭建 这里是基于SpringBoot的,对于 ...
- springboot项目中自定义注解的使用总结、java自定义注解实战(常用注解DEMO)
初学spring的时候使用注解总觉得使用注解很神奇,加一个注解就能实现想要的功能,很好奇,也想自己根据需要写一些自己实现的自定义注解.问题来了,自定义注解到底是什么?肯定会有人和我一样有这个疑惑,我根 ...
- 一小时搞明白自定义注解(Annotation)
原文链接:http://blog.csdn.net/u013045971/article/details/53433874 什么是注解 Annotation(注解)就是Java提供了一种元程序中的元素 ...
- Java自定义注解Annotation的实现原理
文章目录 1.什么是注解? 2.注解的用处: 3.注解的原理: 4.元注解: 5.常见标准的Annotation: 6.自定义注解: 7.自定义注解实例: 1.什么是注解? 对于很多初次接触的开发 ...
- Android 自定义注解(Annotation)
现在市面上很多框架都有使用到注解,比如butterknife库.EventBus库.Retrofit库等等.也是一直好奇他们都是怎么做到的,注解的工作原理是啥.咱们能不能自己去实现一个简单的注解呢.注 ...
- 自定义注解--Annotation
Annotation 概念:注解 原理 是一种接口,通过反射机制中的相关API来访问annotation信息 常见的标准Annotation @Override 方法重写 @Deprecated ...
- 【SpringBoot】通过自定义注解对BigDecimal输出的小数位数进行格式化
文章目录 前言 一.JsonSerializer 二.ContextualSerializer 三.实现 1.继承JsonSerializer类,实现ContextualSerializer接口 2. ...
最新文章
- mysql 插入删除操作_MySQL——增删改操作
- mysql导出数据 程序_MySQL数据导出与导入程序代码
- 编写UEditor插件
- 开关机自动执行脚本方法[以及切换用户执行命令方法]
- JS实现ul,li排序效果
- android+布局倾斜,android – 如何在Eclipse图形布局视图中使斜...
- python中分支结构包括哪些_Python分支结构(switch)操作简介
- APUE第八章学习札记之自建简单解释器以及参数分析
- CC1310在868MHz的电路设计
- 第二十一届国际C语言混乱代码大赛结果公布
- 计算机网络——物理层传输介质
- TOMCAT 连接池数据库密码加密方法
- Java基础学习总结(127)——Java方法应该返回空对象还是null
- 第六章 实验报告(函数与宏定义)
- 帆软根据控件值切换sheet
- 黑马程序员 oc中的类与对象
- centos网卡错误Device eth0 does not seem to be present
- FreeRTOS 教程指南 学习笔记 第四章 队列管理
- 网络扫描实验(win10使用nmap,X-Scan工具使用)
- 如何提高阅读源代码能力
热门文章
- 为什么php不开源,php开不开源
- 为树莓派3B+编译 64位UEFI 固件
- Redis | 客户端
- ffmpeg学习:滤镜(实现视频缩放,裁剪,水印等) -
- 45. Django 2.1.7 项目技巧 - 创建apps应用目录归纳所有应用
- Python3 使用科大讯飞 API 接口实现音频文件转写
- 微信聊天记录删了如何找回
- zcmu --1919(多重背包)
- 无锁队列真的比有锁队列快吗【c++ linux后台开发】
- 沉睡者 - 网赚创业VIP项目课程-持续更新中...