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 — 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 — 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 — 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()} — 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 — 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 — 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} — 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} — 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> — 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 — including a bare lambda implementing only 136 * {@link #startSpan(RestRequest)} — 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 — {@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}