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 — 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(...)} — 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 — rather than duplicated as private literals in each of those two modules 052 * — 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> — 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 — 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}