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 static org.apache.juneau.commons.utils.StringUtils.*;
020
021import java.io.*;
022import java.util.logging.*;
023
024import org.apache.juneau.http.response.*;
025import org.apache.juneau.rest.server.*;
026import org.apache.juneau.rest.server.processor.*;
027
028/**
029 * Writes the W3C Trace Context response headers ({@code traceparent} and, when present,
030 * {@code tracestate}) so a client calling this server can read the resulting trace identifiers back
031 * off the response.
032 *
033 * <h5 class='topic'>How it works</h5>
034 *
035 * <p>
036 * A {@link TracerHook} bridge (e.g. the OpenTelemetry bridge in {@code juneau-rest-server-tracing-otel})
037 * renders the W3C {@code traceparent} / {@code tracestate} values from the <i>server-started</i> span
038 * context at {@link TracerHook#startSpan(RestRequest) span-start} time &mdash; the only point where
039 * that context is reliably active &mdash; and stashes them as the request attributes
040 * {@link #ATTR_TRACEPARENT} / {@link #ATTR_TRACESTATE}. This processor runs later, during response
041 * rendering (after the span scope has already closed), reads those stashed strings, and writes them
042 * as response headers.
043 *
044 * <p>
045 * This capture-at-span-start / write-at-response-time split is deliberate: the framework opens and
046 * closes the tracing scope around the {@code @RestOp} handler in
047 * {@link RestOpInvoker#invokeOp(RestOpSession)}, which completes <i>before</i> response processors
048 * run, so {@code Span.current()} is no longer valid here. Keeping the rendered values on the request
049 * also keeps this processor free of any OpenTelemetry (or other tracer) dependency &mdash; it works
050 * with any {@code TracerHook} bridge that populates the two attributes.
051 *
052 * <h5 class='topic'>Off-by-default behavior</h5>
053 *
054 * <p>
055 * The first action in {@link #process(RestOpSession)} is a single request-attribute read. When no
056 * non-no-op {@code TracerHook} ran for the request, no {@link #ATTR_TRACEPARENT} attribute is present
057 * and the processor returns {@link #NEXT} immediately with no allocations &mdash; matching the
058 * off-by-default, zero-cost contract of the observability hooks. Registration of this processor is
059 * itself gated on {@code RestContext.responseTraceparent} (default {@code true}); setting it to
060 * {@code false} keeps the processor out of the chain entirely.
061 *
062 * <h5 class='topic'>Already-committed responses</h5>
063 *
064 * <p>
065 * If the response has already been committed (for example a streaming / SSE handler that flushed its
066 * headers before returning), the headers can no longer be set. In that case the processor logs a
067 * {@link Level#FINE FINE} message and skips the write &mdash; it never throws.
068 *
069 * <h5 class='section'>See Also:</h5><ul>
070 *    <li class='jc'>{@link TracerHook}
071 *    <li class='link'><a class="doclink" href="https://www.w3.org/TR/trace-context/">W3C Trace Context</a>
072 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerObservability">Observability — Micrometer + OpenTelemetry</a>
073 * </ul>
074 *
075 * @since 10.0.0
076 */
077public class TraceContextResponseProcessor implements ResponseProcessor {
078
079   /**
080    * Request-attribute key under which a {@link TracerHook} bridge stashes the rendered W3C
081    * {@code traceparent} value (e.g. {@code 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01})
082    * for this processor to emit as a response header.
083    */
084   public static final String ATTR_TRACEPARENT = "juneau.traceparent";
085
086   /**
087    * Request-attribute key under which a {@link TracerHook} bridge stashes the rendered W3C
088    * {@code tracestate} value (a comma-separated {@code key=value} list). Only set when the active
089    * trace state is non-empty.
090    */
091   public static final String ATTR_TRACESTATE = "juneau.tracestate";
092
093   /**
094    * Request-attribute key under which a {@link TracerHook} bridge stashes the rendered, non-empty W3C
095    * {@code baggage} value.
096    *
097    * <p>
098    * Unlike {@link #ATTR_TRACEPARENT} / {@link #ATTR_TRACESTATE}, this processor does <b>not</b> read
099    * this attribute and never emits a {@code baggage} response header from it &mdash; {@code baggage} is
100    * stashed purely so a dated MCP adapter can echo it into a successful result's {@code _meta} (added in
101    * a later task). Ordinary HTTP response-header behavior for {@code traceparent} / {@code tracestate} is
102    * unaffected by this constant's existence.
103    *
104    * @since 10.0.0
105    */
106   public static final String ATTR_BAGGAGE = "juneau.baggage";
107
108   /**
109    * Request-attribute key under which {@link RestOpInvoker} stashes the active {@link Scope} for the
110    * duration of one {@code @RestOp} handler invocation.
111    *
112    * <p>
113    * Populated only on the active-tracer path &mdash; see the no-tracer fast-path contract documented on
114    * {@link TracerHook} &mdash; and cleared once that invocation's synchronous or deferred observability
115    * lifecycle closes the scope. A dated MCP adapter running inside the handler already resolves this
116    * same {@link RestRequest} through its own request-scoped {@code BeanStore} entry; reading this
117    * attribute off that request lets it call {@link Scope#recordRpcError(int, String)} before returning a
118    * JSON-RPC error response &mdash; a JSON-RPC error is dispatched over HTTP {@code 200}, so it never
119    * otherwise reaches {@link Scope#setStatusCode(int)} with anything but the adapter's uniform success
120    * status.
121    *
122    * @since 10.0.0
123    */
124   public static final String ATTR_SCOPE = "juneau.tracerScope";
125
126   private static final String HEADER_TRACEPARENT = "traceparent";
127   private static final String HEADER_TRACESTATE = "tracestate";
128
129   private static final Logger LOG = Logger.getLogger(TraceContextResponseProcessor.class.getName());
130
131   @Override /* Overridden from ResponseProcessor */
132   public int process(RestOpSession opSession) throws IOException, BasicHttpException {
133      var req = opSession.getRequest();
134
135      // NoOp short-circuit: no active tracer stashed a trace context for this request.
136      var traceparent = req.getAttribute(ATTR_TRACEPARENT).as(String.class).orElse(null);
137      if (isEmpty(traceparent))
138         return NEXT;
139
140      var res = opSession.getResponse();
141
142      // Headers already flushed (e.g. streaming / SSE response) — body was also written; chain is done.
143      if (res.getHttpServletResponse().isCommitted()) {
144         LOG.log(Level.FINE, () -> "Response already committed; skipping traceparent/tracestate header injection.");
145         return FINISHED;
146      }
147
148      res.setHeader(HEADER_TRACEPARENT, traceparent);
149
150      var tracestate = req.getAttribute(ATTR_TRACESTATE).as(String.class).orElse(null);
151      if (tracestate != null && ! tracestate.isEmpty())
152         res.setHeader(HEADER_TRACESTATE, tracestate);
153
154      // Headers only — let the downstream processors render the body.
155      return NEXT;
156   }
157}