如何将SpringBoot应用的metrics按特定格式输出到日志

我们知道SpringBoot actuator是有一块内容是关于metrics的,可以很容易和prometheus等监控体系进行集成,但是目前还没有接入相关的监控体系,想着能不能定期输出到日志文件中。

方案设计

将监控指标输出到日志,是否有现成方案?

micrometer-core在1.10+是有引入LoggingMeterRegistry来定期输出日志的,但是看了一下具体的输出内容,它是按一个指标一行来输出的,一次输出的行记录比较多,但我们更希望分门别类,每一类一行输出。所以,还是得自己定制一下输出。

基于actuator的metrics,还是从头构建?

从头构建,优点就是可以与框架无关,可以应用于非springboot的一些老工程。但考虑到:

  1. 老工程已经有相应的监控体系和性能信息,目前可以不考虑;

  2. 后续接入prometheus监控,包括新增自定义指标都可以直接切换过去;

所以,选择基于actuator的metrics来开发,但是不开放metrics访问端点。

数据和日志格式是怎样的?

打算输出的指标内容,分为三个部分:

  1. 应用通用的,包括线程、类加载、堆内存、非堆内存、永久区(元空间)内存、GC、CPU、文件句柄。

  2. 请求相关的,包括请求、会话、Tomcat线程池。

  3. 数据库相关的,如果有多个数据源,则分多行输出。

当前每间隔10秒输出一次,按每次1k计算,一天大约8.5M,一个月大约250M。所以,建议按250M大小,保留一个备份。

指标来源

上述的指标,经过对比基本都是内置的,但有几点需要注意:

  1. 对于数据源的话,默认的Hikari是有实现的,其他的数据源例如Druid就得实现DataSourcePoolMetadataProvider接口或者自己定义binder,否则为空。

  2. SpringBoot 2.2.x+默认禁用JMX Tomcat Bean暴露,需要通过参数server.tomcat.mbeanregistry.enabled=false重置为默认打开。

  3. 不少指标是来源于micrometer-core的。例如tomcat的部分指标在micrometer-core较新版本才出现,当然加上也不会出现错误,只是没有数值。例如Spring Boot2.3.x对应的是1.5.14,而最新版本已经是1.9.x了,当前就发现tomcat.connections.config.maxtomcat.connections.current这两个指标需要新版本才有。

源码流程简单分析

mertics入口在哪?

根据spring.factories可以找到一堆metrics相关的配置:

...
org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.export.appoptics.AppOpticsMetricsExportAutoConfiguration,\
...
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration,\
...

主要分类几类:

  • 入口 MetricsAutoConfiguration、MetricsEndpointAutoConfiguration

  • 暴露metrics org.springframework.boot.actuate.autoconfigure.metrics.export

  • web相关的metrics org.springframework.boot.actuate.autoconfigure.metrics.web

  • 其他的,包括JVM/System/Log/Cache等等

MetricsAutoConfiguration应该是核心入口,在这里会初始化一个MeterRegistryPostProcessor,这是一个BeanPostProcessor类型的对象,这里的逻辑很好理解,就是把MeterRegistry类型的Bean注册到MeterRegistryConfigurer去。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 if (bean instanceof MeterRegistry) {
  getConfigurer().configure((MeterRegistry) bean);
 }
 return bean;
}

MeterRegistry默认是一个SimpleMeterRegistry,从SimpleMetricsExportAutoConfiguration出来的。从这里看结构分析,MeterRegistry是可以存在多个的,在export包里边定义。

private void addBinders(MeterRegistry registry) {
 this.binders.orderedStream().forEach((binder) -> binder.bindTo(registry));
}

在上面的configure方法里边就会调用addBinders,它的实现就是把具体的mertics注册到registry去。MeterBinder负责输出指标给MeterRegistry(见bindTo方法),这样整个关系也很好理解了,以Jvm GC指标为例。

JvmMetricsAutoConfiguration会生成JvmGcMetrics,而JvmGcMetrics实现了MeterBinder接口,所以会在启动时候,通过调用bindTo方法,把相应的指标获取方式注册到registry去

mertics数据怎么来的?

public abstract class MeterRegistry {
    ...
    private final List<Consumer<Meter>> meterAddedListeners = new CopyOnWriteArrayList<>();
    private final List<Consumer<Meter>> meterRemovedListeners = new CopyOnWriteArrayList<>();
    private final More more = new More();

    // Even though writes are guarded by meterMapLock, iterators across value space are supported
    // Hence, we use CHM to support that iteration without ConcurrentModificationException risk
    private final Map<Id, Meter> meterMap = new ConcurrentHashMap<>();
    ...
}

核心的结构就是meterMap,里边存放着每一个指标定义。

那数据是怎么来的?以JvmMemoryMetrics为例。

Gauge.builder("jvm.buffer.count", bufferPoolBean, BufferPoolMXBean::getCount)
        .tags(tagsWithId)
        .description("An estimate of the number of buffers in the pool")
        .baseUnit(BaseUnits.BUFFERS)
        .register(registry);

很明显就是通过BufferPoolMXBean::getCount这个方法来获取的。大体就可以理解为:

MeterRegistry通过一个map存放所有的指标定义,每个指标定义包括id, 指标来源对象,指标来源方法引用,需要取值的话,就调用一下。内置的这些通用指标,主要来源于内置的MXBean,主要有ClassLoadMXBean、CompilationMXBean、GarbageCollectorMXBean、MemoryManagerMXBean、MemoryMXBean、MemoryPoolMXBean、OperatingSystemMXBean、RuntimeMXBean、ThreadMXBean。具体的话,可以通过查看JvmMetricsAutoConfiguration、Log4J2MetricsAutoConfiguration、CacheMetricsAutoConfiguration、DataSourcePoolMetricsAutoConfiguration来学习。

metrics数据怎么整理?

由于metrics可能是有多个tag的,我们需要的值可能是需要计算出来的。例如某个指标是总数,就需要忽略tag,其实写起来有点麻烦,但是我们可以直接参考MetricsEndpointAutoConfiguration中用于暴露端点的MetricsEndpoint的计算逻辑。

@ReadOperation
public MetricResponse metric(@Selector String requiredMetricName, @Nullable List<String> tag) {
  // tag的格式是"key1:value1,key2:value2",要转成Tag类型
 List<Tag> tags = parseTags(tag);
  // 根据metric的名称和tag找到指标定义Meter
 Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags);
 if (meters.isEmpty()) {
  return null;
 }
  // 对指标数据进行统计,对应的key是指标统计,可能有多种,例如最大,最小,总数之类
 Map<Statistic, Double> samples = getSamples(meters);
  // 找指标里边包含了哪些可用的tag
 Map<String, Set<String>> availableTags = getAvailableTags(meters);
 tags.forEach((t) -> availableTags.remove(t.getKey()));
 Meter.Id meterId = meters.iterator().next().getId();
 return new MetricResponse(requiredMetricName, meterId.getDescription(), meterId.getBaseUnit(),
   asList(samples, Sample::new), asList(availableTags, AvailableTag::new));
}

参考源码中的实现逻辑,就可以仿照这得到计算指标的逻辑。对于每个指标,主要是确认使用的指标名称是哪个?是否需要采用Tag来具体定位?指标统计是哪种类型?(在io.micrometer.core.instrument.Statistic定义)

另外,最终的数据都是double类型的,对某些指标展示不太友好需要进行转换:

  1. 表示数量等整数类型的,需要转换成int或long;

  2. 表示字节数大小等整数类型的,考虑转换成KB或者MB等单位;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值