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;
018
019import java.time.*;
020
021/**
022 * SPI for receiving per-request metric events from {@code juneau-rest-server}.
023 *
024 * <p>
025 * One {@code record(...)} call fires after every {@code @RestOp} handler invocation &mdash; including
026 * exception paths. Bridge implementations translate the event into the format their downstream
027 * registry expects (Micrometer {@code io.micrometer.core.instrument.Timer}, OpenTelemetry
028 * metric instruments, Dropwizard {@code MetricRegistry}, etc.).
029 *
030 * <h5 class='topic'>Off-by-default contract</h5>
031 *
032 * <p>
033 * {@code juneau-rest-server} resolves the {@code MetricsRecorder} via
034 * {@code RestContext.getBeanStore().getBean(MetricsRecorder.class)}. When no bean is supplied,
035 * {@link NoOpMetricsRecorder#INSTANCE} is used and the framework never allocates a downstream
036 * registry, never reaches Micrometer / OpenTelemetry on its own, and never adds any per-request
037 * cost beyond a single static-field read. To opt in, the consumer registers a {@code MetricsRecorder}
038 * bean &mdash; typically via {@code @Bean MetricsRecorder} on the resource or its parent.
039 *
040 * <h5 class='topic'>Usage</h5>
041 *
042 * <p class='bjava'>
043 *    <ja>@Rest</ja>
044 *    <jk>public class</jk> MyResource <jk>extends</jk> RestServlet {
045 *
046 *       <ja>@Bean</ja>
047 *       <jk>public</jk> MetricsRecorder recorder(MeterRegistry <jv>registry</jv>) {
048 *          <jk>return new</jk> MicrometerMetricsRecorder(<jv>registry</jv>);
049 *       }
050 *    }
051 * </p>
052 *
053 * <h5 class='topic'>Threading</h5>
054 *
055 * <p>
056 * Implementations <b>must</b> be thread-safe &mdash; the same {@code MetricsRecorder} bean is invoked
057 * concurrently from every request thread for the lifetime of the {@code RestContext}. The framework
058 * calls {@code record(...)} synchronously inside the request thread immediately after the response
059 * status is known, so implementations should keep the call cheap (constant-time tag computation,
060 * non-blocking registry writes).
061 *
062 * <h5 class='topic'>Event ordering</h5>
063 *
064 * <p>
065 * For a given {@code @RestOp} invocation, {@code record(...)} fires exactly once after the operation
066 * completes &mdash; whether the handler returned normally, threw a {@code BasicHttpException}, or
067 * triggered an internal-server-error. The {@code elapsed} parameter measures wall-clock time spent
068 * inside the framework's per-op invocation boundary (from just before parameter resolution to just
069 * after the handler returns or throws). It does <b>not</b> include response serialization performed
070 * later in the request pipeline.
071 *
072 * <h5 class='section'>See Also:</h5><ul>
073 *    <li class='jc'>{@link NoOpMetricsRecorder}
074 *    <li class='jc'>{@link org.apache.juneau.rest.server.tracing.TracerHook}
075 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerObservability">Observability — Micrometer + OpenTelemetry</a>
076 * </ul>
077 *
078 * @since 10.0.0
079 */
080@FunctionalInterface
081@SuppressWarnings({
082   "java:S6213" // 'record' is the established SPI method name; renaming would break the public observability API.
083})
084public interface MetricsRecorder {
085
086   /**
087    * Records one per-request metric event.
088    *
089    * @param opName The fully-qualified {@code @RestOp} method name (e.g.
090    *    {@code "com.example.OrderResource.create(java.lang.String,com.example.Order)"}).
091    *    Never <jk>null</jk>; never blank.
092    * @param httpMethod The uppercased HTTP method of the request (e.g. {@code "GET"}, {@code "POST"}).
093    *    Never <jk>null</jk>; never blank.
094    * @param uriTemplate The {@code @RestOp} path template (e.g. {@code "/users/{id}"}), <b>not</b> the
095    *    raw concrete URI. Bridges should use this as the {@code uri} tag to keep cardinality bounded.
096    *    May be empty for resource-level operations that match {@code /*}; never <jk>null</jk>.
097    * @param statusCode The HTTP response status code (e.g. {@code 200}, {@code 404}, {@code 500}).
098    *    If the handler threw before the status was set, this is the framework's inferred status (typically
099    *    {@code 500} for an unmapped exception, or the exception's status for a {@code BasicHttpException}).
100    * @param elapsed Wall-clock duration of the {@code @RestOp} invocation. Never <jk>null</jk>; never
101    *    negative.
102    * @param error The exception thrown by the handler, or <jk>null</jk> if the call completed normally.
103    *    Bridges typically derive an {@code exception} tag from the throwable's simple class name, or
104    *    {@code "None"} when null (mirrors Spring Boot's {@code WebMvcMetricsFilter} convention).
105    * @param metricName Per-op metric name override from {@link org.apache.juneau.rest.server.RestOp#metricName()}.
106    *    Empty string (default) means the implementation should use its own default name derivation.
107    *    Never <jk>null</jk>.
108    * @param metricTags Per-op additional metric tags from {@link org.apache.juneau.rest.server.RestOp#metricTags()}.
109    *    Format: comma-separated {@code key=value} pairs (e.g. {@code "team=payments,region=us-east"}).
110    *    Empty string (default) means no additional tags. Never <jk>null</jk>.
111    */
112   @SuppressWarnings({
113      "java:S107" // The 8 parameters form the stable per-request metric event contract; a holder object would obscure this SPI signature.
114   })
115   void record(String opName, String httpMethod, String uriTemplate, int statusCode, Duration elapsed, Throwable error, String metricName, String metricTags);
116
117   /**
118    * Records one custom (non-request) observation event.
119    *
120    * <p>
121    * Unlike {@link #record(String, String, String, int, Duration, Throwable, String, String)}, this
122    * entry point is <b>not</b> tied to an HTTP request &mdash; it is the substrate for the explicit
123    * programmatic observation API ({@link org.apache.juneau.rest.server.observation.Observations}) so
124    * application code can time an arbitrary block of work. Bridge implementations record a timer named
125    * {@code metricName} carrying {@code metricTags} (plus an {@code exception} tag derived from
126    * {@code error}).
127    *
128    * <p>
129    * The default implementation does nothing &mdash; a bridge that does not override it simply does not
130    * record custom observations, and the no-backend path stays zero-allocation. The shipped Micrometer
131    * bridge overrides it.
132    *
133    * @param metricName The timer name (e.g. {@code "order.load"}). Never <jk>null</jk>; never blank.
134    * @param metricTags Additional tags as comma-separated {@code key=value} pairs (e.g.
135    *    {@code "team=payments,region=us-east"}). Empty string means no additional tags. Never <jk>null</jk>.
136    * @param elapsed Wall-clock duration of the observed block. Never <jk>null</jk>; never negative.
137    * @param error The exception thrown by the observed block, or <jk>null</jk> if it completed normally.
138    */
139   default void record(String metricName, String metricTags, Duration elapsed, Throwable error) {
140      // Default no-op: bridges that support custom observations override this.
141   }
142}