介绍

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埋点相关推荐

  1. spring中自定义注解(annotation)与AOP中获取注解___使用aspectj的@Around注解实现用户操作和操作结果日志

    spring中自定义注解(annotation)与AOP中获取注解 一.自定义注解(annotation) 自定义注解的作用:在反射中获取注解,以取得注解修饰的类.方法或属性的相关解释. packag ...

  2. java自定义注解annotation记录操作日志

    说到注解我们平常用的可以说非常多啦,说几个常用的的注解 @RestController @Service @Autowired 这些都是我们平常使用spring框架最常见的注解了,我们只知道它们非常好 ...

  3. SpringBoot整合AOP + 自定义注解实现简单的权限验证

    1.简介 主要通过自定义注解,使用SpringAOP的环绕通知拦截请求,判断该方法是否有自定义注解,然后判断该用户是否有该权限,这里做的比较简单. 2.项目搭建 这里是基于SpringBoot的,对于 ...

  4. springboot项目中自定义注解的使用总结、java自定义注解实战(常用注解DEMO)

    初学spring的时候使用注解总觉得使用注解很神奇,加一个注解就能实现想要的功能,很好奇,也想自己根据需要写一些自己实现的自定义注解.问题来了,自定义注解到底是什么?肯定会有人和我一样有这个疑惑,我根 ...

  5. 一小时搞明白自定义注解(Annotation)

    原文链接:http://blog.csdn.net/u013045971/article/details/53433874 什么是注解 Annotation(注解)就是Java提供了一种元程序中的元素 ...

  6. Java自定义注解Annotation的实现原理

    文章目录 1.什么是注解? 2.注解的用处: 3.注解的原理: 4.元注解: 5.常见标准的Annotation: 6.自定义注解: 7.自定义注解实例: 1.什么是注解?   对于很多初次接触的开发 ...

  7. Android 自定义注解(Annotation)

    现在市面上很多框架都有使用到注解,比如butterknife库.EventBus库.Retrofit库等等.也是一直好奇他们都是怎么做到的,注解的工作原理是啥.咱们能不能自己去实现一个简单的注解呢.注 ...

  8. 自定义注解--Annotation

    Annotation 概念:注解 原理 是一种接口,通过反射机制中的相关API来访问annotation信息 常见的标准Annotation @Override   方法重写 @Deprecated  ...

  9. 【SpringBoot】通过自定义注解对BigDecimal输出的小数位数进行格式化

    文章目录 前言 一.JsonSerializer 二.ContextualSerializer 三.实现 1.继承JsonSerializer类,实现ContextualSerializer接口 2. ...

最新文章

  1. mysql 插入删除操作_MySQL——增删改操作
  2. mysql导出数据 程序_MySQL数据导出与导入程序代码
  3. 编写UEditor插件
  4. 开关机自动执行脚本方法[以及切换用户执行命令方法]
  5. JS实现ul,li排序效果
  6. android+布局倾斜,android – 如何在Eclipse图形布局视图中使斜...
  7. python中分支结构包括哪些_Python分支结构(switch)操作简介
  8. APUE第八章学习札记之自建简单解释器以及参数分析
  9. CC1310在868MHz的电路设计
  10. 第二十一届国际C语言混乱代码大赛结果公布
  11. 计算机网络——物理层传输介质
  12. TOMCAT 连接池数据库密码加密方法
  13. Java基础学习总结(127)——Java方法应该返回空对象还是null
  14. 第六章 实验报告(函数与宏定义)
  15. 帆软根据控件值切换sheet
  16. 黑马程序员 oc中的类与对象
  17. centos网卡错误Device eth0 does not seem to be present
  18. FreeRTOS 教程指南 学习笔记 第四章 队列管理
  19. 网络扫描实验(win10使用nmap,X-Scan工具使用)
  20. 如何提高阅读源代码能力

热门文章

  1. 为什么php不开源,php开不开源
  2. 为树莓派3B+编译 64位UEFI 固件
  3. Redis | 客户端
  4. ffmpeg学习:滤镜(实现视频缩放,裁剪,水印等) -
  5. 45. Django 2.1.7 项目技巧 - 创建apps应用目录归纳所有应用
  6. Python3 使用科大讯飞 API 接口实现音频文件转写
  7. 微信聊天记录删了如何找回
  8. zcmu --1919(多重背包)
  9. 无锁队列真的比有锁队列快吗【c++ linux后台开发】
  10. 沉睡者 - 网赚创业VIP项目课程-持续更新中...