springboot接入prometheus监控

pom引入


org.springframework.boot
spring-boot-starter-actuator


io.micrometer
micrometer-core


io.micrometer
micrometer-registry-prometheus
1.5.6

yaml配置
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/actuator
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application: ${spring.application.name}

management:
endpoints:
web:
base-path: /actuator
exposure:
include: '*'
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}

点击查看代码

/**
 * 描述 工作JobMetrics
 * */
@Component
@Getter
@Slf4j
public class JobMetrics implements MeterBinder {

    private MeterRegistry meterRegistry;



    @Override
    public void bindTo(MeterRegistry meterRegistry) {

        this.meterRegistry = meterRegistry;
    }

    /**
     * 采集Prometheus指标信息的次数counter
     *
     */
    public void counter(String metricName, String[] tags) {

        try {
            meterRegistry.counter(metricName, tags).increment();
        } catch (Exception e) {
            //log.error("采集summary出现异常: {}", e.getMessage());
        }
    }

    /**
     * 采集Prometheus指标信息的次数timer
     *
     */
    public void timer(String metricName, long val, String[] tags) {

        try {

            Timer.builder(metricName)
                    .tags(tags)
                    .publishPercentiles(0.5, 0.95, 0.99) // 发布分位数
                    .description("ck summery")
                    .register(meterRegistry)
                    .record(val, TimeUnit.MILLISECONDS);

        } catch (Exception e) {
            //log.error("采集summary出现异常: {}", e.getMessage());
        }
    }


}
点击查看代码


@Slf4j
@RefreshScope
@Component
public class CheckResponseFilter implements GlobalFilter, Ordered {

    private static final String APP_INVOKE_COST_TIME = "app_invoke_cost_time";

    private static final String APP_INVOKE_URL_ERROR = "app_invoke_url_error";

    @Resource
    private JobMetrics jobMetrics;

    @Value("${country_code}")
    private String countryCode;

    @Value("${spring.profiles.active:test}")
    private String env;

    @Value("${shared.stateWhiteList:255}")
    private String stateWhiteList;

    private static final DataBufferFactory bufferFactory = new DefaultDataBufferFactory();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long start = System.currentTimeMillis();
        URI uri = exchange.getRequest().getURI();
        //请求路径
        String url = uri.getPath();
        String appName = url.split("/")[2];

        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory wrappedBufferFactory = originalResponse.bufferFactory();

        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            private final ArrayList<DataBuffer> bufferList = new ArrayList<DataBuffer>();

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {

                        // 合并多个流集合,解决返回体分段传输
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer buff = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[buff.readableByteCount()];
                        buff.read(content);
                        DataBufferUtils.release(buff);//释放掉内存

                        //排除Excel导出,不是application/json不打印。若请求是上传图片则在最上面判断。
                        MediaType contentType = originalResponse.getHeaders().getContentType();
                        if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                            return bufferFactory.wrap(content);
                        }

                        // 构建返回
                        String joinData = new String(content);

                        //统计错误
                        countResponseError(appName, url, joinData);

                        return bufferFactory.wrap(content);
                    }));
                } else {
                    log.error("gateway_status_code_error", getStatusCode());
                }
                return super.writeWith(body);
            }

        };

        return chain.filter(exchange.mutate().response(decoratedResponse).build())
                .then(Mono.fromRunnable(() -> {
                    long duration = System.currentTimeMillis() - start;
                    //统计耗时
                    summeryCostTime(appName, url, duration);
                }));

    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }


    private void summeryCostTime(String appName, String url, long timeCost) {

        String[] tagArr = new String[]{
                "env", env,
                "countryCode", countryCode,
                "appName", appName,
                "url", url
        };
        // 执行耗时
        jobMetrics.timer(APP_INVOKE_COST_TIME, timeCost, tagArr);
        //log
        //log.error(APP_INVOKE_COST_TIME + "  timeCost:" + timeCost);

    }


    private void countResponseError(String appName, String url, String responseBody) {

        if (responseBody != null && !responseBody.isEmpty()) {
            JSONObject jsonObject = JSONObject.parseObject(responseBody);
            // 尝试解析JSON响应体
            String state = jsonObject.getString("state");
            //统计失败
            //state 返回代码,200==成功,255==失败,267==需要特殊处理,400==请求无效,403==禁止访问,404==请求地址不存在,503==服务器异常
            if (StringUtils.isNotBlank(state) && !StringUtils.equals(state, "200")) {

                String[] tagArr = new String[]{
                        "env", env,
                        "countryCode", countryCode,
                        "appName", appName,
                        "url", url,
                        "state", state

                };
                //如果不在白名单内
                if (!StringUtils.containsIgnoreCase(stateWhiteList,state)) {
                    //错误统计
                    jobMetrics.counter(APP_INVOKE_URL_ERROR, tagArr);
                }
                //log
                log.error(APP_INVOKE_URL_ERROR + "  url:" + url + "   body:" + responseBody);

            }

        }

    }

}


posted @   kunchengs  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示