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.tracing;
018
019import org.apache.juneau.http.tracing.TraceContextCarrier;
020import org.apache.juneau.rest.server.*;
021
022/**
023 * SPI for receiving per-request tracing events from {@code juneau-rest-server}.
024 *
025 * <p>
026 * {@link #startSpan(RestRequest)} fires immediately before every {@code @RestOp} handler runs &mdash;
027 * the returned {@link Scope} is closed by the framework after the handler completes (including the
028 * exception path). Bridge implementations create whatever the downstream tracer expects (an
029 * OpenTelemetry {@code io.opentelemetry.api.trace.Span}, a Brave {@code Span}, an application
030 * tracing object, etc.) and surface the close / status / error transitions through the {@code Scope}.
031 *
032 * <h5 class='topic'>Off-by-default contract</h5>
033 *
034 * <p>
035 * {@code juneau-rest-server} resolves the {@code TracerHook} via
036 * {@code RestContext.getBeanStore().getBean(TracerHook.class)}. When no bean is supplied,
037 * {@link NoOpTracerHook#INSTANCE} is used &mdash; the framework never reaches for a tracer, never
038 * inspects headers for W3C trace context, and adds no per-request cost beyond a single static-field
039 * read plus a single {@code AutoCloseable.close()} on a singleton noop scope. To opt in, the consumer
040 * registers a {@code TracerHook} bean &mdash; typically via {@code @Bean TracerHook} on the resource
041 * or its parent.
042 *
043 * <h5 class='topic'>Usage</h5>
044 *
045 * <p class='bjava'>
046 *    <ja>@Rest</ja>
047 *    <jk>public class</jk> MyResource <jk>extends</jk> RestServlet {
048 *
049 *       <ja>@Bean</ja>
050 *       <jk>public</jk> TracerHook tracer(OpenTelemetry <jv>otel</jv>) {
051 *          <jk>return new</jk> OtelTracerHook(<jv>otel</jv>);
052 *       }
053 *    }
054 * </p>
055 *
056 * <h5 class='topic'>Lifecycle</h5>
057 *
058 * <p>
059 * For each {@code @RestOp} invocation, the framework:
060 * <ol>
061 *    <li>Calls {@link #startSpan(RestRequest)} just before parameter resolution, passing the in-flight
062 *       {@link RestRequest} so the bridge can read trace-context headers
063 *       ({@code traceparent} / {@code tracestate}) and set request-derived attributes.
064 *    <li>Runs the handler (which may throw).
065 *    <li>Calls {@link Scope#setStatusCode(int)} with the resolved response status, then
066 *       {@link Scope#setError(Throwable)} if the handler threw, then {@link Scope#close()} &mdash;
067 *       always in a {@code finally} block so the span closes even on exception.
068 * </ol>
069 *
070 * <p>
071 * Implementations <b>must</b> be thread-safe &mdash; the same {@code TracerHook} bean is invoked
072 * concurrently from every request thread for the lifetime of the {@code RestContext}.
073 *
074 * <h5 class='section'>See Also:</h5><ul>
075 *    <li class='jc'>{@link NoOpTracerHook}
076 *    <li class='jc'>{@link Scope}
077 *    <li class='jc'>{@link TraceContextCarrier}
078 *    <li class='jc'>{@link TraceContextExtractor}
079 *    <li class='jc'>{@link TraceOperation}
080 *    <li class='jc'>{@link TraceContexts}
081 *    <li class='jc'>{@link org.apache.juneau.rest.server.metrics.MetricsRecorder}
082 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerObservability">Observability — Micrometer + OpenTelemetry</a>
083 * </ul>
084 *
085 * @since 10.0.0
086 */
087@FunctionalInterface
088@SuppressWarnings({
089   "resource" // Scope is returned to the caller for try-with-resources/finally management, not closed here; Eclipse JDT @Owning warning is by design.
090})
091public interface TracerHook {
092
093   /**
094    * Opens a new span for the in-flight request.
095    *
096    * @param request The in-flight {@link RestRequest}. Never <jk>null</jk>. Bridges may read headers
097    *    for distributed-tracing context propagation (e.g. W3C {@code traceparent} / {@code tracestate}),
098    *    the HTTP method, the path, etc. Bridges <b>must not</b> mutate the request.
099    * @return The opened {@link Scope}. Never <jk>null</jk>. Returning {@code null} is a contract
100    *    violation &mdash; bridges that cannot open a span (e.g. because the trace context is invalid)
101    *    should return {@link NoOpTracerHook.NoOpScope#INSTANCE} so the framework's close-in-finally
102    *    contract still holds.
103    */
104   Scope startSpan(RestRequest request);
105
106   /**
107    * Opens a new span for a custom (non-request) observation.
108    *
109    * <p>
110    * Unlike {@link #startSpan(RestRequest)}, this entry point is <b>not</b> tied to an in-flight
111    * {@link RestRequest} &mdash; it is the substrate for the explicit programmatic observation API
112    * ({@link org.apache.juneau.rest.server.observation.Observations}) so application code can trace an
113    * arbitrary block of work that has no associated HTTP request. Bridge implementations typically open
114    * a span of kind {@code INTERNAL} (rather than {@code SERVER}) named {@code spanName}.
115    *
116    * <p>
117    * The default implementation returns {@link NoOpTracerHook.NoOpScope#INSTANCE} &mdash; a bridge that
118    * does not override this method simply does not trace custom observations, and the no-backend path
119    * stays zero-allocation. Both shipped bridges (the OpenTelemetry {@code TracerHook}) override it.
120    *
121    * @param spanName The span name (e.g. {@code "loadOrder"}). Never <jk>null</jk>; never blank.
122    * @return The opened {@link Scope}. Never <jk>null</jk> &mdash; implementations that cannot open a
123    *    span must return {@link NoOpTracerHook.NoOpScope#INSTANCE} so the close-in-finally contract holds.
124    */
125   default Scope startSpan(String spanName) {
126      return NoOpTracerHook.NoOpScope.INSTANCE;
127   }
128
129   /**
130    * Opens a new span for the in-flight request, given a pre-extracted non-HTTP
131    * {@link TraceContextCarrier} and derived {@link TraceOperation}.
132    *
133    * <p>
134    * Added as a source-compatible default overload so every existing {@link TracerHook}
135    * implementation &mdash; including a bare lambda implementing only
136    * {@link #startSpan(RestRequest)} &mdash; keeps compiling unchanged. The default implementation
137    * simply delegates to {@link #startSpan(RestRequest)}, ignoring <c>carrier</c> and
138    * <c>operation</c>; a bridge only needs to override this overload if it wants to honor a
139    * non-HTTP carrier (for example MCP's {@code params._meta}) or name the span after
140    * <c>operation</c> instead of its own HTTP-derived default.
141    *
142    * @param request The in-flight {@link RestRequest}. Never <jk>null</jk>.
143    * @param carrier The {@link TraceContextCarrier} an active {@code TraceContextExtractor}
144    *    recognized among the resolved operation arguments, or <jk>null</jk> if none was recognized
145    *    (the bridge should then fall back to HTTP headers only).
146    * @param operation The {@link TraceOperation} an active {@code TraceContextExtractor} derived
147    *    for this invocation. Never <jk>null</jk>; {@link TraceOperation#DEFAULT} when the extractor
148    *    supplied no override.
149    * @return The opened {@link Scope}. Never <jk>null</jk>.
150    */
151   default Scope startSpan(RestRequest request, TraceContextCarrier carrier, TraceOperation operation) {
152      return startSpan(request);
153   }
154
155   /**
156    * Renders this hook's active trace context (if any) into <c>carrier</c>.
157    *
158    * <p>
159    * The substrate for {@link TraceContexts#inject(TracerHook, TraceContextCarrier)}. The default
160    * implementation is a no-op &mdash; {@link NoOpTracerHook} and any bridge that does not override
161    * this method write nothing. A bridge with an actual trace context to render (for example the
162    * OpenTelemetry bridge in {@code juneau-rest-server-tracing-otel}) overrides this to write
163    * {@code traceparent} / {@code tracestate} / {@code baggage} keys via {@link TraceContextCarrier#set(String, String)}.
164    *
165    * @param carrier The {@link TraceContextCarrier} to write into. Never <jk>null</jk>.
166    */
167   default void inject(TraceContextCarrier carrier) {
168      // Intentionally empty; see class-level javadoc.
169   }
170}