001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.metrics.micrometer; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import org.apache.juneau.rest.server.*; 022 023import io.micrometer.core.instrument.*; 024 025/** 026 * Shared worker for the {@code /metrics} management endpoint: resolves a consumer-provided Micrometer 027 * {@link MeterRegistry} from the host bean store and renders a scrape payload. 028 * 029 * <p> 030 * Consistent with {@code micrometer-core} being a {@code provided} dependency and Juneau's 031 * explicit-over-magic stance, this adapter <b>strictly consumes a consumer-provided registry</b> — it 032 * never auto-registers a default. When no {@link MeterRegistry} bean is present it degrades cleanly 033 * (returns {@code null}, which the endpoints surface as HTTP 501). 034 * 035 * <p> 036 * A Prometheus text scrape is produced when the resolved registry is a {@code PrometheusMeterRegistry}. 037 * That type lives in the separate {@code micrometer-registry-prometheus} artifact which this module does 038 * <b>not</b> depend on, so its {@code scrape()} method is invoked <b>reflectively</b> — keeping the 039 * registry choice entirely the consumer's, with no extra dependency leaked here. 040 * 041 * <h5 class='section'>See Also:</h5><ul> 042 * <li class='jc'>{@link MicrometerMetricsRecorder} 043 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ManagementSurface">Management Surface (Actuator-style endpoints)</a> 044 * </ul> 045 * 046 * @since 10.0.0 047 */ 048public class MetricsManager { 049 050 /** 051 * Resolves the consumer-provided {@link MeterRegistry} from the host context's bean store. 052 * 053 * @param context The REST context whose bean store is searched. May be <jk>null</jk>. 054 * @return The registry, or <jk>null</jk> if none is registered (or the context is <jk>null</jk>). 055 */ 056 @SuppressWarnings({ 057 "resource" // The MeterRegistry is a shared, consumer-owned bean borrowed from the bean store; this adapter must not close it. 058 }) 059 public MeterRegistry resolveRegistry(RestContext context) { 060 if (context == null) 061 return null; 062 return context.getBeanStore().getBean(MeterRegistry.class).orElse(null); 063 } 064 065 /** 066 * Renders a Prometheus text scrape from the registry, if it is a {@code PrometheusMeterRegistry}. 067 * 068 * <p> 069 * Invoked reflectively to avoid a hard dependency on {@code micrometer-registry-prometheus}. Returns 070 * <jk>null</jk> when the registry is <jk>null</jk>, is not a Prometheus registry, or does not expose a 071 * no-arg {@code scrape()} method — the endpoints surface that as HTTP 501. 072 * 073 * @param registry The resolved registry, or <jk>null</jk>. 074 * @return The Prometheus exposition text, or <jk>null</jk> if no scrape is available. 075 */ 076 public String scrape(MeterRegistry registry) { 077 if (registry == null) 078 return null; 079 try { 080 var m = registry.getClass().getMethod("scrape"); 081 var out = m.invoke(registry); 082 return out == null ? null : out.toString(); 083 } catch (@SuppressWarnings("unused") NoSuchMethodException e) { 084 return null; // Not a scrapeable (Prometheus) registry — caller degrades to 501. 085 } catch (ReflectiveOperationException e) { 086 throw rex(e, "Failed to scrape MeterRegistry %s", registry.getClass().getName()); 087 } 088 } 089}