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
019
020import java.util.*;
021
022import org.apache.juneau.http.tracing.TraceContextCarrier;
023import static org.apache.juneau.commons.utils.Shorts.*;
024
025/**
026 * Immutable, low-cardinality description of the operation a {@code TracerHook} bridge should name a
027 * span after, supplied to {@link TracerHook#startSpan(org.apache.juneau.rest.server.RestRequest, TraceContextCarrier, TraceOperation)}
028 * alongside the extracted {@link TraceContextCarrier}.
029 *
030 * <p>
031 * {@code juneau-rest-server} has no notion of MCP, JSON-RPC, or OpenTelemetry &mdash; this type exists
032 * so a neutral {@code TraceContextExtractor} (for example the {@code juneau-rest-server-mcp-v20260728}
033 * binding, added in a later task) can hand a bridge (for example the OpenTelemetry bridge in
034 * {@code juneau-rest-server-tracing-otel}) a low-cardinality span name plus a small set of attributes
035 * derived from resolved request arguments, without either side depending on the other's types.
036 *
037 * <h5 class='topic'>Default operation</h5>
038 *
039 * <p>
040 * {@link #DEFAULT} carries no span name and no attributes. It is the value
041 * {@link TraceContextExtractor#operation} returns when an extractor implementation supplies only
042 * {@link TraceContextExtractor#extract extract(...)} &mdash; a bridge receiving {@link #DEFAULT} keeps
043 * whatever span name / attributes it would otherwise derive from the {@link org.apache.juneau.rest.server.RestRequest}
044 * itself (HTTP method, route, etc.); it is not required to name every span after a non-HTTP operation.
045 *
046 * <h5 class='topic'>Attribute-name constants</h5>
047 *
048 * <p>
049 * The attribute-name constants declared here are the exact, pinned MCP/GenAI span-attribute keys that
050 * a later OpenTelemetry-bridge task and MCP-binding task both need to agree on. They are declared on
051 * this neutral type &mdash; rather than duplicated as private literals in each of those two modules
052 * &mdash; specifically so {@code juneau-rest-server} stays free of any OpenTelemetry or MCP import
053 * while still being the single source of truth for the wire-level attribute name. Attribute
054 * <i>values</i> are supplied per-operation via {@link #getAttributes()}; these constants are only the
055 * keys.
056 *
057 * <p>
058 * Pinned to the OpenTelemetry GenAI semantic-conventions snapshot at commit
059 * {@code c739977ae690961f36e435504e5c1febaef1f7f3} plus core OpenTelemetry semantic conventions
060 * v1.43.0. All of these attributes are <b>experimental/development</b> &mdash; names and requirement
061 * levels may change in a future semantic-conventions release; this pin must be re-checked before any
062 * OpenTelemetry or semantic-conventions dependency upgrade touches MCP tracing.
063 *
064 * <h5 class='section'>See Also:</h5><ul>
065 *    <li class='jc'>{@link TraceContextCarrier}
066 *    <li class='jc'>{@link TraceContextExtractor}
067 *    <li class='jc'>{@link TracerHook}
068 *    <li class='link'><a class="doclink" href="https://github.com/open-telemetry/semantic-conventions-genai/tree/c739977ae690961f36e435504e5c1febaef1f7f3">OpenTelemetry GenAI semantic conventions (pinned snapshot)</a>
069 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerObservability">Observability — Micrometer + OpenTelemetry</a>
070 * </ul>
071 *
072 * @since 10.0.0
073 */
074public final class TraceOperation {
075
076   /** Experimental span-attribute key: the exact MCP method name (e.g. {@code "tools/call"}). */
077   public static final String ATTR_MCP_METHOD_NAME = "mcp.method.name";
078
079   /** Experimental span-attribute key: the negotiated MCP protocol version (e.g. {@code "2026-07-28"}). */
080   public static final String ATTR_MCP_PROTOCOL_VERSION = "mcp.protocol.version";
081
082   /** Experimental span-attribute key: the JSON-RPC request id, in string form. */
083   public static final String ATTR_JSONRPC_REQUEST_ID = "jsonrpc.request.id";
084
085   /** Experimental span-attribute key: the {@code tools/call} target tool name. */
086   public static final String ATTR_GEN_AI_TOOL_NAME = "gen_ai.tool.name";
087
088   /** Experimental span-attribute key: the {@code prompts/get} target prompt name. */
089   public static final String ATTR_GEN_AI_PROMPT_NAME = "gen_ai.prompt.name";
090
091   /** Experimental span-attribute key: the {@code resources/read} target resource URI. */
092   public static final String ATTR_MCP_RESOURCE_URI = "mcp.resource.uri";
093
094   /** Experimental span-attribute key: the GenAI operation name (e.g. {@code "execute_tool"}). */
095   public static final String ATTR_GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
096
097   /** Experimental span-attribute key: the JSON-RPC error response's numeric code, in string form. */
098   public static final String ATTR_RPC_RESPONSE_STATUS_CODE = "rpc.response.status_code";
099
100   /** Experimental span-attribute key: the JSON-RPC error category or handler exception type. */
101   public static final String ATTR_ERROR_TYPE = "error.type";
102
103   /**
104    * The default operation: no span name override, no attributes.
105    *
106    * <p>
107    * Returned by {@link TraceContextExtractor#operation} when an extractor supplies no override.
108    */
109   public static final TraceOperation DEFAULT = new TraceOperation(null, Collections.emptyMap());
110
111   private final String spanName;
112   private final Map<String,String> attributes;
113
114   private TraceOperation(String spanName, Map<String,String> attributes) {
115      this.spanName = spanName;
116      this.attributes = attributes;
117   }
118
119   /**
120    * Creates an operation with a span name and no attributes.
121    *
122    * @param spanName The low-cardinality span name (e.g. {@code "tools/call echo"}). Must not be
123    *    <jk>null</jk> or blank.
124    * @return A new {@link TraceOperation}. Never <jk>null</jk>.
125    */
126   public static TraceOperation of(String spanName) {
127      return of(spanName, Collections.emptyMap());
128   }
129
130   /**
131    * Creates an operation with a span name and attributes.
132    *
133    * @param spanName The low-cardinality span name (e.g. {@code "tools/call echo"}). Must not be
134    *    <jk>null</jk> or blank.
135    * @param attributes The span attributes, keyed by attribute name (typically one of the
136    *    {@code ATTR_*} constants declared on this class). Iteration order is preserved. Must not be
137    *    <jk>null</jk>; may be empty.
138    * @return A new {@link TraceOperation}. Never <jk>null</jk>.
139    */
140   public static TraceOperation of(String spanName, Map<String,String> attributes) {
141      reqnb("spanName", spanName);
142      reqnn("attributes", attributes);
143      return new TraceOperation(spanName, Collections.unmodifiableMap(new LinkedHashMap<>(attributes)));
144   }
145
146   /**
147    * Returns the low-cardinality span name.
148    *
149    * @return The span name, or <jk>null</jk> for {@link #DEFAULT} (no override &mdash; the bridge
150    *    keeps its own default naming).
151    */
152   public String getSpanName() {
153      return spanName;
154   }
155
156   /**
157    * Returns the span attributes.
158    *
159    * @return An unmodifiable, insertion-ordered map of span attributes. Never <jk>null</jk>; empty
160    *    for {@link #DEFAULT}.
161    */
162   public Map<String,String> getAttributes() {
163      return attributes;
164   }
165}