Spring Boot Prometheus要求具有相同名称的所有计量表具有相同的标记密钥集

mdfafbf1  于 2022-11-29  发布在  Spring
关注(0)|答案(1)|浏览(176)

如果@Around only @Timed这样注解方法:
Package 制造遮阳板配置aop;

import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.lang.NonNullApi;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.scheduling.annotation.Scheduled;

import java.lang.reflect.Method;
import java.util.function.Function;

/**
 * The type Targeted timed aspect.
 */
@Aspect
@NonNullApi
public class TargetedTimedAspect {

    public static final String DEFAULT_METRIC_NAME = "method.timed";

    public static final String EXCEPTION_TAG = "exception";
    public static final String BINDING_TAG = "binding";
    public static final String SCHEDULED_CRON_TAG = "cron";

    private final MeterRegistry registry;
    private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;

    public TargetedTimedAspect(MeterRegistry registry) {
        this(registry, pjp ->
            Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
                "method", pjp.getStaticPart().getSignature().getName())
        );
    }

    public TargetedTimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
        this.registry = registry;
        this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
    }

    // enable TimedAspect only for @StreamListener and @Scheduled annotated methods or allowed methods pointcut
    @Around("timedAnnotatedPointcut() )")
    public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();

        StreamListener streamListener = method.getAnnotation(StreamListener.class);
        Scheduled scheduled = method.getAnnotation(Scheduled.class);
        // timed can be on method or class
        Timed timed = method.getAnnotation(Timed.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();
        Timer.Sample sample = Timer.start(registry);
        String exceptionClass = "none";

        try {
            return pjp.proceed();
        } catch (Exception ex) {
            exceptionClass = ex.getClass().getSimpleName();
            throw ex;
        } finally {
            try {
                Timer.Builder timerBuilder = Timer.builder(metricName)
                    .description(timed.description().isEmpty() ? null : timed.description())
                    .tags(timed.extraTags())
                    .tags(EXCEPTION_TAG, exceptionClass)
                    .tags(tagsBasedOnJoinPoint.apply(pjp))
                    .publishPercentileHistogram(timed.histogram())
                    .publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());

                if (streamListener != null) {
                    timerBuilder.tags(
                        BINDING_TAG,
                        streamListener.value().isEmpty() ? streamListener.target() : streamListener.value()
                    );
                } else if (scheduled != null) {
                    timerBuilder.tags(SCHEDULED_CRON_TAG, scheduled.cron());
                }

                sample.stop(timerBuilder.register(registry));
            } catch (Exception e) {
                // ignoring on purpose
            }
        }
    }

    @Pointcut(
        "(@annotation(org.springframework.cloud.stream.annotation.StreamListener) ||" +
        "@annotation(org.springframework.scheduling.annotation.Scheduled))"
    )
    public void asyncAnnotatedPointcut() {
        // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

    @Pointcut("execution(public * ru.fabit.visor.service.impl.StorageClientImpl.*(..)) ||" +
        "execution(public * ru.fabit.visor.service.s3storage.S3StorageClientImpl.*(..))")
    public void allowedMethodPointcut() {
         // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

    @Pointcut("@annotation(io.micrometer.core.annotation.Timed)")
    public void timedAnnotatedPointcut() {
        // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

}

然后返回:异常错误:Prometheus要求所有具有相同名称的量表具有相同的标记键集。已经存在名为“web_photos_gotted_list_seconds”的量表,其中包含标记键[class,exception,method]。您尝试注册的量表具有键[exception,method,outcome,status,uri]。
但是,如果在Pointcut中添加所有@Timed方法,都很好,我不明白,为什么我们需要将所有带注解的方法分别添加到Pointcut中?
本作品:

package ru.fabit.visor.config.aop;

import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.lang.NonNullApi;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.scheduling.annotation.Scheduled;

import java.lang.reflect.Method;
import java.util.function.Function;

/**
 * The type Targeted timed aspect.
 */
@Aspect
@NonNullApi
public class TargetedTimedAspect {

    public static final String DEFAULT_METRIC_NAME = "method.timed";

    public static final String EXCEPTION_TAG = "exception";
    public static final String BINDING_TAG = "binding";
    public static final String SCHEDULED_CRON_TAG = "cron";

    private final MeterRegistry registry;
    private final Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint;

    public TargetedTimedAspect(MeterRegistry registry) {
        this(registry, pjp ->
            Tags.of("class", pjp.getStaticPart().getSignature().getDeclaringTypeName(),
                "method", pjp.getStaticPart().getSignature().getName())
        );
    }

    public TargetedTimedAspect(MeterRegistry registry, Function<ProceedingJoinPoint, Iterable<Tag>> tagsBasedOnJoinPoint) {
        this.registry = registry;
        this.tagsBasedOnJoinPoint = tagsBasedOnJoinPoint;
    }

    // enable TimedAspect only for @StreamListener and @Scheduled annotated methods or allowed methods pointcut
    @Around("timedAnnotatedPointcut() && (asyncAnnotatedPointcut() || allowedMethodPointcut())")
    public Object timedMethod(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();

        StreamListener streamListener = method.getAnnotation(StreamListener.class);
        Scheduled scheduled = method.getAnnotation(Scheduled.class);
        // timed can be on method or class
        Timed timed = method.getAnnotation(Timed.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();
        Timer.Sample sample = Timer.start(registry);
        String exceptionClass = "none";

        try {
            return pjp.proceed();
        } catch (Exception ex) {
            exceptionClass = ex.getClass().getSimpleName();
            throw ex;
        } finally {
            try {
                Timer.Builder timerBuilder = Timer.builder(metricName)
                    .description(timed.description().isEmpty() ? null : timed.description())
                    .tags(timed.extraTags())
                    .tags(EXCEPTION_TAG, exceptionClass)
                    .tags(tagsBasedOnJoinPoint.apply(pjp))
                    .publishPercentileHistogram(timed.histogram())
                    .publishPercentiles(timed.percentiles().length == 0 ? null : timed.percentiles());

                if (streamListener != null) {
                    timerBuilder.tags(
                        BINDING_TAG,
                        streamListener.value().isEmpty() ? streamListener.target() : streamListener.value()
                    );
                } else if (scheduled != null) {
                    timerBuilder.tags(SCHEDULED_CRON_TAG, scheduled.cron());
                }

                sample.stop(timerBuilder.register(registry));
            } catch (Exception e) {
                // ignoring on purpose
            }
        }
    }

    @Pointcut(
        "(@annotation(org.springframework.cloud.stream.annotation.StreamListener) ||" +
        "@annotation(org.springframework.scheduling.annotation.Scheduled))"
    )
    public void asyncAnnotatedPointcut() {
        // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

    @Pointcut("execution(public * ru.fabit.visor.service.impl.StorageClientImpl.*(..)) ||" +
        "execution(public * ru.fabit.visor.service.s3storage.S3StorageClientImpl.*(..))")
    public void allowedMethodPointcut() {
         // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

    @Pointcut("@annotation(io.micrometer.core.annotation.Timed)")
    public void timedAnnotatedPointcut() {
        // Method is empty as this is just a Pointcut, the implementations are in the advices.
    }

}

pom.xml:

<dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-core</artifactId>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>
guz6ccqo

guz6ccqo1#

您所描述的问题与切入点无关。(classexceptionmethod),另一个创建具有完全相同的5个标签的定时器(exceptionmethodoutcomestatusuri),框架明确表示,现在允许这样做。
有几种方法可以解决此问题:

  • 只需为计时器使用另一个名称(如果需要另一个名称)
  • 找到生成该计时器的另一段代码并将其禁用。也许您需要使用调试器并在MeterRegistry.register()'中设置一个条件断点,该断点以计数器名称匹配的条件注册计数器。

PS:使用URI作为标签并不是一个好的做法。问题是任何人使用不同的URI访问你的服务(例如,通过只添加一个随机数),这将以非常高的米数结束,这最终会杀死你的普罗米修斯。

相关问题